mirror of
https://github.com/logos-messaging/logos-messaging-nim.git
synced 2026-01-02 14:03:06 +00:00
refactor(enr): move waku enr multiaddr to typedrecod and builder extensions
This commit is contained in:
parent
f95147f5b7
commit
2ffd2f8010
@ -22,7 +22,14 @@ proc now*(): Timestamp =
|
||||
getNanosecondTime(getTime().toUnixFloat())
|
||||
|
||||
# An accesible bootstrap node. See wakuv2.prod fleets.status.im
|
||||
const bootstrapNodes = @["enr:-Nm4QOdTOKZJKTUUZ4O_W932CXIET-M9NamewDnL78P5u9DOGnZlK0JFZ4k0inkfe6iY-0JAaJVovZXc575VV3njeiABgmlkgnY0gmlwhAjS3ueKbXVsdGlhZGRyc7g6ADg2MW5vZGUtMDEuYWMtY24taG9uZ2tvbmctYy53YWt1djIucHJvZC5zdGF0dXNpbS5uZXQGH0DeA4lzZWNwMjU2azGhAo0C-VvfgHiXrxZi3umDiooXMGY9FvYj5_d1Q4EeS7eyg3RjcIJ2X4N1ZHCCIyiFd2FrdTIP"]
|
||||
|
||||
|
||||
const bootstrapNode = "enr:-Nm4QOdTOKZJKTUUZ4O_W932CXIET-M9NamewDnL78P5u9D" &
|
||||
"OGnZlK0JFZ4k0inkfe6iY-0JAaJVovZXc575VV3njeiABgmlkgn" &
|
||||
"Y0gmlwhAjS3ueKbXVsdGlhZGRyc7g6ADg2MW5vZGUtMDEuYWMtY" &
|
||||
"24taG9uZ2tvbmctYy53YWt1djIucHJvZC5zdGF0dXNpbS5uZXQG" &
|
||||
"H0DeA4lzZWNwMjU2azGhAo0C-VvfgHiXrxZi3umDiooXMGY9FvY" &
|
||||
"j5_d1Q4EeS7eyg3RjcIJ2X4N1ZHCCIyiFd2FrdTIP"
|
||||
|
||||
# careful if running pub and sub in the same machine
|
||||
const wakuPort = 60000
|
||||
@ -33,11 +40,14 @@ proc setupAndPublish(rng: ref HmacDrbgContext) {.async.} =
|
||||
setupLogLevel(logging.LogLevel.NOTICE)
|
||||
notice "starting publisher", wakuPort=wakuPort, discv5Port=discv5Port
|
||||
let
|
||||
nodeKey = crypto.PrivateKey.random(Secp256k1, rng[])[]
|
||||
nodeKey = crypto.PrivateKey.random(Secp256k1, rng[]).get()
|
||||
ip = ValidIpAddress.init("0.0.0.0")
|
||||
node = WakuNode.new(nodeKey, ip, Port(wakuPort))
|
||||
flags = CapabilitiesBitfield.init(lightpush = false, filter = false, store = false, relay = true)
|
||||
|
||||
var bootstrapNodeEnr: enr.Record
|
||||
discard bootstrapNodeEnr.fromURI(bootstrapNode)
|
||||
|
||||
# assumes behind a firewall, so not care about being discoverable
|
||||
node.wakuDiscv5 = WakuDiscoveryV5.new(
|
||||
extIp= none(ValidIpAddress),
|
||||
@ -45,7 +55,7 @@ proc setupAndPublish(rng: ref HmacDrbgContext) {.async.} =
|
||||
extUdpPort = none(Port),
|
||||
bindIP = ip,
|
||||
discv5UdpPort = Port(discv5Port),
|
||||
bootstrapNodes = bootstrapNodes,
|
||||
bootstrapEnrs = @[bootstrapNodeEnr],
|
||||
privateKey = keys.PrivateKey(nodeKey.skkey),
|
||||
flags = flags,
|
||||
rng = node.rng)
|
||||
|
||||
@ -18,7 +18,12 @@ import
|
||||
../../../waku/v2/protocol/waku_discv5
|
||||
|
||||
# An accesible bootstrap node. See wakuv2.prod fleets.status.im
|
||||
const bootstrapNodes = @["enr:-Nm4QOdTOKZJKTUUZ4O_W932CXIET-M9NamewDnL78P5u9DOGnZlK0JFZ4k0inkfe6iY-0JAaJVovZXc575VV3njeiABgmlkgnY0gmlwhAjS3ueKbXVsdGlhZGRyc7g6ADg2MW5vZGUtMDEuYWMtY24taG9uZ2tvbmctYy53YWt1djIucHJvZC5zdGF0dXNpbS5uZXQGH0DeA4lzZWNwMjU2azGhAo0C-VvfgHiXrxZi3umDiooXMGY9FvYj5_d1Q4EeS7eyg3RjcIJ2X4N1ZHCCIyiFd2FrdTIP"]
|
||||
const bootstrapNode = "enr:-Nm4QOdTOKZJKTUUZ4O_W932CXIET-M9NamewDnL78P5u9DOGnZl" &
|
||||
"K0JFZ4k0inkfe6iY-0JAaJVovZXc575VV3njeiABgmlkgnY0gmlwhAjS" &
|
||||
"3ueKbXVsdGlhZGRyc7g6ADg2MW5vZGUtMDEuYWMtY24taG9uZ2tvbmct" &
|
||||
"Yy53YWt1djIucHJvZC5zdGF0dXNpbS5uZXQGH0DeA4lzZWNwMjU2azGh" &
|
||||
"Ao0C-VvfgHiXrxZi3umDiooXMGY9FvYj5_d1Q4EeS7eyg3RjcIJ2X4N1" &
|
||||
"ZHCCIyiFd2FrdTIP"
|
||||
|
||||
# careful if running pub and sub in the same machine
|
||||
const wakuPort = 50000
|
||||
@ -34,6 +39,9 @@ proc setupAndSubscribe(rng: ref HmacDrbgContext) {.async.} =
|
||||
node = WakuNode.new(nodeKey, ip, Port(wakuPort))
|
||||
flags = CapabilitiesBitfield.init(lightpush = false, filter = false, store = false, relay = true)
|
||||
|
||||
var bootstrapNodeEnr: enr.Record
|
||||
discard bootstrapNodeEnr.fromURI(bootstrapNode)
|
||||
|
||||
# assumes behind a firewall, so not care about being discoverable
|
||||
node.wakuDiscv5 = WakuDiscoveryV5.new(
|
||||
extIp= none(ValidIpAddress),
|
||||
@ -41,7 +49,7 @@ proc setupAndSubscribe(rng: ref HmacDrbgContext) {.async.} =
|
||||
extUdpPort = none(Port),
|
||||
bindIP = ip,
|
||||
discv5UdpPort = Port(discv5Port),
|
||||
bootstrapNodes = bootstrapNodes,
|
||||
bootstrapEnrs = @[bootstrapNodeEnr],
|
||||
privateKey = keys.PrivateKey(nodeKey.skkey),
|
||||
flags = flags,
|
||||
rng = node.rng)
|
||||
|
||||
@ -56,7 +56,7 @@ procSuite "Waku Discovery v5":
|
||||
some(extIp), some(nodeTcpPort1), some(nodeUdpPort1),
|
||||
bindIp,
|
||||
nodeUdpPort1,
|
||||
newSeq[string](),
|
||||
newSeq[enr.Record](),
|
||||
false,
|
||||
keys.PrivateKey(nodeKey1.skkey),
|
||||
flags,
|
||||
@ -68,7 +68,7 @@ procSuite "Waku Discovery v5":
|
||||
some(extIp), some(nodeTcpPort2), some(nodeUdpPort2),
|
||||
bindIp,
|
||||
nodeUdpPort2,
|
||||
@[node1.wakuDiscv5.protocol.localNode.record.toURI()], # Bootstrap with node1
|
||||
@[node1.wakuDiscv5.protocol.localNode.record], # Bootstrap with node1
|
||||
false,
|
||||
keys.PrivateKey(nodeKey2.skkey),
|
||||
flags,
|
||||
@ -80,7 +80,7 @@ procSuite "Waku Discovery v5":
|
||||
some(extIp), some(nodeTcpPort3), some(nodeUdpPort3),
|
||||
bindIp,
|
||||
nodeUdpPort3,
|
||||
@[node2.wakuDiscv5.protocol.localNode.record.toURI()], # Bootstrap with node2
|
||||
@[node2.wakuDiscv5.protocol.localNode.record], # Bootstrap with node2
|
||||
false,
|
||||
keys.PrivateKey(nodeKey3.skkey),
|
||||
flags,
|
||||
@ -191,7 +191,7 @@ procSuite "Waku Discovery v5":
|
||||
await sleepAsync(3000.millis) # Give the algorithm some time to work its magic
|
||||
|
||||
let node1Enr = node2.wakuDiscv5.protocol.routingTable.buckets[0].nodes[0].record
|
||||
let multiaddrs = node1Enr.get(MULTIADDR_ENR_FIELD, seq[byte])[].toMultiAddresses()
|
||||
let multiaddrs = node1Enr.toTyped().get().multiaddrs.get()
|
||||
|
||||
check:
|
||||
node1.wakuDiscv5.protocol.nodesDiscovered > 0
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import
|
||||
std/options,
|
||||
stew/[results, byteutils],
|
||||
stew/results,
|
||||
testutils/unittests
|
||||
import
|
||||
../../waku/v2/protocol/waku_enr,
|
||||
@ -37,7 +37,7 @@ suite "Waku ENR - Capabilities bitfield":
|
||||
check:
|
||||
caps == @[Capabilities.Relay, Capabilities.Filter, Capabilities.Lightpush]
|
||||
|
||||
test "encode and extract capabilities from record (EnrBuilder ext)":
|
||||
test "encode and decode record with capabilities field (EnrBuilder ext)":
|
||||
## Given
|
||||
let
|
||||
enrSeqNum = 1u64
|
||||
@ -63,7 +63,7 @@ suite "Waku ENR - Capabilities bitfield":
|
||||
check:
|
||||
bitfield.toCapabilities() == @[Capabilities.Relay, Capabilities.Store]
|
||||
|
||||
test "encode and extract capabilities from record (deprecated)":
|
||||
test "encode and decode record with capabilities field (deprecated)":
|
||||
# TODO: Remove after removing the `Record.init()` proc
|
||||
## Given
|
||||
let enrkey = generatesecp256k1key()
|
||||
@ -84,7 +84,7 @@ suite "Waku ENR - Capabilities bitfield":
|
||||
check:
|
||||
bitfield.toCapabilities() == @[Capabilities.Relay, Capabilities.Store]
|
||||
|
||||
test "cannot extract capabilities from record":
|
||||
test "cannot decode capabilities from record":
|
||||
## Given
|
||||
let
|
||||
enrSeqNum = 1u64
|
||||
@ -154,115 +154,147 @@ suite "Waku ENR - Capabilities bitfield":
|
||||
|
||||
suite "Waku ENR - Multiaddresses":
|
||||
|
||||
test "Parse multiaddr field":
|
||||
let
|
||||
reasonable = "0x000A047F0000010601BADD03".hexToSeqByte()
|
||||
reasonableDns4 = ("0x002F36286E6F64652D30312E646F2D616D73332E77616B7576322E746" &
|
||||
"573742E737461747573696D2E6E65740601BBDE03003837316E6F64652D" &
|
||||
"30312E61632D636E2D686F6E676B6F6E672D632E77616B7576322E74657" &
|
||||
"3742E737461747573696D2E6E65740601BBDE030029BD03ADADEC040BE0" &
|
||||
"47F9658668B11A504F3155001F231A37F54C4476C07FB4CC139ED7E30304D2DE03").hexToSeqByte()
|
||||
tooLong = "0x000B047F0000010601BADD03".hexToSeqByte()
|
||||
tooShort = "0x000A047F0000010601BADD0301".hexToSeqByte()
|
||||
gibberish = "0x3270ac4e5011123c".hexToSeqByte()
|
||||
empty = newSeq[byte]()
|
||||
test "decode record with multiaddrs field":
|
||||
## Given
|
||||
let enrUri = "enr:-QEnuEBEAyErHEfhiQxAVQoWowGTCuEF9fKZtXSd7H_PymHFhGJA3rGAYDVSH" &
|
||||
"KCyJDGRLBGsloNbS8AZF33IVuefjOO6BIJpZIJ2NIJpcIQS39tkim11bHRpYWRkcn" &
|
||||
"O4lgAvNihub2RlLTAxLmRvLWFtczMud2FrdXYyLnRlc3Quc3RhdHVzaW0ubmV0BgG" &
|
||||
"73gMAODcxbm9kZS0wMS5hYy1jbi1ob25na29uZy1jLndha3V2Mi50ZXN0LnN0YXR1" &
|
||||
"c2ltLm5ldAYBu94DACm9A62t7AQL4Ef5ZYZosRpQTzFVAB8jGjf1TER2wH-0zBOe1" &
|
||||
"-MDBNLeA4lzZWNwMjU2azGhAzfsxbxyCkgCqq8WwYsVWH7YkpMLnU2Bw5xJSimxKa" &
|
||||
"v-g3VkcIIjKA"
|
||||
|
||||
## Note: we expect to fail optimistically, i.e. extract
|
||||
## any addresses we can and ignore other errors.
|
||||
## Worst case scenario is we return an empty `multiaddrs` seq.
|
||||
var record: Record
|
||||
require record.fromURI(enrUri)
|
||||
|
||||
let
|
||||
expectedAddr1 = MultiAddress.init("/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss").get()
|
||||
expectedAddr2 = MultiAddress.init("/dns6/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/443/wss").get()
|
||||
expectedAddr3 = MultiAddress.init("/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234/wss").get()
|
||||
|
||||
## When
|
||||
let typedRecord = record.toTyped()
|
||||
require typedRecord.isOk()
|
||||
|
||||
let multiaddrsOpt = typedRecord.value.multiaddrs
|
||||
|
||||
## Then
|
||||
check multiaddrsOpt.isSome()
|
||||
|
||||
let multiaddrs = multiaddrsOpt.get()
|
||||
check:
|
||||
# Expected cases
|
||||
reasonable.toMultiAddresses().contains(MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[])
|
||||
reasonableDns4.toMultiAddresses().contains(MultiAddress.init("/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss")[])
|
||||
# Buffer exceeded
|
||||
tooLong.toMultiAddresses().len() == 0
|
||||
# Buffer remainder
|
||||
tooShort.toMultiAddresses().contains(MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[])
|
||||
# Gibberish
|
||||
gibberish.toMultiAddresses().len() == 0
|
||||
# Empty
|
||||
empty.toMultiAddresses().len() == 0
|
||||
multiaddrs.len == 3
|
||||
multiaddrs.contains(expectedAddr1)
|
||||
multiaddrs.contains(expectedAddr2)
|
||||
multiaddrs.contains(expectedAddr3)
|
||||
|
||||
test "Init ENR for Waku Usage":
|
||||
# Tests RFC31 encoding "happy path"
|
||||
test "encode and decode record with multiaddrs field (EnrBuilder ext)":
|
||||
## Given
|
||||
let
|
||||
enrIp = ValidIpAddress.init("127.0.0.1")
|
||||
enrTcpPort, enrUdpPort = Port(61101)
|
||||
enrKey = generateSecp256k1Key()
|
||||
multiaddrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[],
|
||||
MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss")[]]
|
||||
enrSeqNum = 1u64
|
||||
enrPrivKey = generatesecp256k1key()
|
||||
|
||||
let
|
||||
record = enr.Record.init(1, enrKey, some(enrIp),
|
||||
some(enrTcpPort), some(enrUdpPort),
|
||||
none(CapabilitiesBitfield),
|
||||
multiaddrs)
|
||||
typedRecord = record.toTyped().get()
|
||||
addr1 = MultiAddress.init("/ip4/127.0.0.1/tcp/80/ws").get()
|
||||
addr2 = MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss").get()
|
||||
|
||||
# Check EIP-778 ENR fields
|
||||
## When
|
||||
var builder = EnrBuilder.init(enrPrivKey, seqNum = enrSeqNum)
|
||||
builder.withMultiaddrs(addr1, addr2)
|
||||
|
||||
let recordRes = builder.build()
|
||||
|
||||
require recordRes.isOk()
|
||||
let record = recordRes.tryGet()
|
||||
|
||||
let typedRecord = record.toTyped()
|
||||
require typedRecord.isOk()
|
||||
|
||||
let multiaddrsOpt = typedRecord.value.multiaddrs
|
||||
|
||||
## Then
|
||||
check multiaddrsOpt.isSome()
|
||||
|
||||
let multiaddrs = multiaddrsOpt.get()
|
||||
check:
|
||||
@(typedRecord.secp256k1.get()) == enrKey.getPublicKey()[].getRawBytes()[]
|
||||
ipv4(typedRecord.ip.get()) == enrIp
|
||||
Port(typedRecord.tcp.get()) == enrTcpPort
|
||||
Port(typedRecord.udp.get()) == enrUdpPort
|
||||
multiaddrs.len == 2
|
||||
multiaddrs.contains(addr1)
|
||||
multiaddrs.contains(addr2)
|
||||
|
||||
# Check Waku ENR fields
|
||||
let decodedAddrs = record.get(MultiaddrEnrField, seq[byte]).tryGet().toMultiAddresses()
|
||||
test "encode and decode record with multiaddrs field (deprecated)":
|
||||
# TODO: Remove after removing the `Record.init()` proc
|
||||
## Given
|
||||
let enrkey = generatesecp256k1key()
|
||||
let caps = CapabilitiesBitfield.init(Capabilities.Relay, Capabilities.Store)
|
||||
|
||||
let
|
||||
addr1 = MultiAddress.init("/ip4/127.0.0.1/tcp/80/ws").get()
|
||||
addr2 = MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss").get()
|
||||
|
||||
## When
|
||||
let record = Record.init(1, enrkey, wakuFlags=some(caps), multiaddrs = @[addr1, addr2])
|
||||
|
||||
let typedRecord = record.toTyped()
|
||||
require typedRecord.isOk()
|
||||
|
||||
let multiaddrsOpt = typedRecord.value.multiaddrs
|
||||
|
||||
## Then
|
||||
check multiaddrsOpt.isSome()
|
||||
|
||||
let multiaddrs = multiaddrsOpt.get()
|
||||
check:
|
||||
decodedAddrs.contains(MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[])
|
||||
decodedAddrs.contains(MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss")[])
|
||||
multiaddrs.contains(addr1)
|
||||
multiaddrs.contains(addr2)
|
||||
|
||||
test "Strip multiaddr peerId":
|
||||
# Tests that peerId is stripped of multiaddrs as per RFC31
|
||||
test "cannot decode multiaddresses from record":
|
||||
## Given
|
||||
let
|
||||
enrIp = ValidIpAddress.init("127.0.0.1")
|
||||
enrTcpPort, enrUdpPort = Port(61102)
|
||||
enrKey = generateSecp256k1Key()
|
||||
multiaddrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr31iDQpSN5Qa882BCjjwgrD")[]]
|
||||
enrSeqNum = 1u64
|
||||
enrPrivKey = generatesecp256k1key()
|
||||
|
||||
let record = EnrBuilder.init(enrPrivKey, enrSeqNum).build().tryGet()
|
||||
|
||||
## When
|
||||
let typedRecord = record.toTyped()
|
||||
require typedRecord.isOk()
|
||||
|
||||
let fieldOpt = typedRecord.value.multiaddrs
|
||||
|
||||
## Then
|
||||
check fieldOpt.isNone()
|
||||
|
||||
test "encode and decode record with multiaddresses field - strip peer ID":
|
||||
## Given
|
||||
let
|
||||
enrSeqNum = 1u64
|
||||
enrPrivKey = generatesecp256k1key()
|
||||
|
||||
let
|
||||
record = enr.Record.init(1, enrKey, some(enrIp),
|
||||
some(enrTcpPort), some(enrUdpPort),
|
||||
none(CapabilitiesBitfield),
|
||||
multiaddrs)
|
||||
addr1 = MultiAddress.init("/ip4/127.0.0.1/tcp/80/ws/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr31iDQpSN5Qa882BCjjwgrD").get()
|
||||
addr2 = MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss").get()
|
||||
|
||||
# Check Waku ENR fields
|
||||
let
|
||||
decodedAddrs = record.get(MULTIADDR_ENR_FIELD, seq[byte])[].toMultiAddresses()
|
||||
let expectedAddr1 = MultiAddress.init("/ip4/127.0.0.1/tcp/80/ws").get()
|
||||
|
||||
check decodedAddrs.contains(MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss")[]) # Peer Id has been stripped
|
||||
## When
|
||||
var builder = EnrBuilder.init(enrPrivKey, seqNum = enrSeqNum)
|
||||
builder.withMultiaddrs(addr1, addr2)
|
||||
|
||||
test "Decode ENR with multiaddrs field":
|
||||
let
|
||||
# Known values correspond to shared test vectors with other Waku implementations
|
||||
knownIp = ValidIpAddress.init("18.223.219.100")
|
||||
knownUdpPort = some(9000.uint16)
|
||||
knownTcpPort = none(uint16)
|
||||
knownMultiaddrs = @[MultiAddress.init("/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss")[],
|
||||
MultiAddress.init("/dns6/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/443/wss")[]]
|
||||
knownEnr = "enr:-QEnuEBEAyErHEfhiQxAVQoWowGTCuEF9fKZtXSd7H_PymHFhGJA3rGAYDVSH" &
|
||||
"KCyJDGRLBGsloNbS8AZF33IVuefjOO6BIJpZIJ2NIJpcIQS39tkim11bHRpYWRkcn" &
|
||||
"O4lgAvNihub2RlLTAxLmRvLWFtczMud2FrdXYyLnRlc3Quc3RhdHVzaW0ubmV0BgG" &
|
||||
"73gMAODcxbm9kZS0wMS5hYy1jbi1ob25na29uZy1jLndha3V2Mi50ZXN0LnN0YXR1" &
|
||||
"c2ltLm5ldAYBu94DACm9A62t7AQL4Ef5ZYZosRpQTzFVAB8jGjf1TER2wH-0zBOe1" &
|
||||
"-MDBNLeA4lzZWNwMjU2azGhAzfsxbxyCkgCqq8WwYsVWH7YkpMLnU2Bw5xJSimxKav-g3VkcIIjKA"
|
||||
let recordRes = builder.build()
|
||||
|
||||
var enrRecord: Record
|
||||
require recordRes.isOk()
|
||||
let record = recordRes.tryGet()
|
||||
|
||||
let typedRecord = record.toTyped()
|
||||
require typedRecord.isOk()
|
||||
|
||||
let multiaddrsOpt = typedRecord.value.multiaddrs
|
||||
|
||||
## Then
|
||||
check multiaddrsOpt.isSome()
|
||||
|
||||
let multiaddrs = multiaddrsOpt.get()
|
||||
check:
|
||||
enrRecord.fromURI(knownEnr)
|
||||
|
||||
let typedRecord = enrRecord.toTyped().get()
|
||||
|
||||
# Check EIP-778 ENR fields
|
||||
check:
|
||||
ipv4(typedRecord.ip.get()) == knownIp
|
||||
typedRecord.tcp == knownTcpPort
|
||||
typedRecord.udp == knownUdpPort
|
||||
|
||||
# Check Waku ENR fields
|
||||
let
|
||||
decodedAddrs = enrRecord.get(MULTIADDR_ENR_FIELD, seq[byte])[].toMultiAddresses()
|
||||
|
||||
for knownMultiaddr in knownMultiaddrs:
|
||||
check decodedAddrs.contains(knownMultiaddr)
|
||||
multiaddrs.contains(expectedAddr1)
|
||||
multiaddrs.contains(addr2)
|
||||
|
||||
|
||||
@ -101,7 +101,7 @@ procSuite "Waku Peer Exchange":
|
||||
some(extIp), some(nodeTcpPort1), some(nodeUdpPort1),
|
||||
bindIp,
|
||||
nodeUdpPort1,
|
||||
newSeq[string](),
|
||||
newSeq[enr.Record](),
|
||||
false,
|
||||
keys.PrivateKey(nodeKey1.skkey),
|
||||
flags,
|
||||
@ -113,7 +113,7 @@ procSuite "Waku Peer Exchange":
|
||||
some(extIp), some(nodeTcpPort2), some(nodeUdpPort2),
|
||||
bindIp,
|
||||
nodeUdpPort2,
|
||||
@[node1.wakuDiscv5.protocol.localNode.record.toURI()], # Bootstrap with node1
|
||||
@[node1.wakuDiscv5.protocol.localNode.record], # Bootstrap with node1
|
||||
false,
|
||||
keys.PrivateKey(nodeKey2.skkey),
|
||||
flags,
|
||||
|
||||
@ -27,8 +27,7 @@ logScope:
|
||||
topics = "waku discv5"
|
||||
|
||||
|
||||
type
|
||||
WakuDiscoveryV5* = ref object
|
||||
type WakuDiscoveryV5* = ref object
|
||||
protocol*: protocol.Protocol
|
||||
listening*: bool
|
||||
|
||||
@ -65,52 +64,21 @@ proc addBootstrapNode*(bootstrapAddr: string,
|
||||
return
|
||||
|
||||
let enrRes = parseBootstrapAddress(bootstrapAddr)
|
||||
if enrRes.isOk():
|
||||
bootstrapEnrs.add(enrRes.value)
|
||||
else:
|
||||
warn "Ignoring invalid bootstrap address",
|
||||
bootstrapAddr, reason = enrRes.error
|
||||
if enrRes.isErr():
|
||||
debug "ignoring invalid bootstrap address", reason = enrRes.error
|
||||
return
|
||||
|
||||
proc isWakuNode(node: Node): bool =
|
||||
let wakuField = node.record.tryGet(CapabilitiesEnrField, uint8)
|
||||
bootstrapEnrs.add(enrRes.value)
|
||||
|
||||
if wakuField.isSome():
|
||||
return wakuField.get() != 0x00 # True if any flag set to true
|
||||
|
||||
return false
|
||||
|
||||
####################
|
||||
# Discovery v5 API #
|
||||
####################
|
||||
|
||||
proc findRandomPeers*(wakuDiscv5: WakuDiscoveryV5): Future[Result[seq[RemotePeerInfo], cstring]] {.async.} =
|
||||
## Find random peers to connect to using Discovery v5
|
||||
|
||||
## Query for a random target and collect all discovered nodes
|
||||
let discoveredNodes = await wakuDiscv5.protocol.queryRandom()
|
||||
|
||||
## Filter based on our needs
|
||||
# let filteredNodes = discoveredNodes.filter(isWakuNode) # Currently only a single predicate
|
||||
# TODO: consider node filtering based on ENR; we do not filter based on ENR in the first waku discv5 beta stage
|
||||
|
||||
var discoveredPeers: seq[RemotePeerInfo]
|
||||
|
||||
for node in discoveredNodes:
|
||||
# Convert discovered ENR to RemotePeerInfo and add to discovered nodes
|
||||
let res = node.record.toRemotePeerInfo()
|
||||
|
||||
if res.isOk():
|
||||
discoveredPeers.add(res.get())
|
||||
else:
|
||||
error "Failed to convert ENR to peer info", enr= $node.record, err=res.error
|
||||
waku_discv5_errors.inc(labelValues = ["peer_info_failure"])
|
||||
|
||||
|
||||
return ok(discoveredPeers)
|
||||
|
||||
proc new*(T: type WakuDiscoveryV5,
|
||||
extIp: Option[ValidIpAddress],
|
||||
extTcpPort, extUdpPort: Option[Port],
|
||||
extTcpPort: Option[Port],
|
||||
extUdpPort: Option[Port],
|
||||
bindIP: ValidIpAddress,
|
||||
discv5UdpPort: Port,
|
||||
bootstrapEnrs = newSeq[enr.Record](),
|
||||
@ -120,73 +88,72 @@ proc new*(T: type WakuDiscoveryV5,
|
||||
multiaddrs = newSeq[MultiAddress](),
|
||||
rng: ref HmacDrbgContext,
|
||||
discv5Config: protocol.DiscoveryConfig = protocol.defaultDiscoveryConfig): T =
|
||||
## TODO: consider loading from a configurable bootstrap file
|
||||
|
||||
## We always add the waku field as specified
|
||||
# Add the waku capabilities field
|
||||
var enrInitFields = @[(CapabilitiesEnrField, @[flags.byte])]
|
||||
|
||||
## Add multiaddresses to ENR
|
||||
# Add the waku multiaddrs field
|
||||
if multiaddrs.len > 0:
|
||||
enrInitFields.add((MULTIADDR_ENR_FIELD, multiaddrs.getRawField()))
|
||||
let value = waku_enr.encodeMultiaddrs(multiaddrs)
|
||||
enrInitFields.add((MultiaddrEnrField, value))
|
||||
|
||||
let protocol = newProtocol(
|
||||
privateKey,
|
||||
enrIp = extIp, enrTcpPort = extTcpPort, enrUdpPort = extUdpPort, # We use the external IP & ports for ENR
|
||||
enrIp = extIp,
|
||||
enrTcpPort = extTcpPort,
|
||||
enrUdpPort = extUdpPort,
|
||||
enrInitFields,
|
||||
bootstrapEnrs,
|
||||
bindPort = discv5UdpPort,
|
||||
bindIp = bindIP,
|
||||
enrAutoUpdate = enrAutoUpdate,
|
||||
config = discv5Config,
|
||||
rng = rng)
|
||||
rng = rng
|
||||
)
|
||||
|
||||
return WakuDiscoveryV5(protocol: protocol, listening: false)
|
||||
WakuDiscoveryV5(protocol: protocol, listening: false)
|
||||
|
||||
# constructor that takes bootstrap Enrs in Enr Uri form
|
||||
proc new*(T: type WakuDiscoveryV5,
|
||||
extIp: Option[ValidIpAddress],
|
||||
extTcpPort, extUdpPort: Option[Port],
|
||||
bindIP: ValidIpAddress,
|
||||
discv5UdpPort: Port,
|
||||
bootstrapNodes: seq[string],
|
||||
enrAutoUpdate = false,
|
||||
privateKey: keys.PrivateKey,
|
||||
flags: CapabilitiesBitfield,
|
||||
multiaddrs = newSeq[MultiAddress](),
|
||||
rng: ref HmacDrbgContext,
|
||||
discv5Config: protocol.DiscoveryConfig = protocol.defaultDiscoveryConfig): T =
|
||||
|
||||
var bootstrapEnrs: seq[enr.Record]
|
||||
for node in bootstrapNodes:
|
||||
addBootstrapNode(node, bootstrapEnrs)
|
||||
|
||||
return WakuDiscoveryV5.new(
|
||||
extIP, extTcpPort, extUdpPort,
|
||||
bindIP,
|
||||
discv5UdpPort,
|
||||
bootstrapEnrs,
|
||||
enrAutoUpdate,
|
||||
privateKey,
|
||||
flags,
|
||||
multiaddrs,
|
||||
rng,
|
||||
discv5Config
|
||||
)
|
||||
|
||||
|
||||
proc open*(wakuDiscv5: WakuDiscoveryV5) {.raises: [CatchableError].} =
|
||||
# TODO: Do not raise an exception, return a result
|
||||
proc open*(wd: WakuDiscoveryV5) {.raises: [CatchableError].} =
|
||||
debug "Opening Waku discovery v5 ports"
|
||||
if wd.listening:
|
||||
return
|
||||
|
||||
wakuDiscv5.protocol.open()
|
||||
wakuDiscv5.listening = true
|
||||
wd.protocol.open()
|
||||
wd.listening = true
|
||||
|
||||
proc start*(wakuDiscv5: WakuDiscoveryV5) =
|
||||
debug "Starting Waku discovery v5 service"
|
||||
proc start*(wd: WakuDiscoveryV5) =
|
||||
debug "starting Waku discovery v5 service"
|
||||
wd.protocol.start()
|
||||
|
||||
wakuDiscv5.protocol.start()
|
||||
proc closeWait*(wd: WakuDiscoveryV5) {.async.} =
|
||||
debug "closing Waku discovery v5 node"
|
||||
if not wd.listening:
|
||||
return
|
||||
|
||||
proc closeWait*(wakuDiscv5: WakuDiscoveryV5) {.async.} =
|
||||
debug "Closing Waku discovery v5 node"
|
||||
wd.listening = false
|
||||
await wd.protocol.closeWait()
|
||||
|
||||
wakuDiscv5.listening = false
|
||||
await wakuDiscv5.protocol.closeWait()
|
||||
proc findRandomPeers*(wd: WakuDiscoveryV5): Future[Result[seq[RemotePeerInfo], cstring]] {.async.} =
|
||||
## Find random peers to connect to using Discovery v5
|
||||
|
||||
# Query for a random target and collect all discovered nodes
|
||||
let discoveredNodes = await wd.protocol.queryRandom()
|
||||
|
||||
## Filter based on our needs
|
||||
# let filteredNodes = discoveredNodes.filter(isWakuNode) # Currently only a single predicate
|
||||
# TODO: consider node filtering based on ENR; we do not filter based on ENR in the first waku discv5 beta stage
|
||||
|
||||
var discoveredPeers: seq[RemotePeerInfo]
|
||||
|
||||
for node in discoveredNodes:
|
||||
let res = node.record.toRemotePeerInfo()
|
||||
if res.isErr():
|
||||
error "failed to convert ENR to peer info", enr= $node.record, err=res.error
|
||||
waku_discv5_errors.inc(labelValues = ["peer_info_failure"])
|
||||
continue
|
||||
|
||||
discoveredPeers.add(res.value)
|
||||
|
||||
|
||||
return ok(discoveredPeers)
|
||||
|
||||
@ -115,47 +115,17 @@ proc getCapabilities*(r: Record): seq[Capabilities] =
|
||||
|
||||
## Multiaddress
|
||||
|
||||
func getRawField*(multiaddrs: seq[MultiAddress]): seq[byte] =
|
||||
var fieldRaw: seq[byte]
|
||||
|
||||
func encodeMultiaddrs*(multiaddrs: seq[MultiAddress]): seq[byte] =
|
||||
var buffer = newSeq[byte]()
|
||||
for multiaddr in multiaddrs:
|
||||
|
||||
let
|
||||
maRaw = multiaddr.data.buffer # binary encoded multiaddr
|
||||
maSize = maRaw.len.uint16.toBytes(Endianness.bigEndian) # size as Big Endian unsigned 16-bit integer
|
||||
raw = multiaddr.data.buffer # binary encoded multiaddr
|
||||
size = raw.len.uint16.toBytes(Endianness.bigEndian) # size as Big Endian unsigned 16-bit integer
|
||||
|
||||
assert maSize.len == 2
|
||||
buffer.add(concat(@size, raw))
|
||||
|
||||
fieldRaw.add(concat(@maSize, maRaw))
|
||||
|
||||
return fieldRaw
|
||||
|
||||
func toFieldPair*(multiaddrs: seq[MultiAddress]): FieldPair =
|
||||
## Converts a seq of multiaddrs to a `multiaddrs` ENR
|
||||
## field pair according to https://rfc.vac.dev/spec/31/
|
||||
let fieldRaw = multiaddrs.getRawField()
|
||||
|
||||
return toFieldPair(MULTIADDR_ENR_FIELD, fieldRaw)
|
||||
|
||||
func stripPeerId(multiaddr: MultiAddress): MultiAddress =
|
||||
var cleanAddr = MultiAddress.init()
|
||||
|
||||
for item in multiaddr.items:
|
||||
if item[].protoName()[] != "p2p":
|
||||
# Add all parts except p2p peerId
|
||||
discard cleanAddr.append(item[])
|
||||
|
||||
return cleanAddr
|
||||
|
||||
func stripPeerIds*(multiaddrs: seq[MultiAddress]): seq[MultiAddress] =
|
||||
var cleanAddrs: seq[MultiAddress]
|
||||
|
||||
for multiaddr in multiaddrs:
|
||||
if multiaddr.contains(multiCodec("p2p"))[]:
|
||||
cleanAddrs.add(multiaddr.stripPeerId())
|
||||
else:
|
||||
cleanAddrs.add(multiaddr)
|
||||
|
||||
return cleanAddrs
|
||||
buffer
|
||||
|
||||
func readBytes(rawBytes: seq[byte], numBytes: int, pos: var int = 0): Result[seq[byte], cstring] =
|
||||
## Attempts to read `numBytes` from a sequence, from
|
||||
@ -163,46 +133,71 @@ func readBytes(rawBytes: seq[byte], numBytes: int, pos: var int = 0): Result[seq
|
||||
## an error if `rawBytes` boundary is exceeded.
|
||||
##
|
||||
## If successful, `pos` is advanced by `numBytes`
|
||||
|
||||
if rawBytes[pos..^1].len() < numBytes:
|
||||
return err("Exceeds maximum available bytes")
|
||||
return err("insufficient bytes")
|
||||
|
||||
let slicedSeq = rawBytes[pos..<pos+numBytes]
|
||||
pos += numBytes
|
||||
|
||||
return ok(slicedSeq)
|
||||
|
||||
func toMultiAddresses*(multiaddrsField: seq[byte]): seq[MultiAddress] =
|
||||
func decodeMultiaddrs(buffer: seq[byte]): EnrResult[seq[MultiAddress]] =
|
||||
## Parses a `multiaddrs` ENR field according to
|
||||
## https://rfc.vac.dev/spec/31/
|
||||
var multiaddrs: seq[MultiAddress]
|
||||
|
||||
let totalLen = multiaddrsField.len()
|
||||
if totalLen < 2:
|
||||
return multiaddrs
|
||||
|
||||
var pos = 0
|
||||
while pos < totalLen:
|
||||
let addrLenRes = multiaddrsField.readBytes(2, pos)
|
||||
if addrLenRes.isErr():
|
||||
return multiaddrs
|
||||
|
||||
let addrLen = uint16.fromBytesBE(addrLenRes.get())
|
||||
if addrLen == 0.uint16:
|
||||
while pos < buffer.len():
|
||||
let addrLenRaw = ? readBytes(buffer, 2, pos)
|
||||
let addrLen = uint16.fromBytesBE(addrLenRaw)
|
||||
if addrLen == 0:
|
||||
# Ensure pos always advances and we don't get stuck in infinite loop
|
||||
return multiaddrs
|
||||
return err("malformed multiaddr field: invalid length")
|
||||
|
||||
let addrRaw = multiaddrsField.readBytes(addrLen.int, pos)
|
||||
if addrRaw.isErr():
|
||||
return multiaddrs
|
||||
let addrRaw = ? readBytes(buffer, addrLen.int, pos)
|
||||
let address = MultiAddress.init(addrRaw).get()
|
||||
|
||||
let multiaddr = MultiAddress.init(addrRaw.get())
|
||||
if multiaddr.isErr():
|
||||
return multiaddrs
|
||||
multiaddrs.add(address)
|
||||
|
||||
multiaddrs.add(multiaddr.get())
|
||||
return ok(multiaddrs)
|
||||
|
||||
|
||||
# ENR builder extension
|
||||
func stripPeerId(multiaddr: MultiAddress): MultiAddress =
|
||||
if not multiaddr.contains(multiCodec("p2p")).get():
|
||||
return multiaddr
|
||||
|
||||
var cleanAddr = MultiAddress.init()
|
||||
for item in multiaddr.items:
|
||||
if item.value.protoName().get() != "p2p":
|
||||
# Add all parts except p2p peerId
|
||||
discard cleanAddr.append(item.value)
|
||||
|
||||
return cleanAddr
|
||||
|
||||
func withMultiaddrs*(builder: var EnrBuilder, multiaddrs: seq[MultiAddress]) =
|
||||
let multiaddrs = multiaddrs.map(stripPeerId)
|
||||
let value = encodeMultiaddrs(multiaddrs)
|
||||
builder.addFieldPair(MultiaddrEnrField, value)
|
||||
|
||||
func withMultiaddrs*(builder: var EnrBuilder, multiaddrs: varargs[MultiAddress]) =
|
||||
withMultiaddrs(builder, @multiaddrs)
|
||||
|
||||
# ENR record accessors (e.g., Record, TypedRecord, etc.)
|
||||
|
||||
func multiaddrs*(record: TypedRecord): Option[seq[MultiAddress]] =
|
||||
let field = record.tryGet(MultiaddrEnrField, seq[byte])
|
||||
if field.isNone():
|
||||
return none(seq[MultiAddress])
|
||||
|
||||
let decodeRes = decodeMultiaddrs(field.get())
|
||||
if decodeRes.isErr():
|
||||
return none(seq[MultiAddress])
|
||||
|
||||
some(decodeRes.value)
|
||||
|
||||
## Utils
|
||||
|
||||
return multiaddrs
|
||||
|
||||
|
||||
## ENR
|
||||
@ -228,7 +223,8 @@ func init*(T: type enr.Record,
|
||||
|
||||
# `multiaddrs` field
|
||||
if multiaddrs.len > 0:
|
||||
wakuEnrFields.add(multiaddrs.stripPeerIds().toFieldPair())
|
||||
let value = encodeMultiaddrs(multiaddrs)
|
||||
wakuEnrFields.add(toFieldPair(MultiaddrEnrField, value))
|
||||
|
||||
let
|
||||
rawPk = privateKey.getRawBytes().expect("Private key is valid")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user