From c3e7c589e318b49d81a7d5ee8dd1b1396a964840 Mon Sep 17 00:00:00 2001 From: rymnc <43716372+rymnc@users.noreply.github.com> Date: Fri, 9 Feb 2024 18:00:46 +0530 Subject: [PATCH 1/2] feat(rln-relay-v2): nonce/messageId manager --- apps/chat2/chat2.nim | 35 ++++++---- apps/chat2/config_chat2.nim | 5 ++ apps/wakunode2/app.nim | 30 +++++--- tests/node/test_wakunode_relay_rln.nim | 10 +-- tests/waku_rln_relay/test_all.nim | 3 +- .../waku_rln_relay/test_rln_nonce_manager.nim | 51 ++++++++++++++ tests/waku_rln_relay/test_waku_rln_relay.nim | 6 +- .../test_wakunode_rln_relay.nim | 18 ++--- waku/waku_api/jsonrpc/relay/handlers.nim | 10 ++- waku/waku_api/rest/relay/handlers.nim | 7 +- .../group_manager/group_manager_base.nim | 10 +-- waku/waku_rln_relay/nonce_manager.nim | 69 +++++++++++++++++++ waku/waku_rln_relay/rln_relay.nim | 33 ++++++--- 13 files changed, 223 insertions(+), 64 deletions(-) create mode 100644 tests/waku_rln_relay/test_rln_nonce_manager.nim create mode 100644 waku/waku_rln_relay/nonce_manager.nim diff --git a/apps/chat2/chat2.nim b/apps/chat2/chat2.nim index ec320ffb83..7fb4551d26 100644 --- a/apps/chat2/chat2.nim +++ b/apps/chat2/chat2.nim @@ -187,11 +187,11 @@ proc publish(c: Chat, line: string) = if not isNil(c.node.wakuRlnRelay): # for future version when we support more than one rln protected content topic, # we should check the message content topic as well - let success = c.node.wakuRlnRelay.appendRLNProof(message, float64(time)) - if not success: - debug "could not append rate limit proof to the message", success=success + let appendRes = c.node.wakuRlnRelay.appendRLNProof(message, float64(time)) + if appendRes.isErr(): + debug "could not append rate limit proof to the message" else: - debug "rate limit proof is appended to the message", success=success + debug "rate limit proof is appended to the message" let decodeRes = RateLimitProof.init(message.proof) if decodeRes.isErr(): error "could not decode the RLN proof" @@ -514,14 +514,25 @@ proc processInput(rfd: AsyncFD, rng: ref HmacDrbgContext) {.async.} = echo "rln-relay preparation is in progress..." - let rlnConf = WakuRlnConfig( - rlnRelayDynamic: conf.rlnRelayDynamic, - rlnRelayCredIndex: conf.rlnRelayCredIndex, - rlnRelayEthContractAddress: conf.rlnRelayEthContractAddress, - rlnRelayEthClientAddress: conf.rlnRelayEthClientAddress, - rlnRelayCredPath: conf.rlnRelayCredPath, - rlnRelayCredPassword: conf.rlnRelayCredPassword - ) + when defined(rln_v2): + let rlnConf = WakuRlnConfig( + rlnRelayDynamic: conf.rlnRelayDynamic, + rlnRelayCredIndex: conf.rlnRelayCredIndex, + rlnRelayEthContractAddress: conf.rlnRelayEthContractAddress, + rlnRelayEthClientAddress: conf.rlnRelayEthClientAddress, + rlnRelayCredPath: conf.rlnRelayCredPath, + rlnRelayCredPassword: conf.rlnRelayCredPassword, + rlnRelayUserMessageLimit: conf.rlnRelayUserMessageLimit, + ) + else: + let rlnConf = WakuRlnConfig( + rlnRelayDynamic: conf.rlnRelayDynamic, + rlnRelayCredIndex: conf.rlnRelayCredIndex, + rlnRelayEthContractAddress: conf.rlnRelayEthContractAddress, + rlnRelayEthClientAddress: conf.rlnRelayEthClientAddress, + rlnRelayCredPath: conf.rlnRelayCredPath, + rlnRelayCredPassword: conf.rlnRelayCredPassword, + ) waitFor node.mountRlnRelay(rlnConf, spamHandler=some(spamHandler)) diff --git a/apps/chat2/config_chat2.nim b/apps/chat2/config_chat2.nim index b513ecb81c..9aa736ebbc 100644 --- a/apps/chat2/config_chat2.nim +++ b/apps/chat2/config_chat2.nim @@ -266,6 +266,11 @@ type defaultValue: "" name: "rln-relay-cred-password" }: string + rlnRelayUserMessageLimit* {. + desc: "Set a user message limit for the rln membership registration. Must be a positive integer. Default is 1.", + defaultValue: 1, + name: "rln-relay-user-message-limit" .}: uint64 + # NOTE: Keys are different in nim-libp2p proc parseCmdArg*(T: type crypto.PrivateKey, p: string): T = try: diff --git a/apps/wakunode2/app.nim b/apps/wakunode2/app.nim index cfbb19ea1b..6738ae4596 100644 --- a/apps/wakunode2/app.nim +++ b/apps/wakunode2/app.nim @@ -462,15 +462,27 @@ proc setupProtocols(node: WakuNode, if conf.rlnRelay: - let rlnConf = WakuRlnConfig( - rlnRelayDynamic: conf.rlnRelayDynamic, - rlnRelayCredIndex: conf.rlnRelayCredIndex, - rlnRelayEthContractAddress: conf.rlnRelayEthContractAddress, - rlnRelayEthClientAddress: conf.rlnRelayEthClientAddress, - rlnRelayCredPath: conf.rlnRelayCredPath, - rlnRelayCredPassword: conf.rlnRelayCredPassword, - rlnRelayTreePath: conf.rlnRelayTreePath, - ) + when defined(rln_v2): + let rlnConf = WakuRlnConfig( + rlnRelayDynamic: conf.rlnRelayDynamic, + rlnRelayCredIndex: conf.rlnRelayCredIndex, + rlnRelayEthContractAddress: conf.rlnRelayEthContractAddress, + rlnRelayEthClientAddress: conf.rlnRelayEthClientAddress, + rlnRelayCredPath: conf.rlnRelayCredPath, + rlnRelayCredPassword: conf.rlnRelayCredPassword, + rlnRelayTreePath: conf.rlnRelayTreePath, + rlnRelayUserMessageLimit: conf.rlnRelayUserMessageLimit, + ) + else: + let rlnConf = WakuRlnConfig( + rlnRelayDynamic: conf.rlnRelayDynamic, + rlnRelayCredIndex: conf.rlnRelayCredIndex, + rlnRelayEthContractAddress: conf.rlnRelayEthContractAddress, + rlnRelayEthClientAddress: conf.rlnRelayEthClientAddress, + rlnRelayCredPath: conf.rlnRelayCredPath, + rlnRelayCredPassword: conf.rlnRelayCredPassword, + rlnRelayTreePath: conf.rlnRelayTreePath, + ) try: waitFor node.mountRlnRelay(rlnConf) diff --git a/tests/node/test_wakunode_relay_rln.nim b/tests/node/test_wakunode_relay_rln.nim index e3184441cb..6b8acdee61 100644 --- a/tests/node/test_wakunode_relay_rln.nim +++ b/tests/node/test_wakunode_relay_rln.nim @@ -58,7 +58,7 @@ proc sendRlnMessage( payload: seq[byte] = "Hello".toBytes(), ): Future[bool] {.async.} = var message = WakuMessage(payload: payload, contentTopic: contentTopic) - doAssert(client.wakuRlnRelay.appendRLNProof(message, epochTime())) + doAssert(client.wakuRlnRelay.appendRLNProof(message, epochTime()).isOk()) discard await client.publish(some(pubsubTopic), message) let isCompleted = await completionFuture.withTimeout(FUTURE_TIMEOUT) return isCompleted @@ -249,18 +249,18 @@ suite "Waku RlnRelay - End to End": WakuMessage(payload: @payload150kibPlus, contentTopic: contentTopic) doAssert( - client.wakuRlnRelay.appendRLNProof(message1b, epoch + EpochUnitSeconds * 0) + client.wakuRlnRelay.appendRLNProof(message1b, epoch + EpochUnitSeconds * 0).isOk() ) doAssert( - client.wakuRlnRelay.appendRLNProof(message1kib, epoch + EpochUnitSeconds * 1) + client.wakuRlnRelay.appendRLNProof(message1kib, epoch + EpochUnitSeconds * 1).isOk() ) doAssert( - client.wakuRlnRelay.appendRLNProof(message150kib, epoch + EpochUnitSeconds * 2) + client.wakuRlnRelay.appendRLNProof(message150kib, epoch + EpochUnitSeconds * 2).isOk() ) doAssert( client.wakuRlnRelay.appendRLNProof( message151kibPlus, epoch + EpochUnitSeconds * 3 - ) + ).isOk() ) # When sending the 1B message diff --git a/tests/waku_rln_relay/test_all.nim b/tests/waku_rln_relay/test_all.nim index 5d0c806d08..ed5b690d85 100644 --- a/tests/waku_rln_relay/test_all.nim +++ b/tests/waku_rln_relay/test_all.nim @@ -4,4 +4,5 @@ import ./test_rln_group_manager_onchain, ./test_rln_group_manager_static, ./test_waku_rln_relay, - ./test_wakunode_rln_relay + ./test_wakunode_rln_relay, + ./test_rln_nonce_manager diff --git a/tests/waku_rln_relay/test_rln_nonce_manager.nim b/tests/waku_rln_relay/test_rln_nonce_manager.nim new file mode 100644 index 0000000000..8e5df8a181 --- /dev/null +++ b/tests/waku_rln_relay/test_rln_nonce_manager.nim @@ -0,0 +1,51 @@ +{.used.} + +import + testutils/unittests, + chronos, + os +import + ../../../waku/waku_rln_relay/nonce_manager + + +suite "Nonce manager": + test "should initialize successfully": + let nm = NonceManager.init(nonceLimit = 100.uint) + + check: + nm.nonceLimit == 100.uint + nm.nextNonce == 0.uint + + test "should generate a new nonce": + let nm = NonceManager.init(nonceLimit = 100.uint) + let nonceRes = nm.get() + + assert nonceRes.isOk(), $nonceRes.error + + check: + nonceRes.get() == 0.uint + nm.nextNonce == 1.uint + + test "should fail to generate a new nonce if limit is reached": + let nm = NonceManager.init(nonceLimit = 1.uint) + let nonceRes = nm.get() + let nonceRes2 = nm.get() + + assert nonceRes.isOk(), $nonceRes.error + assert nonceRes2.isErr(), "Expected error, got: " & $nonceRes2.value + + check: + nonceRes2.error.kind == NonceManagerErrorKind.NonceLimitReached + + test "should generate a new nonce if epoch is crossed": + let nm = NonceManager.init(nonceLimit = 1.uint, epoch = float(0.000001)) + let nonceRes = nm.get() + sleep(1) + let nonceRes2 = nm.get() + + assert nonceRes.isOk(), $nonceRes.error + assert nonceRes2.isOk(), $nonceRes2.error + + check: + nonceRes.value == 0.uint + nonceRes2.value == 0.uint \ No newline at end of file diff --git a/tests/waku_rln_relay/test_waku_rln_relay.nim b/tests/waku_rln_relay/test_waku_rln_relay.nim index 481c89aa9d..9e7a1ec736 100644 --- a/tests/waku_rln_relay/test_waku_rln_relay.nim +++ b/tests/waku_rln_relay/test_waku_rln_relay.nim @@ -687,9 +687,9 @@ suite "Waku rln relay": # ensure proofs are added require: - proofAdded1 - proofAdded2 - proofAdded3 + proofAdded1.isOk() + proofAdded2.isOk() + proofAdded3.isOk() # validate messages # validateMessage proc checks the validity of the message fields and adds it to the log (if valid) diff --git a/tests/waku_rln_relay/test_wakunode_rln_relay.nim b/tests/waku_rln_relay/test_wakunode_rln_relay.nim index 4f2365315a..c57a68b83f 100644 --- a/tests/waku_rln_relay/test_wakunode_rln_relay.nim +++ b/tests/waku_rln_relay/test_wakunode_rln_relay.nim @@ -85,7 +85,7 @@ procSuite "WakuNode - RLN relay": # prepare the epoch var message = WakuMessage(payload: @payload, contentTopic: contentTopic) - doAssert(node1.wakuRlnRelay.appendRLNProof(message, epochTime())) + doAssert(node1.wakuRlnRelay.appendRLNProof(message, epochTime()).isOk()) ## node1 publishes a message with a rate limit proof, the message is then relayed to node2 which in turn @@ -155,12 +155,12 @@ procSuite "WakuNode - RLN relay": for i in 0..<3: var message = WakuMessage(payload: ("Payload_" & $i).toBytes(), contentTopic: contentTopics[0]) - doAssert(nodes[0].wakuRlnRelay.appendRLNProof(message, epochTime)) + doAssert(nodes[0].wakuRlnRelay.appendRLNProof(message, epochTime).isOk()) messages1.add(message) for i in 0..<3: var message = WakuMessage(payload: ("Payload_" & $i).toBytes(), contentTopic: contentTopics[1]) - doAssert(nodes[1].wakuRlnRelay.appendRLNProof(message, epochTime)) + doAssert(nodes[1].wakuRlnRelay.appendRLNProof(message, epochTime).isOk()) messages2.add(message) # publish 3 messages from node[0] (last 2 are spam, window is 10 secs) @@ -346,9 +346,9 @@ procSuite "WakuNode - RLN relay": # check proofs are added correctly check: - proofAdded1 - proofAdded2 - proofAdded3 + proofAdded1.isOk() + proofAdded2.isOk() + proofAdded3.isOk() # relay handler for node3 var completionFut1 = newFuture[bool]() @@ -452,9 +452,9 @@ procSuite "WakuNode - RLN relay": # check proofs are added correctly check: - proofAdded1 - proofAdded2 - proofAdded3 + proofAdded1.isOk() + proofAdded2.isOk() + proofAdded3.isOk() # relay handler for node2 var completionFut1 = newFuture[bool]() diff --git a/waku/waku_api/jsonrpc/relay/handlers.nim b/waku/waku_api/jsonrpc/relay/handlers.nim index e57518b598..8d5e67d502 100644 --- a/waku/waku_api/jsonrpc/relay/handlers.nim +++ b/waku/waku_api/jsonrpc/relay/handlers.nim @@ -102,9 +102,8 @@ proc installRelayApiHandlers*(node: WakuNode, server: RpcServer, cache: MessageC # if RLN is mounted, append the proof to the message if not node.wakuRlnRelay.isNil(): # append the proof to the message - let success = node.wakuRlnRelay.appendRLNProof(message, - float64(getTime().toUnix())) - if not success: + node.wakuRlnRelay.appendRLNProof(message, + float64(getTime().toUnix())).isOkOr: raise newException(ValueError, "Failed to publish: error appending RLN proof to message") # validate the message before sending it let result = node.wakuRlnRelay.validateMessageAndUpdateLog(message) @@ -201,9 +200,8 @@ proc installRelayApiHandlers*(node: WakuNode, server: RpcServer, cache: MessageC # if RLN is mounted, append the proof to the message if not node.wakuRlnRelay.isNil(): # append the proof to the message - let success = node.wakuRlnRelay.appendRLNProof(message, - float64(getTime().toUnix())) - if not success: + node.wakuRlnRelay.appendRLNProof(message, + float64(getTime().toUnix())).isOkOr: raise newException(ValueError, "Failed to publish: error appending RLN proof to message") # validate the message before sending it let result = node.wakuRlnRelay.validateMessageAndUpdateLog(message) diff --git a/waku/waku_api/rest/relay/handlers.nim b/waku/waku_api/rest/relay/handlers.nim index 73f2935e43..d3efa02781 100644 --- a/waku/waku_api/rest/relay/handlers.nim +++ b/waku/waku_api/rest/relay/handlers.nim @@ -130,9 +130,8 @@ proc installRelayApiHandlers*(router: var RestRouter, node: WakuNode, cache: Mes # if RLN is mounted, append the proof to the message if not node.wakuRlnRelay.isNil(): # append the proof to the message - let success = node.wakuRlnRelay.appendRLNProof(message, - float64(getTime().toUnix())) - if not success: + node.wakuRlnRelay.appendRLNProof(message, + float64(getTime().toUnix())).isOkOr: return RestApiResponse.internalServerError("Failed to publish: error appending RLN proof to message") (await node.wakuRelay.validateMessage(pubsubTopic, message)).isOkOr: @@ -219,7 +218,7 @@ proc installRelayApiHandlers*(router: var RestRouter, node: WakuNode, cache: Mes # if RLN is mounted, append the proof to the message if not node.wakuRlnRelay.isNil(): - if not node.wakuRlnRelay.appendRLNProof(message, float64(getTime().toUnix())): + node.wakuRlnRelay.appendRLNProof(message, float64(getTime().toUnix())).isOkOr: return RestApiResponse.internalServerError( "Failed to publish: error appending RLN proof to message") diff --git a/waku/waku_rln_relay/group_manager/group_manager_base.nim b/waku/waku_rln_relay/group_manager/group_manager_base.nim index 2fb68df220..1c5cd8e5c6 100644 --- a/waku/waku_rln_relay/group_manager/group_manager_base.nim +++ b/waku/waku_rln_relay/group_manager/group_manager_base.nim @@ -188,10 +188,12 @@ when defined(rln_v2): return err("user message limit is not set") waku_rln_proof_generation_duration_seconds.nanosecondTime: let proof = proofGen(rlnInstance = g.rlnInstance, - data = data, - memKeys = g.idCredentials.get(), - memIndex = g.membershipIndex.get(), - epoch = epoch).valueOr: + data = data, + membership = g.idCredentials.get(), + index = g.membershipIndex.get(), + epoch = epoch, + userMessageLimit = g.userMessageLimit.get(), + messageId = messageId).valueOr: return err("proof generation failed: " & $error) return ok(proof) else: diff --git a/waku/waku_rln_relay/nonce_manager.nim b/waku/waku_rln_relay/nonce_manager.nim new file mode 100644 index 0000000000..df147edcc4 --- /dev/null +++ b/waku/waku_rln_relay/nonce_manager.nim @@ -0,0 +1,69 @@ +when (NimMajor, NimMinor) < (1, 4): + {.push raises: [Defect].} +else: + {.push raises: [].} + +import + chronos, + stew/results, + times +import + ./constants + +export + chronos, + times, + results, + constants + +# This module contains the NonceManager interface +# The NonceManager is responsible for managing the messageId used to generate RLN proofs +# It should be used to fetch a new messageId every time a proof is generated +# It refreshes the messageId every `epoch` seconds + +type + Nonce* = uint64 + NonceManager* = ref object of RootObj + epoch*: float64 + nextNonce*: Nonce + lastNonceTime*: float64 + nonceLimit*: Nonce + + NonceManagerErrorKind* = enum + NonceLimitReached + + NonceManagerError* = object + kind*: NonceManagerErrorKind + error*: string + + NonceManagerResult*[T] = Result[T, NonceManagerError] + +proc `$`*(ne: NonceManagerError): string = + case ne.kind + of NonceLimitReached: + return "NonceLimitReached: " & ne.error + +proc init*(T: type NonceManager, nonceLimit: Nonce, epoch = EpochUnitSeconds): T = + return NonceManager( + epoch: epoch, + nextNonce: 0, + lastNonceTime: 0, + nonceLimit: nonceLimit + ) + + +proc get*(n: NonceManager): NonceManagerResult[Nonce] = + let now = getTime().toUnixFloat() + var retNonce = n.nextNonce + + if now - n.lastNonceTime < n.epoch: retNonce = n.nextNonce + else: retNonce = 0 + + n.nextNonce = retNonce + 1 + n.lastNonceTime = now + + if retNonce >= n.nonceLimit: + return err(NonceManagerError(kind: NonceLimitReached, + error: "Nonce limit reached. Please wait for the next epoch")) + + return ok(retNonce) diff --git a/waku/waku_rln_relay/rln_relay.nim b/waku/waku_rln_relay/rln_relay.nim index 28d0d43c06..174bf6e34f 100644 --- a/waku/waku_rln_relay/rln_relay.nim +++ b/waku/waku_rln_relay/rln_relay.nim @@ -20,6 +20,10 @@ import ./constants, ./protocol_types, ./protocol_metrics + +when defined(rln_v2): + import ./nonce_manager + import ../waku_relay, # for WakuRelayHandler ../waku_core, @@ -37,6 +41,8 @@ type WakuRlnConfig* = object rlnRelayCredPath*: string rlnRelayCredPassword*: string rlnRelayTreePath*: string + when defined(rln_v2): + rlnRelayUserMessageLimit*: uint64 proc createMembershipList*(rln: ptr RLN, n: int): RlnRelayResult[( seq[RawMembershipCredentials], string @@ -78,7 +84,8 @@ type WakuRLNRelay* = ref object of RootObj nullifierLog*: OrderedTable[Epoch, seq[ProofMetadata]] lastEpoch*: Epoch # the epoch of the last published rln message groupManager*: GroupManager - nonce*: uint64 + when defined(rln_v2): + nonceManager: NonceManager method stop*(rlnPeer: WakuRLNRelay) {.async: (raises: [Exception]).} = ## stops the rln-relay protocol @@ -282,7 +289,7 @@ proc toRLNSignal*(wakumessage: WakuMessage): seq[byte] = proc appendRLNProof*(rlnPeer: WakuRLNRelay, msg: var WakuMessage, - senderEpochTime: float64): bool = + senderEpochTime: float64): RlnRelayResult[void] = ## returns true if it can create and append a `RateLimitProof` to the supplied `msg` ## returns false otherwise ## `senderEpochTime` indicates the number of seconds passed since Unix epoch. The fractional part holds sub-seconds. @@ -292,16 +299,16 @@ proc appendRLNProof*(rlnPeer: WakuRLNRelay, let epoch = calcEpoch(senderEpochTime) when defined(rln_v2): - # TODO: add support for incrementing nonce, will address in another PR - let proofGenRes = rlnPeer.groupManager.generateProof(input, epoch, 1) + let nonce = rlnPeer.nonceManager.get().valueOr: + return err("could not get new message id to generate an rln proof: " & $error) + let proof = rlnPeer.groupManager.generateProof(input, epoch, nonce).valueOr: + return err("could not generate rln-v2 proof: " & $error) else: - let proofGenRes = rlnPeer.groupManager.generateProof(input, epoch) + let proof = rlnPeer.groupManager.generateProof(input, epoch).valueOr: + return err("could not generate rln proof: " & $error) - if proofGenRes.isErr(): - return false - - msg.proof = proofGenRes.get().encode().buffer - return true + msg.proof = proof.encode().buffer + return ok() proc clearNullifierLog(rlnPeer: WakuRlnRelay) = # clear the first MaxEpochGap epochs of the nullifer log @@ -397,7 +404,11 @@ proc mount(conf: WakuRlnConfig, # Start the group sync await groupManager.startGroupSync() - return WakuRLNRelay(groupManager: groupManager) + when defined(rln_v2): + return WakuRLNRelay(groupManager: groupManager, + nonceManager: NonceManager.init(conf.rlnRelayUserMessageLimit)) + else: + return WakuRLNRelay(groupManager: groupManager) proc isReady*(rlnPeer: WakuRLNRelay): Future[bool] {.async: (raises: [Exception]).} = ## returns true if the rln-relay protocol is ready to relay messages From 699a5bc5c0c5218b8822ae4f76a2e942a63faf41 Mon Sep 17 00:00:00 2001 From: rymnc <43716372+rymnc@users.noreply.github.com> Date: Mon, 12 Feb 2024 16:59:46 +0530 Subject: [PATCH 2/2] fix: simplify --- waku/waku_rln_relay/nonce_manager.nim | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/waku/waku_rln_relay/nonce_manager.nim b/waku/waku_rln_relay/nonce_manager.nim index df147edcc4..6a0f34b073 100644 --- a/waku/waku_rln_relay/nonce_manager.nim +++ b/waku/waku_rln_relay/nonce_manager.nim @@ -56,9 +56,7 @@ proc get*(n: NonceManager): NonceManagerResult[Nonce] = let now = getTime().toUnixFloat() var retNonce = n.nextNonce - if now - n.lastNonceTime < n.epoch: retNonce = n.nextNonce - else: retNonce = 0 - + if now - n.lastNonceTime >= n.epoch: retNonce = 0 n.nextNonce = retNonce + 1 n.lastNonceTime = now