mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-12 21:34:33 +00:00
Update portal wire protocol according to latest spec changes (#860)
This is not complete, in order to be according to specification two SSZ Union types need to be used.
This commit is contained in:
parent
5c4c1784a0
commit
b77034c870
@ -25,6 +25,10 @@ type
|
||||
ContentKeysList* = List[ByteList, contentKeysLimit]
|
||||
ContentKeysBitList* = BitList[contentKeysLimit]
|
||||
|
||||
# TODO: should become part of the specific networks, considering it is custom.
|
||||
CustomPayload* = object
|
||||
dataRadius*: UInt256
|
||||
|
||||
MessageKind* = enum
|
||||
unused = 0x00
|
||||
|
||||
@ -33,17 +37,17 @@ type
|
||||
findnode = 0x03
|
||||
nodes = 0x04
|
||||
findcontent = 0x05
|
||||
foundcontent = 0x06
|
||||
content = 0x06
|
||||
offer = 0x07
|
||||
accept = 0x08
|
||||
|
||||
PingMessage* = object
|
||||
enrSeq*: uint64
|
||||
dataRadius*: UInt256
|
||||
customPayload*: ByteList
|
||||
|
||||
PongMessage* = object
|
||||
enrSeq*: uint64
|
||||
dataRadius*: UInt256
|
||||
customPayload*: ByteList
|
||||
|
||||
FindNodeMessage* = object
|
||||
distances*: List[uint16, 256]
|
||||
@ -51,14 +55,16 @@ type
|
||||
NodesMessage* = object
|
||||
total*: uint8
|
||||
enrs*: List[ByteList, 32] # ByteList here is the rlp encoded ENR. This could
|
||||
# also be limited to 300 bytes instead of 2048
|
||||
# also be limited to ~300 bytes instead of 2048
|
||||
|
||||
FindContentMessage* = object
|
||||
contentKey*: ByteList
|
||||
|
||||
FoundContentMessage* = object
|
||||
# TODO: Must become an SSZ Union
|
||||
ContentMessage* = object
|
||||
connectionId*: Bytes2
|
||||
content*: ByteList
|
||||
enrs*: List[ByteList, 32]
|
||||
payload*: ByteList
|
||||
|
||||
OfferMessage* = object
|
||||
contentKeys*: ContentKeysList
|
||||
@ -67,6 +73,7 @@ type
|
||||
connectionId*: Bytes2
|
||||
contentKeys*: ContentKeysBitList
|
||||
|
||||
# TODO: Needs to become an SSZ Union
|
||||
Message* = object
|
||||
case kind*: MessageKind
|
||||
of ping:
|
||||
@ -79,8 +86,8 @@ type
|
||||
nodes*: NodesMessage
|
||||
of findcontent:
|
||||
findcontent*: FindContentMessage
|
||||
of foundcontent:
|
||||
foundcontent*: FoundContentMessage
|
||||
of content:
|
||||
content*: ContentMessage
|
||||
of offer:
|
||||
offer*: OfferMessage
|
||||
of accept:
|
||||
@ -91,7 +98,7 @@ type
|
||||
SomeMessage* =
|
||||
PingMessage or PongMessage or
|
||||
FindNodeMessage or NodesMessage or
|
||||
FindContentMessage or FoundContentMessage or
|
||||
FindContentMessage or ContentMessage or
|
||||
OfferMessage or AcceptMessage
|
||||
|
||||
template messageKind*(T: typedesc[SomeMessage]): MessageKind =
|
||||
@ -100,7 +107,7 @@ template messageKind*(T: typedesc[SomeMessage]): MessageKind =
|
||||
elif T is FindNodeMessage: findNode
|
||||
elif T is NodesMessage: nodes
|
||||
elif T is FindContentMessage: findcontent
|
||||
elif T is FoundContentMessage: foundcontent
|
||||
elif T is ContentMessage: content
|
||||
elif T is OfferMessage: offer
|
||||
elif T is AcceptMessage: accept
|
||||
|
||||
@ -144,8 +151,8 @@ proc decodeMessage*(body: openarray[byte]): Result[Message, cstring] =
|
||||
message.nodes = SSZ.decode(body.toOpenArray(1, body.high), NodesMessage)
|
||||
of findcontent:
|
||||
message.findcontent = SSZ.decode(body.toOpenArray(1, body.high), FindContentMessage)
|
||||
of foundcontent:
|
||||
message.foundcontent = SSZ.decode(body.toOpenArray(1, body.high), FoundContentMessage)
|
||||
of content:
|
||||
message.content = SSZ.decode(body.toOpenArray(1, body.high), ContentMessage)
|
||||
of offer:
|
||||
message.offer = SSZ.decode(body.toOpenArray(1, body.high), OfferMessage)
|
||||
of accept:
|
||||
|
@ -11,6 +11,7 @@ import
|
||||
std/[sequtils, sets, algorithm],
|
||||
stew/results, chronicles, chronos, nimcrypto/hash,
|
||||
eth/rlp, eth/p2p/discoveryv5/[protocol, node, enr, routing_table, random2, nodes_verification],
|
||||
eth/ssz/ssz_serialization,
|
||||
./messages
|
||||
|
||||
export messages, routing_table
|
||||
@ -94,8 +95,9 @@ proc neighbours*(p: PortalProtocol, id: NodeId, seenOnly = false): seq[Node] =
|
||||
|
||||
proc handlePing(p: PortalProtocol, ping: PingMessage):
|
||||
seq[byte] =
|
||||
let customPayload = CustomPayload(dataRadius: p.dataRadius)
|
||||
let p = PongMessage(enrSeq: p.baseProtocol.localNode.record.seqNum,
|
||||
dataRadius: p.dataRadius)
|
||||
customPayload: ByteList(SSZ.encode(customPayload)))
|
||||
|
||||
encodeMessage(p)
|
||||
|
||||
@ -126,32 +128,35 @@ proc handleFindNode(p: PortalProtocol, fn: FindNodeMessage): seq[byte] =
|
||||
encodeMessage(NodesMessage(total: 1, enrs: enrs))
|
||||
|
||||
proc handleFindContent(p: PortalProtocol, fc: FindContentMessage): seq[byte] =
|
||||
# TODO: Should we first do a simple check on ContentId versus Radius?
|
||||
# That would needs access to specific toContentId call, or we need to move it
|
||||
# to handleContentRequest, which would need access to the Radius value.
|
||||
let contentHandlingResult = p.handleContentRequest(fc.contentKey)
|
||||
# TODO: Need to check networkId, type, trie path
|
||||
case contentHandlingResult.kind
|
||||
of ContentFound:
|
||||
# TODO: Need to provide uTP connectionId when content is too large for a
|
||||
# single response.
|
||||
let content = contentHandlingResult.content
|
||||
let enrs = List[ByteList, 32](@[]) # Empty enrs when payload is send
|
||||
encodeMessage(FoundContentMessage(
|
||||
enrs: enrs, payload: ByteList(content)))
|
||||
encodeMessage(ContentMessage(
|
||||
enrs: enrs, content: ByteList(content)))
|
||||
of ContentMissing:
|
||||
let
|
||||
contentId = contentHandlingResult.contentId
|
||||
# TODO: Should we first do a simple check on ContentId versus Radius?
|
||||
closestNodes = p.routingTable.neighbours(
|
||||
NodeId(contentId), seenOnly = true)
|
||||
payload = ByteList(@[]) # Empty payload when enrs are send
|
||||
content = ByteList(@[]) # Empty payload when enrs are send
|
||||
enrs =
|
||||
closestNodes.map(proc(x: Node): ByteList = ByteList(x.record.raw))
|
||||
encodeMessage(FoundContentMessage(
|
||||
enrs: List[ByteList, 32](List(enrs)), payload: payload))
|
||||
encodeMessage(ContentMessage(
|
||||
enrs: List[ByteList, 32](List(enrs)), content: content))
|
||||
|
||||
of ContentKeyValidationFailure:
|
||||
# Retrun empty response when content key validation fail
|
||||
# Return empty response when content key validation fails
|
||||
let content = ByteList(@[])
|
||||
let enrs = List[ByteList, 32](@[]) # Empty enrs when payload is send
|
||||
encodeMessage(FoundContentMessage(
|
||||
enrs: enrs, payload: content))
|
||||
encodeMessage(ContentMessage(
|
||||
enrs: enrs, content: content))
|
||||
|
||||
proc handleOffer(p: PortalProtocol, a: OfferMessage): seq[byte] =
|
||||
let
|
||||
@ -160,8 +165,12 @@ proc handleOffer(p: PortalProtocol, a: OfferMessage): seq[byte] =
|
||||
connectionId = Bytes2([byte 0x01, 0x02])
|
||||
# TODO: Not implemented: Based on the content radius and the content that is
|
||||
# already stored, interest in provided content keys needs to be indicated
|
||||
# by setting bits in this BitList
|
||||
# by setting bits in this BitList.
|
||||
# Do we need some protection here on a peer offering lots (64x) of content
|
||||
# that fits our Radius but is actually bogus?
|
||||
contentKeys = ContentKeysBitList.init(a.contentKeys.len)
|
||||
# TODO: What if we don't want any of the content? Reply with empty bitlist
|
||||
# and a connectionId of all zeroes?
|
||||
encodeMessage(
|
||||
AcceptMessage(connectionId: connectionId, contentKeys: contentKeys))
|
||||
|
||||
@ -242,8 +251,9 @@ proc reqResponse[Request: SomeMessage, Response: SomeMessage](
|
||||
|
||||
proc ping*(p: PortalProtocol, dst: Node):
|
||||
Future[PortalResult[PongMessage]] {.async.} =
|
||||
let customPayload = CustomPayload(dataRadius: p.dataRadius)
|
||||
let ping = PingMessage(enrSeq: p.baseProtocol.localNode.record.seqNum,
|
||||
dataRadius: p.dataRadius)
|
||||
customPayload: ByteList(SSZ.encode(customPayload)))
|
||||
|
||||
trace "Send message request", dstId = dst.id, kind = MessageKind.ping
|
||||
return await reqResponse[PingMessage, PongMessage](p, dst, ping)
|
||||
@ -257,11 +267,11 @@ proc findNode*(p: PortalProtocol, dst: Node, distances: List[uint16, 256]):
|
||||
return await reqResponse[FindNodeMessage, NodesMessage](p, dst, fn)
|
||||
|
||||
proc findContent*(p: PortalProtocol, dst: Node, contentKey: ByteList):
|
||||
Future[PortalResult[FoundContentMessage]] {.async.} =
|
||||
Future[PortalResult[ContentMessage]] {.async.} =
|
||||
let fc = FindContentMessage(contentKey: contentKey)
|
||||
|
||||
trace "Send message request", dstId = dst.id, kind = MessageKind.findcontent
|
||||
return await reqResponse[FindContentMessage, FoundContentMessage](p, dst, fc)
|
||||
return await reqResponse[FindContentMessage, ContentMessage](p, dst, fc)
|
||||
|
||||
proc offer*(p: PortalProtocol, dst: Node, contentKeys: ContentKeysList):
|
||||
Future[PortalResult[AcceptMessage]] {.async.} =
|
||||
@ -360,9 +370,9 @@ proc lookup*(p: PortalProtocol, target: NodeId): Future[seq[Node]] {.async.} =
|
||||
p.lastLookup = now(chronos.Moment)
|
||||
return closestNodes
|
||||
|
||||
proc handleFoundContentMessage(p: PortalProtocol, m: FoundContentMessage,
|
||||
proc handleFoundContentMessage(p: PortalProtocol, m: ContentMessage,
|
||||
dst: Node, nodes: var seq[Node]): LookupResult =
|
||||
if (m.enrs.len() != 0 and m.payload.len() == 0):
|
||||
if (m.enrs.len() != 0 and m.content.len() == 0):
|
||||
let records = recordsFromBytes(m.enrs)
|
||||
let verifiedNodes = verifyNodesRecords(records, dst, EnrsResultLimit)
|
||||
nodes.add(verifiedNodes)
|
||||
@ -372,10 +382,10 @@ proc handleFoundContentMessage(p: PortalProtocol, m: FoundContentMessage,
|
||||
discard p.routingTable.addNode(n)
|
||||
|
||||
return LookupResult(kind: Nodes, nodes: nodes)
|
||||
elif (m.payload.len() != 0 and m.enrs.len() == 0):
|
||||
return LookupResult(kind: Content, content: m.payload)
|
||||
elif ((m.payload.len() != 0 and m.enrs.len() != 0)):
|
||||
# Both payload and enrs are filled, which means protocol breach. For now
|
||||
elif (m.content.len() != 0 and m.enrs.len() == 0):
|
||||
return LookupResult(kind: Content, content: m.content)
|
||||
elif ((m.content.len() != 0 and m.enrs.len() != 0)):
|
||||
# Both content and enrs are filled, which means protocol breach. For now
|
||||
# just logging offending node to quickly identify it
|
||||
warn "Invalid foundcontent response form node ", uri = toURI(dst.record)
|
||||
return LookupResult(kind: Nodes, nodes: nodes)
|
||||
|
@ -16,11 +16,13 @@ suite "Portal Wire Protocol Message Encodings":
|
||||
var dataRadius: UInt256
|
||||
let
|
||||
enrSeq = 1'u64
|
||||
p = PingMessage(enrSeq: enrSeq, dataRadius: dataRadius)
|
||||
# Can be any custom payload, testing with just dataRadius here.
|
||||
customPayload = ByteList(SSZ.encode(CustomPayload(dataRadius: dataRadius)))
|
||||
p = PingMessage(enrSeq: enrSeq, customPayload: customPayload)
|
||||
|
||||
let encoded = encodeMessage(p)
|
||||
check encoded.toHex ==
|
||||
"0101000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"0101000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
let decoded = decodeMessage(encoded)
|
||||
check decoded.isOk()
|
||||
|
||||
@ -28,17 +30,19 @@ suite "Portal Wire Protocol Message Encodings":
|
||||
check:
|
||||
message.kind == ping
|
||||
message.ping.enrSeq == enrSeq
|
||||
message.ping.dataRadius == dataRadius
|
||||
message.ping.customPayload == customPayload
|
||||
|
||||
test "Pong Response":
|
||||
var dataRadius: UInt256
|
||||
let
|
||||
enrSeq = 1'u64
|
||||
p = PongMessage(enrSeq: enrSeq, dataRadius: dataRadius)
|
||||
# Can be any custom payload, testing with just dataRadius here.
|
||||
customPayload = ByteList(SSZ.encode(CustomPayload(dataRadius: dataRadius)))
|
||||
p = PongMessage(enrSeq: enrSeq, customPayload: customPayload)
|
||||
|
||||
let encoded = encodeMessage(p)
|
||||
check encoded.toHex ==
|
||||
"0201000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"0201000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
let decoded = decodeMessage(encoded)
|
||||
check decoded.isOk()
|
||||
|
||||
@ -46,7 +50,7 @@ suite "Portal Wire Protocol Message Encodings":
|
||||
check:
|
||||
message.kind == pong
|
||||
message.pong.enrSeq == enrSeq
|
||||
message.pong.dataRadius == dataRadius
|
||||
message.pong.customPayload == customPayload
|
||||
|
||||
test "FindNode Request":
|
||||
let
|
||||
@ -122,25 +126,25 @@ suite "Portal Wire Protocol Message Encodings":
|
||||
message.kind == findcontent
|
||||
message.findcontent.contentKey == contentEncoded
|
||||
|
||||
test "FoundContent Response - payload":
|
||||
test "Content Response - payload":
|
||||
let
|
||||
enrs = List[ByteList, 32](@[])
|
||||
payload = ByteList(@[byte 0x01, 0x02, 0x03])
|
||||
n = FoundContentMessage(enrs: enrs, payload: payload)
|
||||
content = ByteList(@[byte 0x01, 0x02, 0x03])
|
||||
n = ContentMessage(enrs: enrs, content: content)
|
||||
|
||||
let encoded = encodeMessage(n)
|
||||
check encoded.toHex == "060800000008000000010203"
|
||||
check encoded.toHex == "0600000a0000000d000000010203"
|
||||
|
||||
let decoded = decodeMessage(encoded)
|
||||
check decoded.isOk()
|
||||
|
||||
let message = decoded.get()
|
||||
check:
|
||||
message.kind == foundcontent
|
||||
message.foundcontent.enrs.len() == 0
|
||||
message.foundcontent.payload == payload
|
||||
message.kind == MessageKind.content
|
||||
message.content.enrs.len() == 0
|
||||
message.content.content == content
|
||||
|
||||
test "FoundContent Response - enrs":
|
||||
test "Content Response - enrs":
|
||||
var e1, e2: Record
|
||||
check:
|
||||
e1.fromURI("enr:-HW4QBzimRxkmT18hMKaAL3IcZF1UcfTMPyi3Q1pxwZZbcZVRI8DC5infUAB_UauARLOJtYTxaagKoGmIjzQxO2qUygBgmlkgnY0iXNlY3AyNTZrMaEDymNMrg1JrLQB2KTGtv6MVbcNEVv0AHacwUAPMljNMTg")
|
||||
@ -148,22 +152,22 @@ suite "Portal Wire Protocol Message Encodings":
|
||||
|
||||
let
|
||||
enrs = List[ByteList, 32](@[ByteList(e1.raw), ByteList(e2.raw)])
|
||||
payload = ByteList(@[])
|
||||
n = FoundContentMessage(enrs: enrs, payload: payload)
|
||||
content = ByteList(@[])
|
||||
n = ContentMessage(enrs: enrs, content: content)
|
||||
|
||||
let encoded = encodeMessage(n)
|
||||
check encoded.toHex == "0608000000fe000000080000007f000000f875b8401ce2991c64993d7c84c29a00bdc871917551c7d330fca2dd0d69c706596dc655448f030b98a77d4001fd46ae0112ce26d613c5a6a02a81a6223cd0c4edaa53280182696482763489736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138f875b840d7f1c39e376297f81d7297758c64cb37dcc5c3beea9f57f7ce9695d7d5a67553417d719539d6ae4b445946de4d99e680eb8063f29485b555d45b7df16a1850130182696482763489736563703235366b31a1030e2cb74241c0c4fc8e8166f1a79a05d5b0dd95813a74b094529f317d5c39d235"
|
||||
check encoded.toHex == "0600000a0000000a000000080000007f000000f875b8401ce2991c64993d7c84c29a00bdc871917551c7d330fca2dd0d69c706596dc655448f030b98a77d4001fd46ae0112ce26d613c5a6a02a81a6223cd0c4edaa53280182696482763489736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138f875b840d7f1c39e376297f81d7297758c64cb37dcc5c3beea9f57f7ce9695d7d5a67553417d719539d6ae4b445946de4d99e680eb8063f29485b555d45b7df16a1850130182696482763489736563703235366b31a1030e2cb74241c0c4fc8e8166f1a79a05d5b0dd95813a74b094529f317d5c39d235"
|
||||
|
||||
let decoded = decodeMessage(encoded)
|
||||
check decoded.isOk()
|
||||
|
||||
let message = decoded.get()
|
||||
check:
|
||||
message.kind == foundcontent
|
||||
message.foundcontent.enrs.len() == 2
|
||||
message.foundcontent.enrs[0] == ByteList(e1.raw)
|
||||
message.foundcontent.enrs[1] == ByteList(e2.raw)
|
||||
message.foundcontent.payload == payload
|
||||
message.kind == MessageKind.content
|
||||
message.content.enrs.len() == 2
|
||||
message.content.enrs[0] == ByteList(e1.raw)
|
||||
message.content.enrs[1] == ByteList(e2.raw)
|
||||
message.content.content == content
|
||||
|
||||
test "Offer Request":
|
||||
let
|
||||
|
@ -59,10 +59,12 @@ procSuite "Portal Wire Protocol Tests":
|
||||
|
||||
let pong = await test.proto1.ping(test.proto2.localNode)
|
||||
|
||||
let customPayload = ByteList(SSZ.encode(CustomPayload(dataRadius: UInt256.high())))
|
||||
|
||||
check:
|
||||
pong.isOk()
|
||||
pong.get().enrSeq == 1'u64
|
||||
pong.get().dataRadius == UInt256.high()
|
||||
pong.get().customPayload == customPayload
|
||||
|
||||
await test.stopTest()
|
||||
|
||||
@ -110,7 +112,7 @@ procSuite "Portal Wire Protocol Tests":
|
||||
|
||||
await test.stopTest()
|
||||
|
||||
asyncTest "FindContent/FoundContent - send enrs":
|
||||
asyncTest "FindContent/Content - send enrs":
|
||||
let test = defaultTestCase(rng)
|
||||
|
||||
# ping in one direction to add, ping in the other to update as seen.
|
||||
@ -125,13 +127,13 @@ procSuite "Portal Wire Protocol Tests":
|
||||
|
||||
# content does not exist so this should provide us with the closest nodes
|
||||
# to the content, which is the only node in the routing table.
|
||||
let foundContent = await test.proto1.findContent(test.proto2.localNode,
|
||||
let content = await test.proto1.findContent(test.proto2.localNode,
|
||||
contentKey)
|
||||
|
||||
check:
|
||||
foundContent.isOk()
|
||||
foundContent.get().enrs.len() == 1
|
||||
foundContent.get().payload.len() == 0
|
||||
content.isOk()
|
||||
content.get().enrs.len() == 1
|
||||
content.get().content.len() == 0
|
||||
|
||||
await test.stopTest()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user