add configurable checkpoint to blockchain verification; use genesis as default

This commit is contained in:
ThomasV 2017-03-23 11:58:56 +01:00
parent 85f2f667c3
commit dd0b018a35
3 changed files with 48 additions and 11 deletions

View File

@ -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"

View File

@ -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')

View File

@ -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')