Example #1
0
    def server_version(self, client_name='', protocol_version=None):
        '''Returns the server version as a string.

        client_name: a string identifying the client
        protocol_version: the protocol version spoken by the client
        '''
        if self.sv_seen and self.protocol_tuple >= (1, 4):
            raise RPCError(BAD_REQUEST, f'server.version already sent')
        self.sv_seen = True

        if client_name:
            client_name = str(client_name)
            if self.env.drop_client is not None and \
                    self.env.drop_client.match(client_name):
                self.close_after_send = True
                raise RPCError(BAD_REQUEST,
                               f'unsupported client: {client_name}')
            self.client = client_name[:17]

        # Find the highest common protocol version.  Disconnect if
        # that protocol version in unsupported.
        ptuple, client_min = util.protocol_version(
            protocol_version, self.PROTOCOL_MIN, self.PROTOCOL_MAX)
        if ptuple is None:
            if client_min > self.PROTOCOL_MIN:
                self.logger.info(f'client requested future protocol version '
                                 f'{util.version_string(client_min)} '
                                 f'- is your software out of date?')
            self.close_after_send = True
            raise RPCError(BAD_REQUEST,
                           f'unsupported protocol version: {protocol_version}')
        self.set_protocol_handlers(ptuple)

        return (electrumx.version, self.protocol_version_string())
Example #2
0
    def server_version(self, client_name=None, protocol_version=None):
        '''Returns the server version as a string.

        client_name: a string identifying the client
        protocol_version: the protocol version spoken by the client
        '''
        if client_name:
            if self.env.drop_client is not None and \
                    self.env.drop_client.match(client_name):
                self.close_after_send = True
                raise RPCError(BAD_REQUEST,
                               f'unsupported client: {client_name}')
            self.client = str(client_name)[:17]
            try:
                self.client_version = tuple(int(part) for part
                                            in self.client.split('.'))
            except Exception:
                pass

        # Find the highest common protocol version.  Disconnect if
        # that protocol version in unsupported.
        ptuple = self.controller.protocol_tuple(protocol_version)

        if ptuple is None:
            self.logger.info('unsupported protocol version request {}'
                             .format(protocol_version))
            self.close_after_send = True
            raise RPCError(BAD_REQUEST,
                           f'unsupported protocol version: {protocol_version}')

        self.set_protocol_handlers(ptuple)

        return (self.controller.VERSION, self.protocol_version)
Example #3
0
 async def wrapper(*args, **kwargs):
     try:
         return await decorated_function(*args, **kwargs)
     except DaemonError as daemon_error:
         error_dict = daemon_error.args[0]
         message, code = error_dict['message'], error_dict['code']
         raise RPCError(code=code, message=message)
 async def raw_header(self, height):
     '''Return the binary header at the given height.'''
     try:
         return await self.db.raw_header(height)
     except IndexError:
         raise RPCError(BAD_REQUEST, f'height {height:,d} '
                        'out of range') from None
Example #5
0
 def new_subscription(self):
     if self.subs_room <= 0:
         self.subs_room = self.max_subs - self._sub_count()
         if self.subs_room <= 0:
             raise RPCError(BAD_REQUEST, f'server subscription limit '
                            f'{self.max_subs:,d} reached')
     self.subs_room -= 1
    async def transaction_broadcast(self, raw_tx):
        '''Broadcast a raw transaction to the network.

        raw_tx: the raw transaction as a hexadecimal string'''
        # This returns errors as JSON RPC errors, as is natural
        try:
            hex_hash = await self.session_mgr.broadcast_transaction(raw_tx)
        except DaemonError as e:
            error, = e.args
            message = error['message']
            self.logger.info(f'error sending transaction: {message}')
            raise RPCError(BAD_REQUEST, 'the transaction was rejected by '
                           f'network rules.\n\n{message}\n[{raw_tx}]')
        else:
            self.txs_sent += 1
            client_ver = util.protocol_tuple(self.client)
            if client_ver != (0, ):
                msg = self.coin.upgrade_required(client_ver)
                if msg:
                    self.logger.info(f'sent tx: {hex_hash}. and warned user to upgrade their '
                                     f'client from {self.client}')
                    return msg

            self.logger.info(f'sent tx: {hex_hash}')
            return hex_hash
