1
0

interface: _search_headers_backwards: start at small delta

- interface.tip is the server's tip.
- consider scenario:
  - client has chain len 800_000, is up to date
  - client goes offline
  - suddenly there is a short reorg
      e.g. blocks 799_998, 799_999, 800_000 are reorged
  - client was offline for long time, finally comes back online again
  - server tip is 1_000_000, tip_header does not connect to client's local chain
  - PREVIOUSLY before commit, client would start backwards search
    - first it asks for header 800_001, which does not connect
    - then client asks for header ~600k, which checks
    - client will do long binary search to find the forkpoint
  - AFTER commit, client starts backwards search
    - first it asks for header 800_001, which does not connect
    - then client asks for header 799_999, etc
- that is, previously, on average, client did a short backwards search, followed by a long binary search
- now, on average, client does a longer backwards search, followed by a shorter binary search
  - this works much nicer with the headers_cache
  (- and thomasv said the old behaviour was not intentional)
This commit is contained in:
SomberNight
2025-06-09 19:33:16 +00:00
parent 02c6e118f0
commit eb69b6b516
2 changed files with 23 additions and 21 deletions

View File

@@ -1172,11 +1172,11 @@ class Interface(Logger):
await self._maybe_warm_headers_cache( await self._maybe_warm_headers_cache(
from_height=max(0, height-10), to_height=height, mode=ChainResolutionMode.BACKWARD) from_height=max(0, height-10), to_height=height, mode=ChainResolutionMode.BACKWARD)
delta = 2
while await iterate(): while await iterate():
bad, bad_header = height, header bad, bad_header = height, header
delta = self.tip - height # FIXME why compared to tip? would be easier to cache if delta started at 1 height -= delta
assert delta > 0, delta delta *= 2
height = self.tip - 2 * delta
_assert_header_does_not_check_against_any_chain(bad_header) _assert_header_does_not_check_against_any_chain(bad_header)
self.logger.info(f"exiting backward mode at {height}") self.logger.info(f"exiting backward mode at {height}")

View File

