mirror of
https://github.com/waku-org/nwaku.git
synced 2025-02-28 15:00:57 +00:00
Duplicate filtering for bridge (#556)
This commit is contained in:
parent
9b38e5c893
commit
2691b3e506
@ -22,10 +22,13 @@ import
|
|||||||
../test_helpers
|
../test_helpers
|
||||||
|
|
||||||
procSuite "WakuBridge":
|
procSuite "WakuBridge":
|
||||||
let rng = keys.newRng()
|
###############
|
||||||
|
# Suite setup #
|
||||||
|
###############
|
||||||
|
|
||||||
asyncTest "Messages are bridged between Waku v1 and Waku v2":
|
|
||||||
let
|
let
|
||||||
|
rng = keys.newRng()
|
||||||
|
|
||||||
# Bridge
|
# Bridge
|
||||||
nodev1Key = keys.KeyPair.random(rng[])
|
nodev1Key = keys.KeyPair.random(rng[])
|
||||||
nodev2Key = crypto.PrivateKey.random(Secp256k1, rng[])[]
|
nodev2Key = crypto.PrivateKey.random(Secp256k1, rng[])[]
|
||||||
@ -50,30 +53,50 @@ procSuite "WakuBridge":
|
|||||||
payloadV2 = "hello from V2".toBytes()
|
payloadV2 = "hello from V2".toBytes()
|
||||||
message = WakuMessage(payload: payloadV2, contentTopic: contentTopic)
|
message = WakuMessage(payload: payloadV2, contentTopic: contentTopic)
|
||||||
|
|
||||||
await bridge.start()
|
########################
|
||||||
|
# Tests setup/teardown #
|
||||||
|
########################
|
||||||
|
|
||||||
await v2Node.start()
|
setup:
|
||||||
v2Node.mountRelay(@[defaultBridgeTopic])
|
# Runs before each test
|
||||||
|
waitFor bridge.start()
|
||||||
|
|
||||||
discard await v1Node.rlpxConnect(newNode(bridge.nodev1.toENode()))
|
waitFor v2Node.start()
|
||||||
await v2Node.connectToNodes(@[bridge.nodev2.peerInfo])
|
v2Node.mountRelay(@[DefaultBridgeTopic])
|
||||||
|
|
||||||
|
discard waitFor v1Node.rlpxConnect(newNode(bridge.nodev1.toENode()))
|
||||||
|
waitFor v2Node.connectToNodes(@[bridge.nodev2.peerInfo])
|
||||||
|
|
||||||
|
teardown:
|
||||||
|
# Runs after each test
|
||||||
|
bridge.nodeV1.resetMessageQueue()
|
||||||
|
v1Node.resetMessageQueue()
|
||||||
|
waitFor allFutures([bridge.stop(), v2Node.stop()])
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Suite tests #
|
||||||
|
###############
|
||||||
|
|
||||||
|
asyncTest "Messages are bridged between Waku v1 and Waku v2":
|
||||||
var completionFut = newFuture[bool]()
|
var completionFut = newFuture[bool]()
|
||||||
|
|
||||||
proc relayHandler(topic: string, data: seq[byte]) {.async, gcsafe.} =
|
proc relayHandler(topic: string, data: seq[byte]) {.async, gcsafe.} =
|
||||||
let msg = WakuMessage.init(data)
|
let msg = WakuMessage.init(data)
|
||||||
|
|
||||||
if msg.isOk() and msg.value().version == 1:
|
if msg.isOk() and msg.value().version == 1:
|
||||||
check:
|
check:
|
||||||
# Message fields are as expected
|
# Message fields are as expected
|
||||||
msg.value().contentTopic == contentTopic # Topic translation worked
|
msg.value().contentTopic == contentTopic # Topic translation worked
|
||||||
string.fromBytes(msg.value().payload).contains("from V1")
|
string.fromBytes(msg.value().payload).contains("from V1")
|
||||||
|
|
||||||
completionFut.complete(true)
|
completionFut.complete(true)
|
||||||
|
|
||||||
v2Node.subscribe(defaultBridgeTopic, relayHandler)
|
v2Node.subscribe(DefaultBridgeTopic, relayHandler)
|
||||||
|
|
||||||
await sleepAsync(2000.millis)
|
await sleepAsync(2000.millis)
|
||||||
|
|
||||||
# Test bridging from V2 to V1
|
# Test bridging from V2 to V1
|
||||||
await v2Node.publish(defaultBridgeTopic, message)
|
await v2Node.publish(DefaultBridgeTopic, message)
|
||||||
|
|
||||||
await sleepAsync(2000.millis)
|
await sleepAsync(2000.millis)
|
||||||
|
|
||||||
@ -97,5 +120,13 @@ procSuite "WakuBridge":
|
|||||||
# v2Node received payload published by v1Node
|
# v2Node received payload published by v1Node
|
||||||
await completionFut.withTimeout(5.seconds)
|
await completionFut.withTimeout(5.seconds)
|
||||||
|
|
||||||
await bridge.stop()
|
# Test filtering of WakuMessage duplicates
|
||||||
|
v1Node.resetMessageQueue()
|
||||||
|
|
||||||
|
await v2Node.publish(DefaultBridgeTopic, message)
|
||||||
|
|
||||||
|
await sleepAsync(2000.millis)
|
||||||
|
|
||||||
|
check:
|
||||||
|
# v1Node did not receive duplicate of previous message
|
||||||
|
v1Node.protocolState(Waku).queue.items.len == 0
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import
|
import
|
||||||
std/tables,
|
std/[tables, hashes, sequtils],
|
||||||
chronos, confutils, chronicles, chronicles/topics_registry, metrics,
|
chronos, confutils, chronicles, chronicles/topics_registry, metrics,
|
||||||
stew/[byteutils, objects],
|
stew/[byteutils, objects],
|
||||||
stew/shims/net as stewNet, json_rpc/rpcserver,
|
stew/shims/net as stewNet, json_rpc/rpcserver,
|
||||||
@ -15,6 +15,7 @@ import
|
|||||||
./config_bridge
|
./config_bridge
|
||||||
|
|
||||||
declarePublicCounter waku_bridge_transfers, "Number of messages transferred between Waku v1 and v2 networks", ["type"]
|
declarePublicCounter waku_bridge_transfers, "Number of messages transferred between Waku v1 and v2 networks", ["type"]
|
||||||
|
declarePublicCounter waku_bridge_dropped, "Number of messages dropped", ["type"]
|
||||||
|
|
||||||
logScope:
|
logScope:
|
||||||
topics = "wakubridge"
|
topics = "wakubridge"
|
||||||
@ -24,9 +25,10 @@ logScope:
|
|||||||
##################
|
##################
|
||||||
|
|
||||||
const
|
const
|
||||||
defaultBridgeTopic* = "/waku/2/default-bridge/proto"
|
DefaultBridgeTopic* = "/waku/2/default-bridge/proto"
|
||||||
clientIdV1 = "nim-waku v1 node"
|
ClientIdV1 = "nim-waku v1 node"
|
||||||
defaultTTL = 5'u32
|
DefaultTTL = 5'u32
|
||||||
|
DeduplQSize = 20 # Maximum number of seen messages to keep in deduplication queue
|
||||||
|
|
||||||
#########
|
#########
|
||||||
# Types #
|
# Types #
|
||||||
@ -36,11 +38,24 @@ type
|
|||||||
WakuBridge* = ref object of RootObj
|
WakuBridge* = ref object of RootObj
|
||||||
nodev1*: EthereumNode
|
nodev1*: EthereumNode
|
||||||
nodev2*: WakuNode
|
nodev2*: WakuNode
|
||||||
|
seen: seq[hashes.Hash] # FIFO queue of seen WakuMessages. Used for deduplication.
|
||||||
|
|
||||||
###################
|
###################
|
||||||
# Helper funtions #
|
# Helper funtions #
|
||||||
###################
|
###################
|
||||||
|
|
||||||
|
proc containsOrAdd(sequence: var seq[hashes.Hash], hash: hashes.Hash): bool =
|
||||||
|
if sequence.contains(hash):
|
||||||
|
return true
|
||||||
|
|
||||||
|
if sequence.len >= DeduplQSize:
|
||||||
|
trace "Deduplication queue full. Removing oldest item."
|
||||||
|
sequence.delete 0, 0 # Remove first item in queue
|
||||||
|
|
||||||
|
sequence.add(hash)
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
func toWakuMessage(env: Envelope): WakuMessage =
|
func toWakuMessage(env: Envelope): WakuMessage =
|
||||||
# Translate a Waku v1 envelope to a Waku v2 message
|
# Translate a Waku v1 envelope to a Waku v2 message
|
||||||
WakuMessage(payload: env.data,
|
WakuMessage(payload: env.data,
|
||||||
@ -48,17 +63,35 @@ func toWakuMessage(env: Envelope): WakuMessage =
|
|||||||
version: 1)
|
version: 1)
|
||||||
|
|
||||||
proc toWakuV2(bridge: WakuBridge, env: Envelope) {.async.} =
|
proc toWakuV2(bridge: WakuBridge, env: Envelope) {.async.} =
|
||||||
|
let msg = env.toWakuMessage()
|
||||||
|
|
||||||
|
debug "message converted to V2", msg=msg
|
||||||
|
|
||||||
|
if bridge.seen.containsOrAdd(msg.encode().buffer.hash()):
|
||||||
|
# This is a duplicate message. Return
|
||||||
|
trace "Already seen. Dropping.", msg=msg
|
||||||
|
waku_bridge_dropped.inc(labelValues = ["duplicate"])
|
||||||
|
return
|
||||||
|
|
||||||
waku_bridge_transfers.inc(labelValues = ["v1_to_v2"])
|
waku_bridge_transfers.inc(labelValues = ["v1_to_v2"])
|
||||||
|
|
||||||
await bridge.nodev2.publish(defaultBridgeTopic, env.toWakuMessage())
|
await bridge.nodev2.publish(DefaultBridgeTopic, msg)
|
||||||
|
|
||||||
proc toWakuV1(bridge: WakuBridge, msg: WakuMessage) {.gcsafe.} =
|
proc toWakuV1(bridge: WakuBridge, msg: WakuMessage) {.gcsafe.} =
|
||||||
|
debug "sending message to V1", msg=msg
|
||||||
|
|
||||||
|
if bridge.seen.containsOrAdd(msg.encode().buffer.hash()):
|
||||||
|
# This is a duplicate message. Return
|
||||||
|
trace "Already seen. Dropping.", msg=msg
|
||||||
|
waku_bridge_dropped.inc(labelValues = ["duplicate"])
|
||||||
|
return
|
||||||
|
|
||||||
waku_bridge_transfers.inc(labelValues = ["v2_to_v1"])
|
waku_bridge_transfers.inc(labelValues = ["v2_to_v1"])
|
||||||
|
|
||||||
# @TODO: use namespacing to map v2 contentTopics to v1 topics
|
# @TODO: use namespacing to map v2 contentTopics to v1 topics
|
||||||
let v1TopicSeq = msg.contentTopic.toBytes()[0..3]
|
let v1TopicSeq = msg.contentTopic.toBytes()[0..3]
|
||||||
|
|
||||||
discard bridge.nodev1.postMessage(ttl = defaultTTL,
|
discard bridge.nodev1.postMessage(ttl = DefaultTTL,
|
||||||
topic = toArray(4, v1TopicSeq),
|
topic = toArray(4, v1TopicSeq),
|
||||||
payload = msg.payload)
|
payload = msg.payload)
|
||||||
|
|
||||||
@ -79,7 +112,7 @@ proc new*(T: type WakuBridge,
|
|||||||
# Setup Waku v1 node
|
# Setup Waku v1 node
|
||||||
var
|
var
|
||||||
nodev1 = newEthereumNode(keys = nodev1Key, address = nodev1Address,
|
nodev1 = newEthereumNode(keys = nodev1Key, address = nodev1Address,
|
||||||
networkId = NetworkId(1), chain = nil, clientId = clientIdV1,
|
networkId = NetworkId(1), chain = nil, clientId = ClientIdV1,
|
||||||
addAllCapabilities = false, rng = rng)
|
addAllCapabilities = false, rng = rng)
|
||||||
|
|
||||||
nodev1.addCapability Waku # Always enable Waku protocol
|
nodev1.addCapability Waku # Always enable Waku protocol
|
||||||
@ -138,7 +171,7 @@ proc start*(bridge: WakuBridge) {.async.} =
|
|||||||
trace "Bridging message from V2 to V1", msg=msg[]
|
trace "Bridging message from V2 to V1", msg=msg[]
|
||||||
bridge.toWakuV1(msg[])
|
bridge.toWakuV1(msg[])
|
||||||
|
|
||||||
bridge.nodev2.subscribe(defaultBridgeTopic, relayHandler)
|
bridge.nodev2.subscribe(DefaultBridgeTopic, relayHandler)
|
||||||
|
|
||||||
proc stop*(bridge: WakuBridge) {.async.} =
|
proc stop*(bridge: WakuBridge) {.async.} =
|
||||||
await bridge.nodev2.stop()
|
await bridge.nodev2.stop()
|
||||||
@ -182,7 +215,7 @@ when isMainModule:
|
|||||||
|
|
||||||
# Load address configuration
|
# Load address configuration
|
||||||
let
|
let
|
||||||
(nodev1ExtIp, _, _) = setupNat(conf.nat, clientIdV1,
|
(nodev1ExtIp, _, _) = setupNat(conf.nat, ClientIdV1,
|
||||||
Port(conf.devp2pTcpPort + conf.portsShift),
|
Port(conf.devp2pTcpPort + conf.portsShift),
|
||||||
Port(conf.udpPort + conf.portsShift))
|
Port(conf.udpPort + conf.portsShift))
|
||||||
# TODO: EthereumNode should have a better split of binding address and
|
# TODO: EthereumNode should have a better split of binding address and
|
||||||
|
Loading…
x
Reference in New Issue
Block a user