def validate_params(self) -> None:
     params = self.params
     if params is None or not isinstance(params, dict):
         raise RpcInvalidParams(
             self.request_id,
             "Params request field is either missing or not a dictionary type."
         )
     transaction_json = params.get(
         rpc_constants.TRANSACTION_JSON_PARAMS_KEY)
     if not transaction_json or not isinstance(transaction_json, dict):
         raise RpcInvalidParams(
             self.request_id,
             f"Invalid transaction request key: {rpc_constants.TRANSACTION_JSON_PARAMS_KEY} is required"
         )
     if rpc_constants.TAG_PARAMS_KEY in params:
         tag = params[rpc_constants.TAG_PARAMS_KEY]
         if isinstance(tag, int):
             pass
         elif isinstance(tag,
                         str) and tag in {"latest", "pending", "earliest"}:
             pass
         else:
             raise RpcInvalidParams(
                 self.request_id,
                 f"Invalid value for {rpc_constants.TAG_PARAMS_KEY}: {tag}")
Пример #2
0
 def format_filters(self, filters: Any) -> str:
     valid_filters = self.feed_manager.get_valid_feed_filters(self.feed_key)
     invalid_filters = RpcInvalidParams(
         self.request_id,
         f"{filters} is not a valid set of filters. "
         'Valid format/filters: {"include": '
         f"{valid_filters}"
         "}.",
     )
     if not isinstance(filters, str):
         logger.error("Wrong filter type")
         raise invalid_filters
     if not valid_filters:
         raise invalid_filters
     logger_filters.debug("Validating filters")
     try:
         filters, keys = self.feed_manager.validate_feed_filters(
             self.feed_key, filters)
     except Exception:
         raise invalid_filters
     # for key in filters, if not in valid_filters, raise
     for key in keys:
         if key not in valid_filters:
             raise RpcInvalidParams(
                 self.request_id,
                 f"{key} is not a valid filter. "
                 'Valid format/filters: {"include": '
                 f"{valid_filters}"
                 "}.",
             )
     return filters
Пример #3
0
    def validate_params(self) -> None:
        if not self.feed_manager.feeds:
            raise RpcAccountIdError(
                self.request_id,
                f"Account does not have access to the transaction streaming service.",
            )

        params = self.params
        if not isinstance(params, list) or len(params) != 2:
            raise RpcInvalidParams(
                self.request_id,
                "Subscribe RPC request params must be a list of length 2.",
            )

        feed_name, options = params
        feed = self.feed_manager.feeds.get(feed_name)
        if feed is None:
            raise RpcInvalidParams(
                self.request_id,
                f"{feed_name} is an invalid feed. "
                f"Available feeds: {list(self.feed_manager.feeds)}",
            )

        self.node.on_new_subscriber_request()

        self.feed_name = feed_name

        available_fields = self.feed_manager.get_feed_fields(feed_name)
        invalid_options = RpcInvalidParams(
            self.request_id,
            f"{options} is not a valid set of options. "
            'Valid format/fields: {"include": '
            f"{available_fields}"
            "}.",
        )
        if not isinstance(options, dict):
            raise invalid_options

        include = options.get("include", None)
        if include is not None:
            if not isinstance(include, list):
                raise invalid_options

            if any(included_field not in available_fields
                   for included_field in include):
                raise invalid_options
        filters = options.get("filters", None)
        if filters:
            logger_filters.debug(filters)
            formatted_filters = self.format_filters(filters)
            logger_filters.debug("formatted filters: {}", formatted_filters)
            options["filters"] = formatted_filters
        self.options = options
