mirror of
https://github.com/waku-org/nwaku.git
synced 2025-02-25 05:15:53 +00:00
Fix: bridge encoding and missing timestamps (#697)
* Fix: bridge should not re-encode pre-encoded payload * Test payload encoding, fix content topic * Minor improvements * Fix broken unit test
This commit is contained in:
parent
0db4107ae2
commit
9a45aa7055
@ -11,6 +11,7 @@ This release contains the following:
|
||||
### Changes
|
||||
|
||||
- GossipSub [prune backoff period](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#prune-backoff-and-peer-exchange) is now the recommended 1 minute
|
||||
- Bridge now uses content topic format according to [23/WAKU2-TOPICS](https://rfc.vac.dev/spec/23/)
|
||||
|
||||
#### General refactoring
|
||||
|
||||
@ -22,9 +23,11 @@ This release contains the following:
|
||||
|
||||
#### API
|
||||
|
||||
|
||||
### Fixes
|
||||
|
||||
- Bridge no longer re-encodes already encoded payloads when publishing to V1
|
||||
- Bridge now populates WakuMessage timestamps when publishing to V2
|
||||
|
||||
## 2021-07-26 v0.5.1
|
||||
|
||||
This patch release contains the following fix:
|
||||
|
@ -50,11 +50,12 @@ procSuite "WakuBridge":
|
||||
v2NodeKey = crypto.PrivateKey.random(Secp256k1, rng[])[]
|
||||
v2Node = WakuNode.new(v2NodeKey, ValidIpAddress.init("0.0.0.0"), Port(60002))
|
||||
|
||||
contentTopic = ContentTopic("/waku/1/0x1a2b3c4d/rlp")
|
||||
contentTopic = ContentTopic("/waku/1/0x1a2b3c4d/rfc26")
|
||||
topic = [byte 0x1a, byte 0x2b, byte 0x3c, byte 0x4d]
|
||||
payloadV1 = "hello from V1".toBytes()
|
||||
payloadV2 = "hello from V2".toBytes()
|
||||
message = WakuMessage(payload: payloadV2, contentTopic: contentTopic)
|
||||
encodedPayloadV2 = Payload(payload: payloadV2, dst: some(nodev1Key.pubKey))
|
||||
message = WakuMessage(payload: encodedPayloadV2.encode(1, rng[]).get(), contentTopic: contentTopic, version: 1)
|
||||
|
||||
########################
|
||||
# Tests setup/teardown #
|
||||
@ -74,14 +75,14 @@ procSuite "WakuBridge":
|
||||
# Expected cases
|
||||
|
||||
check:
|
||||
toV1Topic(ContentTopic("/waku/1/0x00000000/rlp")) == [byte 0x00, byte 0x00, byte 0x00, byte 0x00]
|
||||
toV2ContentTopic([byte 0x00, byte 0x00, byte 0x00, byte 0x00]) == ContentTopic("/waku/1/0x00000000/rlp")
|
||||
toV1Topic(ContentTopic("/waku/1/0xffffffff/rlp")) == [byte 0xff, byte 0xff, byte 0xff, byte 0xff]
|
||||
toV2ContentTopic([byte 0xff, byte 0xff, byte 0xff, byte 0xff]) == ContentTopic("/waku/1/0xffffffff/rlp")
|
||||
toV1Topic(ContentTopic("/waku/1/0x1a2b3c4d/rlp")) == [byte 0x1a, byte 0x2b, byte 0x3c, byte 0x4d]
|
||||
toV2ContentTopic([byte 0x1a, byte 0x2b, byte 0x3c, byte 0x4d]) == ContentTopic("/waku/1/0x1a2b3c4d/rlp")
|
||||
toV1Topic(ContentTopic("/waku/1/0x00000000/rfc26")) == [byte 0x00, byte 0x00, byte 0x00, byte 0x00]
|
||||
toV2ContentTopic([byte 0x00, byte 0x00, byte 0x00, byte 0x00]) == ContentTopic("/waku/1/0x00000000/rfc26")
|
||||
toV1Topic(ContentTopic("/waku/1/0xffffffff/rfc26")) == [byte 0xff, byte 0xff, byte 0xff, byte 0xff]
|
||||
toV2ContentTopic([byte 0xff, byte 0xff, byte 0xff, byte 0xff]) == ContentTopic("/waku/1/0xffffffff/rfc26")
|
||||
toV1Topic(ContentTopic("/waku/1/0x1a2b3c4d/rfc26")) == [byte 0x1a, byte 0x2b, byte 0x3c, byte 0x4d]
|
||||
toV2ContentTopic([byte 0x1a, byte 0x2b, byte 0x3c, byte 0x4d]) == ContentTopic("/waku/1/0x1a2b3c4d/rfc26")
|
||||
# Topic conversion should still work where '0x' prefix is omitted from <v1 topic byte array>
|
||||
toV1Topic(ContentTopic("/waku/1/1a2b3c4d/rlp")) == [byte 0x1a, byte 0x2b, byte 0x3c, byte 0x4d]
|
||||
toV1Topic(ContentTopic("/waku/1/1a2b3c4d/rfc26")) == [byte 0x1a, byte 0x2b, byte 0x3c, byte 0x4d]
|
||||
|
||||
# Invalid cases
|
||||
|
||||
@ -91,11 +92,11 @@ procSuite "WakuBridge":
|
||||
|
||||
expect ValueError:
|
||||
# Content topic name too short
|
||||
discard toV1Topic(ContentTopic("/waku/1/0x112233/rlp"))
|
||||
discard toV1Topic(ContentTopic("/waku/1/0x112233/rfc26"))
|
||||
|
||||
expect ValueError:
|
||||
# Content topic name not hex
|
||||
discard toV1Topic(ContentTopic("/waku/1/my-content/rlp"))
|
||||
discard toV1Topic(ContentTopic("/waku/1/my-content/rfc26"))
|
||||
|
||||
asyncTest "Messages are bridged between Waku v1 and Waku v2":
|
||||
# Setup test
|
||||
@ -103,7 +104,7 @@ procSuite "WakuBridge":
|
||||
waitFor bridge.start()
|
||||
|
||||
waitFor v2Node.start()
|
||||
v2Node.mountRelay(@[DefaultBridgeTopic])
|
||||
v2Node.mountRelay(@[DefaultBridgeTopic], triggerSelf = false)
|
||||
|
||||
discard waitFor v1Node.rlpxConnect(newNode(bridge.nodev1.toENode()))
|
||||
waitFor v2Node.connectToNodes(@[bridge.nodev2.peerInfo])
|
||||
@ -134,12 +135,14 @@ procSuite "WakuBridge":
|
||||
# v1Node received message published by v2Node
|
||||
v1Node.protocolState(Waku).queue.items.len == 1
|
||||
|
||||
let msg = v1Node.protocolState(Waku).queue.items[0]
|
||||
let
|
||||
msg = v1Node.protocolState(Waku).queue.items[0]
|
||||
decodedPayload = msg.env.data.decode(some(nodev1Key.seckey), none[SymKey]()).get()
|
||||
|
||||
check:
|
||||
# Message fields are as expected
|
||||
msg.env.topic == topic # Topic translation worked
|
||||
string.fromBytes(msg.env.data).contains("from V2")
|
||||
string.fromBytes(decodedPayload.payload).contains("from V2")
|
||||
|
||||
# Test bridging from V1 to V2
|
||||
check:
|
||||
|
@ -65,7 +65,7 @@ proc containsOrAdd(sequence: var seq[hashes.Hash], hash: hashes.Hash): bool =
|
||||
|
||||
proc toV2ContentTopic*(v1Topic: waku_protocol.Topic): ContentTopic =
|
||||
## Convert a 4-byte array v1 topic to a namespaced content topic
|
||||
## with format `/waku/1/<v1-topic-bytes-as-hex>/proto`
|
||||
## with format `/waku/1/<v1-topic-bytes-as-hex>/rfc26`
|
||||
##
|
||||
## <v1-topic-bytes-as-hex> should be prefixed with `0x`
|
||||
|
||||
@ -74,13 +74,13 @@ proc toV2ContentTopic*(v1Topic: waku_protocol.Topic): ContentTopic =
|
||||
namespacedTopic.application = "waku"
|
||||
namespacedTopic.version = "1"
|
||||
namespacedTopic.topicName = "0x" & v1Topic.toHex()
|
||||
namespacedTopic.encoding = "rlp"
|
||||
namespacedTopic.encoding = "rfc26"
|
||||
|
||||
return ContentTopic($namespacedTopic)
|
||||
|
||||
proc toV1Topic*(contentTopic: ContentTopic): waku_protocol.Topic {.raises: [Defect, LPError, ValueError]} =
|
||||
## Extracts the 4-byte array v1 topic from a content topic
|
||||
## with format `/waku/1/<v1-topic-bytes-as-hex>/proto`
|
||||
## with format `/waku/1/<v1-topic-bytes-as-hex>/rfc26`
|
||||
|
||||
hexToByteArray(hexStr = NamespacedTopic.fromString(contentTopic).tryGet().topicName,
|
||||
N = 4) # Byte array length
|
||||
@ -91,6 +91,7 @@ func toWakuMessage(env: Envelope): WakuMessage =
|
||||
# Translate a Waku v1 envelope to a Waku v2 message
|
||||
WakuMessage(payload: env.data,
|
||||
contentTopic: toV2ContentTopic(env.topic),
|
||||
timestamp: float64(env.expiry - env.ttl),
|
||||
version: 1)
|
||||
|
||||
proc toWakuV2(bridge: WakuBridge, env: Envelope) {.async.} =
|
||||
@ -122,9 +123,15 @@ proc toWakuV1(bridge: WakuBridge, msg: WakuMessage) {.gcsafe, raises: [Defect, L
|
||||
# @TODO: use namespacing to map v2 contentTopics to v1 topics
|
||||
let v1TopicSeq = msg.contentTopic.toBytes()[0..3]
|
||||
|
||||
discard bridge.nodev1.postMessage(ttl = DefaultTTL,
|
||||
topic = toV1Topic(msg.contentTopic),
|
||||
payload = msg.payload)
|
||||
case msg.version:
|
||||
of 1:
|
||||
discard bridge.nodev1.postEncoded(ttl = DefaultTTL,
|
||||
topic = toV1Topic(msg.contentTopic),
|
||||
encodedPayload = msg.payload) # The payload is already encoded according to https://rfc.vac.dev/spec/26/
|
||||
else:
|
||||
discard bridge.nodev1.postMessage(ttl = DefaultTTL,
|
||||
topic = toV1Topic(msg.contentTopic),
|
||||
payload = msg.payload)
|
||||
|
||||
##############
|
||||
# Public API #
|
||||
|
@ -497,6 +497,55 @@ proc queueMessage(node: EthereumNode, msg: Message): bool =
|
||||
|
||||
# Public EthereumNode calls ----------------------------------------------------
|
||||
|
||||
proc postEncoded*(node: EthereumNode, ttl: uint32,
|
||||
topic: Topic, encodedPayload: seq[byte],
|
||||
powTime = 1'f,
|
||||
powTarget = defaultMinPow,
|
||||
targetPeer = none[NodeId]()): bool =
|
||||
## Post a message from pre-encoded payload on the message queue.
|
||||
## This will be processed at the next `messageInterval`.
|
||||
## The encodedPayload must be encoded according to RFC 26/WAKU-PAYLOAD
|
||||
## at https://rfc.vac.dev/spec/26/
|
||||
|
||||
var env = Envelope(expiry:epochTime().uint32 + ttl,
|
||||
ttl: ttl, topic: topic, data: encodedPayload, nonce: 0)
|
||||
|
||||
# Allow lightnode to post only direct p2p messages
|
||||
if targetPeer.isSome():
|
||||
return node.sendP2PMessage(targetPeer.get(), [env])
|
||||
else:
|
||||
# non direct p2p message can not have ttl of 0
|
||||
if env.ttl == 0:
|
||||
return false
|
||||
var msg = initMessage(env, powCalc = false)
|
||||
# XXX: make this non blocking or not?
|
||||
# In its current blocking state, it could be noticed by a peer that no
|
||||
# messages are send for a while, and thus that mining PoW is done, and
|
||||
# that next messages contains a message originated from this peer
|
||||
# zah: It would be hard to execute this in a background thread at the
|
||||
# moment. We'll need a way to send custom "tasks" to the async message
|
||||
# loop (e.g. AD2 support for AsyncChannels).
|
||||
if not msg.sealEnvelope(powTime, powTarget):
|
||||
return false
|
||||
|
||||
# need to check expiry after mining PoW
|
||||
if not msg.env.valid():
|
||||
return false
|
||||
|
||||
result = node.queueMessage(msg)
|
||||
|
||||
# Allows light nodes to post via untrusted messages packet.
|
||||
# Queue gets processed immediatly as the node sends only its own messages,
|
||||
# so the privacy ship has already sailed anyhow.
|
||||
# TODO:
|
||||
# - Could be still a concern in terms of efficiency, if multiple messages
|
||||
# need to be send.
|
||||
# - For Waku Mode, the checks in processQueue are rather useless as the
|
||||
# idea is to connect only to 1 node? Also refactor in that case.
|
||||
if node.protocolState(Waku).config.isLightNode:
|
||||
for peer in node.peers(Waku):
|
||||
peer.processQueue()
|
||||
|
||||
proc postMessage*(node: EthereumNode, pubKey = none[PublicKey](),
|
||||
symKey = none[SymKey](), src = none[PrivateKey](),
|
||||
ttl: uint32, topic: Topic, payload: seq[byte],
|
||||
@ -511,44 +560,7 @@ proc postMessage*(node: EthereumNode, pubKey = none[PublicKey](),
|
||||
let payload = encode(node.rng[], Payload(
|
||||
payload: payload, src: src, dst: pubKey, symKey: symKey, padding: padding))
|
||||
if payload.isSome():
|
||||
var env = Envelope(expiry:epochTime().uint32 + ttl,
|
||||
ttl: ttl, topic: topic, data: payload.get(), nonce: 0)
|
||||
|
||||
# Allow lightnode to post only direct p2p messages
|
||||
if targetPeer.isSome():
|
||||
return node.sendP2PMessage(targetPeer.get(), [env])
|
||||
else:
|
||||
# non direct p2p message can not have ttl of 0
|
||||
if env.ttl == 0:
|
||||
return false
|
||||
var msg = initMessage(env, powCalc = false)
|
||||
# XXX: make this non blocking or not?
|
||||
# In its current blocking state, it could be noticed by a peer that no
|
||||
# messages are send for a while, and thus that mining PoW is done, and
|
||||
# that next messages contains a message originated from this peer
|
||||
# zah: It would be hard to execute this in a background thread at the
|
||||
# moment. We'll need a way to send custom "tasks" to the async message
|
||||
# loop (e.g. AD2 support for AsyncChannels).
|
||||
if not msg.sealEnvelope(powTime, powTarget):
|
||||
return false
|
||||
|
||||
# need to check expiry after mining PoW
|
||||
if not msg.env.valid():
|
||||
return false
|
||||
|
||||
result = node.queueMessage(msg)
|
||||
|
||||
# Allows light nodes to post via untrusted messages packet.
|
||||
# Queue gets processed immediatly as the node sends only its own messages,
|
||||
# so the privacy ship has already sailed anyhow.
|
||||
# TODO:
|
||||
# - Could be still a concern in terms of efficiency, if multiple messages
|
||||
# need to be send.
|
||||
# - For Waku Mode, the checks in processQueue are rather useless as the
|
||||
# idea is to connect only to 1 node? Also refactor in that case.
|
||||
if node.protocolState(Waku).config.isLightNode:
|
||||
for peer in node.peers(Waku):
|
||||
peer.processQueue()
|
||||
return node.postEncoded(ttl, topic, payload.get(), powTime, powTarget, targetPeer)
|
||||
else:
|
||||
error "Encoding of payload failed"
|
||||
return false
|
||||
|
Loading…
x
Reference in New Issue
Block a user