URI: 
       tinterface: split up 'step'; binary search of headers - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 1d711eeadc754024bfba9b12822635ec4a013984
   DIR parent 58a5346d724be74de3ca84217ff3a79fdd278129
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Sun, 16 Sep 2018 08:29:01 +0200
       
       interface: split up 'step'; binary search of headers
       
       Diffstat:
         M electrum/interface.py               |     134 ++++++++++++++++---------------
       
       1 file changed, 69 insertions(+), 65 deletions(-)
       ---
   DIR diff --git a/electrum/interface.py b/electrum/interface.py
       t@@ -427,7 +427,7 @@ class Interface(PrintError):
        
                chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header)
                if chain:
       -            self.blockchain = chain
       +            self.blockchain = chain if isinstance(chain, Blockchain) else self.blockchain
                    return 'catchup', height+1
        
                can_connect = blockchain.can_connect(header) if 'mock' not in header else header['mock']['connect'](height)
       t@@ -445,80 +445,84 @@ class Interface(PrintError):
                        self.blockchain.save_header(header)
                    return 'catchup', height
        
       -        # binary
       -        if isinstance(chain, Blockchain):  # not when mocking
       -            self.blockchain = chain
       +        good, height, bad, bad_header = await self._search_headers_binary(height, bad, bad_header, chain)
       +        return await self._resolve_potential_chain_fork_given_forkpoint(good, height, bad, bad_header)
       +
       +    async def _search_headers_binary(self, height, bad, bad_header, chain):
       +        self.blockchain = chain if isinstance(chain, Blockchain) else self.blockchain
                good = height
       -        height = (bad + good) // 2
       -        header = await self.get_block_header(height, 'binary')
                while True:
       -            self.print_error("binary step")
       +            assert good < bad, (good, bad)
       +            height = (good + bad) // 2
       +            self.print_error("binary step. good {}, bad {}, height {}".format(good, bad, height))
       +            header = await self.get_block_header(height, 'binary')
                    chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header)
                    if chain:
       +                self.blockchain = chain if isinstance(chain, Blockchain) else self.blockchain
                        good = height
       -                self.blockchain = self.blockchain if type(chain) in [bool, int] else chain
                    else:
                        bad = height
                        bad_header = header
       -            assert good < bad, (good, bad)
       -            if bad != good + 1:
       -                height = (bad + good) // 2
       -                header = await self.get_block_header(height, 'binary')
       -                continue
       -            mock = bad_header and 'mock' in bad_header and bad_header['mock']['connect'](height)
       -            real = not mock and self.blockchain.can_connect(bad_header, check_height=False)
       -            if not real and not mock:
       -                raise Exception('unexpected bad header during binary' + str(bad_header)) # line 948 in 8e69174374aee87d73cd2f8005fbbe87c93eee9c's network.py
       -            branch = blockchain.blockchains.get(bad)
       -            self.print_error("binary search exited. good {}, bad {}".format(good, bad))
       -            if branch is not None:
       -                self.print_error("existing fork found at bad height {}".format(bad))
       -                ismocking = type(branch) is dict
       -                # FIXME: it does not seem sufficient to check that the branch
       -                # contains the bad_header. what if self.blockchain doesn't?
       -                # the chains shouldn't be joined then. observe the incorrect
       -                # joining on regtest with a server that has a fork of height
       -                # one. the problem is observed only if forking is not during
       -                # electrum runtime
       -                if not ismocking and branch.check_header(bad_header) \
       -                        or ismocking and branch['check'](bad_header):
       -                    self.print_error('joining chain', bad)
       -                    height += 1
       -                    return 'join', height
       -                else:
       -                    height = bad + 1
       -                    if ismocking:
       -                        self.print_error("TODO replace blockchain")
       -                        return 'conflict', height
       -                    self.print_error('forkpoint conflicts with existing fork', branch.path())
       -                    branch.write(b'', 0)
       -                    branch.save_header(bad_header)
       -                    self.blockchain = branch
       -                    return 'conflict', height
       +            if good + 1 == bad:
       +                break
       +
       +        mock = bad_header and 'mock' in bad_header and bad_header['mock']['connect'](height)
       +        real = not mock and self.blockchain.can_connect(bad_header, check_height=False)
       +        if not real and not mock:
       +            raise Exception('unexpected bad header during binary: {}'.format(bad_header))
       +        self.print_error("binary search exited. good {}, bad {}".format(good, bad))
       +        return good, height, bad, bad_header
       +
       +    async def _resolve_potential_chain_fork_given_forkpoint(self, good, height, bad, bad_header):
       +        branch = blockchain.blockchains.get(bad)
       +        if branch is not None:
       +            self.print_error("existing fork found at bad height {}".format(bad))
       +            ismocking = type(branch) is dict
       +            # FIXME: it does not seem sufficient to check that the branch
       +            # contains the bad_header. what if self.blockchain doesn't?
       +            # the chains shouldn't be joined then. observe the incorrect
       +            # joining on regtest with a server that has a fork of height
       +            # one. the problem is observed only if forking is not during
       +            # electrum runtime
       +            if not ismocking and branch.check_header(bad_header) \
       +                    or ismocking and branch['check'](bad_header):
       +                self.print_error('joining chain', bad)
       +                height += 1
       +                return 'join', height
                    else:
       -                bh = self.blockchain.height()
       -                self.print_error("no existing fork yet at bad height {}. local chain height: {}".format(bad, bh))
       -                if bh > good:
       -                    forkfun = self.blockchain.fork
       -                    if 'mock' in bad_header:
       -                        chain = bad_header['mock']['check'](bad_header)
       -                        forkfun = bad_header['mock']['fork'] if 'fork' in bad_header['mock'] else forkfun
       -                    else:
       -                        chain = self.blockchain.check_header(bad_header)
       -                    if not chain:
       -                        b = forkfun(bad_header)
       -                        assert bad not in blockchain.blockchains, (bad, list(blockchain.blockchains.keys()))
       -                        blockchain.blockchains[bad] = b
       -                        self.blockchain = b
       -                        height = b.forkpoint + 1
       -                        assert b.forkpoint == bad
       -                    return 'fork', height
       +                height = bad + 1
       +                if ismocking:
       +                    self.print_error("TODO replace blockchain")
       +                    return 'conflict', height
       +                self.print_error('forkpoint conflicts with existing fork', branch.path())
       +                branch.write(b'', 0)
       +                branch.save_header(bad_header)
       +                self.blockchain = branch
       +                return 'conflict', height
       +        else:
       +            bh = self.blockchain.height()
       +            self.print_error("no existing fork yet at bad height {}. local chain height: {}".format(bad, bh))
       +            if bh > good:
       +                forkfun = self.blockchain.fork
       +                if 'mock' in bad_header:
       +                    chain = bad_header['mock']['check'](bad_header)
       +                    forkfun = bad_header['mock']['fork'] if 'fork' in bad_header['mock'] else forkfun
                        else:
       -                    assert bh == good
       -                    if bh < self.tip:
       -                        self.print_error("catching up from %d"% (bh + 1))
       -                        height = bh + 1
       -                    return 'no_fork', height
       +                    chain = self.blockchain.check_header(bad_header)
       +                if not chain:
       +                    b = forkfun(bad_header)
       +                    assert bad not in blockchain.blockchains, (bad, list(blockchain.blockchains.keys()))
       +                    blockchain.blockchains[bad] = b
       +                    self.blockchain = b
       +                    height = b.forkpoint + 1
       +                    assert b.forkpoint == bad
       +                return 'fork', height
       +            else:
       +                assert bh == good
       +                if bh < self.tip:
       +                    self.print_error("catching up from %d" % (bh + 1))
       +                    height = bh + 1
       +                return 'no_fork', height
        
            async def _search_headers_backwards(self, height, header):
                async def iterate():