Пример #4
0
    def validate_params_include_fields(self):
        if self.service_model and self.service_model.available_fields:
            self.available_fields = [
                field for field in self.available_fields
                if allowed_field(field, self.service_model.available_fields)
            ]

        invalid_options = RpcInvalidParams(
            self.request_id,
            f"{self.options} Invalid feed include parameter. "
            "Your plan does not support all requested include parameters "
            'Valid format/fields: {"include": '
            f"{self.available_fields}"
            "}.",
        )
        if not isinstance(self.options, dict):
            raise invalid_options

        include = self.options.get("include", self.all_fields)
        if not isinstance(include, list):
            raise invalid_options
        # check for empty list
        if not include:
            include = self.all_fields

        if self.available_fields:
            if any(included_field not in self.available_fields
                   for included_field in include):
                raise invalid_options

            # update options["include"] to support if was not specified
            self.options["include"] = include
        else:
            self.options["include"] = self.available_fields
Пример #5
0
    async def post_process_transaction(
        self, network_num: int, account_id: str, quota_type: QuotaType, transaction_str: str
    ) -> JsonRpcResponse:
        try:
            message_converter = self.node.message_converter
            assert message_converter is not None, "Invalid server state!"
            transaction = message_converter.encode_raw_msg(transaction_str)
            bx_tx = message_converter.bdn_tx_to_bx_tx(transaction, network_num, quota_type)
        except (ValueError, ParseError) as e:
            logger.error(common_log_messages.RPC_COULD_NOT_PARSE_TRANSACTION, e)
            raise RpcInvalidParams(
                self.request_id, f"Invalid transaction param: {transaction_str}"
            )
        tx_service = self.node.get_tx_service()
        tx_hash = bx_tx.tx_hash()
        if tx_service.has_transaction_contents(tx_hash):
            short_id = tx_service.get_short_id(tx_hash)
            tx_stats.add_tx_by_hash_event(
                tx_hash,
                TransactionStatEventType.TX_RECEIVED_FROM_RPC_REQUEST_IGNORE_SEEN,
                network_num,
                account_id=account_id, short_id=short_id
            )
            tx_json = {
                "tx_hash": str(tx_hash),
                "quota_type": quota_type.name.lower(),
                "account_id": account_id,
            }
            return self.ok(tx_json)
        tx_stats.add_tx_by_hash_event(
            tx_hash,
            TransactionStatEventType.TX_RECEIVED_FROM_RPC_REQUEST,
            network_num,
            account_id=account_id
        )
        if self.node.has_active_blockchain_peer():
            blockchain_tx_message = self.node.message_converter.bx_tx_to_tx(bx_tx)
            self.node.broadcast(blockchain_tx_message, connection_types=[ConnectionType.BLOCKCHAIN_NODE])

        # All connections outside of this one is a bloXroute server
        broadcast_peers = self.node.broadcast(bx_tx, connection_types=[ConnectionType.RELAY_TRANSACTION])
        tx_stats.add_tx_by_hash_event(
            tx_hash,
            TransactionStatEventType.TX_SENT_FROM_GATEWAY_TO_PEERS,
            network_num,
            peers=broadcast_peers
        )
        tx_stats.add_tx_by_hash_event(
            tx_hash,
            TransactionStatEventType.TX_GATEWAY_RPC_RESPONSE_SENT,
            network_num
        )
        tx_service.set_transaction_contents(tx_hash, bx_tx.tx_val())
        tx_json = {
            "tx_hash": str(tx_hash),
            "quota_type": quota_type.name.lower(),
            "account_id": account_id
        }
        return self.ok(tx_json)
Пример #6
0
 async def process_request(self) -> JsonRpcResponse:
     feed_name = self.unsubscribe_handler(self.subscriber_id)
     if feed_name is None:
         raise RpcInvalidParams(
             self.request_id,
             f"Subscriber {self.subscriber_id} was not found.")
     self.feed_manager.unsubscribe_from_feed(feed_name, self.subscriber_id)
     return JsonRpcResponse(self.request_id, True)
