def setUp(self): self.connectionpool = create_autospec(ElectrodConnectionPool) self.electrod_loop = Mock() self.sut = ElectrodInterface(self.connectionpool, self.electrod_loop) self.loop = asyncio.get_event_loop() self.electrum_header = { 'height': 513526, 'hex': '00000020fe52010fa7c798b97621508b772142dfc7b594df7a3a3200000000000000000097db2dc94e2799bcdd7259f0876467b379fe44b69342df38a8ec2f350722f9a73367a95aa38955173732b883' } self.parsed_header = { 'block_hash': '0000000000000000001e9a7d5bdb53bf19487d50d9e68e04b7254662d693a6ea', 'block_height': 513526, 'header_bytes': b'\x00\x00\x00 \xfeR\x01\x0f\xa7\xc7\x98\xb9v!P\x8bw!B\xdf\xc7\xb5\x94\xdfz:2\x00\x00' b'\x00\x00\x00\x00\x00\x00\x00\x97\xdb-\xc9N\'\x99\xbc\xddrY\xf0\x87dg\xb3y\xfeD\xb6' b'\x93B\xdf8\xa8\xec/5\x07"\xf9\xa73g\xa9Z\xa3\x89U\x1772\xb8\x83', 'prev_block_hash': '000000000000000000323a7adf94b5c7df4221778b502176b998c7a70f0152fe', 'timestamp': 1521051443 }
def build(ctx, loop=asyncio.get_event_loop()): # pragma: no cover from spruned.daemon.electrod.electrod_connection import ElectrodConnectionPool from spruned.daemon.electrod.electrod_interface import ElectrodInterface from spruned.daemon.electrod.electrod_fee_estimation import EstimateFeeConsensusProjector, \ EstimateFeeConsensusCollector network = ctx.get_network() peers = load_electrum_servers(ctx) fees_collector = EstimateFeeConsensusCollector(proxy=ctx.proxy) _ = [ fees_collector.add_peer(peer) for peer in [x[0] + '/' + x[1] for x in peers] ] electrod_pool = ElectrodConnectionPool( connections=network['electrum_concurrency'], peers=peers, ipv6=False, proxy=ctx.proxy, tor=ctx.tor) electrod_interface = ElectrodInterface( electrod_pool, loop, fees_projector=EstimateFeeConsensusProjector(), fees_collector=fees_collector) electrod_interface.add_on_connected_callback( electrod_interface.bootstrap_collector) return electrod_pool, electrod_interface
def setUp(self): self.connectionpool = create_autospec(ElectrodConnectionPool) self.electrod_loop = Mock() self.sut = ElectrodInterface(self.connectionpool, self.electrod_loop) self.loop = asyncio.get_event_loop() self.electrum_header = { 'block_height': 513526, 'version': 536870912, 'prev_block_hash': '000000000000000000323a7adf94b5c7df4221778b502176b998c7a70f0152fe', 'merkle_root': 'a7f92207352feca838df4293b644fe79b3676487f05972ddbc99274ec92ddb97', 'timestamp': 1521051443, 'bits': 391481763, 'nonce': 2209886775 } self.parsed_header = { 'block_hash': '0000000000000000001e9a7d5bdb53bf19487d50d9e68e04b7254662d693a6ea', 'block_height': 513526, 'header_bytes': b'\x00\x00\x00 \xfeR\x01\x0f\xa7\xc7\x98\xb9v!P\x8bw!B\xdf\xc7\xb5\x94\xdfz:2\x00\x00' b'\x00\x00\x00\x00\x00\x00\x00\x97\xdb-\xc9N\'\x99\xbc\xddrY\xf0\x87dg\xb3y\xfeD\xb6' b'\x93B\xdf8\xa8\xec/5\x07"\xf9\xa73g\xa9Z\xa3\x89U\x1772\xb8\x83', 'prev_block_hash': '000000000000000000323a7adf94b5c7df4221778b502176b998c7a70f0152fe', 'timestamp': 1521051443 }
def setUp(self): self.pool = Mock() self.fees_projector = EstimateFeeConsensusProjector() self.fees_collector = EstimateFeeConsensusCollector() self.fees_collector.add_permanent_connections_pool(self.pool) self.sut = ElectrodInterface(self.pool, fees_collector=self.fees_collector, fees_projector=self.fees_projector) self.loop = asyncio.get_event_loop()
def build(ctx, loop=asyncio.get_event_loop()): # pragma: no cover from spruned.daemon.electrod.electrod_connection import ElectrodConnectionPool from spruned.daemon.electrod.electrod_interface import ElectrodInterface network = ctx.get_network() electrod_pool = ElectrodConnectionPool( connections=network["electrum_concurrency"], peers=load_electrum_servers(ctx)) electrod_interface = ElectrodInterface(electrod_pool, loop) return electrod_pool, electrod_interface
def build(network, loop=asyncio.get_event_loop()): # pragma: no cover def load_electrum_servers(network): _current_path = os.path.dirname(os.path.abspath(__file__)) with open(_current_path + '/electrum_servers.json', 'r') as f: servers = json.load(f) return servers[network] electrod_pool = ElectrodConnectionPool( connections=network["electrum_concurrency"], peers=load_electrum_servers(network["electrum_servers"])) electrod_interface = ElectrodInterface(electrod_pool, loop) return electrod_pool, electrod_interface
class TestFeeEstimation(TestCase): def setUp(self): self.pool = Mock() self.fees_projector = EstimateFeeConsensusProjector() self.fees_collector = EstimateFeeConsensusCollector() self.fees_collector.add_permanent_connections_pool(self.pool) self.sut = ElectrodInterface(self.pool, fees_collector=self.fees_collector, fees_projector=self.fees_projector) self.loop = asyncio.get_event_loop() def load_fee_response(self, target): m = Mock() m.RPC.side_effect = lambda x, y: async_coro(target) return m def test_estimatefee_1(self): disagree = Mock(hostname='peer3', client=self.load_fee_response(0.00005)) self.pool.established_connections = [ Mock(hostname='peer1', client=self.load_fee_response(0.00003)), Mock(hostname='peer2', client=self.load_fee_response(0.00003)), disagree ] self.pool.get_peer_for_hostname.return_value = disagree x = self.loop.run_until_complete(self.sut.estimatefee(6)) int(x.pop('timestamp')) points = x.pop('points') points.remove(5) points.remove(3) points.remove(3) self.assertEqual( x, { 'agree': True, 'agreement': 66, 'average': 3, 'average_satoshi_per_kb': 0.00003, 'disagree': ['peer3'], 'median': 3, }) Mock.assert_called_with(disagree.disconnect)
class TestElectrodInterface(unittest.TestCase): def setUp(self): self.connectionpool = create_autospec(ElectrodConnectionPool) self.electrod_loop = Mock() self.sut = ElectrodInterface(self.connectionpool, self.electrod_loop) self.loop = asyncio.get_event_loop() self.electrum_header = { 'height': 513526, 'hex': '00000020fe52010fa7c798b97621508b772142dfc7b594df7a3a3200000000000000000097db2dc94e2799bcdd7259f0876467b379fe44b69342df38a8ec2f350722f9a73367a95aa38955173732b883' } self.parsed_header = { 'block_hash': '0000000000000000001e9a7d5bdb53bf19487d50d9e68e04b7254662d693a6ea', 'block_height': 513526, 'header_bytes': b'\x00\x00\x00 \xfeR\x01\x0f\xa7\xc7\x98\xb9v!P\x8bw!B\xdf\xc7\xb5\x94\xdfz:2\x00\x00' b'\x00\x00\x00\x00\x00\x00\x00\x97\xdb-\xc9N\'\x99\xbc\xddrY\xf0\x87dg\xb3y\xfeD\xb6' b'\x93B\xdf8\xa8\xec/5\x07"\xf9\xa73g\xa9Z\xa3\x89U\x1772\xb8\x83', 'prev_block_hash': '000000000000000000323a7adf94b5c7df4221778b502176b998c7a70f0152fe', 'timestamp': 1521051443 } def tearDown(self): self.connectionpool.reset_mock() self.electrod_loop.reset_mock() def test_get_header_ok(self): peer = Mock() self.connectionpool.call.return_value = async_coro( (peer, self.electrum_header)) res = self.loop.run_until_complete(self.sut.get_header(513526)) self.assertEqual(res, self.parsed_header) bitcoind_header = "00000020fe52010fa7c798b97621508b772142dfc7b594df7a3a3200000000000000000097db2dc9" \ "4e2799bcdd7259f0876467b379fe44b69342df38a8ec2f350722f9a73367a95aa38955173732b883" self.assertEqual(binascii.unhexlify(bitcoind_header), self.parsed_header['header_bytes']) def test_get_header_invalid_pow(self): peer = Mock() peer.disconnect.return_value = 'coroutine' electrum_header = {k: v for k, v in self.electrum_header.items()} electrum_header.update({ 'hex': '00000020fe52010fa7c798b97621508b772142dfc7b594df7a3a320000000000000000001' '23456794e2791bcdd7259f0876467b379fe44169342df38a8ec2f350722f9a73' 'fffffffffffffffffffffffffff' }) self.connectionpool.call.return_value = async_coro( (peer, electrum_header)) res = self.loop.run_until_complete(self.sut.get_header(513526)) self.assertIsNone(res) Mock.assert_called_once_with(self.electrod_loop.create_task, 'coroutine') def test_get_header_checkpoint_failure(self): peer = Mock() peer.disconnect.return_value = 'coroutine' electrum_header = {k: v for k, v in self.electrum_header.items()} electrum_header.update({'height': 0}) # <- target to the moon self.connectionpool.call.return_value = async_coro( (peer, electrum_header)) res = self.loop.run_until_complete(self.sut.get_header(0)) self.assertIsNone(res) Mock.assert_called_once_with(self.electrod_loop.create_task, 'coroutine') Mock.assert_called_with(self.connectionpool.call, 'blockchain.block.get_header', 0, get_peer=True) def test_subscription_observers(self): data = [] async def callback(peer, header): data.append((peer, header)) peer = Mock() observers = [] self.connectionpool.add_header_observer.side_effect = lambda x: observers.append( x) self.sut.add_header_subscribe_callback(callback) self.loop.run_until_complete(observers[0](peer, self.electrum_header)) self.assertEqual(data[0][0], peer) self.assertEqual(data[0][1], self.parsed_header) def test_subscription_observers_pow_error(self): callback = lambda x, y: async_coro(Mock()) peer = Mock() peer.disconnect.return_value = 'coroutine' observers = [] electrum_header = {k: v for k, v in self.electrum_header.items()} electrum_header.update({ 'hex': '00000020fe52010fa7c798b97621508b772142dfc7b594df7a3a320000000000000000009' '7db2dc94e2799bcdd7259f0876467b379fe44b69342df38a8ec2f350722f9a73' 'fffffffffffffffffffffff' }) # <- target to the moon self.connectionpool.add_header_observer.side_effect = lambda x: observers.append( x) self.connectionpool.call.return_value = async_coro( (peer, electrum_header)) self.sut.add_header_subscribe_callback(callback) self.loop.run_until_complete(observers[0](peer, electrum_header)) res = self.loop.run_until_complete(self.sut.get_header(513526)) self.assertIsNone(res) Mock.assert_called_with(self.electrod_loop.create_task, 'coroutine') def test_get_headers_no_response(self): self.connectionpool.call.side_effect = [ ElectrodMissingResponseException, ElectrodMissingResponseException, async_coro(('a', self.electrum_header)) ] with self.assertRaises(ElectrodMissingResponseException): self.loop.run_until_complete(self.sut.get_header(10)) self.assertIsNone( self.loop.run_until_complete( self.sut.get_header(10, fail_silent_out_of_range=True))) peer, res = self.loop.run_until_complete( self.sut.get_header(10, get_peer=1)) self.assertEqual(peer, 'a') self.assertEqual(res, self.parsed_header) Mock.assert_called_with(self.connectionpool.call, 'blockchain.block.get_header', 10, get_peer=True) def test_var_methods(self): self.electrod_loop.create_task.return_value = 1 self.loop.run_until_complete(self.sut.start()) peer = create_autospec(ElectrodConnection) self.loop.run_until_complete(self.sut.disconnect_from_peer(peer)) Mock.assert_called_with(peer.disconnect) self.assertEqual(2, len(self.electrod_loop.method_calls)) self.electrod_loop.reset_mock() self.sut.add_on_connected_callback('callback') self.connectionpool.add_on_connected_observer.return_value = True Mock.assert_called_with(self.connectionpool.add_on_connected_observer, 'callback') self.connectionpool.call.side_effect = [ async_coro('cafe'), async_coro({'hex': 'rawtx'}), async_coro('chunk_of_headers'), async_coro({'list': 'unspents'}), async_coro({'txs': 'list'}), async_coro({'fee': 'rate'}) ] peer = Mock() self.connectionpool.on_peer_error.return_value = async_coro( lambda x: x.close()) self.loop.run_until_complete(self.sut.getrawtransaction('cafebabe')) self.loop.run_until_complete( self.sut.getrawtransaction('cafebabe', verbose=True)) self.loop.run_until_complete(self.sut.get_chunk(1)) self.loop.run_until_complete( self.sut.listunspents_by_address('address')) self.loop.run_until_complete(self.sut.getaddresshistory('scripthash')) self.loop.run_until_complete(self.sut.handle_peer_error(peer)) Mock.assert_has_calls(self.connectionpool.call, calls=[ call('blockchain.transaction.get', 'cafebabe', 0), call('blockchain.transaction.get', 'cafebabe', 1), call('blockchain.block.get_chunk', 1, get_peer=False), call('blockchain.address.listunspent', 'address'), call('blockchain.address.get_history', 'scripthash'), ]) Mock.assert_called_with(self.connectionpool.on_peer_error, peer) def test_get_headers_in_range(self): async def get_chunk(method, chunk, get_peer=False): self.assertEqual(method, 'blockchain.block.get_chunk') bitcoind_header = "00000020fe52010fa7c798b97621508b772142dfc7b594df7a3a3200000000000000000097db2dc9" \ "4e2799bcdd7259f0876467b379fe44b69342df38a8ec2f350722f9a73367a95aa38955173732b883" i = {1: 2016, 2: 1024} return bitcoind_header * i[chunk] self.connectionpool.call.side_effect = get_chunk res = self.loop.run_until_complete( self.sut.get_headers_in_range_from_chunks(1, 3)) Mock.assert_has_calls(self.connectionpool.call, calls=[ call('blockchain.block.get_chunk', 1, get_peer=False), call('blockchain.block.get_chunk', 2, get_peer=False) ], any_order=True) i = 0 for header in res: self.assertEqual(header.get('block_height'), i + 2016) header['block_height'] = 513526 i += 1 header_from_chunk = { 'bits': 391481763, 'block_hash': '0000000000000000001e9a7d5bdb53bf19487d50d9e68e04b7254662d693a6ea', 'block_height': 513526, 'header_bytes': b'\x00\x00\x00 \xfeR\x01\x0f\xa7\xc7\x98\xb9v!P\x8bw!B\xdf' b'\xc7\xb5\x94\xdfz:2\x00\x00\x00\x00\x00\x00\x00\x00\x00' b"\x97\xdb-\xc9N'\x99\xbc\xddrY\xf0\x87dg\xb3y\xfeD\xb6" b'\x93B\xdf8\xa8\xec/5\x07"\xf9\xa73g\xa9Z\xa3\x89U\x17' b'72\xb8\x83', 'merkle_root': 'a7f92207352feca838df4293b644fe79b3676487f05972ddbc99274ec92ddb97', 'nonce': 2209886775, 'prev_block_hash': '000000000000000000323a7adf94b5c7df4221778b502176b998c7a70f0152fe', 'timestamp': 1521051443, 'version': 536870912 } self.assertEqual(header, header_from_chunk, msg='%s %s' % (header_from_chunk, header)) self.assertEqual(len(res), 3040) def test_get_chunks_no_chunk(self): self.connectionpool.call.return_value = async_coro(None) self.assertIsNone( self.loop.run_until_complete(self.sut.get_headers_from_chunk(1))) def test_get_headers_in_range_ok(self): peer = Mock() self.connectionpool.call.side_effect = [ async_coro((peer, self.electrum_header)), async_coro((peer, self.electrum_header)) ] res = self.loop.run_until_complete(self.sut.get_headers_in_range(1, 3)) self.assertEqual(res, [self.parsed_header, self.parsed_header])