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:
Hanno Cornelius 2021-08-26 14:24:00 +02:00 committed by GitHub
parent 0db4107ae2
commit 9a45aa7055
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 85 additions and 60 deletions

View File

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

View File

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

View File

@ -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.} =
@ -121,10 +122,16 @@ 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 #

View File

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