Пример #7
0
 def validate_params(self) -> None:
     params = self.params
     if (not isinstance(params, list) or len(params) != 1
             or not isinstance(params[0], str)):
         raise RpcInvalidParams(
             self.request_id,
             "Unsubscribe RPC request params must be a list of length 1.")
     self.subscriber_id = params[0]
Пример #8
0
    def validate_transaction_param(
            self, params: Union[Dict[str, Any], List[Any], None]) -> None:
        assert params is not None
        assert isinstance(params, dict)

        if rpc_constants.TRANSACTION_PARAMS_KEY not in params:
            raise RpcInvalidParams(
                self.request_id,
                f"Invalid transaction request params type: {self.params}")
Пример #9
0
 def subscribe(
         self, options: Dict[str,
                             Any]) -> Subscriber[EthTransactionFeedEntry]:
     include_from_blockchain = options.get("include_from_blockchain", None)
     if include_from_blockchain is not None:
         if not isinstance(include_from_blockchain, bool):
             raise RpcInvalidParams(
                 '"include_from_blockchain" must be a boolean')
     return super().subscribe(options)
    def subscribe(
            self, options: Dict[str,
                                Any]) -> Subscriber[EthTransactionFeedEntry]:
        duplicates = options.get("duplicates", None)
        if duplicates is not None:
            if not isinstance(duplicates, bool):
                raise RpcInvalidParams('"duplicates" must be a boolean')

        return super().subscribe(options)
Пример #11
0
 def validate_params(self) -> None:
     params = self.params
     if params is None or not isinstance(params, dict):
         raise RpcInvalidParams(
             self.request_id,
             "Params request field is either missing or not a dictionary type."
         )
     assert params is not None
     self.validate_transaction_param(params)
Пример #12
0
 def validate_params_feed_details(self):
     feed = self.feed_manager.get_feed(self.feed_key)
     if feed is None:
         raise RpcInvalidParams(
             self.request_id,
             f"{self.feed_name} is an invalid feed. "
             f"Available feeds: {[key.name for key in self.feed_manager.get_feed_keys(self.feed_network)]}",
         )
     self.available_fields = feed.FIELDS
     self.all_fields = feed.ALL_FIELDS
Пример #13
0
def process_call_params_method(method: Optional[str]) -> EthCommandMethod:
    if method is None:
        eth_command_method = DEFAULT_METHOD
    else:
        try:
            eth_command_method = EthCommandMethod.from_string(method)
        except ValueError:
            raise RpcInvalidParams(
                f"Invalid Value for method provided {method} use {[str(item) for item in EthCommandMethod]}"
            )
    return eth_command_method
 def parse_enode(self, enode: str) -> BlockchainPeerInfo:
     # Make sure enode is at least as long as the public key
     if not argument_parsers.enode_is_valid_length(enode):
         raise RpcInvalidParams(
             self.request_id,
             f"Invalid enode: {enode}, with length: {len(enode)}. "
             f"Expected format: enode://<eth node public key>@<eth node ip>:<port>"
         )
     try:
         pub_key, ip, port = argument_parsers.get_enode_parts(enode)
         if not port.isnumeric():
             raise RpcInvalidParams(self.request_id,
                                    f"Invalid port: {port}")
     except ValueError:
         raise RpcInvalidParams(
             self.request_id, f"Invalid enode: {enode}. "
             f"Expected format: enode://<eth node public key>@<eth node ip>:<port>"
         )
     else:
         return BlockchainPeerInfo(ip, int(port), pub_key)
Пример #15
0
def process_call_params_tag(call_tag: Optional[TAG_TYPE]) -> int:
    if call_tag is None or (isinstance(call_tag, str)
                            and call_tag == "latest"):
        block_offset = 0
    elif isinstance(call_tag, int) and call_tag <= 0:
        block_offset = call_tag
    else:
        raise RpcInvalidParams(
            f"Invalid Value for tag provided {call_tag} use latest, 0 or a negative number"
        )
    return block_offset