@@ -110,14 +110,14 @@ class TestNetwork(ElectrumTestCase):
server is on other side of chain split, the last common block is height 6. server is on other side of chain split, the last common block is height 6.
""" """
ifa = self.interface ifa = self.interface
ifa.tip = 12 # FIXME how could the server tip be this high? ifa.tip = 8
ifa.blockchain = MockBlockchain(["00a", "01a", "02a", "03a", "04a", "05a", "06a", "07a", "08a", "09a", "10a", "11a", "12a"]) ifa.blockchain = MockBlockchain(["00a", "01a", "02a", "03a", "04a", "05a", "06a", "07a", "08a", "09a", "10a", "11a", "12a"])
blockchain.blockchains = {"00a": ifa.blockchain} blockchain.blockchains = {
"00a": ifa.blockchain,
}
ifa.q.put_nowait({'block_height': 8, 'mock': {CRM.CATCHUP:1, 'id': '08b', 'prev_id': '07b'}}) ifa.q.put_nowait({'block_height': 8, 'mock': {CRM.CATCHUP:1, 'id': '08b', 'prev_id': '07b'}})
ifa.q.put_nowait({'block_height': 7, 'mock': {CRM.BACKWARD:1, 'id': '07b', 'prev_id': '06a'}}) ifa.q.put_nowait({'block_height': 7, 'mock': {CRM.BACKWARD:1, 'id': '07b', 'prev_id': '06a'}})
ifa.q.put_nowait({'block_height': 2, 'mock': {CRM.BACKWARD:1, 'id': '02a', 'prev_id': '01a'}}) ifa.q.put_nowait({'block_height': 5, 'mock': {CRM.BACKWARD:1, 'id': '05a', 'prev_id': '04a'}})
ifa.q.put_nowait({'block_height': 4, 'mock': {CRM.BINARY:1, 'id': '04a', 'prev_id': '03a'}})
ifa.q.put_nowait({'block_height': 5, 'mock': {CRM.BINARY:1, 'id': '05a', 'prev_id': '04a'}})
ifa.q.put_nowait({'block_height': 6, 'mock': {CRM.BINARY:1, 'id': '06a', 'prev_id': '05a'}}) ifa.q.put_nowait({'block_height': 6, 'mock': {CRM.BINARY:1, 'id': '06a', 'prev_id': '05a'}})
res = await ifa.sync_until(8, next_height=7) res = await ifa.sync_until(8, next_height=7)
self.assertEqual((CRM.FORK, 8), res) self.assertEqual((CRM.FORK, 8), res)
@@ -125,24 +125,23 @@ class TestNetwork(ElectrumTestCase):
# finds forkpoint during backwards, existing fork # finds forkpoint during backwards, existing fork
async def test_can_connect_during_backward(self): async def test_can_connect_during_backward(self):
"""client starts on main chain. client already knows about another fork, which has local height 1. """client starts on main chain. client already knows about another fork, which has local height 4.
server is on that fork but has more blocks. server is on that fork but has more blocks.
client happens to ask for header at height 2 during backward search (which directly builds on top the existing fork). client happens to ask for header at height 5 during backward search (which directly builds on top the existing fork).
""" """
ifa = self.interface ifa = self.interface
ifa.tip = 12 # FIXME how could the server tip be this high? ifa.tip = 8
ifa.blockchain = MockBlockchain(["00a", "01a", "02a", "03a", "04a", "05a", "06a", "07a", "08a", "09a", "10a", "11a", "12a"]) ifa.blockchain = MockBlockchain(["00a", "01a", "02a", "03a", "04a", "05a", "06a", "07a", "08a", "09a", "10a", "11a", "12a"])
blockchain.blockchains = { blockchain.blockchains = {
"00a": ifa.blockchain, "00a": ifa.blockchain,
"01b": MockBlockchain(["00a", "01b"]), "03b": MockBlockchain(["00a", "01a", "02a", "03b", "04b"]),
} }
ifa.q.put_nowait({'block_height': 8, 'mock': {CRM.CATCHUP:1, 'id': '08b', 'prev_id': '07b'}}) ifa.q.put_nowait({'block_height': 8, 'mock': {CRM.CATCHUP:1, 'id': '08b', 'prev_id': '07b'}})
ifa.q.put_nowait({'block_height': 7, 'mock': {CRM.BACKWARD:1, 'id': '07b', 'prev_id': '06b'}}) ifa.q.put_nowait({'block_height': 7, 'mock': {CRM.BACKWARD:1, 'id': '07b', 'prev_id': '06b'}})
ifa.q.put_nowait({'block_height': 2, 'mock': {CRM.BACKWARD:1, 'id': '02b', 'prev_id': '01b'}}) ifa.q.put_nowait({'block_height': 5, 'mock': {CRM.BACKWARD:1, 'id': '05b', 'prev_id': '04b'}})
ifa.q.put_nowait({'block_height': 3, 'mock': {CRM.CATCHUP:1, 'id': '03b', 'prev_id': '02b'}}) ifa.q.put_nowait({'block_height': 6, 'mock': {CRM.CATCHUP:1, 'id': '06b', 'prev_id': '05b'}})
ifa.q.put_nowait({'block_height': 4, 'mock': {CRM.CATCHUP:1, 'id': '04b', 'prev_id': '03b'}}) res = await ifa.sync_until(8, next_height=6)
res = await ifa.sync_until(8, next_height=4) self.assertEqual((CRM.CATCHUP, 7), res)
self.assertEqual((CRM.CATCHUP, 5), res)
self.assertEqual(ifa.q.qsize(), 0) self.assertEqual(ifa.q.qsize(), 0)
# finds forkpoint during binary, new fork # finds forkpoint during binary, new fork
@@ -151,14 +150,17 @@ class TestNetwork(ElectrumTestCase):
server is on other side of chain split, the last common block is height 3. server is on other side of chain split, the last common block is height 3.
""" """
ifa = self.interface ifa = self.interface
ifa.tip = 12 # FIXME how could the server tip be this high? ifa.tip = 8
ifa.blockchain = MockBlockchain(["00a", "01a", "02a", "03a", "04a", "05a", "06a", "07a", "08a", "09a", "10a", "11a", "12a"]) ifa.blockchain = MockBlockchain(["00a", "01a", "02a", "03a", "04a", "05a", "06a", "07a", "08a", "09a", "10a", "11a", "12a"])
blockchain.blockchains = {"00a": ifa.blockchain} blockchain.blockchains = {
"00a": ifa.blockchain,
}
ifa.q.put_nowait({'block_height': 8, 'mock': {CRM.CATCHUP:1, 'id': '08b', 'prev_id': '07b'}}) ifa.q.put_nowait({'block_height': 8, 'mock': {CRM.CATCHUP:1, 'id': '08b', 'prev_id': '07b'}})
ifa.q.put_nowait({'block_height': 7, 'mock': {CRM.BACKWARD:1, 'id': '07b', 'prev_id': '06b'}}) ifa.q.put_nowait({'block_height': 7, 'mock': {CRM.BACKWARD:1, 'id': '07b', 'prev_id': '06b'}})
ifa.q.put_nowait({'block_height': 2, 'mock': {CRM.BACKWARD:1, 'id': '02a', 'prev_id': '01a'}}) ifa.q.put_nowait({'block_height': 5, 'mock': {CRM.BACKWARD:1, 'id': '05b', 'prev_id': '04b'}})
ifa.q.put_nowait({'block_height': 4, 'mock': {CRM.BINARY:1, 'id': '04b', 'prev_id': '03a'}}) ifa.q.put_nowait({'block_height': 1, 'mock': {CRM.BACKWARD:1, 'id': '01a', 'prev_id': '00a'}})
ifa.q.put_nowait({'block_height': 3, 'mock': {CRM.BINARY:1, 'id': '03a', 'prev_id': '02a'}}) ifa.q.put_nowait({'block_height': 3, 'mock': {CRM.BINARY:1, 'id': '03a', 'prev_id': '02a'}})
ifa.q.put_nowait({'block_height': 4, 'mock': {CRM.BINARY:1, 'id': '04b', 'prev_id': '03a'}})
ifa.q.put_nowait({'block_height': 5, 'mock': {CRM.CATCHUP:1, 'id': '05b', 'prev_id': '04b'}}) ifa.q.put_nowait({'block_height': 5, 'mock': {CRM.CATCHUP:1, 'id': '05b', 'prev_id': '04b'}})
ifa.q.put_nowait({'block_height': 6, 'mock': {CRM.CATCHUP:1, 'id': '06b', 'prev_id': '05b'}}) ifa.q.put_nowait({'block_height': 6, 'mock': {CRM.CATCHUP:1, 'id': '06b', 'prev_id': '05b'}})
res = await ifa.sync_until(8, next_height=6) res = await ifa.sync_until(8, next_height=6)