def setUp(self): self.interface = create_autospec(P2PInterface) self.repo = create_autospec(Repository) self.loopmock = Mock() self.delayer = Mock() self.sut = BlocksReactor(self.repo, self.interface, self.loopmock, keep_blocks=5, delayed_task=self.delayer) self.loop = asyncio.get_event_loop()
def builder(ctx: Context): # pragma: no cover from spruned.application.cache import CacheAgent from spruned.repositories.repository import Repository from spruned.daemon.tasks.blocks_reactor import BlocksReactor from spruned.daemon.tasks.headers_reactor import HeadersReactor from spruned.application import spruned_vo_service from spruned.application.jsonrpc_server import JSONRPCServer from spruned.daemon.electrod import build as electrod_builder from spruned.daemon.bitcoin_p2p import build as p2p_builder electrod_connectionpool, electrod_interface = electrod_builder(ctx) p2p_connectionpool, p2p_interface = p2p_builder(ctx.get_network()) repository = Repository.instance() cache = CacheAgent(repository, int(ctx.cache_size)) repository.set_cache(cache) service = spruned_vo_service.SprunedVOService(electrod_interface, p2p_interface, repository=repository, cache=cache) jsonrpc_server = JSONRPCServer(ctx.rpcbind, ctx.rpcport, ctx.rpcuser, ctx.rpcpassword) jsonrpc_server.set_vo_service(service) headers_reactor = HeadersReactor(repository.headers, electrod_interface) blocks_reactor = BlocksReactor(repository, p2p_interface, prune=int(ctx.keep_blocks)) return jsonrpc_server, headers_reactor, blocks_reactor, repository, cache
def builder(ctx: Context): # pragma: no cover from spruned.application.cache import CacheAgent from spruned.repositories.repository import Repository from spruned.daemon.tasks.blocks_reactor import BlocksReactor from spruned.daemon.tasks.headers_reactor import HeadersReactor from spruned.application import spruned_vo_service from spruned.application.jsonrpc_server import JSONRPCServer from spruned.daemon.electrod import build as electrod_builder from spruned.daemon.bitcoin_p2p import build as p2p_builder electrod_connectionpool, electrod_interface = electrod_builder(ctx) p2p_connectionpool, p2p_interface = p2p_builder(ctx) repository = Repository.instance() cache = CacheAgent(repository, int(ctx.cache_size)) repository.set_cache(cache) service = spruned_vo_service.SprunedVOService( electrod_interface, p2p_interface, repository=repository, cache_agent=cache, context=ctx, fallback_non_segwit_blocks=True) jsonrpc_server = JSONRPCServer(ctx.rpcbind, ctx.rpcport, ctx.rpcuser, ctx.rpcpassword) jsonrpc_server.set_vo_service(service) headers_reactor = HeadersReactor(repository.headers, electrod_interface) if ctx.mempool_size: from spruned.application.mempool_observer import MempoolObserver mempool_observer = MempoolObserver(repository, p2p_interface) headers_reactor.add_on_new_header_callback( mempool_observer.on_block_header) p2p_interface.mempool = repository.mempool p2p_connectionpool.add_on_transaction_callback( mempool_observer.on_transaction) p2p_connectionpool.add_on_transaction_hash_callback( mempool_observer.on_transaction_hash) else: mempool_observer = None zmq_context = zmq_observer = None if ctx.is_zmq_enabled(): zmq_context, zmq_observer = build_zmq(ctx, mempool_observer, headers_reactor, ctx.mempool_size, service) blocks_reactor = BlocksReactor(repository, p2p_interface, keep_blocks=int(ctx.keep_blocks)) headers_reactor.add_on_best_height_hit_persistent_callbacks( p2p_connectionpool.set_best_header) return jsonrpc_server, headers_reactor, blocks_reactor, repository, \ cache, zmq_context, zmq_observer, p2p_interface
class TestBlocksReactory(TestCase): def setUp(self): self.interface = create_autospec(P2PInterface) self.repo = create_autospec(Repository) self.loopmock = Mock() self.delayer = Mock() self.sut = BlocksReactor(self.repo, self.interface, self.loopmock, prune=5, delayed_task=self.delayer) self.loop = asyncio.get_event_loop() def test_check_blockchain_local_behind_remote(self): self.sut.set_last_processed_block({ 'block_hash': 'cafe', 'block_height': 9 }) self.repo.headers.get_best_header.return_value = { 'block_hash': 'babe', 'block_height': 10 } self.interface.get_blocks.return_value = async_coro( {'babe': { 'block_hash': 'babe', 'block_bytes': b'raw' }}) self.repo.headers.get_headers_since_height.return_value = [{ 'block_hash': 'babe', 'block_height': 10 }] self.repo.blockchain.get_block.return_value = None self.repo.blockchain.save_blocks.side_effect = lambda *x: x self.loop.run_until_complete(self.sut.check()) Mock.assert_called_once_with(self.repo.blockchain.save_blocks, { 'block_hash': 'babe', 'block_bytes': b'raw' }) self.assertEqual(self.sut._last_processed_block, { 'block_hash': 'babe', 'block_height': 10 }) Mock.assert_called_once_with(self.repo.headers.get_best_header) Mock.assert_called_once_with(self.interface.get_blocks, 'babe') Mock.assert_called_once_with( self.repo.headers.get_headers_since_height, 9, limit=10) Mock.assert_called_once_with(self.repo.blockchain.get_block, 'babe', with_transactions=False) Mock.assert_called_once_with(self.repo.blockchain.save_blocks, { 'block_hash': 'babe', 'block_bytes': b'raw' }) def test_check_blockchain_local_behind_remote_but_block_already_stored( self): self.sut.set_last_processed_block({ 'block_hash': 'cafe', 'block_height': 9 }) self.repo.headers.get_best_header.return_value = { 'block_hash': 'babe', 'block_height': 10 } self.interface.get_blocks.return_value = async_coro( {'babe': { 'block_hash': 'babe', 'block_bytes': b'raw' }}) self.repo.headers.get_headers_since_height.return_value = [{ 'block_hash': 'babe', 'block_height': 10 }] self.repo.blockchain.get_block.return_value = { 'block_hash': 'babe', 'block_bytes': b'raw' } self.repo.blockchain.save_blocks.side_effect = lambda *x: x self.loop.run_until_complete(self.sut.check()) Mock.assert_called_once_with(self.repo.headers.get_best_header) Mock.assert_called_once_with( self.repo.headers.get_headers_since_height, 9, limit=10) Mock.assert_called_once_with(self.repo.blockchain.get_block, 'babe', with_transactions=False) Mock.assert_not_called(self.interface.get_blocks) Mock.assert_not_called(self.repo.blockchain.save_blocks) self.assertEqual(self.sut._last_processed_block, { 'block_hash': 'babe', 'block_height': 10 }) def test_check_blockchain_local_behind_remote_error_saving_block(self): self.sut.set_last_processed_block({ 'block_hash': 'cafe', 'block_height': 9 }) self.repo.headers.get_best_header.return_value = { 'block_hash': 'babe', 'block_height': 10 } self.interface.get_blocks.return_value = async_coro( {'babe': { 'block_hash': 'babe', 'block_bytes': b'raw' }}) self.repo.headers.get_headers_since_height.return_value = [{ 'block_hash': 'babe', 'block_height': 10 }] self.repo.blockchain.get_block.return_value = None self.repo.blockchain.save_blocks.side_effect = ValueError self.loop.run_until_complete(self.sut.check()) Mock.assert_called_once_with(self.repo.blockchain.save_blocks, { 'block_hash': 'babe', 'block_bytes': b'raw' }) Mock.assert_called_once_with(self.repo.headers.get_best_header) Mock.assert_called_once_with(self.interface.get_blocks, 'babe') Mock.assert_called_once_with( self.repo.headers.get_headers_since_height, 9, limit=10) Mock.assert_called_once_with(self.repo.blockchain.get_block, 'babe', with_transactions=False) self.assertEqual(self.sut._last_processed_block, { 'block_hash': 'cafe', 'block_height': 9 }) def test_check_blockchain_local_a_lot_behind(self): """ something bad happened around block 16, we saved it, we didn't tracked it and we start over 5 blocks later. also, the last block tracked by the blockheader reactor was stuck ad block 9. basically, everything is screwed up, but we recover and download the needed blocks. """ self.sut.set_last_processed_block({ 'block_hash': 'cafe', 'block_height': 9 }) self.repo.headers.get_best_header.return_value = { 'block_hash': 'babe', 'block_height': 20 } self.interface.get_blocks.return_value = async_coro({ 'block17': { 'block_hash': 'block17', 'block_bytes': b'raw' }, 'block18': { 'block_hash': 'block18', 'block_bytes': b'raw' }, 'block19': { 'block_hash': 'block19', 'block_bytes': b'raw' }, 'block20': { 'block_hash': 'block20', 'block_bytes': b'raw' } }) self.repo.headers.get_headers_since_height.return_value = [{ 'block_hash': 'block16', 'block_height': 16 }, { 'block_hash': 'block17', 'block_height': 17 }, { 'block_hash': 'block18', 'block_height': 18 }, { 'block_hash': 'block19', 'block_height': 19 }, { 'block_hash': 'block20', 'block_height': 20 }] self.repo.blockchain.get_block.side_effect = [{ 'block_hash': 'block16', 'block_bytes': b'raw' }, None, None, None, None] self.repo.blockchain.save_blocks.side_effect = [{ 'block_hash': 'block17', 'block_bytes': b'raw' }, { 'block_hash': 'block18', 'block_bytes': b'raw' }, { 'block_hash': 'block19', 'block_bytes': b'raw' }, { 'block_hash': 'block20', 'block_bytes': b'raw' }] self.repo.blockchain.save_blocks.side_effect = lambda *x: x self.loop.run_until_complete(self.sut.check()) Mock.assert_called_once_with(self.repo.headers.get_best_header) Mock.assert_called_once_with( self.repo.headers.get_headers_since_height, 15, limit=10) Mock.assert_has_calls(self.repo.blockchain.get_block, calls=[ call('block16', with_transactions=False), call('block17', with_transactions=False), call('block18', with_transactions=False), call('block19', with_transactions=False), call('block20', with_transactions=False) ]) Mock.assert_called_once_with(self.interface.get_blocks, 'block17', 'block18', 'block19', 'block20') Mock.assert_called_once_with(self.repo.blockchain.save_blocks, { 'block_hash': 'block17', 'block_bytes': b'raw' }, { 'block_hash': 'block18', 'block_bytes': b'raw' }, { 'block_hash': 'block19', 'block_bytes': b'raw' }, { 'block_hash': 'block20', 'block_bytes': b'raw' }) self.assertEqual(self.sut._last_processed_block, { 'block_hash': 'block20', 'block_height': 20 }) def test_check_corners_orphaned(self): self.sut.set_last_processed_block({ 'block_hash': 'cafe', 'block_height': 9 }) self.assertEqual(self.sut._last_processed_block, { 'block_hash': 'cafe', 'block_height': 9 }) self.repo.headers.get_best_header.return_value = { 'block_hash': 'babe', 'block_height': 8 } self.loop.run_until_complete(self.sut.check()) self.assertEqual(self.sut._last_processed_block, None) def test_check_corners_reorg(self): self.sut.set_last_processed_block({ 'block_hash': 'cafe', 'block_height': 9 }) self.assertEqual(self.sut._last_processed_block, { 'block_hash': 'cafe', 'block_height': 9 }) self.repo.headers.get_best_header.return_value = { 'block_hash': 'babe', 'block_height': 9 } self.loop.run_until_complete(self.sut.check()) self.assertEqual(self.sut._last_processed_block, None) def test_bootstrap(self): self.interface.pool = Mock(_busy_peers=[]) self.interface.pool.required_connections = 1 self.interface.pool.established_connections = [] async def add_peers(): await asyncio.sleep(0) for _ in range(0, 8): self.interface.pool.established_connections.append(1) self.repo.headers.get_best_header.return_value = { 'block_hash': 'cafe', 'block_height': 6 } self.repo.headers.get_headers_since_height.return_value = [{ 'block_hash': 'block2', 'block_height': 2 }, { 'block_hash': 'block3', 'block_height': 3 }, { 'block_hash': 'block4', 'block_height': 4 }, { 'block_hash': 'block5', 'block_height': 5 }, { 'block_hash': 'block6', 'block_height': 6 }] self.repo.blockchain.get_block.side_effect = [{ 'block_hash': 'block2', 'block_bytes': b'raw' }, None, None, None, None] self.interface.get_block.side_effect = [ async_coro({ 'block_hash': 'block3', 'block_bytes': b'raw' }), async_coro({ 'block_hash': 'block4', 'block_bytes': b'raw' }), async_coro({ 'block_hash': 'block5', 'block_bytes': b'raw' }), async_coro({ 'block_hash': 'block6', 'block_bytes': b'raw' }), ] self.loop.run_until_complete( asyncio.gather(add_peers(), self.sut.bootstrap_blocks())) Mock.assert_has_calls(self.repo.blockchain.save_block, calls=[ call({ 'block_hash': 'block3', 'block_bytes': b'raw' }), call({ 'block_hash': 'block4', 'block_bytes': b'raw' }), call({ 'block_hash': 'block5', 'block_bytes': b'raw' }), call({ 'block_hash': 'block6', 'block_bytes': b'raw' }) ])