async def test_multiple_rpc_calls_mixed_response(self): # unlikely to ever actually happen in practice, but should be handled async with WsProvider(self.ws_uri) as ws: self.assertEqual(1, len(self.server._connections)) connection_handler = self.server._connections[0] connection_handler.rpc_handler.handle_request = AsyncMock() subscribe_task_1 = asyncio.create_task( ws.call_bx(RpcRequestType.SUBSCRIBE, ["newTxs"], request_id="123")) await asyncio.sleep(0) subscribe_task_2 = asyncio.create_task( ws.call_bx(RpcRequestType.SUBSCRIBE, ["newTxs"], request_id="124")) server_ws = connection_handler.ws assert server_ws is not None # send responses out of order await server_ws.send(JsonRpcResponse("124", "subid2").to_jsons()) await server_ws.send(JsonRpcResponse("123", "subid1").to_jsons()) await asyncio.sleep(0.01) self.assertTrue(subscribe_task_1.done()) self.assertTrue(subscribe_task_2.done()) subscription_1 = subscribe_task_1.result() self.assertEqual("123", subscription_1.id) self.assertEqual("subid1", subscription_1.result) subscription_2 = subscribe_task_2.result() self.assertEqual("subid2", subscription_2.result)
async def test_send_receive_messages(self): await self.provider.initialize() rpc_message = JsonRpcResponse("1", "foo") await self.provider.get_test_websocket().recv_messages.put( rpc_message.to_jsons()) emitted_message = await self.provider.get_rpc_response("1") self.assertEqual(rpc_message, emitted_message)
def test_serialize_deserialize_result(self): rpc_response = JsonRpcResponse( "1", "result", None ) 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.assertEqual(rpc_response.error, deserialized.error)
def format_rpc_error(rpc_error: RpcError, status_code: int, content_type: ContentType) -> Response: if content_type == ContentType.PLAIN: return web.json_response(JsonRpcResponse(rpc_error.id, error=rpc_error).to_jsons(), status=status_code, content_type=ContentType.PLAIN.value) else: return web.json_response( text=JsonRpcResponse(rpc_error.id, error=rpc_error).to_jsons(), status=status_code, )
async def test_onblock_feed_default_subscribe(self): self.gateway_node.opts.eth_ws_uri = f"ws://{constants.LOCALHOST}:8005" block_height = 100 name = "abc123" self.gateway_node.feed_manager.register_feed( EthOnBlockFeed(self.gateway_node)) self.gateway_node.eth_ws_proxy_publisher = MockEthWsProxyPublisher( "", None, None, self.gateway_node) self.gateway_node.eth_ws_proxy_publisher.call_rpc = AsyncMock( return_value=JsonRpcResponse(request_id=1)) async with WsProvider(self.ws_uri) as ws: subscription_id = await ws.subscribe( rpc_constants.ETH_ON_BLOCK_FEED_NAME, {"call_params": [{ "data": "0x", "name": name }]}, ) self.gateway_node.feed_manager.publish_to_feed( FeedKey(rpc_constants.ETH_ON_BLOCK_FEED_NAME), EventNotification(block_height=block_height), ) subscription_message = await ws.get_next_subscription_notification_by_id( subscription_id) self.assertEqual(subscription_id, subscription_message.subscription_id) print(subscription_message) self.assertEqual(block_height, subscription_message.notification["blockHeight"]) self.assertEqual(name, subscription_message.notification["name"])
async def test_camel_case(self): await self.server.stop() self.server = WsServer(constants.LOCALHOST, 8005, self.feed_manager, self.gateway_node, Case.CAMEL) await self.server.start() feed = TestFeed("foo") self.feed_manager.register_feed(feed) @dataclass class DataEntry: field_one: str def publish(): feed.publish(DataEntry("123")) feed.publish(DataEntry("234")) async with websockets.connect(self.ws_uri) as ws: asyncio.get_event_loop().call_later(0.1, publish) await ws.send( BxJsonRpcRequest("2", RpcRequestType.SUBSCRIBE, ["foo", {}]).to_jsons()) response = await ws.recv() parsed_response = JsonRpcResponse.from_jsons(response) self.assertEqual("2", parsed_response.id) self.assertIsNotNone("1", parsed_response.result) subscriber_id = parsed_response.result self._assert_notification({"fieldOne": "123"}, subscriber_id, await ws.recv()) self._assert_notification({"fieldOne": "234"}, subscriber_id, await ws.recv())
def setUp(self) -> None: self._account_model = BdnAccountModelBase( account_id="", logical_account_name="", certificate="", expire_date=utils_constants.DEFAULT_EXPIRATION_DATE.isoformat(), cloud_api=BdnServiceModelConfigBase( msg_quota=None, permit=BdnServiceModelBase(service_type=BdnServiceType.PERMIT), expire_date=utils_constants.DEFAULT_EXPIRATION_DATE.isoformat( ), ), blockchain_protocol="Ethereum", blockchain_network="Mainnet", ) self.rpc_port = helpers.get_free_port() opts = gateway_helpers.get_gateway_opts( 8000, rpc_port=self.rpc_port, account_model=self._account_model, blockchain_protocol="Ethereum", ) self.node = MockGatewayNode(opts) self.node.eth_ws_proxy_publisher = MockEthWsProxyPublisher( None, None, None, self.node) self.node.eth_ws_proxy_publisher.call_rpc = AsyncMock( return_value=JsonRpcResponse(request_id=1)) self.sut = EthOnBlockFeed(self.node)
async def call_rpc( self, method: str, params: Union[List[Any], Dict[Any, Any], None], request_id: Optional[str] = None ) -> JsonRpcResponse: return JsonRpcResponse(request_id=request_id, result={})
async def handle_request(self, websocket: WebSocketServerProtocol, _path: str) -> None: async for message in websocket: try: response = await self.rpc_handler.handle_request(message) except RpcError as err: response = JsonRpcResponse(err.id, error=err).to_jsons() await websocket.send(response)
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)
async def process_request(self) -> JsonRpcResponse: transaction_hash = self.transaction_hash assert transaction_hash is not None transaction_service = self.node.get_tx_service(self.get_network_num()) transaction_key = transaction_service.get_transaction_key( transaction_hash) status = "unknown" short_ids = [] assigned_time = "n/a" if transaction_service.has_transaction_contents_by_key( transaction_key): status = "pending short ID" if transaction_service.has_transaction_short_id_by_key( transaction_key): status = "assigned short ID" short_ids = transaction_service.get_short_ids_by_key( transaction_key) most_recent_timestamp = max([ transaction_service.get_short_id_assign_time(short_id) for short_id in short_ids ]) assigned_time = datetime.datetime.fromtimestamp( most_recent_timestamp).isoformat() payload = { "status": status, "short_ids": short_ids, "assignment_time": assigned_time } return JsonRpcResponse(self.request_id, payload)
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)
async def request(self, req: BxJsonRpcRequest): headers = { rpc_constants.CONTENT_TYPE_HEADER_KEY: ContentType.JSON.value } async with ClientSession() as session: async with session.post(self.rpc_url, data=req.to_jsons(), headers=headers) as response: return JsonRpcResponse.from_json(await response.json())
async def consumer(ws, _path): try: async for message in ws: rpc_request = JsonRpcRequest.from_jsons(message) if rpc_request.method_name == "eth_subscribe": await ws.send( JsonRpcResponse(rpc_request.id, self.eth_subscription_id).to_jsons() ) elif rpc_request.method_name == "eth_getTransactionByHash": nonce = int(rpc_request.id) await ws.send( JsonRpcResponse( rpc_request.id, tx_to_eth_rpc_json(self.sample_transactions[nonce]) ).to_jsons() ) except Exception as e: # server closed, exit pass
async def process_request(self) -> JsonRpcResponse: params = self.params assert isinstance(params, list) subscriber = self.feed_manager.subscribe_to_feed( self.feed_key, self.options) assert subscriber is not None # already validated self.subscribe_handler(subscriber, self.feed_key) return JsonRpcResponse(self.request_id, subscriber.subscription_id)
async def test_subscribe_and_unsubscribe(self): feed = TestFeed("foo") self.feed_manager.register_feed(feed) def publish(): feed.publish(1) feed.publish(2) feed.publish(3) async with websockets.unix_connect(self.ipc_path) as ws: asyncio.get_event_loop().call_later( 0.1, publish ) await ws.send( BxJsonRpcRequest( "2", RpcRequestType.SUBSCRIBE, ["foo", {}] ).to_jsons() ) response = await ws.recv() parsed_response = JsonRpcResponse.from_jsons(response) self.assertEqual("2", parsed_response.id) self.assertIsNotNone("1", parsed_response.result) subscriber_id = parsed_response.result self._assert_notification(1, subscriber_id, await ws.recv()) self._assert_notification(2, subscriber_id, await ws.recv()) self._assert_notification(3, subscriber_id, await ws.recv()) await ws.send( BxJsonRpcRequest( "3", RpcRequestType.UNSUBSCRIBE, [subscriber_id] ).to_jsons() ) response = await ws.recv() parsed_response = JsonRpcResponse.from_jsons(response) self.assertEqual("3", parsed_response.id) self.assertTrue(parsed_response.result) publish() with self.assertRaises(TimeoutError): await asyncio.wait_for(ws.recv(), 0.1)
async def handle_request(self, request: Request) -> None: websocket = self.ws await websocket.prepare(request) async for message in websocket: try: response = await self.ws_rpc_handler.handle_request( # pyre-ignore[6] Expected `multidict.CIMultiDictProxy[typing.Any]` WsRequest(cast(WSMessage, message), request.headers)) except RpcError as err: response = JsonRpcResponse(err.id, error=err).to_jsons() await websocket.send_str(response)
async def test_eth_transaction_receipts_feed_specify_include(self): self.gateway_node.opts.eth_ws_uri = f"ws://{constants.LOCALHOST}:8005" self.gateway_node.feed_manager.register_feed( EthTransactionReceiptsFeed(self.gateway_node, 0)) self.gateway_node.eth_ws_proxy_publisher = MockEthWsProxyPublisher( "", None, None, self.gateway_node) receipt_response = { "blockHash": "0xe6f67c6948158c45dct10b169ad6bf3a96c6402489733a03051feaf7d09e7b54", "blockNumber": "0xaf25e5", "cumulativeGasUsed": "0xbdb9ae", "from": "0x82170dd1cec50107963bf1ba1e80955ea302c5ce", "gasUsed": "0x5208", "logs": [], "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "status": "0x1", "to": "0xa09f63d9a0b0fbe89e41e51282ad660e7c876165", "transactionHash": "0xbcdc5b22bf463f9b8766dd61cc133caf13472b6ae8474061134d9dc2983625f6", "transactionIndex": "0x90" } receipt_result = { "transactionHash": "0xbcdc5b22bf463f9b8766dd61cc133caf13472b6ae8474061134d9dc2983625f6" } self.gateway_node.eth_ws_proxy_publisher.call_rpc = AsyncMock( return_value=JsonRpcResponse(request_id=1, result=receipt_response)) block = mock_eth_messages.get_dummy_block(5) internal_block_info = InternalEthBlockInfo.from_new_block_msg( NewBlockEthProtocolMessage(None, block, 1)) eth_raw_block = EthRawBlock( 1, internal_block_info.block_hash(), FeedSource.BLOCKCHAIN_RPC, get_block_message_lazy(internal_block_info)) async with WsProvider(self.ws_uri) as ws: subscription_id = await ws.subscribe( rpc_constants.ETH_TRANSACTION_RECEIPTS_FEED_NAME, {"include": ["receipt.transaction_hash"]}) self.gateway_node.feed_manager.publish_to_feed( FeedKey(rpc_constants.ETH_TRANSACTION_RECEIPTS_FEED_NAME), eth_raw_block) for i in range(len(block.transactions)): subscription_message = await ws.get_next_subscription_notification_by_id( subscription_id) self.assertEqual(subscription_id, subscription_message.subscription_id) self.assertEqual(subscription_message.notification, {"receipt": receipt_result})
async def test_subscribe_and_unsubscribe(self): self.gateway_node.account_model.get_feed_service_config_by_name = MagicMock( return_value=self.gateway_node.account_model. new_transaction_streaming) feed = TestFeed("foo") self.feed_manager.register_feed(feed) def publish(): feed.publish(1) feed.publish(2) feed.publish(3) async with websockets.connect(self.ws_uri) as ws: asyncio.get_event_loop().call_later(0.1, publish) await ws.send( BxJsonRpcRequest("2", RpcRequestType.SUBSCRIBE, ["foo", {}]).to_jsons()) response = await ws.recv() parsed_response = JsonRpcResponse.from_jsons(response) self.assertEqual("2", parsed_response.id) self.assertIsNotNone("1", parsed_response.result) subscriber_id = parsed_response.result self._assert_notification(1, subscriber_id, await ws.recv()) self._assert_notification(2, subscriber_id, await ws.recv()) self._assert_notification(3, subscriber_id, await ws.recv()) await ws.send( BxJsonRpcRequest("3", RpcRequestType.UNSUBSCRIBE, [subscriber_id]).to_jsons()) response = await ws.recv() parsed_response = JsonRpcResponse.from_jsons(response) self.assertEqual("3", parsed_response.id) self.assertTrue(parsed_response.result) publish() with self.assertRaises(TimeoutError): await asyncio.wait_for(ws.recv(), 0.1)
async def request(self, req: BxJsonRpcRequest): headers = dict() headers[rpc_constants.CONTENT_TYPE_HEADER_KEY] = rpc_constants.PLAIN_HEADER_TYPE headers[rpc_constants.AUTHORIZATION_HEADER_KEY] = base64.b64encode( f"{self.rpc_user}:{self.rpc_password}".encode("utf-8") ).decode("utf-8") async with ClientSession() as session: async with session.post( self.rpc_url, data=req.to_jsons(), headers=headers ) as response: return JsonRpcResponse.from_jsons(await response.json())
async def request(self, req: BxJsonRpcRequest): headers = { rpc_constants.CONTENT_TYPE_HEADER_KEY: ContentType.JSON.value, rpc_constants.AUTHORIZATION_HEADER_KEY: base64.b64encode(f"{self.rpc_user}:{self.rpc_password}".encode( "utf-8")).decode("utf-8") } async with ClientSession() as session: async with session.post(self.rpc_url, data=req.to_jsons(), headers=headers) as response: return JsonRpcResponse.from_json(await response.json())
async def process_transaction(self, network_num: int, account_id: str, transaction_str: str) -> JsonRpcResponse: if self.synchronous: return await self.post_process_transaction(network_num, account_id, self.track_flag, transaction_str) else: asyncio.create_task( self.post_process_transaction(network_num, account_id, self.track_flag, transaction_str)) return JsonRpcResponse(self.request_id, { "tx_hash": "not available with async", })
async def test_blxr_eth_call(self): self.gateway_node.eth_ws_proxy_publisher = MockEthWsProxyPublisher( None, None, None, None) self.gateway_node.eth_ws_proxy_publisher.call_rpc = AsyncMock( return_value=JsonRpcResponse(request_id=1)) result = await self.request( BxJsonRpcRequest( "1", RpcRequestType.BLXR_ETH_CALL, { rpc_constants.TRANSACTION_JSON_PARAMS_KEY: TRANSACTION_JSON, rpc_constants.TAG_PARAMS_KEY: "latest" })) self.gateway_node.eth_ws_proxy_publisher.call_rpc.mock.assert_called_with( "eth_call", [TRANSACTION_JSON, "latest"]) self.assertIsNone(result.error)
async def test_disconnect_after_startup_retry(self): gateway_constants.WS_MIN_RECONNECT_TIMEOUT_S = 10 self.provider.retry_connection = True await self.provider.initialize() await self.provider.ws.close() await asyncio.sleep(0) self.assertTrue(self.provider.running) self.assertFalse(self.provider.connected_event.is_set()) rpc_message = JsonRpcResponse("1", "foo") await self.provider.get_test_websocket().recv_messages.put( rpc_message.to_jsons()) with self.assertRaises(WsException): await self.provider.get_rpc_response("1") send_task = asyncio.create_task( self.provider.call(JsonRpcRequest("2", "123", None))) await asyncio.sleep(0.1) self.assertFalse(send_task.done()) self.assertEqual(0, len(self.provider.get_test_websocket().send_messages)) # reconnect now await self.provider.connect() await asyncio.sleep(0) # re-emit new message await self.provider.get_test_websocket().recv_messages.put( rpc_message.to_jsons()) received_message = await self.provider.get_rpc_response("1") self.assertEqual(rpc_message, received_message) # still waiting for response self.assertFalse(send_task.done()) self.assertEqual(1, len(self.provider.get_test_websocket().send_messages)) # task finished response_rpc_message = JsonRpcResponse("2", "foo") await self.provider.get_test_websocket().recv_messages.put( response_rpc_message.to_jsons()) await asyncio.sleep(0.01) self.assertTrue(send_task.done()) self.assertEqual(response_rpc_message, send_task.result())
async def receive(self) -> None: # TODO: preferably this would be an infinite loop that waits on # self.connected_event.wait(), but it seems that `async for` never # throws an exception, even when the websocket gets disconnected. logger.trace("Started receiving on websocket.") ws = self.ws assert ws is not None async for next_message in ws: logger.trace("Received message on websocket: {}", next_message) # process response messages # noinspection PyBroadException try: response_message = JsonRpcResponse.from_jsons(next_message) # pylint: disable=broad-except except Exception: pass else: await self.response_messages.put(response_message) continue # process notification messages try: subscription_message = JsonRpcRequest.from_jsons(next_message) params = subscription_message.params assert isinstance(params, dict) await self.subscription_manager.receive_message( SubscriptionNotification( params["subscription"], params["result"], )) # pylint: disable=broad-except except Exception as e: logger.warning(log_messages.ETH_RPC_PROCESSING_ERROR, next_message, e, exc_info=True) logger.trace( "Temporarily stopped receiving message on websocket. Awaiting reconnection." )
async def handle_get_request(self, request: Request) -> Response: try: self.authenticate_request(request) except HTTPUnauthorized as e: return format_http_error(e, self._handler.content_type) else: response_dict = { "result": { "required_request_type": "POST", "required_headers": [{ rpc_constants.CONTENT_TYPE_HEADER_KEY: ContentType.PLAIN.value }], "payload_structures": await self._handler.help(), } } json_response = JsonRpcResponse.from_json(response_dict) return web.json_response(json_response.to_jsons(), dumps=json_encoder.to_json)
async def process_transaction( self, network_num: int, account_id: str, quota_type: QuotaType, transaction_str: str ) -> JsonRpcResponse: if self.synchronous: return await self.post_process_transaction( network_num, account_id, quota_type, transaction_str ) else: asyncio.create_task( self.post_process_transaction( network_num, account_id, quota_type, transaction_str ) ) return JsonRpcResponse( self.request_id, { "tx_hash": "not available with async", "quota_type": quota_type.name.lower(), "synchronous": str(self.synchronous) } )
def test_serialize_to_camelCase(self): result = { "field_name": "foo", "otherFieldName": "bar", "nested_field": { "nested_field_name": "baz" } } rpc_response = JsonRpcResponse("1", result, None) json_serialized = rpc_response.to_json(Case.CAMEL) self.assertEqual("foo", json_serialized["result"]["fieldName"]) self.assertEqual("bar", json_serialized["result"]["otherFieldName"]) self.assertEqual("baz", json_serialized["result"]["nestedField"]["nestedFieldName"]) simple_rpc_response = JsonRpcResponse("1", "foo", None) simple_json = simple_rpc_response.to_json(Case.CAMEL) self.assertEqual("foo", simple_json["result"])
def test_serialize_error_rpc_notification(self): rpc_notification = JsonRpcRequest(None, "sub", ["subid", {}]) with self.assertRaises(ValueError): JsonRpcResponse.from_json(rpc_notification.to_json())
async def request(self, req: BxJsonRpcRequest) -> JsonRpcResponse: async with websockets.unix_connect(self.ipc_path) as ws: await ws.send(req.to_jsons()) return JsonRpcResponse.from_jsons(await ws.recv())