Пример #16
0
    def validate_params_get_options(self):
        params = self.params

        if not isinstance(params, list) or len(params) != 2:
            raise RpcInvalidParams(
                self.request_id,
                "Subscribe RPC request params must be a list of length 2.",
            )
        feed_name, options = params
        self.feed_name = feed_name
        self.feed_key = FeedKey(self.feed_name, self.feed_network)

        self.options = options
Пример #17
0
    def validate_params(self) -> None:
        params = self.params
        if not isinstance(params, dict):
            raise RpcInvalidParams(
                self.request_id,
                "Params request field is either missing or not a dictionary type."
            )

        if TRANSACTION_HASH_KEY not in params:
            raise RpcInvalidParams(
                self.request_id,
                "Transaction hash was missing from RPC params.")

        transaction_hash_str = params[TRANSACTION_HASH_KEY]
        try:
            transaction_hash = Sha256Hash.from_string(transaction_hash_str)
        except Exception as _e:
            raise RpcInvalidParams(
                self.request_id,
                f"Invalid transaction hash: {transaction_hash_str}")
        else:
            self.transaction_hash = transaction_hash
Пример #18
0
 def validate_params(self) -> None:
     super().validate_params()
     params = self.params
     if params is None or not isinstance(params, dict):
         raise RpcInvalidParams(
             self.request_id,
             "Params request field is either missing or not a dictionary type."
         )
     if rpc_constants.BLOCKCHAIN_PEER_PARAMS_KEY in params:
         peer = params[rpc_constants.BLOCKCHAIN_PEER_PARAMS_KEY]
         blockchain_protocol = self.node.opts.blockchain_protocol
         if blockchain_protocol is not None:
             self._blockchain_peer_info = self.parse_peer(
                 blockchain_protocol, peer)
         else:
             raise RpcInternalError(
                 self.request_id,
                 "Could not process request to add/remove blockchain peer. Please contact bloXroute support."
             )
     else:
         raise RpcInvalidParams(
             self.request_id,
             f"Missing param: {rpc_constants.BLOCKCHAIN_PEER_PARAMS_KEY}.")
Пример #19
0
def process_call_params(call_params: List[Dict[str, Any]]) -> Dict[str, Any]:
    if call_params is None or not isinstance(call_params, list):
        raise RpcInvalidParams("call_params must be a list")
    calls = {}
    for counter, call in enumerate(call_params):

        call_name = call.get("name", str(counter))
        if call_name in calls:
            raise RpcInvalidParams(
                "unique name must be provided for each call")

        block_offset = process_call_params_tag(call.get("tag"))
        eth_command_method = process_call_params_method(call.get("method"))
        call_payload = process_call_params_payload(call)

        calls[call_name] = EthCallOption(
            eth_command_method,
            block_offset,
            call_name,
            call_payload,
            active=call.get("active", True),
        )
    return calls
Пример #20
0
    def test_serialize_deserialize_error(self):
        rpc_response = JsonRpcResponse(
            "1", None, RpcInvalidParams("1", "bad message")
        )

        serialized = rpc_response.to_jsons()
        deserialized = JsonRpcResponse.from_jsons(serialized)

        self.assertEqual(rpc_response.id, deserialized.id)
        self.assertEqual(rpc_response.result, deserialized.result)

        self.assertNotEqual(rpc_response.error, deserialized.error)
        self.assertIsInstance(deserialized.error, RpcError)
        self.assertEqual(rpc_response.error.code, deserialized.error.code)
        self.assertEqual(rpc_response.error.message, deserialized.error.message)
        self.assertEqual(rpc_response.error.data, deserialized.error.data)
Пример #21
0
    def subscribe(self, options: Dict[str,
                                      Any]) -> Subscriber[OnBlockFeedEntry]:
        call_params = options.get("call_params", [])
        calls = process_call_params(call_params)
        subscription_id = options.get("subscription_id")
        if subscription_id:
            subscriber = self.subscribers.get(subscription_id)
            if subscriber is None:
                raise RpcInvalidParams(
                    f"Subscriber id {subscription_id} for feed {self.NAME} not found"
                )

            subscriber.options["calls"].update(calls)
            return subscriber
        else:
            options["calls"] = calls
            return super().subscribe(options)
