async def test_processing_messages3(self): # we got 2 cases for which we need to test without using a backend settings.network.magic = 769 settings.storage.use_default = False socket_mock = NeoNodeSocketMock(self.loop, '127.0.0.1', 1111) m_inv1 = message.Message( msg_type=message.MessageType.INV, payload=payloads.InventoryPayload( payloads.InventoryType.BLOCK, hashes=[ types.UInt256.from_string( "65793a030c0dcd4fff4da8a6a6d5daa8b570750da4fdeea1bbc43bdf124aedc9" ) ])) m_ping = message.Message(msg_type=message.MessageType.PING, payload=payloads.PingPayload(0)) def _recv_data2(self): print("my recv data 2 called ") # first do handshake yield self.m_send_version.to_array() yield self.m_verack.to_array() yield m_inv1.to_array() yield m_ping.to_array() socket_mock.recv_data = _recv_data2(socket_mock) with self.assertLogs(network_logger, 'DEBUG') as log_context: with mock.patch('neo3.network.node.NeoNode.send_message', new_callable=asynctest.CoroutineMock): # with asynctest.patch('neo3.network.node.NeoNode.send_message', return_value=asynctest.CoroutineMock()): n, _ = await node.NeoNode.connect_to(socket=socket_mock) await asyncio.sleep(0.1) await n.disconnect(payloads.DisconnectReason.SHUTTING_DOWN)
def _recv_data(self): caps = [capabilities.FullNodeCapability(0)] m_send_version = message.Message(msg_type=message.MessageType.VERSION, payload=payloads.VersionPayload( nonce=123, user_agent="NEO3-MOCK-CLIENT", capabilities=caps)) m_verack = message.Message(msg_type=message.MessageType.VERACK) yield m_send_version.to_array() yield m_verack.to_array()
def __init__(self, loop, hostaddr: str, port: int): super(NeoNodeSocketMock, self).__init__() self.type = socket.SOCK_STREAM self.recv_buffer = bytearray() self.loop = loop self.hostaddr = hostaddr self.port = port self.recv_data = self._recv_data() caps = [capabilities.FullNodeCapability(0), capabilities.ServerCapability(n_type=capabilities.NodeCapabilityType.TCPSERVER, port=10333)] self.m_send_version = message.Message(msg_type=message.MessageType.VERSION, payload=payloads.VersionPayload(nonce=123, user_agent="NEO3-MOCK-CLIENT", capabilities=caps)) self.m_verack = message.Message(msg_type=message.MessageType.VERACK)
async def test_processing_messages(self): settings.network.magic = 769 socket_mock = NeoNodeSocketMock(self.loop, '127.0.0.1', 1111) m_addr = message.Message(msg_type=message.MessageType.ADDR, payload=payloads.AddrPayload([])) m_block = message.Message(msg_type=message.MessageType.BLOCK, payload=payloads.EmptyPayload()) m_inv1 = message.Message(msg_type=message.MessageType.INV, payload=payloads.InventoryPayload( payloads.InventoryType.BLOCK, hashes=[types.UInt256.from_string("65793a030c0dcd4fff4da8a6a6d5daa8b570750da4fdeea1bbc43bdf124aedc9")] )) m_inv2 = message.Message(msg_type=message.MessageType.INV, payload=payloads.InventoryPayload(payloads.InventoryType.TX, [])) m_getaddr = message.Message(msg_type=message.MessageType.GETADDR, payload=payloads.EmptyPayload()) m_mempool = message.Message(msg_type=message.MessageType.MEMPOOL, payload=payloads.EmptyPayload()) # taken from the Headers testcase in `test_payloads` raw_headers_payload = binascii.unhexlify(b'0000000001FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00A402FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00A400000000000000007B00000000F7B4D00143932F3B6243CFC06CB4A68F22C739E201020102020304') m_headers = message.Message(msg_type=message.MessageType.HEADERS, payload=payloads.HeadersPayload.deserialize_from_bytes(raw_headers_payload)) m_ping = message.Message(msg_type=message.MessageType.PING, payload=payloads.PingPayload(0)) m_pong = message.Message(msg_type=message.MessageType.PONG, payload=payloads.PingPayload(0)) m_reject = message.Message(msg_type=message.MessageType.REJECT, payload=payloads.EmptyPayload()) def _recv_data2(self): # first do handshake yield self.m_send_version.to_array() yield self.m_verack.to_array() # next send all types of messages we handle yield m_addr.to_array() yield m_block.to_array() yield m_inv1.to_array() yield m_inv2.to_array() yield m_getaddr.to_array() yield m_mempool.to_array() yield m_headers.to_array() yield m_ping.to_array() yield m_pong.to_array() yield m_reject.to_array() socket_mock.recv_data = _recv_data2(socket_mock) with self.assertLogs(network_logger, 'DEBUG') as log_context: try: n, _ = await node.NeoNode.connect_to(socket=socket_mock) except Exception as e: print(f"GVD {e}") await asyncio.sleep(0.5) await n.disconnect(payloads.DisconnectReason.SHUTTING_DOWN)
async def request_blocks(self, hash_start: types.UInt256, count: int = None) -> None: """ Send a request for retrieving block hashes from `hash_start` to `hash_start`+`count`. Not specifying a `count` results in requesting at most 500 blocks. Note: The remote node is expected to reply with a Message with the :const:`~neo3.network.message.MessageType.INV` type containing the hashes of the requested blocks. Use :meth:`~neo3.network.node.NeoNode.request_data` in combination with these hashes to return the actual :class:`~neo3.network.payloads.block.Block` objects. See also: :meth:`~neo3.network.node.NeoNode.request_block_data()` to immediately retrieve :class:`~neo3.network.payloads.block.Block` objects. Args: hash_start: count: """ m = message.Message(msg_type=message.MessageType.GETBLOCKS, payload=payloads.GetBlocksPayload( hash_start, count)) await self.send_message(m)
async def _monitor_node_height(self) -> None: now = datetime.utcnow().timestamp() for node in self.nodes: if now - node.best_height_last_update > self.MAX_HEIGHT_UPDATE_DURATION: logger.debug( f"Disconnecting node {node.nodeid} Reason: max height update threshold exceeded." ) asyncio.create_task( node.disconnect( reason=payloads.DisconnectReason.POOR_PERFORMANCE)) else: logger.debug( f"Asking node {node.nodeid_human} to send us a height update (PING)" ) # Request latest height from node if settings.database: height = max(0, blockchain.Blockchain().height) else: height = 0 m = message.Message( msg_type=message.MessageType.PING, payload=payloads.PingPayload(height=height)) task = asyncio.create_task(node.send_message(m)) self.tasks.append(task) task.add_done_callback(lambda fut: self.tasks.remove(fut))
def test_deserialization_without_payload(self): # some message types like PING/PONG have no payload m = message.Message(message.MessageType.PING) data = m.to_array() m2 = message.Message.deserialize_from_bytes(data) self.assertEqual(message.MessageType.PING, m2.type) self.assertEqual(0, len(m2.payload))
def test_create_inv_message(self): hashes = [UInt256.zero()] inv_payload = payloads.InventoryPayload(payloads.InventoryType.BLOCK, hashes) m = message.Message(message.MessageType.INV, inv_payload) data = m.to_array() self.assertEqual(message.MessageType.INV, m.type) self.assertEqual(message.MessageConfig.NONE, m.config) self.assertIsInstance(m.payload, payloads.InventoryPayload) """ Taken from constructing the same object in C# UInt256[] hashes = { UInt256.Zero }; var inv_payload = InvPayload.Create(InventoryType.Block, hashes); ISerializable message = Message.Create(MessageCommand.Inv, inv_payload); using (MemoryStream ms = new MemoryStream()) using (BinaryWriter writer = new BinaryWriter(ms)) { message.Serialize(writer); writer.Flush(); byte[] data = ms.ToArray(); Console.WriteLine($"b\'{BitConverter.ToString(data).Replace("-","")}\'"); } """ expected_data = binascii.unhexlify( b'0027222C010000000000000000000000000000000000000000000000000000000000000000' ) self.assertEqual(expected_data, data)
def test_deserialization_with_unsupported_payload_type(self): hashes = [UInt256.zero()] inv_payload = payloads.InventoryPayload(payloads.InventoryType.BLOCK, hashes) m = message.Message(message.MessageType.ALERT, inv_payload) m2 = message.Message.deserialize_from_bytes(m.to_array()) self.assertIsInstance(m2.payload, payloads.EmptyPayload)
def test_deserialization_from_stream(self): # see test_create_compressed_inv_message() how it was obtained raw_data = binascii.unhexlify(b'01270D3F2C0400010067500000000000') with serialization.BinaryReader(raw_data) as br: m = message.Message() m.deserialize(br) self.assertEqual(m.type, message.MessageType.INV) self.assertEqual(m.payload.type, payloads.inventory.InventoryType.BLOCK)
async def send_ping(self): if settings.database: height = max(0, blockchain.Blockchain().height) else: height = 0 ping = payloads.PingPayload(height) m = message.Message(msg_type=message.MessageType.PING, payload=ping) await self.send_message(m)
async def test_basic_setup(self): n = await node.NeoNode.connect_to('127.0.0.1', 40333) m = message.Message( message.MessageType.GETFULLBLOCKS, payloads.GetFullBlocksPayload(index_start=1, count=10)) if n: await n.send_message(m) # await n.request_address_list() await asyncio.sleep(500) self.assertIsInstance(n, node.NeoNode)
async def send_address_list( self, network_addresses: List[payloads.NetworkAddress]) -> None: """ Send network addresses. Args: network_addresses: """ m = message.Message( msg_type=message.MessageType.ADDR, payload=payloads.AddrPayload(addresses=network_addresses)) await self.send_message(m)
async def _do_handshake( self) -> Tuple[bool, Optional[payloads.DisconnectReason]]: caps: List[capabilities.NodeCapability] = [ capabilities.FullNodeCapability(0) ] # TODO: fix nonce and port if a service is running send_version = message.Message(msg_type=message.MessageType.VERSION, payload=payloads.VersionPayload.create( nonce=123, user_agent="NEO3-PYTHON", capabilities=caps)) await self.send_message(send_version) m = await self.read_message(timeout=3) if not m or m.type != message.MessageType.VERSION: await self.disconnect( payloads.DisconnectReason.HANDSHAKE_VERSION_ERROR) return (False, payloads.DisconnectReason.HANDSHAKE_VERSION_ERROR) if not self._validate_version(m.payload): await self.disconnect( payloads.DisconnectReason.HANDSHAKE_VERSION_ERROR) return (False, payloads.DisconnectReason.HANDSHAKE_VERSION_ERROR) m_verack = message.Message(msg_type=message.MessageType.VERACK) await self.send_message(m_verack) m = await self.read_message(timeout=3) if not m or m.type != message.MessageType.VERACK: await self.disconnect( payloads.DisconnectReason.HANDSHAKE_VERACK_ERROR) return (False, payloads.DisconnectReason.HANDSHAKE_VERACK_ERROR) logger.debug( f"Connected to {self.version.user_agent} @ {self.address.address}: {self.best_height}." ) msgrouter.on_node_connected(self) return (True, None)
async def send_headers(self, headers: List[payloads.Header]) -> None: """ Send a list of Header objects. Args: headers: """ if len(headers) > 2000: headers = headers[:2000] m = message.Message(msg_type=message.MessageType.HEADERS, payload=payloads.HeadersPayload(headers)) await self.send_message(m)
def handler_ping(self, msg: message.Message) -> None: """ Handler for a message with the PING type. Args: msg: """ if settings.database: height = max(0, blockchain.Blockchain().height) else: height = 0 m = message.Message(msg_type=message.MessageType.PONG, payload=payloads.PingPayload(height=height)) self._create_task_with_cleanup(self.send_message(m))
async def request_data(self, type: payloads.InventoryType, hashes: List[types.UInt256]) -> None: """ Send a request for receiving the specified inventory data. Args: type: hashes: """ if len(hashes) < 1: return m = message.Message(msg_type=message.MessageType.GETDATA, payload=payloads.InventoryPayload(type, hashes)) await self.send_message(m)
async def request_headers(self, hash_start: types.UInt256, count: int = None) -> None: """ Send a request for headers from `hash_start` to `hash_start`+`count`. Not specifying a `count` results in requesting at most 2000 headers. Args: hash_start: count: """ m = message.Message(msg_type=message.MessageType.GETHEADERS, payload=payloads.GetBlocksPayload( hash_start, count)) await self.send_message(m)
def test_create_compressed_inv_message(self): hashes = [UInt256.zero(), UInt256.zero(), UInt256.zero(), UInt256.zero()] inv_payload = payloads.InventoryPayload(payloads.InventoryType.BLOCK, hashes) m = message.Message(message.MessageType.INV, inv_payload) data = m.to_array() # triggers payload compression self.assertEqual(message.MessageType.INV, m.type) self.assertEqual(message.MessageConfig.COMPRESSED, m.config) self.assertIsInstance(m.payload, payloads.InventoryPayload) """ Data created in the same fashion as how it's done in test_create_inv_message() The deviation is `hashes` now contains 4 x UInt256.zero() """ expected_data = binascii.unhexlify(b'01270D3F2C0400010067500000000000') self.assertEqual(expected_data, data)
def handler_getdata(self, msg: message.Message) -> None: """ Handler for a message with the GETDATA type. Args: msg: """ payload = cast(payloads.InventoryPayload, msg.payload) for h in payload.hashes: item = relaycache.RelayCache().try_get(h) if item is None: # for the time being we only support data retrieval for our own relays continue if payload.type == payloads.InventoryType.TX: m = message.Message(msg_type=message.MessageType.TRANSACTION, payload=item) self._create_task_with_cleanup(self.send_message(m))
async def request_block_data(self, index_start, count) -> None: """ Send a request for `count` blocks starting from `index_start`. Count cannot exceed :attr:`~neo3.network.payloads.block.GetBlockDataPayload.MAX_BLOCKS_COUNT`. See also: :meth:`~neo3.network.node.NeoNode.request_blocks()` to only request block hashes. Args: index_start: block index to start from. count: number of blocks to return. """ m = message.Message(msg_type=message.MessageType.GETBLOCKDATA, payload=payloads.GetBlockDataPayload( index_start, count)) await self.send_message(m)
def handler_inv(self, msg: message.Message) -> None: """ Handler for a message with the INV type. Args: msg: """ payload = cast(payloads.InventoryPayload, msg.payload) if payload.type == payloads.InventoryType.BLOCK: # neo-cli broadcasts INV messages on a regular interval. We can use those as trigger to request # their latest block height if len(payload.hashes) > 0: if settings.database: height = max(0, blockchain.Blockchain().height) else: height = 0 m = message.Message( msg_type=message.MessageType.PING, payload=payloads.PingPayload(height=height)) self._create_task_with_cleanup(self.send_message(m)) else: logger.debug( f"Message with type INV received. No processing for payload type " # type:ignore f"{payload.type.name} implemented")
def test_create_no_payload(self): m = message.Message(message.MessageType.PING, payload=None) self.assertEqual(message.MessageType.PING, m.type) self.assertEqual(message.MessageConfig.NONE, m.config)
async def send_inventory(self, inv_type: payloads.InventoryType, inv_hash: types.UInt256): inv = payloads.InventoryPayload(type=inv_type, hashes=[inv_hash]) m = message.Message(msg_type=message.MessageType.INV, payload=inv) await self.send_message(m)
async def request_address_list(self) -> None: """ Send a request for receiving known network addresses. """ m = message.Message(msg_type=message.MessageType.GETADDR) await self.send_message(m)