Example #7
0
    async def transaction_get_merkle(self, tx_hash, height):
        '''Return the markle tree to a confirmed transaction given its hash
        and height.

        tx_hash: the transaction hash as a hexadecimal string
        height: the height of the block it is in
        '''
        self.assert_tx_hash(tx_hash)
        height = non_negative_integer(height)

        hex_hashes = await self.daemon_request('block_hex_hashes', height, 1)
        block_hash = hex_hashes[0]
        block = await self.daemon_request('deserialised_block', block_hash)
        tx_hashes = block['tx']
        try:
            pos = tx_hashes.index(tx_hash)
        except ValueError:
            raise RPCError(
                BAD_REQUEST, f'tx hash {tx_hash} not in '
                f'block {block_hash} at height {height:,d}')

        hashes = [hex_str_to_hash(hash) for hash in tx_hashes]
        branch, root = self.bp.merkle.branch_and_root(hashes, pos)
        branch = [hash_to_hex_str(hash) for hash in branch]

        return {"block_height": height, "merkle": branch, "pos": pos}
Example #8
0
    async def tx_merkle(self, tx_hash, height):
        '''tx_hash is a hex string.'''
        hex_hashes = await self.daemon_request('block_hex_hashes', height, 1)
        block = await self.daemon_request('deserialised_block', hex_hashes[0])
        tx_hashes = block['tx']
        try:
            pos = tx_hashes.index(tx_hash)
        except ValueError:
            raise RPCError(
                BAD_REQUEST, f'tx hash {tx_hash} not in '
                f'block {hex_hashes[0]} at height {height:,d}')

        idx = pos
        hashes = [hex_str_to_hash(txh) for txh in tx_hashes]
        merkle_branch = []
        while len(hashes) > 1:
            if len(hashes) & 1:
                hashes.append(hashes[-1])
            idx = idx - 1 if (idx & 1) else idx + 1
            merkle_branch.append(hash_to_str(hashes[idx]))
            idx //= 2
            hashes = [
                double_sha256(hashes[n] + hashes[n + 1])
                for n in range(0, len(hashes), 2)
            ]

        return {"block_height": height, "merkle": merkle_branch, "pos": pos}
Example #9
0
def scripthash_to_hashX(scripthash):
    try:
        bin_hash = hex_str_to_hash(scripthash)
        if len(bin_hash) == 32:
            return bin_hash[:HASHX_LEN]
    except Exception:
        pass
    raise RPCError(BAD_REQUEST, f'{scripthash} is not a valid script hash')
 async def rpc_daemon_url(self, daemon_url):
     '''Replace the daemon URL.'''
     daemon_url = daemon_url or self.env.daemon_url
     try:
         self.daemon.set_url(daemon_url)
     except Exception as e:
         raise RPCError(BAD_REQUEST, f'an error occured: {e!r}')
     return f'now using daemon at {self.daemon.logged_url()}'
Example #11
0
 def rpc_daemon_url(self, daemon_url=None):
     '''Replace the daemon URL.'''
     daemon_url = daemon_url or self.env.daemon_url
     try:
         self.daemon.set_urls(self.env.coin.daemon_urls(daemon_url))
     except Exception as e:
         raise RPCError(BAD_REQUEST, f'an error occured: {e}')
     return 'now using daemon at {}'.format(self.daemon.logged_url())
Example #12
0
 def assert_tx_hash(self, value):
     '''Raise an RPCError if the value is not a valid transaction
     hash.'''
     try:
         if len(util.hex_to_bytes(value)) == 32:
             return
     except Exception:
         pass
     raise RPCError(BAD_REQUEST, f'{value} should be a transaction hash')
Example #13
0
    def rpc_reorg(self, count=3):
        '''Force a reorg of the given number of blocks.

        count: number of blocks to reorg (default 3)
        '''
        count = non_negative_integer(count)
        if not self.controller.bp.force_chain_reorg(count):
            raise RPCError(BAD_REQUEST, 'still catching up with daemon')
        return 'scheduled a reorg of {:,d} blocks'.format(count)
Example #14
0
async def test_estimatefee(daemon):
    method_not_found = RPCError(JSONRPCv1.METHOD_NOT_FOUND, 'nope')
    if isinstance(daemon, FakeEstimateFeeDaemon):
        result = daemon.coin.ESTIMATE_FEE
    else:
        result = -1
    with ClientSessionGood(('estimatesmartfee', [], method_not_found),
                           ('estimatefee', [2], result)):
        assert await daemon.estimatefee(2) == result