Пример #22
0
 def validate_params_get_options(self):
     super().validate_params_get_options()
     if (
         self.feed_name in {rpc_constants.ETH_TRANSACTION_RECEIPTS_FEED_NAME, rpc_constants.ETH_ON_BLOCK_FEED_NAME}
         and (not self.node.opts.ws or not self.node.opts.eth_ws_uri or not self.node.get_ws_server_status())
     ):
         raise RpcInvalidParams(
             self.request_id,
             f"In order to use the {self.feed_name} feed, "
             f"your gateway must be connected to your Ethereum node's websocket server "
             f"and websocket RPC must be enabled on your gateway. "
             f"To do so, start your gateway with the following parameters: "
             f"--ws True "
             f"--ws-host <IP address of client application> "
             f"--ws-port 28333 "
             f"--eth-ws-uri ws://[ip_address]:[port]."
         )
Пример #23
0
    def validate_params(self) -> None:
        params = self.params
        if params is None or not isinstance(params, dict):
            raise RpcInvalidParams(
                self.request_id,
                "Params request field is either missing or not a dictionary type."
            )
        if (
            rpc_constants.TRANSACTION_JSON_PARAMS_KEY in params
            and self.get_network_protocol() == BlockchainProtocol.ETHEREUM
        ):
            tx_json = params[rpc_constants.TRANSACTION_JSON_PARAMS_KEY]
            tx_bytes = Transaction.from_json_with_validation(tx_json).contents().tobytes()
            params[rpc_constants.TRANSACTION_PARAMS_KEY] = tx_bytes.hex()

        super(GatewayBlxrTransactionRpcRequest, self).validate_params()

        if self.SYNCHRONOUS in params:
            synchronous = params[rpc_constants.SYNCHRONOUS_PARAMS_KEY]
            self.synchronous = convert.str_to_bool(str(synchronous).lower(), default=True)
    async def process_request(self) -> JsonRpcResponse:
        params = self.params
        assert isinstance(params, dict)
        account_id = self.get_account_id()
        if self.node.opts.blockchain_protocol != BlockchainProtocol.ETHEREUM:
            raise RpcInvalidParams(
                self.request_id,
                f"Gateway does not support {BlockchainProtocol.ETHEREUM} protocol methods"
            )
        if not account_id:
            raise RpcAccountIdError(
                self.request_id,
                "Gateway does not have an associated account. Please register the gateway with an account to submit "
                "calls through RPC.")

        # Hook, to verify EthNode WS connection health
        self.node.on_new_subscriber_request()

        transaction_obj: Dict[str, Any] = params[
            rpc_constants.TRANSACTION_JSON_PARAMS_KEY]
        tag: TAG_TYPE = params.get(rpc_constants.TAG_PARAMS_KEY, "latest")
        return await self.process_eth_call(transaction_obj, tag)
