1
0

tests: test_network: add more header chain resolution test cases

This commit is contained in:
SomberNight
2025-06-09 20:20:47 +00:00
parent eb69b6b516
commit c2e8188568
2 changed files with 106 additions and 10 deletions

View File

@@ -1111,7 +1111,7 @@ class Interface(Logger):
raise Exception('unexpected bad header during binary: {}'.format(bad_header)) raise Exception('unexpected bad header during binary: {}'.format(bad_header))
_assert_header_does_not_check_against_any_chain(bad_header) _assert_header_does_not_check_against_any_chain(bad_header)
self.logger.info(f"binary search exited. good {good}, bad {bad}") self.logger.info(f"binary search exited. good {good}, bad {bad}. {chain=}")
return good, bad, bad_header return good, bad, bad_header
async def _resolve_potential_chain_fork_given_forkpoint( async def _resolve_potential_chain_fork_given_forkpoint(

View File

@@ -71,7 +71,7 @@ class MockInterface(Interface):
async def get_block_header(self, height: int, *, mode: ChainResolutionMode) -> dict: async def get_block_header(self, height: int, *, mode: ChainResolutionMode) -> dict:
assert self.q.qsize() > 0, (height, mode) assert self.q.qsize() > 0, (height, mode)
item = await self.q.get() item = await self.q.get()
self.logger.debug(f"step with {height=}. {item=}") self.logger.debug(f"step with {height=}. {mode=}. will get {item=}")
assert item['block_height'] == height, (item['block_height'], height) assert item['block_height'] == height, (item['block_height'], height)
assert mode in item['mock'], (mode, item) assert mode in item['mock'], (mode, item)
return item return item
@@ -83,7 +83,7 @@ class MockInterface(Interface):
return return
class TestNetwork(ElectrumTestCase): class TestHeaderChainResolution(ElectrumTestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
@@ -104,10 +104,75 @@ class TestNetwork(ElectrumTestCase):
self.config = SimpleConfig({'electrum_path': self.electrum_path}) self.config = SimpleConfig({'electrum_path': self.electrum_path})
self.interface = MockInterface(self.config) self.interface = MockInterface(self.config)
# finds forkpoint during binary, new fork async def test_catchup_one_block_behind(self):
"""Single chain, but client is behind. The client's height is 5, server is on block 6.
- first missing block found during *catchup* phase
"""
ifa = self.interface
ifa.tip = 6
ifa.blockchain = MockBlockchain(["00a", "01a", "02a", "03a", "04a", "05a"])
blockchain.blockchains = {
"00a": ifa.blockchain,
}
ifa.q.put_nowait({'block_height': 6, 'mock': {CRM.CATCHUP:1, 'id': '06a', 'prev_id': '05a'}})
res = await ifa.sync_until(ifa.tip)
self.assertEqual((CRM.CATCHUP, 7), res)
self.assertEqual(ifa.q.qsize(), 0)
self.assertEqual(len(blockchain.blockchains), 1)
async def test_catchup_already_up_to_date(self):
"""Single chain, local chain tip already matches server tip."""
ifa = self.interface
ifa.tip = 5
ifa.blockchain = MockBlockchain(["00a", "01a", "02a", "03a", "04a", "05a"])
blockchain.blockchains = {
"00a": ifa.blockchain,
}
ifa.q.put_nowait({'block_height': 5, 'mock': {CRM.CATCHUP:1, 'id': '05a', 'prev_id': '04a'}})
res = await ifa.sync_until(ifa.tip)
self.assertEqual((CRM.CATCHUP, 6), res)
self.assertEqual(ifa.q.qsize(), 0)
self.assertEqual(len(blockchain.blockchains), 1)
async def test_catchup_client_ahead_of_lagging_server(self):
"""Single chain, server is lagging."""
ifa = self.interface
ifa.tip = 3
ifa.blockchain = MockBlockchain(["00a", "01a", "02a", "03a", "04a", "05a"])
blockchain.blockchains = {
"00a": ifa.blockchain,
}
ifa.q.put_nowait({'block_height': 3, 'mock': {CRM.CATCHUP:1, 'id': '03a', 'prev_id': '02a'}})
res = await ifa.sync_until(ifa.tip)
self.assertEqual((CRM.CATCHUP, 4), res)
self.assertEqual(ifa.q.qsize(), 0)
self.assertEqual(len(blockchain.blockchains), 1)
async def test_catchup_fast_forward(self):
"""Single chain, but client is behind. The client's height is 5, server is already on block 12.
- first missing block found during *backward* phase
"""
ifa = self.interface
ifa.tip = 12
ifa.blockchain = MockBlockchain(["00a", "01a", "02a", "03a", "04a", "05a"])
blockchain.blockchains = {
"00a": ifa.blockchain,
}
ifa.q.put_nowait({'block_height': 12, 'mock': {CRM.CATCHUP:1, 'id': '12a', 'prev_id': '11a'}})
ifa.q.put_nowait({'block_height': 6, 'mock': {CRM.BACKWARD:1, 'id': '06a', 'prev_id': '05a'}})
ifa.q.put_nowait({'block_height': 7, 'mock': {CRM.CATCHUP: 1, 'id': '07a', 'prev_id': '06a'}})
ifa.q.put_nowait({'block_height': 8, 'mock': {CRM.CATCHUP: 1, 'id': '08a', 'prev_id': '07a'}})
ifa.q.put_nowait({'block_height': 9, 'mock': {CRM.CATCHUP: 1, 'id': '09a', 'prev_id': '08a'}})
res = await ifa.sync_until(ifa.tip, next_height=9)
self.assertEqual((CRM.CATCHUP, 10), res)
self.assertEqual(ifa.q.qsize(), 0)
self.assertEqual(len(blockchain.blockchains), 1)
async def test_fork(self): async def test_fork(self):
"""client starts on main chain, has no knowledge of any fork. """client starts on main chain, has no knowledge of any fork.
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.
- first missing block found during *binary* phase
- is *new* fork
""" """
ifa = self.interface ifa = self.interface
ifa.tip = 8 ifa.tip = 8
@@ -119,15 +184,16 @@ class TestNetwork(ElectrumTestCase):
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': 5, 'mock': {CRM.BACKWARD:1, 'id': '05a', 'prev_id': '04a'}}) ifa.q.put_nowait({'block_height': 5, 'mock': {CRM.BACKWARD: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(ifa.tip, next_height=7)
self.assertEqual((CRM.FORK, 8), res) self.assertEqual((CRM.FORK, 8), res)
self.assertEqual(ifa.q.qsize(), 0) self.assertEqual(ifa.q.qsize(), 0)
self.assertEqual(len(blockchain.blockchains), 2)
# 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 4. """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 5 during backward search (which directly builds on top the existing fork). - first missing block found during *backward* phase
- is *existing* fork
""" """
ifa = self.interface ifa = self.interface
ifa.tip = 8 ifa.tip = 8
@@ -140,14 +206,16 @@ class TestNetwork(ElectrumTestCase):
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': 5, 'mock': {CRM.BACKWARD:1, 'id': '05b', 'prev_id': '04b'}}) ifa.q.put_nowait({'block_height': 5, 'mock': {CRM.BACKWARD: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(ifa.tip, next_height=6)
self.assertEqual((CRM.CATCHUP, 7), res) self.assertEqual((CRM.CATCHUP, 7), res)
self.assertEqual(ifa.q.qsize(), 0) self.assertEqual(ifa.q.qsize(), 0)
self.assertEqual(len(blockchain.blockchains), 2)
# finds forkpoint during binary, new fork
async def test_chain_false_during_binary(self): async def test_chain_false_during_binary(self):
"""client starts on main chain, has no knowledge of any fork. """client starts on main chain, has no knowledge of any fork.
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.
- first missing block found during *binary* phase
- is *new* fork
""" """
ifa = self.interface ifa = self.interface
ifa.tip = 8 ifa.tip = 8
@@ -163,9 +231,37 @@ class TestNetwork(ElectrumTestCase):
ifa.q.put_nowait({'block_height': 4, 'mock': {CRM.BINARY:1, 'id': '04b', 'prev_id': '03a'}}) 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(ifa.tip, next_height=6)
self.assertEqual((CRM.CATCHUP, 7), res) self.assertEqual((CRM.CATCHUP, 7), res)
self.assertEqual(ifa.q.qsize(), 0) self.assertEqual(ifa.q.qsize(), 0)
self.assertEqual(len(blockchain.blockchains), 2)
async def test_chain_true_during_binary(self):
"""client starts on main chain. client already knows about another fork, which has local height 10.
server is on that fork but has more blocks.
- first missing block found during *binary* phase
- is *existing* fork
"""
ifa = self.interface
ifa.tip = 20
ifa.blockchain = MockBlockchain(["00a", "01a", "02a", "03a", "04a", "05a", "06a", "07a", "08a", "09a", "10a", "11a", "12a", "13a", "14a"])
blockchain.blockchains = {
"00a": ifa.blockchain,
"07b": MockBlockchain(["00a", "01a", "02a", "03a", "04a", "05a", "06a", "07b", "08b", "09b", "10b"]),
}
ifa.q.put_nowait({'block_height': 20, 'mock': {CRM.CATCHUP:1, 'id': '20b', 'prev_id': '19b'}})
ifa.q.put_nowait({'block_height': 15, 'mock': {CRM.BACKWARD:1, 'id': '15b', 'prev_id': '14b'}})
ifa.q.put_nowait({'block_height': 13, 'mock': {CRM.BACKWARD:1, 'id': '13b', 'prev_id': '12b'}})
ifa.q.put_nowait({'block_height': 9, 'mock': {CRM.BACKWARD:1, 'id': '09b', 'prev_id': '08b'}})
ifa.q.put_nowait({'block_height': 11, 'mock': {CRM.BINARY:1, 'id': '11b', 'prev_id': '10b'}})
ifa.q.put_nowait({'block_height': 10, 'mock': {CRM.BINARY:1, 'id': '10b', 'prev_id': '09b'}})
ifa.q.put_nowait({'block_height': 11, 'mock': {CRM.CATCHUP:1, 'id': '11b', 'prev_id': '10b'}})
ifa.q.put_nowait({'block_height': 12, 'mock': {CRM.CATCHUP:1, 'id': '12b', 'prev_id': '11b'}})
ifa.q.put_nowait({'block_height': 13, 'mock': {CRM.CATCHUP:1, 'id': '13b', 'prev_id': '12b'}})
res = await ifa.sync_until(ifa.tip, next_height=13)
self.assertEqual((CRM.CATCHUP, 14), res)
self.assertEqual(ifa.q.qsize(), 0)
self.assertEqual(len(blockchain.blockchains), 2)
if __name__ == "__main__": if __name__ == "__main__":