tremove blockchain fork detection threshold - electrum - Electrum Bitcoin wallet
 (HTM) git clone https://git.parazyd.org/electrum
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
       ---
 (DIR) commit bf7deaa1fb2ac3b608305db3e30b32ff7c7d05ce
 (DIR) parent 82c2c22a6e4b9307dcb45fd7b40ab2fbc48a7b0f
 (HTM) Author: ThomasV <thomasv@electrum.org>
       Date:   Thu, 13 Jul 2017 16:23:41 +0200
       
       remove blockchain fork detection threshold
       
       Diffstat:
         M lib/blockchain.py                   |      94 ++++++++++++++++++++-----------
         M lib/network.py                      |      32 ++++++++++++-------------------
       
       2 files changed, 72 insertions(+), 54 deletions(-)
       ---
 (DIR) diff --git a/lib/blockchain.py b/lib/blockchain.py
       t@@ -62,26 +62,30 @@ def hash_header(header):
        
        
        class Blockchain(util.PrintError):
       +
            '''Manages blockchain headers and their verification'''
       -    def __init__(self, config, checkpoint):
       +
       +    def __init__(self, config, filename, fork_point):
                self.config = config
       -        self.checkpoint = checkpoint
       -        self.filename = 'blockchain_headers' if checkpoint == 0 else 'blockchain_fork_%d'%checkpoint
       -        self.set_local_height()
       +        self.filename = filename
                self.catch_up = None # interface catching up
       +        if fork_point is None:
       +            self.is_saved = True
       +            self.checkpoint = int(filename[16:]) if filename.startswith('blockchain_fork_') else 0
       +        else:
       +            self.is_saved = False
       +            self.checkpoint = fork_point
       +        self.headers = []
       +        self.set_local_height()
        
            def height(self):
       -        return self.local_height
       +        return self.local_height + len(self.headers)
        
            def verify_header(self, header, prev_header, bits, target):
                prev_hash = hash_header(prev_header)
                _hash = hash_header(header)
                if prev_hash != header.get('prev_block_hash'):
                    raise BaseException("prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash')))
       -        #if not self.pass_checkpoint(header):
       -        #    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'):
       t@@ -115,24 +119,50 @@ class Blockchain(util.PrintError):
                return os.path.join(d, self.filename)
        
            def save_chunk(self, index, chunk):
       +        if not self.is_saved:
       +            self.fork_and_save()
                filename = self.path()
       -        f = open(filename, 'rb+')
       -        f.seek(index * 2016 * 80)
       -        f.truncate()
       -        h = f.write(chunk)
       -        f.close()
       +        with open(filename, 'rb+') as f:
       +            f.seek(index * 2016 * 80)
       +            f.truncate()
       +            h = f.write(chunk)
                self.set_local_height()
        
       +    def fork_and_save(self):
       +        import shutil
       +        self.print_error("save fork")
       +        height = self.checkpoint
       +        filename = "blockchain_fork_%d"%height
       +        new_path = os.path.join(util.get_headers_dir(self.config), filename)
       +        shutil.copy(self.path(), new_path)
       +        with open(new_path, 'rb+') as f:
       +            f.seek((height) * 80)
       +            f.truncate()
       +        self.filename = filename
       +        self.is_saved = True
       +        for h in self.headers:
       +            self.write_header(h)
       +        self.headers = []
       +
            def save_header(self, header):
       +        height = header.get('block_height')
       +        if not self.is_saved:
       +            assert height == self.checkpoint + len(self.headers) + 1
       +            self.headers.append(header)
       +            if len(self.headers) > 10:
       +                self.fork_and_save()
       +            return
       +        self.write_header(header)
       +
       +    def write_header(self, header):
       +        height = header.get('block_height')
                data = serialize_header(header).decode('hex')
                assert len(data) == 80
       -        height = header.get('block_height')
                filename = self.path()
       -        f = open(filename, 'rb+')
       -        f.seek(height * 80)
       -        f.truncate()
       -        h = f.write(data)
       -        f.close()
       +        with open(filename, 'rb+') as f:
       +            f.seek(height * 80)
       +            f.truncate()
       +            h = f.write(data)
                self.set_local_height()
        
            def set_local_height(self):
       t@@ -143,15 +173,22 @@ class Blockchain(util.PrintError):
                    if self.local_height != h:
                        self.local_height = h
        
       -    def read_header(self, block_height):
       +    def read_header(self, height):
       +        if not self.is_saved and height >= self.checkpoint:
       +            i = height - self.checkpoint
       +            if i >= len(self.headers):
       +                return None
       +            header = self.headers[i]
       +            assert header.get('block_height') == height
       +            return header
                name = self.path()
                if os.path.exists(name):
                    f = open(name, 'rb')
       -            f.seek(block_height * 80)
       +            f.seek(height * 80)
                    h = f.read(80)
                    f.close()
                    if len(h) == 80:
       -                h = deserialize_header(h, block_height)
       +                h = deserialize_header(h, height)
                        return h
        
            def get_hash(self, height):
       t@@ -173,17 +210,6 @@ class Blockchain(util.PrintError):
                f.truncate()
                f.close()
        
       -    def fork(self, height):
       -        import shutil
       -        filename = "blockchain_fork_%d"%height
       -        new_path = os.path.join(util.get_headers_dir(self.config), filename)
       -        shutil.copy(self.path(), new_path)
       -        with open(new_path, 'rb+') as f:
       -            f.seek((height) * 80)
       -            f.truncate()
       -            f.close()
       -        return filename
       -
            def get_target(self, index, chain=None):
                if bitcoin.TESTNET:
                    return 0, 0
 (DIR) diff --git a/lib/network.py b/lib/network.py
       t@@ -205,13 +205,12 @@ class Network(util.DaemonThread):
                    config = {}  # Do not use mutables as default values!
                util.DaemonThread.__init__(self)
                self.config = SimpleConfig(config) if type(config) == type({}) else config
       -        self.num_server = 8 if not self.config.get('oneserver') else 0
       -        self.blockchains = { 0:Blockchain(self.config, 0) }
       +        self.num_server = 18 if not self.config.get('oneserver') else 0
       +        self.blockchains = { 0:Blockchain(self.config, 'blockchain_headers', None) }
                for x in os.listdir(self.config.path):
                    if x.startswith('blockchain_fork_'):
       -                n = int(x[16:])
       -                b = Blockchain(self.config, n)
       -                self.blockchains[n] = b
       +                b = Blockchain(self.config, x, None)
       +                self.blockchains[b.checkpoint] = b
                self.print_error("blockchains", self.blockchains.keys())
                self.blockchain_index = config.get('blockchain_index', 0)
                if self.blockchain_index not in self.blockchains.keys():
       t@@ -864,23 +863,16 @@ class Network(util.DaemonThread):
                    if interface.bad != interface.good + 1:
                        next_height = (interface.bad + interface.good) // 2
                    else:
       -                interface.print_error("found connection at %d"% interface.good)
                        delta1 = interface.blockchain.height() - interface.good
                        delta2 = interface.tip - interface.good
       -                threshold = self.config.get('fork_threshold', 5)
       -                if delta1 > threshold and delta2 > threshold:
       -                    interface.print_error("chain split detected: %d (%d %d)"% (interface.good, delta1, delta2))
       -                    interface.blockchain.fork(interface.bad)
       -                    interface.blockchain = Blockchain(self.config, interface.bad)
       -                    self.blockchains[interface.bad] = interface.blockchain
       -                if interface.blockchain.catch_up is None:
       -                    interface.blockchain.catch_up = interface.server
       -                    interface.print_error("catching up")
       -                    interface.mode = 'catch_up'
       -                    next_height = interface.good
       -                else:
       -                    # todo: if current catch_up is too slow, queue others
       -                    next_height = None
       +                interface.print_error("chain split detected at %d"%interface.good, delta1, delta2)
       +                interface.blockchain = Blockchain(self.config, False, interface.bad)
       +                interface.blockchain.catch_up = interface.server
       +                self.blockchains[interface.bad] = interface.blockchain
       +                interface.print_error("catching up")
       +                interface.mode = 'catch_up'
       +                next_height = interface.good
       +
                elif interface.mode == 'catch_up':
                    if can_connect:
                        interface.blockchain.save_header(header)