예제 #1
0
 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()
예제 #2
0
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
예제 #3
0
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
예제 #4
0
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'
                                  })
                              ])