Example #15
0
 def assert_claim_id(self, value):
     '''Raise an RPCError if the value is not a valid claim id
     hash.'''
     try:
         if len(util.hex_to_bytes(value)) == 20:
             return
     except Exception:
         pass
     raise RPCError(1, f'{value} should be a claim id hash')
    async def rpc_reorg(self, count):
        '''Force a reorg of the given number of blocks.

        count: number of blocks to reorg
        '''
        count = non_negative_integer(count)
        if not self.bp.force_chain_reorg(count):
            raise RPCError(BAD_REQUEST, 'still catching up with daemon')
        return f'scheduled a reorg of {count:,d} blocks'
Example #17
0
def non_negative_integer(value):
    '''Return param value it is or can be converted to a non-negative
    integer, otherwise raise an RPCError.'''
    try:
        value = int(value)
        if value >= 0:
            return value
    except ValueError:
        pass
    raise RPCError(BAD_REQUEST, f'{value} should be a non-negative integer')
Example #18
0
    async def hashX_subscribe(self, hashX, alias):
        # First check our limit.
        if len(self.hashX_subs) >= self.max_subs:
            raise RPCError(BAD_REQUEST, 'your address subscription limit '
                           f'{self.max_subs:,d} reached')

        # Now let the controller check its limit
        self.controller.new_subscription()
        self.hashX_subs[hashX] = alias
        return await self.address_status(hashX)
Example #19
0
 async def masternode_announce_broadcast(self, signmnb):
     '''Pass through the masternode announce message to be broadcast
     by the daemon.'''
     try:
         return await self.daemon.masternode_broadcast(['relay', signmnb])
     except DaemonError as e:
         error, = e.args
         message = error['message']
         self.logger.info('masternode_broadcast: {}'.format(message))
         raise RPCError(BAD_REQUEST, 'the masternode broadcast was '
                        f'rejected.\n\n{message}\n[{signmnb}]')
 async def contract_event_subscribe(self, hash160, contract_addr, topic):
     if len(self.contract_subs) >= self.max_subs:
         raise RPCError(
             'your contract subscription limit {:,d} reached'.format(
                 self.max_subs))
     hashY = self.controller.coin.hash160_contract_to_hashY(
         hash160, contract_addr)
     hashY = hashY + topic.encode()
     self.contract_subs[hashY] = (hash160, contract_addr, topic)
     return await self.hash160_contract_status(hash160, contract_addr,
                                               topic)
Example #21
0
async def test_warming_up(daemon, caplog):
    warming_up = RPCError(-28, 'reading block index')
    height = 100
    daemon.init_retry = 0.01
    with caplog.at_level(logging.INFO):
        with ClientSessionGood(('getblockcount', [], warming_up),
                               ('getblockcount', [], height)):
            assert await daemon.height() == height

    assert in_caplog(caplog, "starting up checking blocks")
    assert in_caplog(caplog, "running normally")
    async def transaction_get(self, tx_hash, verbose=False):
        '''Return the serialized raw transaction given its hash

        tx_hash: the transaction hash as a hexadecimal string
        verbose: passed on to the daemon
        '''
        self.assert_tx_hash(tx_hash)
        if verbose not in (True, False):
            raise RPCError(BAD_REQUEST, f'"verbose" must be a boolean')

        return await self.daemon_request('getrawtransaction', tx_hash, verbose)
Example #23
0
 def _merkle_proof(self, cp_height, height):
     max_height = self.height()
     if not height <= cp_height <= max_height:
         raise RPCError(BAD_REQUEST,
                        f'require header height {height:,d} <= '
                        f'cp_height {cp_height:,d} <= '
                        f'chain height {max_height:,d}')
     branch, root = self.bp.header_mc.branch_and_root(cp_height + 1, height)
     return {
         'branch': [hash_to_hex_str(elt) for elt in branch],
         'root': hash_to_hex_str(root),
     }
    async def transaction_id_from_pos(self, height, tx_pos, merkle=False):
        '''Return the txid and optionally a merkle proof, given
        a block height and position in the block.
        '''
        tx_pos = non_negative_integer(tx_pos)
        height = non_negative_integer(height)
        if merkle not in (True, False):
            raise RPCError(BAD_REQUEST, f'"merkle" must be a boolean')

        block_hash, tx_hashes = await self._block_hash_and_tx_hashes(height)
        try:
            tx_hash = tx_hashes[tx_pos]
        except IndexError:
            raise RPCError(BAD_REQUEST, f'no tx at position {tx_pos:,d} in '
                           f'block {block_hash} at height {height:,d}')

        if merkle:
            branch = self._get_merkle_branch(tx_hashes, tx_pos)
            return {"tx_hash": tx_hash, "merkle": branch}
        else:
            return tx_hash