Пример #25
0
    def format_filters(self, filters: Any) -> Dict[str, Any]:
        valid_filters = self.feed_manager.get_valid_feed_filters(
            self.feed_name)
        invalid_filters = RpcInvalidParams(
            self.request_id,
            f"{filters} is not a valid set of filters. "
            'Valid format/filters: {"include": '
            f"{valid_filters}"
            "}.",
        )

        if not isinstance(filters, dict):
            raise invalid_filters
        if not valid_filters:
            raise invalid_filters
        logger_filters.debug("Formatting filters")
        try:
            filters = self.feed_manager.reformat_feed_filters(
                self.feed_name, filters)
        except Exception:
            raise invalid_filters
        return filters
    async def post_process_transaction(
            self, network_num: int, account_id: str,
            transaction_flag: TransactionFlag,
            transaction_str: str) -> JsonRpcResponse:
        try:
            message_converter = self.node.message_converter
            assert message_converter is not None, "Invalid server state!"
            transaction = message_converter.encode_raw_msg(transaction_str)
            bx_tx = message_converter.bdn_tx_to_bx_tx(transaction, network_num,
                                                      transaction_flag,
                                                      account_id)
        except (ValueError, ParseError) as e:
            logger.error(common_log_messages.RPC_COULD_NOT_PARSE_TRANSACTION,
                         e)
            raise RpcInvalidParams(
                self.request_id,
                f"Invalid transaction param: {transaction_str}")
        tx_service = self.node.get_tx_service()
        tx_hash = bx_tx.tx_hash()
        transaction_key = tx_service.get_transaction_key(tx_hash)
        if (tx_service.has_transaction_contents_by_key(transaction_key)
                or tx_service.removed_transaction_by_key(transaction_key)):
            short_id = tx_service.get_short_id_by_key(transaction_key)
            tx_stats.add_tx_by_hash_event(
                tx_hash,
                TransactionStatEventType.
                TX_RECEIVED_FROM_RPC_REQUEST_IGNORE_SEEN,
                network_num,
                account_id=account_id,
                short_id=short_id)
            tx_json = {
                "tx_hash": str(tx_hash),
            }
            return self.ok(tx_json)
        tx_stats.add_tx_by_hash_event(
            tx_hash,
            TransactionStatEventType.TX_RECEIVED_FROM_RPC_REQUEST,
            network_num,
            account_id=account_id)
        if self.node.has_active_blockchain_peer():
            blockchain_tx_message = self.node.message_converter.bx_tx_to_tx(
                bx_tx)
            self.node.broadcast(
                blockchain_tx_message,
                connection_types=(ConnectionType.BLOCKCHAIN_NODE, ))

        # All connections outside of this one is a bloXroute server
        broadcast_peers = self.node.broadcast(
            bx_tx, connection_types=(ConnectionType.RELAY_TRANSACTION, ))
        tx_stats.add_tx_by_hash_event(
            tx_hash,
            TransactionStatEventType.TX_SENT_FROM_GATEWAY_TO_PEERS,
            network_num,
            peers=broadcast_peers)
        tx_stats.add_tx_by_hash_event(
            tx_hash, TransactionStatEventType.TX_GATEWAY_RPC_RESPONSE_SENT,
            network_num)
        tx_service.set_transaction_contents_by_key(transaction_key,
                                                   bx_tx.tx_val())
        tx_json = {
            "tx_hash": str(tx_hash),
        }
        if not self.node.account_model.is_account_valid():
            raise RpcAccountIdError(
                self.request_id,
                "The account associated with this gateway has expired. "
                "Please visit https://portal.bloxroute.com to renew your subscription."
            )
        if self.node.quota_level == constants.FULL_QUOTA_PERCENTAGE:
            raise RpcBlocked(
                self.request_id,
                "The account associated with this gateway has exceeded its daily transaction quota."
            )
        else:
            return self.ok(tx_json)
 def parse_ip_port(self, ip_port_string: str) -> BlockchainPeerInfo:
     ip, port = argument_parsers.get_ip_port_string_parts(ip_port_string)
     if not port.isnumeric():
         raise RpcInvalidParams(self.request_id, f"Invalid port: {port}")
     return BlockchainPeerInfo(ip, int(port))
Пример #28
0
 def validate_item_in_payload(self, item) -> None:
     if item not in self.call_payload:
         raise RpcInvalidParams(
             f"Expected {item} element in request payload for {self.command_method}"
         )
Пример #29
0
 def validate_params(self) -> None:
     params = self.params
     if not isinstance(params, dict):
         raise RpcInvalidParams(self.request_id, "Params request field must be a dictionary type.")