commit dd0b018a35e1ab66f7e4f814f9404e6042b31abb
parent 85f2f667c3172230a970a181f29a31291c76c0e5
Author: ThomasV <thomasv@electrum.org>
Date: Thu, 23 Mar 2017 11:58:56 +0100
add configurable checkpoint to blockchain verification; use genesis as default
Diffstat:
3 files changed, 48 insertions(+), 11 deletions(-)
diff --git a/lib/bitcoin.py b/lib/bitcoin.py
@@ -45,11 +45,13 @@ ADDRTYPE_P2WPKH = 6
XPRV_HEADER = 0x0488ade4
XPUB_HEADER = 0x0488b21e
HEADERS_URL = "https://headers.electrum.org/blockchain_headers"
+GENESIS = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
def set_testnet():
global ADDRTYPE_P2PKH, ADDRTYPE_P2SH, ADDRTYPE_P2WPKH
global XPRV_HEADER, XPUB_HEADER
global TESTNET, HEADERS_URL
+ global GENESIS
TESTNET = True
ADDRTYPE_P2PKH = 111
ADDRTYPE_P2SH = 196
@@ -57,18 +59,21 @@ def set_testnet():
XPRV_HEADER = 0x04358394
XPUB_HEADER = 0x043587cf
HEADERS_URL = "https://headers.electrum.org/testnet_headers"
+ GENESIS = "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
def set_nolnet():
global ADDRTYPE_P2PKH, ADDRTYPE_P2SH, ADDRTYPE_P2WPKH
global XPRV_HEADER, XPUB_HEADER
global NOLNET, HEADERS_URL
- NOLNET = True
+ global GENESIS
+ TESTNET = True
ADDRTYPE_P2PKH = 0
ADDRTYPE_P2SH = 5
ADDRTYPE_P2WPKH = 6
XPRV_HEADER = 0x0488ade4
XPUB_HEADER = 0x0488b21e
HEADERS_URL = "https://headers.electrum.org/nolnet_headers"
+ GENESIS = "663c88be18d07c45f87f910b93a1a71ed9ef1946cad50eb6a6f3af4c424625c6"
diff --git a/lib/blockchain.py b/lib/blockchain.py
@@ -37,7 +37,9 @@ class Blockchain(util.PrintError):
def __init__(self, config, network):
self.config = config
self.network = network
- self.local_height = 0
+ self.checkpoint_height = self.config.get('checkpoint_height', 0)
+ self.checkpoint_hash = self.config.get('checkpoint_value', bitcoin.GENESIS)
+ self.check_truncate_headers()
self.set_local_height()
def height(self):
@@ -55,11 +57,19 @@ class Blockchain(util.PrintError):
def verify_header(self, header, prev_header, bits, target):
prev_hash = self.hash_header(prev_header)
- assert prev_hash == header.get('prev_block_hash'), "prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash'))
- if bitcoin.TESTNET or bitcoin.NOLNET: return
- assert bits == header.get('bits'), "bits mismatch: %s vs %s" % (bits, header.get('bits'))
_hash = self.hash_header(header)
- assert int('0x' + _hash, 16) <= target, "insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target)
+ if prev_hash != header.get('prev_block_hash'):
+ raise BaseException("prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash')))
+ if self.checkpoint_height == header.get('block_height') and self.checkpoint_hash != _hash:
+ raise BaseException('failed checkpoint')
+ if self.checkpoint_height == header.get('block_height'):
+ self.print_error("validated checkpoint", self.checkpoint_height)
+ if bitcoin.TESTNET:
+ return
+ if bits != header.get('bits'):
+ raise BaseException("bits mismatch: %s vs %s" % (bits, header.get('bits')))
+ if int('0x' + _hash, 16) > target:
+ raise BaseException("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target))
def verify_chain(self, chain):
first_header = chain[0]
@@ -78,7 +88,7 @@ class Blockchain(util.PrintError):
bits, target = self.get_target(index)
for i in range(num):
raw_header = data[i*80:(i+1) * 80]
- header = self.deserialize_header(raw_header)
+ header = self.deserialize_header(raw_header, index*2016 + i)
self.verify_header(header, prev_header, bits, target)
prev_header = header
@@ -91,7 +101,7 @@ class Blockchain(util.PrintError):
+ int_to_hex(int(res.get('nonce')), 4)
return s
- def deserialize_header(self, s):
+ def deserialize_header(self, s, height):
hex_to_int = lambda s: int('0x' + s[::-1].encode('hex'), 16)
h = {}
h['version'] = hex_to_int(s[0:4])
@@ -100,6 +110,7 @@ class Blockchain(util.PrintError):
h['timestamp'] = hex_to_int(s[68:72])
h['bits'] = hex_to_int(s[72:76])
h['nonce'] = hex_to_int(s[76:80])
+ h['block_height'] = height
return h
def hash_header(self, header):
@@ -146,6 +157,7 @@ class Blockchain(util.PrintError):
self.set_local_height()
def set_local_height(self):
+ self.local_height = 0
name = self.path()
if os.path.exists(name):
h = os.path.getsize(name)/80 - 1
@@ -160,10 +172,25 @@ class Blockchain(util.PrintError):
h = f.read(80)
f.close()
if len(h) == 80:
- h = self.deserialize_header(h)
+ h = self.deserialize_header(h, block_height)
return h
+ def check_truncate_headers(self):
+ checkpoint = self.read_header(self.checkpoint_height)
+ if checkpoint is None:
+ return
+ if self.hash_header(checkpoint) == self.checkpoint_hash:
+ return
+ self.print_error('Truncating headers file at height %d'%self.checkpoint_height)
+ name = self.path()
+ f = open(name, 'rwb+')
+ f.seek(self.checkpoint_height * 80)
+ f.truncate()
+ f.close()
+
def get_target(self, index, chain=None):
+ if bitcoin.TESTNET:
+ return 0, 0
if index == 0:
return 0x1d00ffff, MAX_TARGET
first = self.read_header((index-1) * 2016)
@@ -176,9 +203,11 @@ class Blockchain(util.PrintError):
# bits to target
bits = last.get('bits')
bitsN = (bits >> 24) & 0xff
- assert bitsN >= 0x03 and bitsN <= 0x1d, "First part of bits should be in [0x03, 0x1d]"
+ if not (bitsN >= 0x03 and bitsN <= 0x1d):
+ raise BaseException("First part of bits should be in [0x03, 0x1d]")
bitsBase = bits & 0xffffff
- assert bitsBase >= 0x8000 and bitsBase <= 0x7fffff, "Second part of bits should be in [0x8000, 0x7fffff]"
+ if not (bitsBase >= 0x8000 and bitsBase <= 0x7fffff):
+ raise BaseException("Second part of bits should be in [0x8000, 0x7fffff]")
target = bitsBase << (8 * (bitsN-3))
# new target
nActualTimespan = last.get('timestamp') - first.get('timestamp')
diff --git a/lib/network.py b/lib/network.py
@@ -737,6 +737,9 @@ class Network(util.DaemonThread):
def on_get_chunk(self, interface, response):
'''Handle receiving a chunk of block headers'''
+ if response.get('error'):
+ interface.print_error(response.get('error'))
+ return
if self.bc_requests:
req_if, data = self.bc_requests[0]
req_idx = data.get('chunk_idx')