electrum

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

commit 1d711eeadc754024bfba9b12822635ec4a013984
parent 58a5346d724be74de3ca84217ff3a79fdd278129
Author: SomberNight <somber.night@protonmail.com>
Date:   Sun, 16 Sep 2018 08:29:01 +0200

interface: split up 'step'; binary search of headers

Diffstat:
Melectrum/interface.py | 134+++++++++++++++++++++++++++++++++++++++++--------------------------------------
1 file changed, 69 insertions(+), 65 deletions(-)

diff --git a/electrum/interface.py b/electrum/interface.py @@ -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) @@ -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():