diff --git a/fluffy/network/beacon/beacon_network.nim b/fluffy/network/beacon/beacon_network.nim index 9ee4de945a..4191293f52 100644 --- a/fluffy/network/beacon/beacon_network.nim +++ b/fluffy/network/beacon/beacon_network.nim @@ -1,5 +1,5 @@ # fluffy -# Copyright (c) 2022-2024 Status Research & Development GmbH +# Copyright (c) 2022-2025 Status Research & Development GmbH # Licensed and distributed under either of # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). @@ -14,7 +14,7 @@ import eth/p2p/discoveryv5/[protocol, enr], beacon_chain/spec/forks, beacon_chain/gossip_processing/light_client_processor, - ../wire/[portal_protocol, portal_stream, portal_protocol_config], + ../wire/[portal_protocol, portal_stream, portal_protocol_config, ping_extensions], "."/[beacon_content, beacon_db, beacon_validation, beacon_chain_historical_summaries] export beacon_content, beacon_db @@ -22,6 +22,8 @@ export beacon_content, beacon_db logScope: topics = "portal_beacon" +const pingExtensionCapabilities = {CapabilitiesType, BasicRadiusType} + type BeaconNetwork* = ref object portalProtocol*: PortalProtocol beaconDb*: BeaconDb @@ -213,6 +215,7 @@ proc new*( stream, bootstrapRecords, config = portalConfig, + pingExtensionCapabilities = pingExtensionCapabilities, ) let beaconBlockRoot = diff --git a/fluffy/network/history/history_network.nim b/fluffy/network/history/history_network.nim index c9f320274b..59f7334bdf 100644 --- a/fluffy/network/history/history_network.nim +++ b/fluffy/network/history/history_network.nim @@ -1,5 +1,5 @@ # Fluffy -# Copyright (c) 2021-2024 Status Research & Development GmbH +# Copyright (c) 2021-2025 Status Research & Development GmbH # Licensed and distributed under either of # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). @@ -17,7 +17,7 @@ import ../../common/common_types, ../../database/content_db, ../../network_metadata, - ../wire/[portal_protocol, portal_stream, portal_protocol_config], + ../wire/[portal_protocol, portal_stream, portal_protocol_config, ping_extensions], "."/[history_content, history_validation, history_type_conversions], ../beacon/beacon_chain_historical_roots, ./content/content_deprecated @@ -30,6 +30,8 @@ logScope: export blocks_rlp +const pingExtensionCapabilities = {CapabilitiesType, HistoryRadiusType} + type HistoryNetwork* = ref object portalProtocol*: PortalProtocol @@ -350,6 +352,7 @@ proc new*( stream, bootstrapRecords, config = portalConfig, + pingExtensionCapabilities = pingExtensionCapabilities, ) HistoryNetwork( diff --git a/fluffy/network/state/state_network.nim b/fluffy/network/state/state_network.nim index fe4f82e403..6871b7130e 100644 --- a/fluffy/network/state/state_network.nim +++ b/fluffy/network/state/state_network.nim @@ -1,5 +1,5 @@ # Fluffy -# Copyright (c) 2021-2024 Status Research & Development GmbH +# Copyright (c) 2021-2025 Status Research & Development GmbH # Licensed and distributed under either of # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). @@ -16,7 +16,7 @@ import eth/p2p/discoveryv5/[protocol, enr], ../../database/content_db, ../history/history_network, - ../wire/[portal_protocol, portal_stream, portal_protocol_config], + ../wire/[portal_protocol, portal_stream, portal_protocol_config, ping_extensions], ./state_content, ./state_validation, ./state_gossip @@ -31,6 +31,8 @@ declareCounter state_network_offers_success, declareCounter state_network_offers_failed, "Portal state network offers which failed validation", labels = ["protocol_id"] +const pingExtensionCapabilities = {CapabilitiesType, BasicRadiusType} + type StateNetwork* = ref object portalProtocol*: PortalProtocol contentQueue*: AsyncQueue[(Opt[NodeId], ContentKeysList, seq[seq[byte]])] @@ -69,6 +71,7 @@ proc new*( s, bootstrapRecords, config = portalConfig, + pingExtensionCapabilities = pingExtensionCapabilities, ) StateNetwork( diff --git a/fluffy/network/wire/messages.nim b/fluffy/network/wire/messages.nim index 1f6515cfbc..10b15f28fa 100644 --- a/fluffy/network/wire/messages.nim +++ b/fluffy/network/wire/messages.nim @@ -1,5 +1,5 @@ # Nimbus - Portal Network- Message types -# Copyright (c) 2021-2024 Status Research & Development GmbH +# Copyright (c) 2021-2025 Status Research & Development GmbH # Licensed and distributed under either of # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). @@ -29,10 +29,6 @@ type ContentKeysList* = List[ContentKeyByteList, contentKeysLimit] ContentKeysBitList* = BitList[contentKeysLimit] - # TODO: should become part of the specific networks, considering it is custom. - CustomPayload* = object - dataRadius*: UInt256 - MessageKind* = enum ping = 0x00 pong = 0x01 @@ -50,11 +46,13 @@ type PingMessage* = object enrSeq*: uint64 - customPayload*: ByteList[2048] + payload_type*: uint16 + payload*: ByteList[1100] PongMessage* = object enrSeq*: uint64 - customPayload*: ByteList[2048] + payload_type*: uint16 + payload*: ByteList[1100] FindNodesMessage* = object distances*: List[uint16, 256] diff --git a/fluffy/network/wire/ping_extensions.nim b/fluffy/network/wire/ping_extensions.nim new file mode 100644 index 0000000000..9e4dc278da --- /dev/null +++ b/fluffy/network/wire/ping_extensions.nim @@ -0,0 +1,63 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +{.push raises: [].} + +import ssz_serialization + +const + # Extension types + CapabilitiesType* = 0'u16 + BasicRadiusType* = 1'u16 + HistoryRadiusType* = 2'u16 + ErrorType* = 65535'u16 + + # Limits + MAX_CLIENT_INFO_BYTE_LENGTH* = 200 + MAX_CAPABILITIES_LENGTH* = 400 + MAX_ERROR_BYTE_LENGTH* = 300 + +# Different ping extension payloads, TODO: could be moved to each their own file? +type + CapabilitiesPayload* = object + client_info*: ByteList[MAX_CLIENT_INFO_BYTE_LENGTH] + data_radius*: UInt256 + capabilities*: List[uint16, MAX_CAPABILITIES_LENGTH] + + BasicRadiusPayload* = object + data_radius*: UInt256 + + HistoryRadiusPayload* = object + data_radius*: UInt256 + ephemeral_header_count*: uint16 + + ErrorPayload* = object + error_code*: uint16 + message*: ByteList[MAX_ERROR_BYTE_LENGTH] + + CustomPayload* = + CapabilitiesPayload | BasicRadiusPayload | HistoryRadiusPayload | ErrorPayload + + ErrorCode* = enum + ExtensionNotSupported = 0 + RequestedDataNotFound = 1 + FailedToDecodePayload = 2 + SystemError = 3 + +func encodePayload*(payload: CustomPayload): ByteList[1100] = + ByteList[1100].init(SSZ.encode(payload)) + +func encodeErrorPayload*(code: ErrorCode): (uint16, ByteList[1100]) = + ( + ErrorType, + encodePayload( + ErrorPayload( + error_code: uint16(ord(code)), + message: ByteList[MAX_ERROR_BYTE_LENGTH].init(@[]), + ) + ), + ) diff --git a/fluffy/network/wire/portal_protocol.nim b/fluffy/network/wire/portal_protocol.nim index 4dcba8bf96..fd2fbaf3a0 100644 --- a/fluffy/network/wire/portal_protocol.nim +++ b/fluffy/network/wire/portal_protocol.nim @@ -24,7 +24,7 @@ import minilru, eth/rlp, eth/p2p/discoveryv5/[protocol, node, enr, routing_table, random2, nodes_verification], - "."/[portal_stream, portal_protocol_config], + "."/[portal_stream, portal_protocol_config, ping_extensions], ./messages from std/times import epochTime # For system timestamp in traceContentLookup @@ -186,6 +186,7 @@ type offerWorkers: seq[Future[void]] pingTimings: Table[NodeId, chronos.Moment] config*: PortalProtocolConfig + pingExtensionCapabilities*: set[uint16] PortalResult*[T] = Result[T, string] @@ -334,26 +335,68 @@ func truncateEnrs( enrs +proc handlePingExtension( + p: PortalProtocol, + payloadType: uint16, + encodedPayload: ByteList[1100], + srcId: NodeId, +): (uint16, ByteList[1100]) = + if payloadType notin p.pingExtensionCapabilities: + return encodeErrorPayload(ErrorCode.ExtensionNotSupported) + + case payloadType + of CapabilitiesType: + let payload = decodeSsz(encodedPayload.asSeq(), CapabilitiesPayload).valueOr: + return encodeErrorPayload(ErrorCode.FailedToDecodePayload) + + p.radiusCache.put(srcId, payload.data_radius) + + ( + payloadType, + encodePayload( + CapabilitiesPayload( + client_info: ByteList[MAX_CLIENT_INFO_BYTE_LENGTH].init(@[]), + data_radius: p.dataRadius(), + capabilities: List[uint16, MAX_CAPABILITIES_LENGTH].init( + p.pingExtensionCapabilities.toSeq() + ), + ) + ), + ) + of BasicRadiusType: + let payload = decodeSsz(encodedPayload.asSeq(), BasicRadiusPayload).valueOr: + return encodeErrorPayload(ErrorCode.FailedToDecodePayload) + + p.radiusCache.put(srcId, payload.data_radius) + + (payloadType, encodePayload(HistoryRadiusPayload(data_radius: p.dataRadius()))) + of HistoryRadiusType: + let payload = decodeSsz(encodedPayload.asSeq(), HistoryRadiusPayload).valueOr: + return encodeErrorPayload(ErrorCode.FailedToDecodePayload) + + p.radiusCache.put(srcId, payload.data_radius) + + ( + payloadType, + encodePayload( + HistoryRadiusPayload(data_radius: p.dataRadius(), ephemeral_header_count: 0) + ), + ) + else: + encodeErrorPayload(ErrorCode.ExtensionNotSupported) + proc handlePing(p: PortalProtocol, ping: PingMessage, srcId: NodeId): seq[byte] = - # TODO: This should become custom per Portal Network # TODO: Need to think about the effect of malicious actor sending lots of # pings from different nodes to clear the LRU. - let customPayloadDecoded = - try: - SSZ.decode(ping.customPayload.asSeq(), CustomPayload) - except SerializationError: - # invalid custom payload, send empty back - return @[] - p.radiusCache.put(srcId, customPayloadDecoded.dataRadius) + let (payloadType, payload) = + handlePingExtension(p, ping.payload_type, ping.payload, srcId) - let customPayload = CustomPayload(dataRadius: p.dataRadius()) - let p = PongMessage( - enrSeq: p.localNode.record.seqNum, - customPayload: ByteList[2048](SSZ.encode(customPayload)), + encodeMessage( + PongMessage( + enrSeq: p.localNode.record.seqNum, payload_type: payloadType, payload: payload + ) ) - encodeMessage(p) - proc handleFindNodes(p: PortalProtocol, fn: FindNodesMessage): seq[byte] = if fn.distances.len == 0: let enrs = List[ByteList[2048], 32](@[]) @@ -573,6 +616,7 @@ proc new*( bootstrapRecords: openArray[Record] = [], distanceCalculator: DistanceCalculator = XorDistanceCalculator, config: PortalProtocolConfig = defaultPortalProtocolConfig, + pingExtensionCapabilities: set[uint16] = {CapabilitiesType}, ): T = let proto = PortalProtocol( protocolHandler: messageHandler, @@ -595,6 +639,7 @@ proc new*( offerQueue: newAsyncQueue[OfferRequest](config.maxConcurrentOffers), pingTimings: Table[NodeId, chronos.Moment](), config: config, + pingExtensionCapabilities: pingExtensionCapabilities, ) proto.baseProtocol.registerTalkProtocol(@(proto.protocolId), proto).expect( @@ -657,10 +702,19 @@ proc reqResponse[Request: SomeMessage, Response: SomeMessage]( proc pingImpl*( p: PortalProtocol, dst: Node ): Future[PortalResult[PongMessage]] {.async: (raises: [CancelledError]).} = - let customPayload = CustomPayload(dataRadius: p.dataRadius()) + let pingPayload = encodePayload( + CapabilitiesPayload( + client_info: ByteList[MAX_CLIENT_INFO_BYTE_LENGTH].init(@[]), + data_radius: p.dataRadius(), + capabilities: + List[uint16, MAX_CAPABILITIES_LENGTH].init(p.pingExtensionCapabilities.toSeq()), + ) + ) + let ping = PingMessage( enrSeq: p.localNode.record.seqNum, - customPayload: ByteList[2048](SSZ.encode(customPayload)), + payload_type: CapabilitiesType, + payload: pingPayload, ) return await reqResponse[PingMessage, PongMessage](p, dst, ping) @@ -703,7 +757,9 @@ proc recordsFromBytes*( proc ping*( p: PortalProtocol, dst: Node -): Future[PortalResult[PongMessage]] {.async: (raises: [CancelledError]).} = +): Future[PortalResult[(uint64, CapabilitiesPayload)]] {. + async: (raises: [CancelledError]) +.} = let pongResponse = await p.pingImpl(dst) if pongResponse.isOk(): @@ -711,17 +767,20 @@ proc ping*( p.pingTimings[dst.id] = now(chronos.Moment) let pong = pongResponse.get() - # TODO: This should become custom per Portal Network - let customPayloadDecoded = - try: - SSZ.decode(pong.customPayload.asSeq(), CustomPayload) - except SerializationError: - # invalid custom payload - return err("Pong message contains invalid custom payload") - p.radiusCache.put(dst.id, customPayloadDecoded.dataRadius) + # Note: currently only decoding as capabilities payload as this is the only + # one that we support sending. + if pong.payload_type != CapabilitiesType: + return err("Pong message contains invalid or error payload") - return pongResponse + let payload = decodeSsz(pong.payload.asSeq(), CapabilitiesPayload).valueOr: + return err("Pong message contains invalid CapabilitiesPayload") + + p.radiusCache.put(dst.id, payload.data_radius) + + ok((pong.enrSeq, payload)) + else: + err(pongResponse.error) proc findNodes*( p: PortalProtocol, dst: Node, distances: seq[uint16] @@ -1706,8 +1765,8 @@ proc revalidateNode*(p: PortalProtocol, n: Node) {.async: (raises: [CancelledErr let pong = await p.ping(n) if pong.isOk(): - let res = pong.get() - if res.enrSeq > n.record.seqNum: + let (enrSeq, _) = pong.get() + if enrSeq > n.record.seqNum: # Request new ENR let nodesMessage = await p.findNodes(n, @[0'u16]) if nodesMessage.isOk(): diff --git a/fluffy/rpc/rpc_portal_common_api.nim b/fluffy/rpc/rpc_portal_common_api.nim index 013719e799..4b311d3a82 100644 --- a/fluffy/rpc/rpc_portal_common_api.nim +++ b/fluffy/rpc/rpc_portal_common_api.nim @@ -1,5 +1,5 @@ # fluffy -# Copyright (c) 2021-2024 Status Research & Development GmbH +# Copyright (c) 2021-2025 Status Research & Development GmbH # Licensed and distributed under either of # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). @@ -83,16 +83,8 @@ proc installPortalCommonApiHandlers*( if pong.isErr(): raise newException(ValueError, $pong.error) else: - let - p = pong.get() - # Note: the SSZ.decode cannot fail here as it has already been verified - # in the ping call. - decodedPayload = - try: - SSZ.decode(p.customPayload.asSeq(), CustomPayload) - except MalformedSszError, SszSizeMismatchError: - raiseAssert("Already verified") - return (p.enrSeq, decodedPayload.dataRadius) + let (enrSeq, pongPayload) = pong.get() + return (enrSeq, pongPayload.data_radius) rpcServer.rpc("portal_" & networkStr & "FindNodes") do( enr: Record, distances: seq[uint16] diff --git a/fluffy/tests/wire_protocol_tests/all_wire_protocol_tests.nim b/fluffy/tests/wire_protocol_tests/all_wire_protocol_tests.nim index 856aa70929..ae47106f63 100644 --- a/fluffy/tests/wire_protocol_tests/all_wire_protocol_tests.nim +++ b/fluffy/tests/wire_protocol_tests/all_wire_protocol_tests.nim @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2024 Status Research & Development GmbH +# Copyright (c) 2025 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) # * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) @@ -7,4 +7,7 @@ {.warning[UnusedImport]: off.} -import ./test_portal_wire_encoding, ./test_portal_wire_protocol +import + ./test_portal_wire_encoding, + ./test_portal_wire_protocol, + ./test_ping_extensions_encoding diff --git a/fluffy/tests/wire_protocol_tests/test_ping_extensions_encoding.nim b/fluffy/tests/wire_protocol_tests/test_ping_extensions_encoding.nim new file mode 100644 index 0000000000..20a5828bec --- /dev/null +++ b/fluffy/tests/wire_protocol_tests/test_ping_extensions_encoding.nim @@ -0,0 +1,205 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +{.used.} + +import + unittest2, + stint, + stew/byteutils, + results, + ../../network/wire/[messages, ping_extensions] + +suite "Portal Wire Ping Extension Encodings - Type 0x00": + test "SSZ encoded Ping request - with client info": + let + enr_seq = 1'u64 + data_radius = UInt256.high() - 1 # Full radius - 1 + client_info = "trin/v0.1.1-b61fdc5c/linux-x86_64/rustc1.81.0" + capabilities = @[uint16 0, 1, 65535] + + payload = CapabilitiesPayload( + client_info: ByteList[MAX_CLIENT_INFO_BYTE_LENGTH](client_info.toBytes()), + data_radius: data_radius, + capabilities: List[uint16, MAX_CAPABILITIES_LENGTH].init(capabilities), + ) + customPayload = encodePayload(payload) + ping = PingMessage( + enrSeq: enr_seq, payload_type: CapabilitiesType, payload: customPayload + ) + + let encoded = encodeMessage(ping) + check encoded.to0xHex == + "0x00010000000000000000000e00000028000000feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff550000007472696e2f76302e312e312d62363166646335632f6c696e75782d7838365f36342f7275737463312e38312e3000000100ffff" + + let decoded = decodeMessage(encoded) + check decoded.isOk() + let message = decoded.value() + check: + message.kind == MessageKind.ping + message.ping.enrSeq == enr_seq + message.ping.payload_type == CapabilitiesType + message.ping.payload == customPayload + + let decodedPayload = decodeSsz(message.ping.payload.asSeq(), CapabilitiesPayload) + check: + decodedPayload.isOk() + decodedPayload.value().client_info.asSeq() == client_info.toBytes() + decodedPayload.value().data_radius == data_radius + decodedPayload.value().capabilities.asSeq() == capabilities + + test "SSZ encoded Ping request - with empty client info": + let + enr_seq = 1'u64 + data_radius = UInt256.high() - 1 # Full radius - 1 + client_info = "" + capabilities = @[uint16 0, 1, 65535] + + payload = CapabilitiesPayload( + client_info: ByteList[MAX_CLIENT_INFO_BYTE_LENGTH](client_info.toBytes()), + data_radius: data_radius, + capabilities: List[uint16, MAX_CAPABILITIES_LENGTH].init(capabilities), + ) + customPayload = encodePayload(payload) + ping = PingMessage( + enrSeq: enr_seq, payload_type: CapabilitiesType, payload: customPayload + ) + + let encoded = encodeMessage(ping) + check encoded.to0xHex == + "0x00010000000000000000000e00000028000000feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2800000000000100ffff" + let decoded = decodeMessage(encoded) + check decoded.isOk() + + test "SSZ encoded Pong response - with client info": + let + enr_seq = 1'u64 + data_radius = UInt256.high() - 1 # Full radius - 1 + client_info = "trin/v0.1.1-b61fdc5c/linux-x86_64/rustc1.81.0" + capabilities = @[uint16 0, 1, 65535] + + payload = CapabilitiesPayload( + client_info: ByteList[MAX_CLIENT_INFO_BYTE_LENGTH](client_info.toBytes()), + data_radius: data_radius, + capabilities: List[uint16, MAX_CAPABILITIES_LENGTH].init(capabilities), + ) + customPayload = encodePayload(payload) + pong = PongMessage( + enrSeq: enr_seq, payload_type: CapabilitiesType, payload: customPayload + ) + + let encoded = encodeMessage(pong) + check encoded.to0xHex == + "0x01010000000000000000000e00000028000000feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff550000007472696e2f76302e312e312d62363166646335632f6c696e75782d7838365f36342f7275737463312e38312e3000000100ffff" + + test "SSZ encoded Pong response - with empty client info": + let + enr_seq = 1'u64 + data_radius = UInt256.high() - 1 # Full radius - 1 + client_info = "" + capabilities = @[uint16 0, 1, 65535] + + payload = CapabilitiesPayload( + client_info: ByteList[MAX_CLIENT_INFO_BYTE_LENGTH](client_info.toBytes()), + data_radius: data_radius, + capabilities: List[uint16, MAX_CAPABILITIES_LENGTH].init(capabilities), + ) + customPayload = encodePayload(payload) + pong = PongMessage( + enrSeq: enr_seq, payload_type: CapabilitiesType, payload: customPayload + ) + + let encoded = encodeMessage(pong) + check encoded.to0xHex == + "0x01010000000000000000000e00000028000000feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2800000000000100ffff" + +suite "Portal Wire Ping Extension Encodings - Type 0x01": + test "SSZ encoded Ping request": + let + enr_seq = 1'u64 + data_radius = UInt256.high() - 1 # Full radius - 1 + + payload = BasicRadiusPayload(data_radius: data_radius) + customPayload = encodePayload(payload) + ping = PingMessage( + enrSeq: enr_seq, payload_type: BasicRadiusType, payload: customPayload + ) + + let encoded = encodeMessage(ping) + check encoded.to0xHex == + "0x00010000000000000001000e000000feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + + test "SSZ encoded Pong response": + let + enr_seq = 1'u64 + data_radius = UInt256.high() - 1 # Full radius - 1 + + payload = BasicRadiusPayload(data_radius: data_radius) + customPayload = encodePayload(payload) + pong = PongMessage( + enrSeq: enr_seq, payload_type: BasicRadiusType, payload: customPayload + ) + + let encoded = encodeMessage(pong) + check encoded.to0xHex == + "0x01010000000000000001000e000000feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + +suite "Portal Wire Ping Extension Encodings - Type 0x02": + test "SSZ encoded Ping request": + let + enr_seq = 1'u64 + data_radius = UInt256.high() - 1 # Full radius - 1 + ephemeral_header_count = 4242'u16 + + payload = HistoryRadiusPayload( + data_radius: data_radius, ephemeral_header_count: ephemeral_header_count + ) + customPayload = encodePayload(payload) + ping = PingMessage( + enrSeq: enr_seq, payload_type: HistoryRadiusType, payload: customPayload + ) + + let encoded = encodeMessage(ping) + check encoded.to0xHex == + "0x00010000000000000002000e000000feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9210" + + test "SSZ encoded Pong response": + let + enr_seq = 1'u64 + data_radius = UInt256.high() - 1 # Full radius - 1 + ephemeral_header_count = 4242'u16 + + payload = HistoryRadiusPayload( + data_radius: data_radius, ephemeral_header_count: ephemeral_header_count + ) + customPayload = encodePayload(payload) + pong = PongMessage( + enrSeq: enr_seq, payload_type: HistoryRadiusType, payload: customPayload + ) + + let encoded = encodeMessage(pong) + check encoded.to0xHex == + "0x01010000000000000002000e000000feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9210" + +suite "Portal Wire Ping Extension Encodings - Type 0x03": + test "SSZ encoded Pong response": + let + enr_seq = 1'u64 + error_code = 2'u16 + message = "hello world" + + payload = ErrorPayload( + error_code: error_code, + message: ByteList[MAX_ERROR_BYTE_LENGTH].init(message.toBytes()), + ) + customPayload = encodePayload(payload) + pong = + PongMessage(enrSeq: enr_seq, payload_type: ErrorType, payload: customPayload) + + let encoded = encodeMessage(pong) + check encoded.to0xHex == + "0x010100000000000000ffff0e00000002000600000068656c6c6f20776f726c64" diff --git a/fluffy/tests/wire_protocol_tests/test_portal_wire_encoding.nim b/fluffy/tests/wire_protocol_tests/test_portal_wire_encoding.nim index 3a9e939fbc..e55c381ac5 100644 --- a/fluffy/tests/wire_protocol_tests/test_portal_wire_encoding.nim +++ b/fluffy/tests/wire_protocol_tests/test_portal_wire_encoding.nim @@ -1,5 +1,5 @@ -# Fluffy -# Copyright (c) 2021-2024 Status Research & Development GmbH +# Nimbus +# Copyright (c) 2021-2025 Status Research & Development GmbH # Licensed and distributed under either of # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). @@ -21,15 +21,13 @@ import suite "Portal Wire Protocol Message Encodings": test "Ping Request": let - dataRadius = UInt256.high() - 1 # Full radius - 1 enrSeq = 1'u64 - # Can be any custom payload, testing with just dataRadius here. - customPayload = ByteList[2048](SSZ.encode(CustomPayload(dataRadius: dataRadius))) - p = PingMessage(enrSeq: enrSeq, customPayload: customPayload) + # Can be any custom payload, testing with meaningless string of bytes. + customPayload = ByteList[1100].init(@[byte 0x01, 0x02, 0x03, 0x04]) + p = PingMessage(enrSeq: enrSeq, payload_type: 42'u16, payload: customPayload) let encoded = encodeMessage(p) - check encoded.toHex == - "0001000000000000000c000000feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + check encoded.toHex == "0001000000000000002a000e00000001020304" let decoded = decodeMessage(encoded) check decoded.isOk() @@ -37,19 +35,18 @@ suite "Portal Wire Protocol Message Encodings": check: message.kind == ping message.ping.enrSeq == enrSeq - message.ping.customPayload == customPayload + message.ping.payload_type == 42'u16 + message.ping.payload == customPayload test "Pong Response": let - dataRadius = UInt256.high() div 2.stuint(256) # Radius of half the UInt256 enrSeq = 1'u64 - # Can be any custom payload, testing with just dataRadius here. - customPayload = ByteList[2048](SSZ.encode(CustomPayload(dataRadius: dataRadius))) - p = PongMessage(enrSeq: enrSeq, customPayload: customPayload) + # Can be any custom payload, testing with meaningless string of bytes. + customPayload = ByteList[1100].init(@[byte 0x01, 0x02, 0x03, 0x04]) + p = PongMessage(enrSeq: enrSeq, payload_type: 42'u16, payload: customPayload) let encoded = encodeMessage(p) - check encoded.toHex == - "0101000000000000000c000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f" + check encoded.toHex == "0101000000000000002a000e00000001020304" let decoded = decodeMessage(encoded) check decoded.isOk() @@ -57,7 +54,8 @@ suite "Portal Wire Protocol Message Encodings": check: message.kind == pong message.pong.enrSeq == enrSeq - message.pong.customPayload == customPayload + message.pong.payload_type == 42'u16 + message.pong.payload == customPayload test "FindNodes Request": let diff --git a/fluffy/tests/wire_protocol_tests/test_portal_wire_protocol.nim b/fluffy/tests/wire_protocol_tests/test_portal_wire_protocol.nim index 41b734401f..93c982cb99 100644 --- a/fluffy/tests/wire_protocol_tests/test_portal_wire_protocol.nim +++ b/fluffy/tests/wire_protocol_tests/test_portal_wire_protocol.nim @@ -1,5 +1,5 @@ # Fluffy -# Copyright (c) 2021-2024 Status Research & Development GmbH +# Copyright (c) 2021-2025 Status Research & Development GmbH # Licensed and distributed under either of # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). @@ -16,7 +16,8 @@ import eth/p2p/discoveryv5/routing_table, nimcrypto/[hash, sha2], eth/p2p/discoveryv5/protocol as discv5_protocol, - ../../network/wire/[portal_protocol, portal_stream, portal_protocol_config], + ../../network/wire/ + [portal_protocol, portal_stream, portal_protocol_config, ping_extensions], ../../database/content_db, ../test_helpers @@ -77,13 +78,20 @@ procSuite "Portal Wire Protocol Tests": let pong = await proto1.ping(proto2.localNode) - let customPayload = - ByteList[2048](SSZ.encode(CustomPayload(dataRadius: UInt256.high()))) + let customPayload = CapabilitiesPayload( + client_info: ByteList[MAX_CLIENT_INFO_BYTE_LENGTH].init(@[]), + data_radius: UInt256.high(), + capabilities: List[uint16, MAX_CAPABILITIES_LENGTH].init( + proto1.pingExtensionCapabilities.toSeq() + ), + ) + + check pong.isOk() + let (enrSeq, payload) = pong.value() check: - pong.isOk() - pong.get().enrSeq == 1'u64 - pong.get().customPayload == customPayload + enrSeq == 1'u64 + payload == customPayload await proto1.stopPortalProtocol() await proto2.stopPortalProtocol()