Example #1
0
 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
     }
Example #2
0
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
Example #3
0
 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
     }
Example #4
0
 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()
Example #5
0
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
Example #6
0
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
Example #7
0
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)
Example #8
0
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])