Minimal implementation of Portal ping payload extensions spec (#3010)

* Minimal implementation of Portal ping payload extensions spec

* Remove serialization layer for CustomPayload (spec change)
This commit is contained in:
kdeme 2025-01-27 19:26:23 +01:00 committed by GitHub
parent c4bc2a4eea
commit 28f9b792d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 412 additions and 77 deletions

View File

@ -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 =

View File

@ -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
@ -352,6 +354,7 @@ proc new*(
stream,
bootstrapRecords,
config = portalConfig,
pingExtensionCapabilities = pingExtensionCapabilities,
)
HistoryNetwork(

View File

@ -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(

View File

@ -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]

View File

@ -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(@[]),
)
),
)

View File

@ -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)
@ -701,7 +755,9 @@ proc recordsFromBytes(rawRecords: List[ByteList[2048], 32]): PortalResult[seq[Re
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():
@ -709,17 +765,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]
@ -1691,8 +1750,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():

View File

@ -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]

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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()