URI: 
       tinterface: block header search simplifications - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit da23e71db1f270c26ffbb217ca29f3df6649a315
   DIR parent ab94a47b8e5a84fc9735e840903f728f30d15d14
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Sun, 16 Sep 2018 07:34:05 +0200
       
       interface: block header search simplifications
       
       Diffstat:
         M electrum/interface.py               |      35 ++++++++++++++-----------------
         M electrum/tests/test_network.py      |      15 +++++++--------
       
       2 files changed, 23 insertions(+), 27 deletions(-)
       ---
   DIR diff --git a/electrum/interface.py b/electrum/interface.py
       t@@ -314,6 +314,7 @@ class Interface(PrintError):
        
            async def get_block_header(self, height, assert_mode):
                # use lower timeout as we usually have network.bhi_lock here
       +        self.print_error('requesting block header {} in mode {}'.format(height, assert_mode))
                timeout = 5 if not self.proxy else 10
                res = await self.session.send_request('blockchain.block.header', [height], timeout=timeout)
                return blockchain.deserialize_header(bytes.fromhex(res), height)
       t@@ -385,20 +386,20 @@ class Interface(PrintError):
                        raise GracefulDisconnect('server tip below max checkpoint')
                    self.mark_ready()
                    async with self.network.bhi_lock:
       -                if self.blockchain.height() < height - 1:
       -                    _, height = await self.sync_until(height, None)
                        if self.blockchain.height() >= height and self.blockchain.check_header(header):
                            # another interface amended the blockchain
                            self.print_error("skipping header", height)
                            continue
       -                height = min(height, self.tip)
                        _, height = await self.step(height, header)
       +                # in the simple case, height == self.tip+1
       +                if height <= self.tip:
       +                    await self.sync_until(height)
        
            async def sync_until(self, height, next_height=None):
                if next_height is None:
                    next_height = self.tip
                last = None
       -        while last is None or height < next_height:
       +        while last is None or height <= next_height:
                    if next_height > height + 10:
                        could_connect, num_headers = await self.request_chunk(height, next_height)
                        if not could_connect:
       t@@ -463,7 +464,9 @@ class Interface(PrintError):
                    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?
       t@@ -477,24 +480,18 @@ class Interface(PrintError):
                            height += 1
                            return 'join', height
                        else:
       -                    if not ismocking and branch.parent().check_header(header) \
       -                            or ismocking and branch['parent']['check'](header):
       -                        self.print_error('reorg', bad, self.tip)
       -                        self.blockchain = branch.parent() if not ismocking else branch['parent']
       -                        height = bad
       -                        header = await self.get_block_header(height, 'binary')
       -                    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
       +                    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:
   DIR diff --git a/electrum/tests/test_network.py b/electrum/tests/test_network.py
       t@@ -49,7 +49,7 @@ class TestNetwork(unittest.TestCase):
                self.interface.q.put_nowait({'block_height': 5, 'mock': {'binary':1,'check':lambda x: True, 'connect': lambda x: True}})
                self.interface.q.put_nowait({'block_height': 6, 'mock': {'binary':1,'check':lambda x: True, 'connect': lambda x: True}})
                ifa = self.interface
       -        self.assertEqual(('fork', 8), asyncio.get_event_loop().run_until_complete(ifa.sync_until(8, next_height=8)))
       +        self.assertEqual(('fork', 8), asyncio.get_event_loop().run_until_complete(ifa.sync_until(8, next_height=7)))
                self.assertEqual(self.interface.q.qsize(), 0)
        
            def test_new_can_connect_during_backward(self):
       t@@ -62,7 +62,7 @@ class TestNetwork(unittest.TestCase):
                self.interface.q.put_nowait({'block_height': 3, 'mock': {'catchup':1, 'check': lambda x: False, 'connect': lambda x: True}})
                self.interface.q.put_nowait({'block_height': 4, 'mock': {'catchup':1, 'check': lambda x: False, 'connect': lambda x: True}})
                ifa = self.interface
       -        self.assertEqual(('catchup', 5), asyncio.get_event_loop().run_until_complete(ifa.sync_until(8, next_height=5)))
       +        self.assertEqual(('catchup', 5), asyncio.get_event_loop().run_until_complete(ifa.sync_until(8, next_height=4)))
                self.assertEqual(self.interface.q.qsize(), 0)
        
            def mock_fork(self, bad_header):
       t@@ -79,7 +79,7 @@ class TestNetwork(unittest.TestCase):
                self.interface.q.put_nowait({'block_height': 5, 'mock': {'catchup':1, 'check': lambda x: False, 'connect': lambda x: True}})
                self.interface.q.put_nowait({'block_height': 6, 'mock': {'catchup':1, 'check': lambda x: False, 'connect': lambda x: True}})
                ifa = self.interface
       -        self.assertEqual(('catchup', 7), asyncio.get_event_loop().run_until_complete(ifa.sync_until(8, next_height=7)))
       +        self.assertEqual(('catchup', 7), asyncio.get_event_loop().run_until_complete(ifa.sync_until(8, next_height=6)))
                self.assertEqual(self.interface.q.qsize(), 0)
        
            def test_new_join(self):
       t@@ -91,7 +91,7 @@ class TestNetwork(unittest.TestCase):
                self.interface.q.put_nowait({'block_height': 5, 'mock': {'binary':1, 'check': lambda x: True, 'connect': lambda x: False}})
                self.interface.q.put_nowait({'block_height': 6, 'mock': {'binary':1, 'check': lambda x: True, 'connect': lambda x: True}})
                ifa = self.interface
       -        self.assertEqual(('join', 7), asyncio.get_event_loop().run_until_complete(ifa.sync_until(8, next_height=7)))
       +        self.assertEqual(('join', 7), asyncio.get_event_loop().run_until_complete(ifa.sync_until(8, next_height=6)))
                self.assertEqual(self.interface.q.qsize(), 0)
        
            def test_new_reorg(self):
       t@@ -100,7 +100,7 @@ class TestNetwork(unittest.TestCase):
                    nonlocal times
                    self.assertEqual(header['block_height'], 7)
                    times += 1
       -            return times != 1
       +            return False
                blockchain.blockchains = {7: {'check': check, 'parent': {'check': lambda x: True}}}
                self.interface.q.put_nowait({'block_height': 8, 'mock': {'catchup':1, 'check': lambda x: False, 'connect': lambda x: False}})
                self.interface.q.put_nowait({'block_height': 7, 'mock': {'backward':1, 'check': lambda x: False, 'connect': lambda height: height == 6}})
       t@@ -108,11 +108,10 @@ class TestNetwork(unittest.TestCase):
                self.interface.q.put_nowait({'block_height': 4, 'mock': {'binary':1, 'check': lambda x: 1, 'connect': lambda x: False}})
                self.interface.q.put_nowait({'block_height': 5, 'mock': {'binary':1, 'check': lambda x: 1, 'connect': lambda x: False}})
                self.interface.q.put_nowait({'block_height': 6, 'mock': {'binary':1, 'check': lambda x: 1, 'connect': lambda x: True}})
       -        self.interface.q.put_nowait({'block_height': 7, 'mock': {'binary':1, 'check': lambda x: False, 'connect': lambda x: True}})
                ifa = self.interface
       -        self.assertEqual(('join', 8), asyncio.get_event_loop().run_until_complete(ifa.sync_until(8, next_height=8)))
       +        self.assertEqual(('conflict', 8), asyncio.get_event_loop().run_until_complete(ifa.sync_until(8, next_height=7)))
                self.assertEqual(self.interface.q.qsize(), 0)
       -        self.assertEqual(times, 2)
       +        self.assertEqual(times, 1)
        
        if __name__=="__main__":
            constants.set_regtest()