class BtcBlockQueuingServiceTest(AbstractTestCase): def setUp(self): self.node = MockGatewayNode( gateway_helpers.get_gateway_opts(8000, max_block_interval=0) ) self.node_connection = Mock() self.node_connection.is_active = MagicMock(return_value=True) self.node.set_known_total_difficulty = MagicMock() self.node.node_conn = self.node_connection self.block_queuing_service = BtcBlockQueuingService(self.node) self.node.block_queuing_service = self.block_queuing_service self.node.block_cleanup_service = BtcNormalBlockCleanupService(self.node, 1) self.block_hashes = [] self.block_msg = self._get_sample_block() self.block_queuing_service.store_block_data(self.block_msg.block_hash(), self.block_msg) self.node.get_tx_service().track_seen_short_ids(self.block_msg.block_hash(), []) def _get_sample_block(self, file_path=__file__): root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(file_path)))) with open(os.path.join(root_dir, "btc_sample_block.txt")) as sample_file: btc_block = sample_file.read().strip("\n") buf = bytearray(convert.hex_to_bytes(btc_block)) parsed_block = BlockBtcMessage(buf=buf) return parsed_block def test_tracked_block_cleanup(self): tx_service = self.node.get_tx_service() self.assertEqual(1, len(tx_service.get_tracked_blocks())) self.node.block_cleanup_service.block_cleanup_request(self.block_msg.block_hash()) time.time = MagicMock(return_value=time.time() + constants.MIN_SLEEP_TIMEOUT) self.node.alarm_queue.fire_alarms() self.assertEqual(0, len(tx_service.get_tracked_blocks()))
class OntBlockQueuingServiceTest(AbstractTestCase): def setUp(self): self.node = MockGatewayNode( gateway_helpers.get_gateway_opts(8000, max_block_interval=0)) self.node_connection = Mock() self.node_connection.is_active = MagicMock(return_value=True) self.node.set_known_total_difficulty = MagicMock() self.node.node_conn = self.node_connection self.block_queuing_service = OntBlockQueuingService(self.node) self.block_hashes = [] self.block_msg = self._get_sample_block() self.block_queuing_service.store_block_data( self.block_msg.block_hash(), self.block_msg) def test_get_recent_blocks(self): recent_blocks = list( self.block_queuing_service.iterate_recent_block_hashes(10)) self.assertEqual(len(recent_blocks), 1) self.assertEqual(recent_blocks[0], self.block_msg.block_hash()) def _get_sample_block(self, file_path=__file__): root_dir = os.path.dirname( os.path.dirname(os.path.dirname(os.path.abspath(file_path)))) with open(os.path.join(root_dir, "ont_sample_block.txt")) as sample_file: ont_block = sample_file.read().strip("\n") buf = bytearray(convert.hex_to_bytes(ont_block)) parsed_block = BlockOntMessage(buf=buf) return parsed_block def test_tracked_block_cleanup(self): self.node.block_queuing_service = self.block_queuing_service tx_service = self.node.get_tx_service() for block_hash in self.block_queuing_service.iterate_recent_block_hashes( 10): tx_service.track_seen_short_ids(block_hash, []) self.node._tracked_block_cleanup()
class EthBlockQueuingServiceFetchingTest(AbstractTestCase): def setUp(self): self.node = MockGatewayNode( gateway_helpers.get_gateway_opts(8000, max_block_interval_s=0)) self.node.block_parts_storage = ExpiringDict( self.node.alarm_queue, gateway_constants.MAX_BLOCK_CACHE_TIME_S, "eth_block_queue_parts", ) self.node_connection = Mock() self.node_connection.is_active = MagicMock(return_value=True) self.node.set_known_total_difficulty = MagicMock() self.node_conn = self.node_connection self.block_queuing_service = EthBlockQueuingService( self.node, self.node_conn) self.node.block_queuing_service_manager.add_block_queuing_service( self.node_conn, self.block_queuing_service) self.node_conn.enqueue_msg = MagicMock() self.block_hashes = [] self.block_messages = [] self.block_headers = [] self.block_bodies = [] # block numbers: 1000-1019 prev_block_hash = None for i in range(20): block_message = InternalEthBlockInfo.from_new_block_msg( mock_eth_messages.new_block_eth_protocol_message( i, i + 1000, prev_block_hash=prev_block_hash)) block_hash = block_message.block_hash() self.block_hashes.append(block_hash) self.block_messages.append(block_message) block_parts = block_message.to_new_block_parts() self.block_headers.append( BlockHeadersEthProtocolMessage.from_header_bytes( block_parts.block_header_bytes).get_block_headers()[0]) self.block_bodies.append( BlockBodiesEthProtocolMessage.from_body_bytes( block_parts.block_body_bytes).get_blocks()[0]) self.node.block_queuing_service_manager.push( block_hash, block_message) prev_block_hash = block_hash for block_hash in self.block_hashes: self.block_queuing_service.remove_from_queue(block_hash) self.block_queuing_service.best_sent_block = (1019, self.block_hashes[-1], time.time()) self.block_queuing_service.best_accepted_block = ( 1019, self.block_hashes[-1]) def test_get_block_hashes_from_hash(self): # request: start: 1019, count: 1 # result: 1019 success, hashes = self.block_queuing_service.get_block_hashes_starting_from_hash( self.block_hashes[19], 1, 0, False) self.assertTrue(success) self.assertEqual(1, len(hashes)) self.assertEqual(self.block_hashes[19], hashes[0]) # request: start: 1010, count: 10 # result: 1010, 1011, .. 1019 success, hashes = self.block_queuing_service.get_block_hashes_starting_from_hash( self.block_hashes[10], 10, 0, False) self.assertTrue(success) self.assertEqual(10, len(hashes)) for i, block_hash in enumerate(hashes): self.assertEqual(self.block_hashes[i + 10], hashes[i]) # request: start: 1010, count: 2, skip: 5 # result: 1010, 1016 success, hashes = self.block_queuing_service.get_block_hashes_starting_from_hash( self.block_hashes[10], 2, 5, False) self.assertTrue(success) self.assertEqual(2, len(hashes)) self.assertEqual(self.block_hashes[10], hashes[0]) self.assertEqual(self.block_hashes[16], hashes[1]) # request: start: 1010, count: 2, skip: 5, reverse = True # result: 1010, 1004 success, hashes = self.block_queuing_service.get_block_hashes_starting_from_hash( self.block_hashes[10], 2, 5, True) self.assertTrue(success) self.assertEqual(2, len(hashes)) self.assertEqual(self.block_hashes[10], hashes[0]) self.assertEqual(self.block_hashes[4], hashes[1]) def test_get_block_hashes_from_height(self): success, hashes = self.block_queuing_service.get_block_hashes_starting_from_height( 1019, 1, 0, False) self.assertTrue(success) self.assertEqual(1, len(hashes)) self.assertEqual(self.block_hashes[19], hashes[0]) def test_get_block_by_hash_orphaned_not_found(self): # create fork at block 17 block_message = InternalEthBlockInfo.from_new_block_msg( mock_eth_messages.new_block_eth_protocol_message(21, 1017)) block_hash = block_message.block_hash() self.block_queuing_service.push(block_hash, block_message) success, hashes = self.block_queuing_service.get_block_hashes_starting_from_hash( block_hash, 1, 0, False) self.assertTrue(success) self.assertEqual(0, len(hashes)) def test_get_block_by_number_ambiguous_succeeds(self): # create fork at block 17 block_message = InternalEthBlockInfo.from_new_block_msg( mock_eth_messages.new_block_eth_protocol_message(21, 1017)) block_hash = block_message.block_hash() self.block_queuing_service.push(block_hash, block_message) success, hashes = self.block_queuing_service.get_block_hashes_starting_from_height( 1017, 1, 0, False) self.assertTrue(success) self.assertEqual(self.block_hashes[17], hashes[0]) def test_get_block_hashes_fork_follows_last_sent(self): # create fork at block 17 block_message = InternalEthBlockInfo.from_new_block_msg( mock_eth_messages.new_block_eth_protocol_message(21, 1017)) block_hash = block_message.block_hash() self.block_queuing_service.push(block_hash, block_message) success, hashes = self.block_queuing_service.get_block_hashes_starting_from_hash( self.block_hashes[15], 5, 0, False) self.assertTrue(success) self.assertEqual(5, len(hashes)) self.assertEqual(self.block_hashes[15:20], hashes) def test_get_blocks_from_height_not_all_found_at_end(self): success, hashes = self.block_queuing_service.get_block_hashes_starting_from_height( 1018, 5, 0, False) self.assertTrue(success) self.assertEqual(2, len(hashes)) self.assertEqual(self.block_hashes[18], hashes[0]) self.assertEqual(self.block_hashes[19], hashes[1]) def test_get_blocks_from_height_not_all_found_at_beginning(self): success, hashes = self.block_queuing_service.get_block_hashes_starting_from_height( 950, 5, 0, False) self.assertFalse(success) self.assertEqual(0, len(hashes)) def test_iterate_recent_block_hashes(self): top_blocks = list( self.block_queuing_service.iterate_recent_block_hashes( max_count=10)) block_hash = top_blocks[0] self.assertEqual( self.block_queuing_service._height_by_block_hash[block_hash], self.block_queuing_service._highest_block_number) self.assertEqual(10, len(top_blocks)) def test_get_transactions_hashes_from_message(self): last_block_hash = list( self.block_queuing_service._block_hashes_by_height[ self.block_queuing_service._highest_block_number])[0] self.assertIsNotNone( self.block_queuing_service.get_block_body_from_message( last_block_hash)) self.assertIsNone( self.block_queuing_service.get_block_body_from_message(bytes(64))) def test_tracked_block_cleanup(self): from bxgateway.services.eth.eth_normal_block_cleanup_service import EthNormalBlockCleanupService self.node.block_cleanup_service = EthNormalBlockCleanupService( self.node, 1) tx_service = self.node.get_tx_service() for block_hash in self.block_queuing_service.iterate_recent_block_hashes( self.node.network.block_confirmations_count + 2): tx_service.track_seen_short_ids(block_hash, []) self.node.block_cleanup_service.block_cleanup_request = MagicMock() self.node._tracked_block_cleanup() self.node.block_cleanup_service.block_cleanup_request.assert_called_once( ) def test_try_send_headers_to_node_success(self): self.node_conn.enqueue_msg.reset_mock() result = self.block_queuing_service.try_send_headers_to_node( self.block_hashes[:4]) self.assertTrue(result) self.node_conn.enqueue_msg.assert_called_once_with( BlockHeadersEthProtocolMessage(None, self.block_headers[:4])) def test_try_send_headers_to_node_unknown_block(self): self.node.broadcast = MagicMock() result = self.block_queuing_service.try_send_headers_to_node( [self.block_hashes[0], helpers.generate_object_hash()]) self.assertFalse(result) self.node.broadcast.assert_not_called() def test_try_send_bodies_to_node_success(self): self.node_conn.enqueue_msg.reset_mock() result = self.block_queuing_service.try_send_bodies_to_node( self.block_hashes[:4]) self.assertTrue(result) self.node_conn.enqueue_msg.assert_called_once_with( BlockBodiesEthProtocolMessage(None, self.block_bodies[:4])) def test_try_send_bodies_to_node_unknown_block(self): self.node_conn.enqueue_msg.reset_mock() result = self.block_queuing_service.try_send_bodies_to_node( [self.block_hashes[0], helpers.generate_object_hash()]) self.assertFalse(result) self.node_conn.enqueue_msg.assert_not_called()
class EthWsProxyPublisherTest(AbstractTestCase): @async_test async def setUp(self) -> None: crypto_utils.recover_public_key = MagicMock(return_value=bytes(32)) account_model = BdnAccountModelBase( "account_id", "account_name", "fake_certificate", new_transaction_streaming=BdnServiceModelConfigBase( expire_date=date(2999, 1, 1).isoformat())) self.eth_ws_port = helpers.get_free_port() self.eth_ws_uri = f"ws://127.0.0.1:{self.eth_ws_port}" self.eth_ws_server_message_queue = asyncio.Queue() await self.start_server() gateway_opts = gateway_helpers.get_gateway_opts( 8000, eth_ws_uri=self.eth_ws_uri, ws=True) gateway_opts.set_account_options(account_model) self.gateway_node = MockGatewayNode(gateway_opts) self.gateway_node.transaction_streamer_peer = OutboundPeerModel( "127.0.0.1", 8006, node_type=NodeType.INTERNAL_GATEWAY) self.gateway_node.feed_manager.register_feed( EthPendingTransactionFeed(self.gateway_node.alarm_queue)) self.eth_ws_proxy_publisher = EthWsProxyPublisher( self.eth_ws_uri, self.gateway_node.feed_manager, self.gateway_node.get_tx_service(), self.gateway_node) self.subscriber: Subscriber[ RawTransactionFeedEntry] = self.gateway_node.feed_manager.subscribe_to_feed( EthPendingTransactionFeed.NAME, {}) self.assertIsNotNone(self.subscriber) await self.eth_ws_proxy_publisher.start() await asyncio.sleep(0.01) self.assertEqual(len(self.eth_ws_proxy_publisher.receiving_tasks), 2) self.assertEqual(0, self.subscriber.messages.qsize()) self.sample_transactions = { i: mock_eth_messages.get_dummy_transaction(i) for i in range(10) } async def start_server(self) -> None: self.eth_test_ws_server = await websockets.serve( self.ws_test_serve, constants.LOCALHOST, self.eth_ws_port) async def ws_test_serve(self, websocket, path): 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, str(rpc_request.params[0])).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: # server closed, exit pass async def producer(ws, _path): try: while True: subscription, message = await self.eth_ws_server_message_queue.get( ) await ws.send( JsonRpcRequest( None, "eth_subscription", { "subscription": subscription, "result": message }, ).to_jsons()) except Exception: # server closed, exit pass consumer_task = asyncio.create_task(consumer(websocket, path)) producer_task = asyncio.create_task(producer(websocket, path)) done, pending = await asyncio.wait( [consumer_task, producer_task], return_when=asyncio.FIRST_COMPLETED, ) for task in pending: task.cancel() @async_test async def test_subscription(self): tx_hash = helpers.generate_object_hash() tx_contents = mock_eth_messages.get_dummy_transaction(1) self.gateway_node.get_tx_service().set_transaction_contents( tx_hash, rlp.encode(tx_contents)) tx_hash_2 = helpers.generate_object_hash() tx_contents_2 = mock_eth_messages.get_dummy_transaction(2) self.gateway_node.get_tx_service().set_transaction_contents( tx_hash_2, rlp.encode(tx_contents_2)) await self.eth_ws_server_message_queue.put( (TX_SUB_ID, f"0x{convert.bytes_to_hex(tx_hash.binary)}")) await self.eth_ws_server_message_queue.put( (TX_SUB_ID, f"0x{convert.bytes_to_hex(tx_hash_2.binary)}")) await asyncio.sleep(0.01) self.assertEqual(2, self.subscriber.messages.qsize()) tx_message_1 = await self.subscriber.receive() self.assertEqual(f"0x{convert.bytes_to_hex(tx_hash.binary)}", tx_message_1["tx_hash"]) self.assertEqual(tx_contents.to_json(), tx_message_1["tx_contents"]) tx_message_2 = await self.subscriber.receive() self.assertEqual(f"0x{convert.bytes_to_hex(tx_hash_2.binary)}", tx_message_2["tx_hash"]) self.assertEqual(tx_contents_2.to_json(), tx_message_2["tx_contents"]) @async_test async def test_subscription_with_no_content_filled(self): self.maxDiff = None tx_hash = helpers.generate_hash() await self.eth_ws_server_message_queue.put( (TX_SUB_ID, f"0x{convert.bytes_to_hex(tx_hash)}")) await asyncio.sleep(0.01) self.assertEqual(1, self.subscriber.messages.qsize()) tx_message = await self.subscriber.receive() self.assertEqual(f"0x{convert.bytes_to_hex(tx_hash)}", tx_message["tx_hash"]) expected_contents = self.sample_transactions[3].to_json() self.assertEqual(expected_contents, tx_message["tx_contents"]) @patch("bxcommon.constants.WS_RECONNECT_TIMEOUTS", [0.01]) @patch("bxcommon.constants.WS_MIN_RECONNECT_TIMEOUT_S", 0) @async_test async def test_disconnect_server(self): tx_hash = helpers.generate_object_hash() tx_contents = mock_eth_messages.get_dummy_transaction(1) self.gateway_node.get_tx_service().set_transaction_contents( tx_hash, rlp.encode(tx_contents)) self.eth_test_ws_server.close() await self.eth_test_ws_server.wait_closed() await self.eth_ws_server_message_queue.put( (TX_SUB_ID, f"0x{convert.bytes_to_hex(tx_hash.binary)}")) await asyncio.sleep(0.05) self.assertEqual(0, self.subscriber.messages.qsize()) self.assertFalse(self.eth_ws_proxy_publisher.running) @patch("bxcommon.constants.WS_RECONNECT_TIMEOUTS", [0.01, 0.05]) @patch("bxcommon.constants.WS_MIN_RECONNECT_TIMEOUT_S", 0) @async_test async def test_disconnect_server_reconnect(self): tx_hash = helpers.generate_object_hash() tx_contents = mock_eth_messages.get_dummy_transaction(1) self.gateway_node.get_tx_service().set_transaction_contents( tx_hash, rlp.encode(tx_contents)) self.eth_test_ws_server.close() await self.eth_test_ws_server.wait_closed() await self.eth_ws_server_message_queue.put( (TX_SUB_ID, f"0x{convert.bytes_to_hex(tx_hash.binary)}")) await asyncio.sleep(0.02) self.assertEqual(0, self.subscriber.messages.qsize()) self.assertTrue(self.eth_ws_proxy_publisher.running) self.assertFalse(self.eth_ws_proxy_publisher.connected_event.is_set()) await self.start_server() await asyncio.sleep(0.1) self.assertTrue(self.eth_ws_proxy_publisher.connected_event.is_set()) self.assertEqual(0, self.subscriber.messages.qsize()) await self.eth_ws_server_message_queue.put( (TX_SUB_ID, f"0x{convert.bytes_to_hex(tx_hash.binary)}")) await asyncio.sleep(0.01) self.assertEqual(1, self.subscriber.messages.qsize()) @patch("bxcommon.constants.WS_RECONNECT_TIMEOUTS", [0.01]) @patch("bxcommon.constants.WS_MIN_RECONNECT_TIMEOUT_S", 0) @async_test async def test_disconnect_server_revive(self): tx_hash = helpers.generate_object_hash() tx_contents = mock_eth_messages.get_dummy_transaction(1) self.gateway_node.get_tx_service().set_transaction_contents( tx_hash, rlp.encode(tx_contents)) self.eth_test_ws_server.close() await self.eth_test_ws_server.wait_closed() await self.eth_ws_server_message_queue.put( (TX_SUB_ID, f"0x{convert.bytes_to_hex(tx_hash.binary)}")) await asyncio.sleep(0.05) self.assertEqual(0, self.subscriber.messages.qsize()) self.assertFalse(self.eth_ws_proxy_publisher.running) await self.start_server() await asyncio.sleep(0) await self.eth_ws_proxy_publisher.revive() await self.eth_ws_server_message_queue.put( (TX_SUB_ID, f"0x{convert.bytes_to_hex(tx_hash.binary)}")) await asyncio.sleep(0.01) self.assertEqual(1, self.subscriber.messages.qsize()) @async_test async def tearDown(self) -> None: await self.eth_ws_proxy_publisher.stop() self.eth_test_ws_server.close() await self.eth_test_ws_server.wait_closed() crypto_utils.recover_public_key = _recover_public_key
class AbstractGatewayRpcIntegrationTest(AbstractTestCase): def __init__(self, *args, **kwargs): # hack to avoid unit test discovery of this class super().__init__(*args, **kwargs) if self.__class__ != AbstractGatewayRpcIntegrationTest: # pylint: disable=no-value-for-parameter self.run = unittest.TestCase.run.__get__(self, self.__class__) else: self.run = lambda self, *args, **kwargs: None self._account_model = None # pyre-ignore async setup problems async def setUp(self) -> None: self.gateway_node = MockGatewayNode(self.get_gateway_opts()) self.gateway_node.requester = ThreadedRequestService( "mock_thread_service", self.gateway_node.alarm_queue, constants.THREADED_HTTP_POOL_SLEEP_INTERVAL_S) self.gateway_node.requester.start() self.gateway_node.account_id = ACCOUNT_ID self.node_endpoint_1 = IpEndpoint("127.0.0.1", 7000) self.blockchain_connection_1 = EthBaseConnection( MockSocketConnection(node=self.gateway_node, ip_address="127.0.0.1", port=7000), cast(EthGatewayNode, self.gateway_node)) self.blockchain_connection_1.state = ConnectionState.ESTABLISHED @abstractmethod def get_gateway_opts(self) -> GatewayOpts: feed_service_model = FeedServiceModelBase(allow_filtering=True, available_fields=["all"]) base_feed_service_model = BdnFeedServiceModelConfigBase( expire_date="2999-01-01", feed=feed_service_model) 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( )), new_transaction_streaming=base_feed_service_model, new_pending_transaction_streaming=base_feed_service_model, on_block_feed=base_feed_service_model, new_block_streaming=base_feed_service_model, transaction_state_feed=base_feed_service_model, blockchain_protocol="Ethereum", blockchain_network="Mainnet", ) @abstractmethod async def request(self, req: BxJsonRpcRequest) -> JsonRpcResponse: pass @async_test async def test_blxr_tx(self): result = await self.request( BxJsonRpcRequest( "1", RpcRequestType.BLXR_TX, { rpc_constants.TRANSACTION_PARAMS_KEY: RAW_TRANSACTION_HEX, rpc_constants.STATUS_TRACK_PARAMS_KEY: "True" })) self.assertEqual("1", result.id) self.assertIsNone(result.error) self.assertEqual(TRANSACTION_HASH, result.result["tx_hash"]) self.assertEqual(ACCOUNT_ID, result.result["account_id"]) self.assertEqual(TransactionFlag.PAID_TX.name.lower(), result.result["transaction_flag"].lower()) self.assertEqual(1, len(self.gateway_node.broadcast_messages)) self.assertEqual(Sha256Hash(convert.hex_to_bytes(TRANSACTION_HASH)), self.gateway_node.broadcast_messages[0][0].tx_hash()) @async_test async def test_blxr_tx_expired(self): self.gateway_node.account_model.is_account_valid = MagicMock( return_value=False) result = await self.request( BxJsonRpcRequest( "2", RpcRequestType.BLXR_TX, { rpc_constants.TRANSACTION_PARAMS_KEY: RAW_TRANSACTION_HEX, })) self.assertEqual("2", result.id) self.assertEqual("Invalid Account ID", result.error.message) self.assertIsNone(result.result) self.assertIsNotNone(result.error) @async_test async def test_blxr_tx_quota_exceeded(self): self.gateway_node.quota_level = 100 result = await self.request( BxJsonRpcRequest( "3", RpcRequestType.BLXR_TX, { rpc_constants.TRANSACTION_PARAMS_KEY: RAW_TRANSACTION_HEX, })) self.assertEqual("3", result.id) self.assertEqual("Insufficient quota", result.error.message) self.assertIsNone(result.result) self.assertIsNotNone(result.error) @async_test async def test_blxr_tx_from_json(self): tx_json = { 'from': '0xc165599b5e418bb9d8a19090696ea2403b2927ed', 'gas': "0x5208", 'gasPrice': "0xc1b759d70", 'hash': '0xd569674ad9fcaaedcb6867b7896067b445d4a838316be4292c474df17bf4bd50', 'input': '0x', 'nonce': "0x14", 'to': '0xdb5f0c1f4198bc6ffa98b35f7188f82740b8caf7', 'value': "0x3e871b540c000", 'v': '0x26', 'r': '0x484bc950fb2d595500baa604774cb8d156a677198e087801936f38ca0b27049', 's': '0x7ca82ef7b6938c2e96966dd940b186c9cdc0c7f42b2843adbc0751bb6e67a2d4' } result = await self.request( BxJsonRpcRequest( "1", RpcRequestType.BLXR_TX, { rpc_constants.TRANSACTION_JSON_PARAMS_KEY: tx_json, })) self.assertEqual("1", result.id) self.assertIsNone(result.error) self.assertEqual( "ad6f9332384194f80d8e49af8f093ad019b3f6b7173eb2956a46c9a0d8c4d03c", result.result["tx_hash"]) self.assertEqual(ACCOUNT_ID, result.result["account_id"]) self.assertEqual(TransactionFlag.PAID_TX.name.lower(), result.result["transaction_flag"].lower()) self.assertEqual(1, len(self.gateway_node.broadcast_messages)) self.assertEqual( Sha256Hash( convert.hex_to_bytes( "ad6f9332384194f80d8e49af8f093ad019b3f6b7173eb2956a46c9a0d8c4d03c" )), self.gateway_node.broadcast_messages[0][0].tx_hash()) @async_test 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_test async def test_gateway_status(self): result_summary = await self.request( BxJsonRpcRequest("2", RpcRequestType.GATEWAY_STATUS, None)) self.assertEqual("2", result_summary.id) self.assertIsNone(result_summary.error) self.assertEqual(constants.LOCALHOST, result_summary.result["ip_address"]) self.assertEqual("NA", result_summary.result["continent"]) self.assertEqual("United States", result_summary.result["country"]) result_detailed = await self.request( BxJsonRpcRequest( "2.5", RpcRequestType.GATEWAY_STATUS, { rpc_constants.DETAILS_LEVEL_PARAMS_KEY: GatewayStatusDetailsLevel.DETAILED.name })) self.assertEqual("2.5", result_detailed.id) self.assertIsNone(result_detailed.error) self.assertEqual(constants.LOCALHOST, result_detailed.result["summary"]["ip_address"]) self.assertEqual("NA", result_detailed.result["summary"]["continent"]) self.assertEqual("United States", result_detailed.result["summary"]["country"]) @async_test async def test_stop(self): result = await self.request( BxJsonRpcRequest("3", RpcRequestType.STOP, None)) self.assertEqual("3", result.id) self.assertIsNone(result.error) self.assertEqual({}, result.result) self.gateway_node.should_force_exit = True self.gateway_node.should_restart_on_high_memory = False @async_test async def test_memory(self): result = await self.request( BxJsonRpcRequest("4", RpcRequestType.MEMORY, None)) self.assertEqual("4", result.id) self.assertIsNone(result.error) self.assertEqual( 0, result.result[gateway_memory_rpc_request.TOTAL_CACHED_TX]) self.assertEqual( "0 bytes", result.result[gateway_memory_rpc_request.TOTAL_CACHED_TX_SIZE]) @async_test async def test_peers(self): result = await self.request( BxJsonRpcRequest("5", RpcRequestType.PEERS, None)) self.assertEqual("5", result.id) self.assertIsNone(result.error) self.assertEqual([], result.result) @async_test async def test_bdn_performance_single_node(self): self.gateway_node.connection_pool.add(20, self.node_endpoint_1.ip_address, self.node_endpoint_1.port, self.blockchain_connection_1) gateway_bdn_performance_stats_service.set_node(self.gateway_node) gateway_bdn_performance_stats_service.log_block_from_bdn() gateway_bdn_performance_stats_service.log_block_from_bdn() gateway_bdn_performance_stats_service.log_block_from_blockchain_node( self.node_endpoint_1) gateway_bdn_performance_stats_service.log_tx_from_bdn() gateway_bdn_performance_stats_service.log_tx_from_blockchain_node( self.node_endpoint_1) gateway_bdn_performance_stats_service.log_tx_from_blockchain_node( self.node_endpoint_1) gateway_bdn_performance_stats_service.close_interval_data() result = await self.request( BxJsonRpcRequest("6", RpcRequestType.BDN_PERFORMANCE, None)) self.assertEqual("6", result.id) self.assertIsNone(result.error) node_stats = result.result[str(self.node_endpoint_1)] self.assertEqual("66.67%", node_stats["blocks_from_bdn_percentage"]) self.assertEqual("33.33%", node_stats["transactions_from_bdn_percentage"]) self.assertEqual(3, node_stats["total_blocks_seen"]) @async_test async def test_bdn_performance_multi_node(self): self.gateway_node.connection_pool.add(20, self.node_endpoint_1.ip_address, self.node_endpoint_1.port, self.blockchain_connection_1) blockchain_connection_2 = EthBaseConnection( MockSocketConnection(node=self.gateway_node, ip_address="127.0.0.1", port=333), self.gateway_node) blockchain_connection_2.state = ConnectionState.ESTABLISHED self.gateway_node.mock_add_blockchain_peer(blockchain_connection_2) node_endpoint_2 = IpEndpoint("127.0.0.1", 333) self.gateway_node.connection_pool.add(21, node_endpoint_2.ip_address, node_endpoint_2.port, blockchain_connection_2) gateway_bdn_performance_stats_service.set_node(self.gateway_node) gateway_bdn_performance_stats_service.log_block_from_bdn() gateway_bdn_performance_stats_service.log_block_from_bdn() gateway_bdn_performance_stats_service.log_block_from_blockchain_node( self.node_endpoint_1) gateway_bdn_performance_stats_service.log_tx_from_bdn() gateway_bdn_performance_stats_service.log_tx_from_blockchain_node( self.node_endpoint_1) gateway_bdn_performance_stats_service.log_tx_from_blockchain_node( self.node_endpoint_1) gateway_bdn_performance_stats_service.close_interval_data() result = await self.request( BxJsonRpcRequest("6", RpcRequestType.BDN_PERFORMANCE, None)) self.assertEqual("6", result.id) self.assertIsNone(result.error) node_stats = result.result[str(self.node_endpoint_1)] self.assertEqual("66.67%", node_stats["blocks_from_bdn_percentage"]) self.assertEqual("33.33%", node_stats["transactions_from_bdn_percentage"]) self.assertEqual(3, node_stats["total_blocks_seen"]) node_stats_2 = result.result[str(node_endpoint_2)] self.assertEqual("100.00%", node_stats_2["blocks_from_bdn_percentage"]) self.assertEqual("100.00%", node_stats_2["transactions_from_bdn_percentage"]) self.assertEqual(3, node_stats_2["total_blocks_seen"]) @async_test async def test_transaction_status(self): time.time = MagicMock(return_value=time.time()) expected_assignment_time = datetime.datetime.fromtimestamp( time.time()).isoformat() short_id = 123 transaction_hash = helpers.generate_object_hash() transaction_contents = helpers.generate_bytearray(250) tx_service = self.gateway_node.get_tx_service() transaction_key = tx_service.get_transaction_key(transaction_hash) tx_service.set_transaction_contents_by_key(transaction_key, transaction_contents) tx_service.assign_short_id_by_key(transaction_key, short_id) result = await self.request( BxJsonRpcRequest("7", RpcRequestType.TX_STATUS, { "transaction_hash": convert.bytes_to_hex(transaction_hash.binary) })) self.assertEqual("7", result.id) self.assertIsNone(result.error) self.assertEqual( { "status": "assigned short ID", "short_ids": [123], "assignment_time": expected_assignment_time }, result.result) @async_test async def test_add_blockchain_peer(self): self.gateway_node.enqueue_connection = MagicMock() result = await self.request( BxJsonRpcRequest( "8", RpcRequestType.ADD_BLOCKCHAIN_PEER, { "peer": "enode://d76d7d11a822fab02836f8b0ea462205916253eb630935d15191fb6f9d218cd94a768fc5b3d5516b9ed5010a4765f95aea7124a39d0ab8aaf6fa3d57e21ef396@127.0.0.1:30302" })) self.gateway_node.enqueue_connection.assert_called_once_with( "127.0.0.1", 30302, ConnectionType.BLOCKCHAIN_NODE) self.assertIn(BlockchainPeerInfo("127.0.0.1", 30302), self.gateway_node.blockchain_peers) self.assertEqual("8", result.id) self.assertIsNone(result.error) self.assertEqual({"new_peer": "127.0.0.1:30302"}, result.result) @async_test async def test_remove_blockchain_peer(self): conn = MockConnection( MockSocketConnection(9, ip_address="127.0.0.1", port=30302), self.gateway_node) conn.mark_for_close = MagicMock() self.gateway_node.connection_pool.add(9, "127.0.0.1", 30302, conn) self.gateway_node.blockchain_peers.add( BlockchainPeerInfo( "127.0.0.1", 30302, "d76d7d11a822fab02836f8b0ea462205916253eb630935d15191fb6f9d218cd94a768fc5b3d5516b9ed5010a4765f95aea7124a39d0ab8aaf6fa3d57e21ef396" )) result = await self.request( BxJsonRpcRequest( "9", RpcRequestType.REMOVE_BLOCKCHAIN_PEER, { "peer": "enode://d76d7d11a822fab02836f8b0ea462205916253eb630935d15191fb6f9d218cd94a768fc5b3d5516b9ed5010a4765f95aea7124a39d0ab8aaf6fa3d57e21ef396@127.0.0.1:30302" })) self.assertNotIn(BlockchainPeerInfo("127.0.0.1", 30302), self.gateway_node.blockchain_peers) conn.mark_for_close.assert_called_once_with(False) self.assertEqual("9", result.id) self.assertIsNone(result.error) self.assertEqual({"removed_peer": "127.0.0.1:30302"}, result.result) @async_test async def test_remove_blockchain_peer_not_in_pool(self): self.gateway_node.alarm_queue.register_alarm = MagicMock() self.gateway_node.blockchain_peers.add( BlockchainPeerInfo( "127.0.0.1", 30302, "d76d7d11a822fab02836f8b0ea462205916253eb630935d15191fb6f9d218cd94a768fc5b3d5516b9ed5010a4765f95aea7124a39d0ab8aaf6fa3d57e21ef396" )) result = await self.request( BxJsonRpcRequest( "9", RpcRequestType.REMOVE_BLOCKCHAIN_PEER, { "peer": "enode://d76d7d11a822fab02836f8b0ea462205916253eb630935d15191fb6f9d218cd94a768fc5b3d5516b9ed5010a4765f95aea7124a39d0ab8aaf6fa3d57e21ef396@127.0.0.1:30302" })) self.assertNotIn(BlockchainPeerInfo("127.0.0.1", 30302), self.gateway_node.blockchain_peers) self.gateway_node.alarm_queue.register_alarm.assert_called() self.assertEqual("9", result.id) self.assertIsNone(result.error) self.assertEqual({"removed_peer": "127.0.0.1:30302"}, result.result)
class EthNodeConnectionProtocolTest(AbstractTestCase): def setUp(self) -> None: opts = gateway_helpers.get_gateway_opts(8000, include_default_eth_args=True) self.node = MockGatewayNode(opts) self.connection = MagicMock(spec=AbstractGatewayBlockchainConnection) self.connection.node = self.node self.connection.is_active = MagicMock(return_value=True) self.connection.network_num = 5 self.connection.format_connection_desc = "127.0.0.1:12345 - B" self.node.set_known_total_difficulty = MagicMock() self.node.node_conn = self.connection self.node.message_converter = EthNormalMessageConverter() self.cleanup_service = EthNormalBlockCleanupService( self.node, NETWORK_NUM ) self.node.block_cleanup_service = self.cleanup_service self.node.block_processing_service = EthBlockProcessingService( self.node ) self.node.block_processing_service.queue_block_for_processing = MagicMock() self.node.block_queuing_service = EthBlockQueuingService(self.node) dummy_private_key = crypto_utils.make_private_key( helpers.generate_bytearray(111) ) dummy_public_key = crypto_utils.private_to_public_key(dummy_private_key) self.sut = EthNodeConnectionProtocol( self.connection, True, dummy_private_key, dummy_public_key ) self.sut._waiting_checkpoint_headers_request = False def test_request_block_bodies(self): self.cleanup_service.clean_block_transactions_by_block_components = ( MagicMock() ) block_hashes_sets = [ [helpers.generate_object_hash(), helpers.generate_object_hash()], [helpers.generate_object_hash()], [helpers.generate_object_hash()], ] block_bodies_messages = [ BlockBodiesEthProtocolMessage( None, [ mock_eth_messages.get_dummy_transient_block_body(1), mock_eth_messages.get_dummy_transient_block_body(2), ], ), BlockBodiesEthProtocolMessage( None, [mock_eth_messages.get_dummy_transient_block_body(3)] ), BlockBodiesEthProtocolMessage( None, [mock_eth_messages.get_dummy_transient_block_body(4)] ), ] transactions = [ [ [ tx.hash() for tx in BlockBodiesEthProtocolMessage.from_body_bytes( block_body_bytes ) .get_blocks()[0] .transactions ] for block_body_bytes in msg.get_block_bodies_bytes() ] for msg in block_bodies_messages ] for block_bodies_message in block_bodies_messages: block_bodies_message.serialize() for block_hashes_set in block_hashes_sets: for block_hash in block_hashes_set: self.node.block_cleanup_service._block_hash_marked_for_cleanup.add( block_hash ) self.sut.request_block_body(block_hashes_set) self.sut.msg_block_bodies(block_bodies_messages[0]) call_args_list = ( self.cleanup_service.clean_block_transactions_by_block_components.call_args_list ) first_call = call_args_list[0] first_kwargs = first_call[1] self.assertEqual( first_kwargs["transaction_service"], self.node.get_tx_service() ) self.assertEqual(first_kwargs["block_hash"], block_hashes_sets[0][0]) self.assertEqual( list(first_kwargs["transactions_list"]), transactions[0][0] ) second_call = call_args_list[1] second_kwargs = second_call[1] self.assertEqual( second_kwargs["transaction_service"], self.node.get_tx_service() ) self.assertEqual(second_kwargs["block_hash"], block_hashes_sets[0][1]) self.assertEqual( list(second_kwargs["transactions_list"]), transactions[0][1] ) self.cleanup_service.clean_block_transactions_by_block_components.reset_mock() self.sut.msg_block_bodies(block_bodies_messages[1]) call_args_list = ( self.cleanup_service.clean_block_transactions_by_block_components.call_args_list ) first_call = call_args_list[0] first_kwargs = first_call[1] self.assertEqual( first_kwargs["transaction_service"], self.node.get_tx_service() ) self.assertEqual(first_kwargs["block_hash"], block_hashes_sets[1][0]) self.assertEqual( list(first_kwargs["transactions_list"]), transactions[1][0] ) self.cleanup_service.clean_block_transactions_by_block_components.reset_mock() self.sut.msg_block_bodies(block_bodies_messages[2]) call_args_list = ( self.cleanup_service.clean_block_transactions_by_block_components.call_args_list ) first_call = call_args_list[0] first_kwargs = first_call[1] self.assertEqual( first_kwargs["transaction_service"], self.node.get_tx_service() ) self.assertEqual(first_kwargs["block_hash"], block_hashes_sets[2][0]) self.assertEqual( list(first_kwargs["transactions_list"]), transactions[2][0] ) def test_msg_get_block_headers_unknown(self): block_hash = helpers.generate_hash() self.sut.msg_proxy_request = MagicMock() message = GetBlockHeadersEthProtocolMessage(None, block_hash, 1, 0, 0) self.sut.msg_get_block_headers(message) self.sut.msg_proxy_request.assert_called_once() def test_msg_get_block_headers_known_single(self): header = mock_eth_messages.get_dummy_block_header(12) block = mock_eth_messages.get_dummy_block(1, header) raw_hash = header.hash() block_hash = header.hash_object() new_block_message = NewBlockEthProtocolMessage(None, block, 10) eth_block_info = InternalEthBlockInfo.from_new_block_msg( new_block_message ) self.node.block_queuing_service.push(block_hash, eth_block_info) self.node.send_to_node_messages.clear() self.sut.msg_proxy_request = MagicMock() message = GetBlockHeadersEthProtocolMessage(None, raw_hash, 1, 0, 0) self.sut.msg_get_block_headers(message) self.sut.msg_proxy_request.assert_not_called() self.assertEqual(1, len(self.node.send_to_node_messages)) headers_sent = self.node.send_to_node_messages[0] self.assertIsInstance(headers_sent, BlockHeadersEthProtocolMessage) self.assertEqual(1, len(headers_sent.get_block_headers())) self.assertEqual( block_hash, headers_sent.get_block_headers()[0].hash_object() ) self.node.block_queuing_service.mark_block_seen_by_blockchain_node( block_hash, eth_block_info ) self.sut.msg_get_block_headers(message) self.sut.msg_proxy_request.assert_not_called() self.assertEqual(2, len(self.node.send_to_node_messages)) def test_msg_get_block_headers_block_number(self): block_number = 123456 header = mock_eth_messages.get_dummy_block_header( 12, block_number=block_number ) block = mock_eth_messages.get_dummy_block(1, header) block_hash = header.hash_object() new_block_message = NewBlockEthProtocolMessage(None, block, 10) eth_block_info = InternalEthBlockInfo.from_new_block_msg( new_block_message ) self.node.block_queuing_service.push(block_hash, eth_block_info) self.node.send_to_node_messages.clear() self.sut.msg_proxy_request = MagicMock() block_number_bytes = struct.pack(">I", block_number) message = GetBlockHeadersEthProtocolMessage( None, block_number_bytes, 1, 0, 0 ) self.sut.msg_get_block_headers(message) self.sut.msg_proxy_request.assert_not_called() self.assertEqual(1, len(self.node.send_to_node_messages)) headers_sent = self.node.send_to_node_messages[0] self.assertIsInstance(headers_sent, BlockHeadersEthProtocolMessage) self.assertEqual(1, len(headers_sent.get_block_headers())) self.assertEqual( block_hash, headers_sent.get_block_headers()[0].hash_object() ) def test_msg_get_block_headers_known_many(self): self.node.opts.max_block_interval = 0 block_hashes = [] block_hash = None for i in range(20): block_message = InternalEthBlockInfo.from_new_block_msg( mock_eth_messages.new_block_eth_protocol_message( i, i + 1000, block_hash ) ) block_hash = block_message.block_hash() block_hashes.append(block_hash) self.node.block_queuing_service.push(block_hash, block_message) self.node.block_queuing_service.best_sent_block = (1019, block_hashes[-1], 0) self.node.send_to_node_messages.clear() self.sut.msg_proxy_request = MagicMock() message = GetBlockHeadersEthProtocolMessage( None, block_hashes[10].binary, 10, 0, 0 ) self.sut.msg_get_block_headers(message) self.sut.msg_proxy_request.assert_not_called() self.assertEqual(1, len(self.node.send_to_node_messages)) headers_sent = self.node.send_to_node_messages[0] self.assertIsInstance(headers_sent, BlockHeadersEthProtocolMessage) self.assertEqual(10, len(headers_sent.get_block_headers())) for i in range(10): self.assertEqual( block_hashes[i + 10], headers_sent.get_block_headers()[i].hash_object(), ) def test_msg_get_block_headers_fork(self): self.node.opts.max_block_interval = 0 block_hashes = [] for i in range(20): block_message = InternalEthBlockInfo.from_new_block_msg( mock_eth_messages.new_block_eth_protocol_message(i, i + 1000) ) block_hash = block_message.block_hash() block_hashes.append(block_hash) self.node.block_queuing_service.push(block_hash, block_message) # create fork block_message = InternalEthBlockInfo.from_new_block_msg( mock_eth_messages.new_block_eth_protocol_message(34, 1017) ) block_hash = block_message.block_hash() block_hashes.append(block_hash) self.node.block_queuing_service.push(block_hash, block_message) self.node.send_to_node_messages.clear() self.sut.msg_proxy_request = MagicMock() message = GetBlockHeadersEthProtocolMessage( None, block_hashes[-1].binary, 10, 0, 1 ) self.sut.msg_get_block_headers(message) self.sut.msg_proxy_request.assert_called_once() self.assertEqual(0, len(self.node.send_to_node_messages)) def test_msg_get_block_headers_missing_block(self): self.node.opts.max_block_interval = 0 block_hashes = [] for i in (j for j in range(20) if j != 17): block_message = InternalEthBlockInfo.from_new_block_msg( mock_eth_messages.new_block_eth_protocol_message(i, i + 1000) ) block_hash = block_message.block_hash() block_hashes.append(block_hash) self.node.block_queuing_service.push(block_hash, block_message) self.node.send_to_node_messages.clear() self.sut.msg_proxy_request = MagicMock() message = GetBlockHeadersEthProtocolMessage( None, block_hashes[-1].binary, 10, 0, 1 ) self.sut.msg_get_block_headers(message) self.sut.msg_proxy_request.assert_called_once() self.assertEqual(0, len(self.node.send_to_node_messages)) def test_msg_get_block_headers_future_block(self): self.node.opts.max_block_interval = 0 block_hashes = [] block_heights = [] for i in range(20): block_message = InternalEthBlockInfo.from_new_block_msg( mock_eth_messages.new_block_eth_protocol_message(i, i + 1000) ) block_hash = block_message.block_hash() block_heights.append(block_message.block_number()) block_hashes.append(block_hash) self.node.block_queuing_service.push(block_hash, block_message) self.node.send_to_node_messages.clear() self.sut.msg_proxy_request = MagicMock() block_height_bytes = block_heights[-1] + 4 block_height_bytes = block_height_bytes.to_bytes(8, "big") message = GetBlockHeadersEthProtocolMessage( None, block_height_bytes, 1, 0, 0 ) self.sut.msg_get_block_headers(message) self.sut.msg_proxy_request.assert_not_called() self.assertEqual(1, len(self.node.send_to_node_messages)) headers_sent = self.node.send_to_node_messages[0] self.assertIsInstance(headers_sent, BlockHeadersEthProtocolMessage) self.assertEqual(0, len(headers_sent.get_block_headers())) def test_msg_block_adds_headers_and_bodies(self): self.node.opts.max_block_interval = 0 self.sut.is_valid_block_timestamp = MagicMock(return_value=True) block_hashes = [] block_messages = [] block_hash = None for i in range(20): block_message = InternalEthBlockInfo.from_new_block_msg( mock_eth_messages.new_block_eth_protocol_message(i, i + 1000, block_hash) ) block_hash = block_message.block_hash() block_hashes.append(block_hash) block_messages.append(block_message) # push all blocks, except for # 17 for i in (j for j in range(20) if j != 17): self.node.block_queuing_service.push( block_hashes[i], block_messages[i] ) self.node.block_queuing_service.remove_from_queue(block_hashes[i]) self.node.block_queuing_service.best_sent_block = (1019, block_hashes[-1], 0) self.node.send_to_node_messages.clear() self.sut.msg_proxy_request = MagicMock() message = GetBlockHeadersEthProtocolMessage( None, block_hashes[10].binary, 10, 0, 0 ) self.sut.msg_get_block_headers(message) self.sut.msg_proxy_request.assert_called_once() self.assertEqual(0, len(self.node.send_to_node_messages)) self.sut.msg_proxy_request.reset_mock() self.sut.msg_block(block_messages[17].to_new_block_msg()) self.sut.msg_get_block_headers(message) self.sut.msg_proxy_request.assert_not_called() self.assertEqual(1, len(self.node.send_to_node_messages)) headers_sent = self.node.send_to_node_messages[0] self.assertIsInstance(headers_sent, BlockHeadersEthProtocolMessage) self.assertEqual(10, len(headers_sent.get_block_headers())) for i in range(10): self.assertEqual( block_hashes[i + 10], headers_sent.get_block_headers()[i].hash_object(), ) def test_msg_tx(self): self.node.feed_manager.publish_to_feed = MagicMock() self.node.opts.ws = True transaction = mock_eth_messages.get_dummy_transaction(1) transaction_hash = transaction.hash() transaction_contents = transaction.contents() messages = TransactionsEthProtocolMessage(None, [transaction]) self.sut.msg_tx(messages) # published to both feeds self.node.feed_manager.publish_to_feed.assert_has_calls( [ call( EthNewTransactionFeed.NAME, EthRawTransaction(transaction_hash, transaction_contents) ), call( EthPendingTransactionFeed.NAME, EthRawTransaction(transaction_hash, transaction_contents) ), ] ) # broadcast transactions self.assertEqual(1, len(self.node.broadcast_messages))