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}")
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
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
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
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)
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)
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]
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}")
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)
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)
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
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)
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
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
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
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}.")
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
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)
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)
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]." )
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)
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))
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}" )
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.")