electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

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:
Mlib/bitcoin.py | 7++++++-
Mlib/blockchain.py | 49+++++++++++++++++++++++++++++++++++++++----------
Mlib/network.py | 3+++
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')