Example #25
0
    def _for_each_session(self, session_ids, operation):
        if not isinstance(session_ids, list):
            raise RPCError(BAD_REQUEST, 'expected a list of session IDs')

        result = []
        for session_id in session_ids:
            session = self._lookup_session(session_id)
            if session:
                result.append(operation(session))
            else:
                result.append('unknown session: {}'.format(session_id))
        return result
Example #26
0
    def server_version(self, client_name=None, protocol_version=None):
        '''Returns the server version as a string.

        client_name: a string identifying the client
        protocol_version: the protocol version spoken by the client
        '''
        if client_name:
            if self.env.drop_client is not None and \
                    self.env.drop_client.match(client_name):
                self.close_after_send = True
                raise RPCError(BAD_REQUEST,
                               f'unsupported client: {client_name}')
            self.client = str(client_name)[:17]
            try:
                self.client_version = tuple(
                    int(part) for part in self.client.split('.'))
            except Exception:
                pass

        # Find the highest common protocol version.  Disconnect if
        # that protocol version in unsupported.
        ptuple = util.protocol_version(protocol_version, version.PROTOCOL_MIN,
                                       version.PROTOCOL_MAX)

        # From protocol version 1.1, protocol_version cannot be omitted
        if ptuple is None or (ptuple >= (1, 1) and protocol_version is None):
            self.logger.info('unsupported protocol version request {}'.format(
                protocol_version))
            self.close_after_send = True
            raise RPCError(
                BAD_REQUEST,
                f'unsupported protocol version: {protocol_version}')

        self.set_protocol_handlers(ptuple)

        # The return value depends on the protocol version
        if ptuple < (1, 1):
            return version.VERSION
        else:
            return (version.VERSION, self.protocol_version)
Example #27
0
async def test_estimatefee_smart(daemon):
    bad_args = RPCError(JSONRPCv1.INVALID_ARGS, 'bad args')
    if isinstance(daemon, FakeEstimateFeeDaemon):
        return
    rate = 0.0002
    result = {'feerate': rate}
    with ClientSessionGood(('estimatesmartfee', [], bad_args),
                           ('estimatesmartfee', [2], result)):
        assert await daemon.estimatefee(2) == rate

    # Test the rpc_available_cache is used
    with ClientSessionGood(('estimatesmartfee', [2], result)):
        assert await daemon.estimatefee(2) == rate
async def test_warming_up_batch(daemon, caplog):
    warming_up = RPCError(-28, 'reading block index')
    first = 5
    count = 1
    daemon.init_retry = 0.01
    hashes = ['hex_hash5']
    with caplog.at_level(logging.INFO):
        with ClientSessionGood(('getblockhash', [[first]], [warming_up]),
                               ('getblockhash', [[first]], hashes)):
            assert await daemon.block_hex_hashes(first, count) == hashes

    assert in_caplog(caplog, "starting up checking blocks")
    assert in_caplog(caplog, "running normally")
async def test_get_raw_transactions(daemon):
    hex_hashes = ['deadbeef0', 'deadbeef1']
    args_list = [[hex_hash, 0] for hex_hash in hex_hashes]
    raw_txs_hex = ['fffefdfc', '0a0b0c0d']
    raw_txs = [bytes.fromhex(raw_tx) for raw_tx in raw_txs_hex]
    # Test 0 - old daemon's reject False
    with ClientSessionGood(('getrawtransaction', args_list, raw_txs_hex)):
        assert await daemon.getrawtransactions(hex_hashes) == raw_txs

    # Test one error
    tx_not_found = RPCError(-1, 'some error message')
    results = ['ff0b7d', tx_not_found]
    raw_txs = [bytes.fromhex(results[0]), None]
    with ClientSessionGood(('getrawtransaction', args_list, results)):
        assert await daemon.getrawtransactions(hex_hashes) == raw_txs
Example #30
0
    async def transaction_broadcast(self, raw_tx):
        '''Broadcast a raw transaction to the network.

        raw_tx: the raw transaction as a hexadecimal string'''
        # This returns errors as JSON RPC errors, as is natural
        try:
            tx_hash = await self.daemon.sendrawtransaction([raw_tx])
            self.txs_sent += 1
            self.logger.info('sent tx: {}'.format(tx_hash))
            self.controller.sent_tx(tx_hash)
            return tx_hash
        except DaemonError as e:
            error, = e.args
            message = error['message']
            self.logger.info('sendrawtransaction: {}'.format(message))
            raise RPCError(BAD_REQUEST, 'the transaction was rejected by '
                           f'network rules.\n\n{message}\n[{raw_tx}]')