Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(api): validate rln message before sending (rest + rpc) #1968

Merged
merged 5 commits into from
Sep 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions tests/waku_rln_relay/test_waku_rln_relay.nim
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ suite "Waku rln relay":
# it is a duplicate
result3.value == true

asyncTest "validateMessage test":
asyncTest "validateMessageAndUpdateLog test":
let index = MembershipIndex(5)

let rlnConf = WakuRlnConfig(rlnRelayDynamic: false,
Expand Down Expand Up @@ -695,13 +695,13 @@ suite "Waku rln relay":
# validate messages
# validateMessage proc checks the validity of the message fields and adds it to the log (if valid)
let
msgValidate1 = wakuRlnRelay.validateMessage(wm1, some(time))
msgValidate1 = wakuRlnRelay.validateMessageAndUpdateLog(wm1, some(time))
# wm2 is published within the same Epoch as wm1 and should be found as spam
msgValidate2 = wakuRlnRelay.validateMessage(wm2, some(time))
msgValidate2 = wakuRlnRelay.validateMessageAndUpdateLog(wm2, some(time))
# a valid message should be validated successfully
msgValidate3 = wakuRlnRelay.validateMessage(wm3, some(time))
msgValidate3 = wakuRlnRelay.validateMessageAndUpdateLog(wm3, some(time))
# wm4 has no rln proof and should not be validated
msgValidate4 = wakuRlnRelay.validateMessage(wm4, some(time))
msgValidate4 = wakuRlnRelay.validateMessageAndUpdateLog(wm4, some(time))


check:
Expand Down Expand Up @@ -750,13 +750,13 @@ suite "Waku rln relay":
# validateMessage proc checks the validity of the message fields and adds it to the log (if valid)
let
# this should be no verification, Valid
msgValidate1 = wakuRlnRelay.validateMessage(wm1, some(time))
msgValidate1 = wakuRlnRelay.validateMessageAndUpdateLog(wm1, some(time))
# this should be verification, Valid
msgValidate2 = wakuRlnRelay.validateMessage(wm2, some(time))
msgValidate2 = wakuRlnRelay.validateMessageAndUpdateLog(wm2, some(time))
# this should be verification, Invalid
msgValidate3 = wakuRlnRelay.validateMessage(wm3, some(time))
msgValidate3 = wakuRlnRelay.validateMessageAndUpdateLog(wm3, some(time))
# this should be verification, Spam
msgValidate4 = wakuRlnRelay.validateMessage(wm4, some(time))
msgValidate4 = wakuRlnRelay.validateMessageAndUpdateLog(wm4, some(time))

check:
msgValidate1 == MessageValidationResult.Valid
Expand Down Expand Up @@ -848,7 +848,7 @@ suite "Waku rln relay":

# getMembershipCredentials returns the credential in the keystore which matches
# the query, in this case the query is =
# chainId = "5" and
# chainId = "5" and
# address = "0x0123456789012345678901234567890123456789" and
# treeIndex = 1
let readKeystoreMembership = readKeystoreRes.get()
Expand Down
24 changes: 21 additions & 3 deletions tests/wakunode_jsonrpc/test_jsonrpc_relay.nim
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{.used.}

import
std/[options, sequtils],
std/[options, sequtils, tempfiles],
stew/shims/net as stewNet,
testutils/unittests,
chronicles,
Expand All @@ -21,6 +21,10 @@ import
../testlib/wakucore,
../testlib/wakunode

when defined(rln):
import
../../../waku/waku_rln_relay


proc newTestMessageCache(): relay_api.MessageCache =
relay_api.MessageCache.init(capacity=30)
Expand Down Expand Up @@ -100,6 +104,15 @@ suite "Waku v2 JSON-RPC API - Relay":
await srcNode.mountRelay(@[pubSubTopic])
await dstNode.mountRelay(@[pubSubTopic])

when defined(rln):
await srcNode.mountRlnRelay(WakuRlnConfig(rlnRelayDynamic: false,
rlnRelayCredIndex: 1,
rlnRelayTreePath: genTempPath("rln_tree", "wakunode_1")))

await dstNode.mountRlnRelay(WakuRlnConfig(rlnRelayDynamic: false,
rlnRelayCredIndex: 2,
rlnRelayTreePath: genTempPath("rln_tree", "wakunode_2")))

await srcNode.connectToNodes(@[dstNode.peerInfo.toRemotePeerInfo()])


Expand Down Expand Up @@ -139,7 +152,12 @@ suite "Waku v2 JSON-RPC API - Relay":
response == true
await dstHandlerFut.withTimeout(chronos.seconds(5))

let (topic, msg) = dstHandlerFut.read()
var (topic, msg) = dstHandlerFut.read()

# proof is injected under the hood, we compare just the message
when defined(rln):
msg.proof = @[]

check:
topic == pubSubTopic
msg == message
Expand Down Expand Up @@ -171,7 +189,7 @@ suite "Waku v2 JSON-RPC API - Relay":

# RPC server (destination node)
let
rpcPort = Port(8548)
rpcPort = Port(8549)
ta = initTAddress(ValidIpAddress.init("0.0.0.0"), rpcPort)
server = newRpcHttpServer([ta])

Expand Down
9 changes: 8 additions & 1 deletion tests/wakunode_rest/test_rest_relay.nim
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{.used.}

import
std/sequtils,
std/[sequtils,tempfiles],
stew/byteutils,
stew/shims/net,
testutils/unittests,
Expand All @@ -22,6 +22,9 @@ import
../testlib/wakucore,
../testlib/wakunode

when defined(rln):
import
../../../waku/waku_rln_relay

proc testWakuNode(): WakuNode =
let
Expand Down Expand Up @@ -183,6 +186,10 @@ suite "Waku v2 Rest API - Relay":
let node = testWakuNode()
await node.start()
await node.mountRelay()
when defined(rln):
await node.mountRlnRelay(WakuRlnConfig(rlnRelayDynamic: false,
rlnRelayCredIndex: 1,
rlnRelayTreePath: genTempPath("rln_tree", "wakunode_1")))

# RPC server setup
let restPort = Port(58014)
Expand Down
41 changes: 31 additions & 10 deletions waku/node/jsonrpc/relay/handlers.nim
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ from std/times import toUnix

when defined(rln):
import
../../../waku_rln_relay
../../../waku_rln_relay,
../../../waku_rln_relay/rln/wrappers

logScope:
topics = "waku node jsonrpc relay_api"
Expand Down Expand Up @@ -77,9 +78,9 @@ proc installRelayApiHandlers*(node: WakuNode, server: RpcServer, cache: MessageC

return true

server.rpc("post_waku_v2_relay_v1_message") do (topic: PubsubTopic, msg: WakuMessageRPC) -> bool:
server.rpc("post_waku_v2_relay_v1_message") do (pubsubTopic: PubsubTopic, msg: WakuMessageRPC) -> bool:
## Publishes a WakuMessage to a PubSub topic
debug "post_waku_v2_relay_v1_message"
debug "post_waku_v2_relay_v1_message", pubsubTopic=pubsubTopic

let payloadRes = base64.decode(msg.payload)
if payloadRes.isErr():
Expand All @@ -93,18 +94,38 @@ proc installRelayApiHandlers*(node: WakuNode, server: RpcServer, cache: MessageC
timestamp: msg.timestamp.get(Timestamp(0)),
ephemeral: msg.ephemeral.get(false)
)


# ensure the node is subscribed to the pubsubTopic. otherwise it risks publishing
# to a topic with no connected peers
if pubsubTopic notin node.wakuRelay.subscribedTopics():
raise newException(ValueError, "Failed to publish: Node not subscribed to pubsubTopic: " & pubsubTopic)

# if RLN is mounted, append the proof to the message
when defined(rln):
if not node.wakuRlnRelay.isNil():
let success = node.wakuRlnRelay.appendRLNProof(message,
# append the proof to the message
let success = node.wakuRlnRelay.appendRLNProof(message,
float64(getTime().toUnix()))
if not success:
raise newException(ValueError, "Failed to append RLN proof to message")

let publishFut = node.publish(topic, message)

raise newException(ValueError, "Failed to publish: error appending RLN proof to message")
# validate the message before sending it
let result = node.wakuRlnRelay.validateMessage(message)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unsure if its ok to use validateMessage here, since inside it updates a bunch of metrics and call updateLog. Any opinion @rymnc

Copy link
Contributor

@rymnc rymnc Aug 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I don't think we should use validateMessage, because if you try publishing the same message it would get stuck since the proofMetadata would exist in the nullifierLog afaik. should probably split the function into the check, and then the state update

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like this 74d38f5 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes!

if result == MessageValidationResult.Invalid:
raise newException(ValueError, "Failed to publish: invalid RLN proof")
elif result == MessageValidationResult.Spam:
raise newException(ValueError, "Failed to publish: limit exceeded, try again later")
elif result == MessageValidationResult.Valid:
debug "RLN proof validated successfully", pubSubTopic=pubSubTopic
else:
raise newException(ValueError, "Failed to publish: unknown RLN proof validation result")
else:
raise newException(ValueError, "Failed to publish: RLN enabled but not mounted")

# if we reach here its either a non-RLN message or a RLN message with a valid proof
debug "Publishing message", pubSubTopic=pubSubTopic, rln=defined(rln)
let publishFut = node.publish(pubsubTopic, message)
if not await publishFut.withTimeout(futTimeout):
raise newException(ValueError, "Failed to publish to topic " & topic)
raise newException(ValueError, "Failed to publish: timed out")

return true

Expand Down
44 changes: 42 additions & 2 deletions waku/node/rest/relay/handlers.nim
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,20 @@ import
presto/common
import
../../waku_node,
../../../waku_relay/protocol,
../serdes,
../responses,
./types,
./topic_cache

from std/times import getTime
from std/times import toUnix

when defined(rln):
import
../../../waku_rln_relay,
../../../waku_rln_relay/rln/wrappers

export types


Expand Down Expand Up @@ -127,6 +136,11 @@ proc installRelayPostMessagesV1Handler*(router: var RestRouter, node: WakuNode)
return RestApiResponse.badRequest()
let pubSubTopic = topic.get()

# ensure the node is subscribed to the topic. otherwise it risks publishing
# to a topic with no connected peers
if pubSubTopic notin node.wakuRelay.subscribedTopics():
return RestApiResponse.badRequest("Failed to publish: Node not subscribed to topic: " & pubsubTopic)

# Check the request body
if contentBody.isNone():
return RestApiResponse.badRequest()
Expand All @@ -144,9 +158,35 @@ proc installRelayPostMessagesV1Handler*(router: var RestRouter, node: WakuNode)
if resMessage.isErr():
return RestApiResponse.badRequest()

var message = resMessage.get()

# if RLN is mounted, append the proof to the message
when defined(rln):
if not node.wakuRlnRelay.isNil():
# append the proof to the message
let success = node.wakuRlnRelay.appendRLNProof(message,
float64(getTime().toUnix()))
if not success:
return RestApiResponse.internalServerError("Failed to publish: error appending RLN proof to message")

# validate the message before sending it
let result = node.wakuRlnRelay.validateMessage(message)
if result == MessageValidationResult.Invalid:
return RestApiResponse.internalServerError("Failed to publish: invalid RLN proof")
elif result == MessageValidationResult.Spam:
return RestApiResponse.badRequest("Failed to publish: limit exceeded, try again later")
elif result == MessageValidationResult.Valid:
debug "RLN proof validated successfully", pubSubTopic=pubSubTopic
else:
return RestApiResponse.internalServerError("Failed to publish: unknown RLN proof validation result")
else:
return RestApiResponse.internalServerError("Failed to publish: RLN enabled but not mounted")

# if we reach here its either a non-RLN message or a RLN message with a valid proof
debug "Publishing message", pubSubTopic=pubSubTopic, rln=defined(rln)
if not (waitFor node.publish(pubSubTopic, resMessage.value).withTimeout(futTimeout)):
error "Failed to publish message to topic", topic=pubSubTopic
return RestApiResponse.internalServerError()
error "Failed to publish message to topic", pubSubTopic=pubSubTopic
return RestApiResponse.internalServerError("Failed to publish: timedout")

return RestApiResponse.ok()

Expand Down
7 changes: 3 additions & 4 deletions waku/waku_relay/protocol.nim
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,8 @@ proc validator(pubsubTopic: string, message: messages.Message): Future[Validatio
proc isSubscribed*(w: WakuRelay, topic: PubsubTopic): bool =
GossipSub(w).topics.hasKey(topic)

iterator subscribedTopics*(w: WakuRelay): lent PubsubTopic =
for topic in GossipSub(w).topics.keys():
yield topic
proc subscribedTopics*(w: WakuRelay): seq[PubsubTopic] =
return toSeq(GossipSub(w).topics.keys())
Comment on lines +194 to +195
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Morning! Is this directly related to the purpose of the PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, but imho there is no need to use generators for this. the amount of topics is not enough to justify it. also using poorly documented features such as lent lent is a bad idea, unless strictly needed.


proc subscribe*(w: WakuRelay, pubsubTopic: PubsubTopic, handler: WakuRelayHandler) =
debug "subscribe", pubsubTopic=pubsubTopic
Expand All @@ -202,7 +201,7 @@ proc subscribe*(w: WakuRelay, pubsubTopic: PubsubTopic, handler: WakuRelayHandle
let wrappedHandler = proc(pubsubTopic: string, data: seq[byte]): Future[void] {.gcsafe, raises: [].} =
let decMsg = WakuMessage.decode(data)
if decMsg.isErr():
# fine if triggerSelf enabled, since validators are bypassed
# fine if triggerSelf enabled, since validators are bypassed
error "failed to decode WakuMessage, validator passed a wrong message", error = decMsg.error
let fut = newFuture[void]()
fut.complete()
Expand Down
Loading