URI: 
       tadd configurable checkpoint to blockchain verification; use genesis as default - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit dd0b018a35e1ab66f7e4f814f9404e6042b31abb
   DIR parent 85f2f667c3172230a970a181f29a31291c76c0e5
  HTML 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:
         M lib/bitcoin.py                      |       7 ++++++-
         M lib/blockchain.py                   |      49 ++++++++++++++++++++++++-------
         M lib/network.py                      |       3 +++
       
       3 files changed, 48 insertions(+), 11 deletions(-)
       ---
   DIR diff --git a/lib/bitcoin.py b/lib/bitcoin.py
       t@@ -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
       t@@ -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"
        
        
        
   DIR diff --git a/lib/blockchain.py b/lib/blockchain.py
       t@@ -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):
       t@@ -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]
       t@@ -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
        
       t@@ -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])
       t@@ -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):
       t@@ -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
       t@@ -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)
       t@@ -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')
   DIR diff --git a/lib/network.py b/lib/network.py
       t@@ -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')