tests: test_network: intro MockBlockchain. rewrite tests to use it.
interface.py no longer has knowledge about mocking! :P
This commit is contained in:
@@ -224,7 +224,7 @@ class TestBlockchain(ElectrumTestCase):
|
||||
self.assertEqual(os.path.join(self.data_dir, "blockchain_headers"), chain_l.path())
|
||||
self.assertEqual(11 * 80, os.stat(chain_l.path()).st_size)
|
||||
for b in (chain_u, chain_l):
|
||||
self.assertTrue(all([b.can_connect(b.read_header(i), False) for i in range(b.height())]))
|
||||
self.assertTrue(all([b.can_connect(b.read_header(i), check_height=False) for i in range(b.height())]))
|
||||
|
||||
self._append_header(chain_u, self.HEADERS['S'])
|
||||
self._append_header(chain_u, self.HEADERS['T'])
|
||||
@@ -259,7 +259,7 @@ class TestBlockchain(ElectrumTestCase):
|
||||
self.assertEqual(os.path.join(self.data_dir, "forks", "fork2_6_5c400c7966145d56291080b6482716a16aa644eefe590f984c1da0ee46ed33b8_aff81830e28e01ef7d23277c56779a6b93f251a2d50dcc09d7c87d119e1e8ab"), chain_u.path())
|
||||
self.assertEqual(7 * 80, os.stat(chain_u.path()).st_size)
|
||||
for b in (chain_u, chain_l, chain_z):
|
||||
self.assertTrue(all([b.can_connect(b.read_header(i), False) for i in range(b.height())]))
|
||||
self.assertTrue(all([b.can_connect(b.read_header(i), check_height=False) for i in range(b.height())]))
|
||||
|
||||
self.assertEqual(constants.net.GENESIS, chain_z.get_hash(0))
|
||||
self.assertEqual(hash_header(self.HEADERS['F']), chain_z.get_hash(5))
|
||||
@@ -334,7 +334,7 @@ class TestBlockchain(ElectrumTestCase):
|
||||
self.assertEqual(hash_header(self.HEADERS['X']), chain_z.get_hash(11))
|
||||
|
||||
for b in (chain_u, chain_l, chain_z):
|
||||
self.assertTrue(all([b.can_connect(b.read_header(i), False) for i in range(b.height())]))
|
||||
self.assertTrue(all([b.can_connect(b.read_header(i), check_height=False) for i in range(b.height())]))
|
||||
|
||||
def get_chains_that_contain_header_helper(self, header: dict):
|
||||
height = header['block_height']
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import asyncio
|
||||
import tempfile
|
||||
import unittest
|
||||
from typing import List
|
||||
|
||||
from electrum import constants
|
||||
from electrum.simple_config import SimpleConfig
|
||||
@@ -16,6 +17,42 @@ from . import ElectrumTestCase
|
||||
CRM = ChainResolutionMode
|
||||
|
||||
|
||||
class MockBlockchain:
|
||||
|
||||
def __init__(self, headers: List[str]):
|
||||
self._headers = headers
|
||||
self.forkpoint = len(headers)
|
||||
|
||||
def height(self) -> int:
|
||||
return len(self._headers) - 1
|
||||
|
||||
def save_header(self, header: dict) -> None:
|
||||
assert header['block_height'] == self.height()+1, f"new {header['block_height']=}, cur {self.height()=}"
|
||||
self._headers.append(header['mock']['id'])
|
||||
|
||||
def check_header(self, header: dict) -> bool:
|
||||
return header['mock']['id'] in self._headers
|
||||
|
||||
def can_connect(self, header: dict, *, check_height: bool = True) -> bool:
|
||||
height = header['block_height']
|
||||
if check_height and self.height() != height - 1:
|
||||
return False
|
||||
if self.check_header(header):
|
||||
return True
|
||||
return header['mock']['prev_id'] in self._headers
|
||||
|
||||
def fork(parent, header: dict) -> 'MockBlockchain':
|
||||
if not parent.can_connect(header, check_height=False):
|
||||
raise Exception("forking header does not connect to parent chain")
|
||||
forkpoint = header.get('block_height')
|
||||
self = MockBlockchain(parent._headers[:forkpoint])
|
||||
self.save_header(header)
|
||||
chain_id = header['mock']['id']
|
||||
with blockchain.blockchains_lock:
|
||||
blockchain.blockchains[chain_id] = self
|
||||
return self
|
||||
|
||||
|
||||
class MockNetwork:
|
||||
|
||||
def __init__(self, config: SimpleConfig):
|
||||
@@ -30,9 +67,6 @@ class MockInterface(Interface):
|
||||
network = MockNetwork(config)
|
||||
super().__init__(network=network, server=ServerAddr.from_str('mock-server:50000:t'))
|
||||
self.q = asyncio.Queue()
|
||||
self.blockchain = blockchain.Blockchain(config=self.config, forkpoint=0,
|
||||
parent=None, forkpoint_hash=constants.net.GENESIS, prev_hash=None)
|
||||
self.set_tip(0)
|
||||
|
||||
async def get_block_header(self, height: int, *, mode: ChainResolutionMode) -> dict:
|
||||
assert self.q.qsize() > 0, (height, mode)
|
||||
@@ -48,10 +82,6 @@ class MockInterface(Interface):
|
||||
async def _maybe_warm_headers_cache(self, *args, **kwargs):
|
||||
return
|
||||
|
||||
def set_tip(self, tip: int):
|
||||
self.tip = tip
|
||||
self.blockchain._size = self.tip + 1
|
||||
|
||||
|
||||
class TestNetwork(ElectrumTestCase):
|
||||
|
||||
@@ -65,6 +95,10 @@ class TestNetwork(ElectrumTestCase):
|
||||
super().tearDownClass()
|
||||
constants.BitcoinMainnet.set_as_network()
|
||||
|
||||
def tearDown(self):
|
||||
blockchain.blockchains = {}
|
||||
super().tearDown()
|
||||
|
||||
async def asyncSetUp(self):
|
||||
await super().asyncSetUp()
|
||||
self.config = SimpleConfig({'electrum_path': self.electrum_path})
|
||||
@@ -76,16 +110,15 @@ class TestNetwork(ElectrumTestCase):
|
||||
server is on other side of chain split, the last common block is height 6.
|
||||
"""
|
||||
ifa = self.interface
|
||||
ifa.set_tip(12) # FIXME how could the server tip be this high? for local chain, it's ok though.
|
||||
blockchain.blockchains = {}
|
||||
ifa.q.put_nowait({'block_height': 8, 'mock': {CRM.CATCHUP:1, 'check': lambda x: False, 'connect': lambda x: False}})
|
||||
def mock_connect(height):
|
||||
return height == 6
|
||||
ifa.q.put_nowait({'block_height': 7, 'mock': {CRM.BACKWARD:1,'check': lambda x: False, 'connect': mock_connect, 'fork': self.mock_fork}})
|
||||
ifa.q.put_nowait({'block_height': 2, 'mock': {CRM.BACKWARD:1,'check':lambda x: True, 'connect': lambda x: False}})
|
||||
ifa.q.put_nowait({'block_height': 4, 'mock': {CRM.BINARY:1,'check':lambda x: True, 'connect': lambda x: True}})
|
||||
ifa.q.put_nowait({'block_height': 5, 'mock': {CRM.BINARY:1,'check':lambda x: True, 'connect': lambda x: True}})
|
||||
ifa.q.put_nowait({'block_height': 6, 'mock': {CRM.BINARY:1,'check':lambda x: True, 'connect': lambda x: True}})
|
||||
ifa.tip = 12 # FIXME how could the server tip be this high?
|
||||
ifa.blockchain = MockBlockchain(["00a", "01a", "02a", "03a", "04a", "05a", "06a", "07a", "08a", "09a", "10a", "11a", "12a"])
|
||||
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': 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': 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'}})
|
||||
res = await ifa.sync_until(8, next_height=7)
|
||||
self.assertEqual((CRM.FORK, 8), res)
|
||||
self.assertEqual(ifa.q.qsize(), 0)
|
||||
@@ -97,42 +130,37 @@ class TestNetwork(ElectrumTestCase):
|
||||
client happens to ask for header at height 2 during backward search (which directly builds on top the existing fork).
|
||||
"""
|
||||
ifa = self.interface
|
||||
ifa.set_tip(12) # FIXME how could the server tip be this high? for local chain, it's ok though.
|
||||
blockchain.blockchains = {}
|
||||
ifa.q.put_nowait({'block_height': 8, 'mock': {CRM.CATCHUP:1, 'check': lambda x: False, 'connect': lambda x: False}})
|
||||
def mock_connect(height):
|
||||
return height == 2
|
||||
ifa.q.put_nowait({'block_height': 7, 'mock': {CRM.BACKWARD:1, 'check': lambda x: False, 'connect': mock_connect}})
|
||||
ifa.q.put_nowait({'block_height': 2, 'mock': {CRM.BACKWARD:1, 'check': lambda x: False, 'connect': mock_connect}})
|
||||
ifa.q.put_nowait({'block_height': 3, 'mock': {CRM.CATCHUP:1, 'check': lambda x: False, 'connect': lambda x: True}})
|
||||
ifa.q.put_nowait({'block_height': 4, 'mock': {CRM.CATCHUP:1, 'check': lambda x: False, 'connect': lambda x: True}})
|
||||
ifa.tip = 12 # FIXME how could the server tip be this high?
|
||||
ifa.blockchain = MockBlockchain(["00a", "01a", "02a", "03a", "04a", "05a", "06a", "07a", "08a", "09a", "10a", "11a", "12a"])
|
||||
blockchain.blockchains = {
|
||||
"00a": ifa.blockchain,
|
||||
"01b": MockBlockchain(["00a", "01b"]),
|
||||
}
|
||||
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': 2, 'mock': {CRM.BACKWARD:1, 'id': '02b', 'prev_id': '01b'}})
|
||||
ifa.q.put_nowait({'block_height': 3, 'mock': {CRM.CATCHUP:1, 'id': '03b', 'prev_id': '02b'}})
|
||||
ifa.q.put_nowait({'block_height': 4, 'mock': {CRM.CATCHUP:1, 'id': '04b', 'prev_id': '03b'}})
|
||||
res = await ifa.sync_until(8, next_height=4)
|
||||
self.assertEqual((CRM.CATCHUP, 5), res)
|
||||
self.assertEqual(ifa.q.qsize(), 0)
|
||||
|
||||
def mock_fork(self, bad_header):
|
||||
forkpoint = bad_header['block_height']
|
||||
self.interface.logger.debug(f"mock_fork() called with {forkpoint=}")
|
||||
b = blockchain.Blockchain(config=self.config, forkpoint=forkpoint, parent=None,
|
||||
forkpoint_hash=sha256(str(forkpoint)).hex(), prev_hash=sha256(str(forkpoint-1)).hex())
|
||||
return b
|
||||
|
||||
# finds forkpoint during binary, new fork
|
||||
async def test_chain_false_during_binary(self):
|
||||
"""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.
|
||||
"""
|
||||
ifa = self.interface
|
||||
ifa.set_tip(12) # FIXME how could the server tip be this high? for local chain, it's ok though.
|
||||
blockchain.blockchains = {}
|
||||
ifa.q.put_nowait({'block_height': 8, 'mock': {CRM.CATCHUP:1, 'check': lambda x: False, 'connect': lambda x: False}})
|
||||
mock_connect = lambda height: height == 3
|
||||
ifa.q.put_nowait({'block_height': 7, 'mock': {CRM.BACKWARD:1, 'check': lambda x: False, 'connect': mock_connect}})
|
||||
ifa.q.put_nowait({'block_height': 2, 'mock': {CRM.BACKWARD:1, 'check': lambda x: True, 'connect': mock_connect}})
|
||||
ifa.q.put_nowait({'block_height': 4, 'mock': {CRM.BINARY:1, 'check': lambda x: False, 'fork': self.mock_fork, 'connect': mock_connect}})
|
||||
ifa.q.put_nowait({'block_height': 3, 'mock': {CRM.BINARY:1, 'check': lambda x: True, 'connect': lambda x: True}})
|
||||
ifa.q.put_nowait({'block_height': 5, 'mock': {CRM.CATCHUP:1, 'check': lambda x: False, 'connect': lambda x: True}})
|
||||
ifa.q.put_nowait({'block_height': 6, 'mock': {CRM.CATCHUP:1, 'check': lambda x: False, 'connect': lambda x: True}})
|
||||
ifa.tip = 12 # FIXME how could the server tip be this high?
|
||||
ifa.blockchain = MockBlockchain(["00a", "01a", "02a", "03a", "04a", "05a", "06a", "07a", "08a", "09a", "10a", "11a", "12a"])
|
||||
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': 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': 4, 'mock': {CRM.BINARY:1, 'id': '04b', 'prev_id': '03a'}})
|
||||
ifa.q.put_nowait({'block_height': 3, 'mock': {CRM.BINARY:1, 'id': '03a', 'prev_id': '02a'}})
|
||||
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'}})
|
||||
res = await ifa.sync_until(8, next_height=6)
|
||||
self.assertEqual((CRM.CATCHUP, 7), res)
|
||||
self.assertEqual(ifa.q.qsize(), 0)
|
||||
|
||||
Reference in New Issue
Block a user