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:
Kim De Mey 2021-10-13 21:35:54 +02:00 committed by GitHub
parent 5c4c1784a0
commit b77034c870
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 84 additions and 61 deletions

View File

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

View File

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

View File

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

View File

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