fix: refact rln-relay and post sync test (#3434)

This commit is contained in:
Darshan K 2025-09-10 16:18:51 +05:30 committed by GitHub
parent 413c6371d8
commit 753891217e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 672 additions and 61755 deletions

View File

@ -1,42 +1,59 @@
import
math,
std/sequtils,
results,
options,
std/[strutils, times, sequtils, osproc], math, results, options, testutils/unittests
import
waku/[
waku_rln_relay/protocol_types,
waku_rln_relay/rln,
waku_rln_relay,
waku_rln_relay/conversion_utils,
waku_rln_relay/group_manager/static/group_manager,
]
import std/[times, os]
waku_rln_relay/group_manager/on_chain/group_manager,
],
tests/waku_rln_relay/utils_onchain
proc main(): Future[string] {.async, gcsafe.} =
let rlnIns = createRLNInstance(20).get()
let credentials = toSeq(0 .. 1000).mapIt(membershipKeyGen(rlnIns).get())
# Spin up a local Ethereum JSON-RPC (Anvil) and deploy the RLN contract
let anvilProc = runAnvil()
defer:
stopAnvil(anvilProc)
let manager = StaticGroupManager(
rlnInstance: rlnIns,
groupSize: 1000,
membershipIndex: some(MembershipIndex(900)),
groupKeys: credentials,
)
# Set up an On-chain group manager (includes contract deployment)
let manager = await setupOnchainGroupManager()
(await manager.init()).isOkOr:
raiseAssert $error
await manager.init()
# Register a new member so that we can later generate proofs
let idCredentials = generateCredentials(manager.rlnInstance)
try:
await manager.register(idCredentials, UserMessageLimit(100))
except Exception, CatchableError:
assert false, "exception raised: " & getCurrentExceptionMsg()
let rootUpdated = await manager.updateRoots()
if rootUpdated:
let proofResult = await manager.fetchMerkleProofElements()
if proofResult.isErr():
error "Failed to fetch Merkle proof", error = proofResult.error
manager.merkleProofCache = proofResult.get()
let epoch = default(Epoch)
debug "epoch in bytes", epochHex = epoch.inHex()
let data: seq[byte] = newSeq[byte](1024)
var proofGenTimes: seq[times.Duration] = @[]
var proofVerTimes: seq[times.Duration] = @[]
for i in 0 .. 50:
for i in 1 .. 100:
var time = getTime()
let proof = manager.generateProof(data, default(Epoch)).get()
let proof = manager.generateProof(data, epoch, MessageId(i.uint8)).valueOr:
raiseAssert $error
proofGenTimes.add(getTime() - time)
time = getTime()
let res = manager.verifyProof(data, proof).get()
let ok = manager.verifyProof(data, proof).valueOr:
raiseAssert $error
proofVerTimes.add(getTime() - time)
echo "Proof generation times: ", sum(proofGenTimes) div len(proofGenTimes)
@ -44,6 +61,6 @@ proc main(): Future[string] {.async, gcsafe.} =
when isMainModule:
try:
waitFor(main())
discard waitFor main()
except CatchableError as e:
raise e

View File

@ -1,7 +1,7 @@
{.used.}
import
std/[options, tempfiles, net],
std/[options, tempfiles, net, osproc],
testutils/unittests,
chronos,
std/strformat,
@ -17,8 +17,9 @@ import
waku_lightpush_legacy/protocol_metrics,
waku_rln_relay,
],
../testlib/[wakucore, wakunode, testasync, futures],
../resources/payloads
../testlib/[wakucore, wakunode, testasync, futures, testutils],
../resources/payloads,
../waku_rln_relay/[rln/waku_rln_relay_utils, utils_onchain]
suite "Waku Legacy Lightpush - End To End":
var
@ -110,6 +111,8 @@ suite "RLN Proofs as a Lightpush Service":
server {.threadvar.}: WakuNode
client {.threadvar.}: WakuNode
anvilProc {.threadvar.}: Process
manager {.threadvar.}: OnchainGroupManager
serverRemotePeerInfo {.threadvar.}: RemotePeerInfo
pubsubTopic {.threadvar.}: PubsubTopic
@ -131,13 +134,14 @@ suite "RLN Proofs as a Lightpush Service":
server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0))
client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0))
anvilProc = runAnvil()
manager = waitFor setupOnchainGroupManager()
# mount rln-relay
let wakuRlnConfig = WakuRlnConfig(
dynamic: false,
credIndex: some(1.uint),
userMessageLimit: 1,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "wakunode"),
let wakuRlnConfig = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "wakunode_1"),
index = MembershipIndex(1),
)
await allFutures(server.start(), client.start())
@ -149,6 +153,24 @@ suite "RLN Proofs as a Lightpush Service":
await server.mountLegacyLightPush()
client.mountLegacyLightPushClient()
let manager1 = cast[OnchainGroupManager](server.wakuRlnRelay.groupManager)
let idCredentials1 = generateCredentials(manager1.rlnInstance)
try:
waitFor manager1.register(idCredentials1, UserMessageLimit(20))
except Exception, CatchableError:
assert false,
"exception raised when calling register: " & getCurrentExceptionMsg()
let rootUpdated1 = waitFor manager1.updateRoots()
debug "Updated root for node1", rootUpdated1
if rootUpdated1:
let proofResult = waitFor manager1.fetchMerkleProofElements()
if proofResult.isErr():
error "Failed to fetch Merkle proof", error = proofResult.error
manager1.merkleProofCache = proofResult.get()
serverRemotePeerInfo = server.peerInfo.toRemotePeerInfo()
pubsubTopic = DefaultPubsubTopic
contentTopic = DefaultContentTopic
@ -156,6 +178,7 @@ suite "RLN Proofs as a Lightpush Service":
asyncTeardown:
await server.stop()
stopAnvil(anvilProc)
suite "Lightpush attaching RLN proofs":
asyncTest "Message is published when RLN enabled":

View File

@ -1,7 +1,7 @@
{.used.}
import
std/[options, tempfiles],
std/[options, tempfiles, osproc],
testutils/unittests,
chronos,
std/strformat,
@ -10,7 +10,8 @@ import
import
waku/[waku_core, node/peer_manager, node/waku_node, waku_lightpush, waku_rln_relay],
../testlib/[wakucore, wakunode, testasync, futures],
../resources/payloads
../resources/payloads,
../waku_rln_relay/[rln/waku_rln_relay_utils, utils_onchain]
const PublishedToOnePeer = 1
@ -104,6 +105,8 @@ suite "RLN Proofs as a Lightpush Service":
server {.threadvar.}: WakuNode
client {.threadvar.}: WakuNode
anvilProc {.threadvar.}: Process
manager {.threadvar.}: OnchainGroupManager
serverRemotePeerInfo {.threadvar.}: RemotePeerInfo
pubsubTopic {.threadvar.}: PubsubTopic
@ -125,13 +128,14 @@ suite "RLN Proofs as a Lightpush Service":
server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0))
client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0))
anvilProc = runAnvil()
manager = waitFor setupOnchainGroupManager()
# mount rln-relay
let wakuRlnConfig = WakuRlnConfig(
dynamic: false,
credIndex: some(1.uint),
userMessageLimit: 1,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "wakunode"),
let wakuRlnConfig = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "wakunode_1"),
index = MembershipIndex(1),
)
await allFutures(server.start(), client.start())
@ -143,6 +147,24 @@ suite "RLN Proofs as a Lightpush Service":
await server.mountLightPush()
client.mountLightPushClient()
let manager1 = cast[OnchainGroupManager](server.wakuRlnRelay.groupManager)
let idCredentials1 = generateCredentials(manager1.rlnInstance)
try:
waitFor manager1.register(idCredentials1, UserMessageLimit(20))
except Exception, CatchableError:
assert false,
"exception raised when calling register: " & getCurrentExceptionMsg()
let rootUpdated1 = waitFor manager1.updateRoots()
debug "Updated root for node1", rootUpdated1
if rootUpdated1:
let proofResult = waitFor manager1.fetchMerkleProofElements()
if proofResult.isErr():
error "Failed to fetch Merkle proof", error = proofResult.error
manager1.merkleProofCache = proofResult.get()
serverRemotePeerInfo = server.peerInfo.toRemotePeerInfo()
pubsubTopic = DefaultPubsubTopic
contentTopic = DefaultContentTopic
@ -150,6 +172,7 @@ suite "RLN Proofs as a Lightpush Service":
asyncTeardown:
await server.stop()
stopAnvil(anvilProc)
suite "Lightpush attaching RLN proofs":
asyncTest "Message is published when RLN enabled":

View File

@ -15,7 +15,8 @@ import
waku/waku_rln_relay/rln/wrappers,
./waku_rln_relay_utils,
../../testlib/[simple_mock, assertions],
../../waku_keystore/utils
../../waku_keystore/utils,
../../testlib/testutils
from std/times import epochTime
@ -98,7 +99,7 @@ suite "RlnConfig":
suite "createRLNInstance":
test "ok":
# When we create the RLN instance
let rlnRes: RLNResult = createRLNInstance(15, "my.db")
let rlnRes: RLNResult = createRLNInstance(15)
# Then it succeeds
check:
@ -124,7 +125,7 @@ suite "RlnConfig":
newCircuitMock
# When we create the RLN instance
let rlnRes: RLNResult = createRLNInstance(15, "my.db")
let rlnRes: RLNResult = createRLNInstance(15)
# Then it fails
check:
@ -133,42 +134,3 @@ suite "RlnConfig":
# Cleanup
mock(new_circuit):
backup
suite "proofGen":
test "Valid zk proof":
# this test vector is from zerokit
let rlnInstanceRes = createRLNInstanceWrapper()
assertResultOk(rlnInstanceRes)
let rlnInstance = rlnInstanceRes.value
let identityCredential = defaultIdentityCredential()
assert rlnInstance.insertMember(identityCredential.idCommitment)
let merkleRootRes = rlnInstance.getMerkleRoot()
assertResultOk(merkleRootRes)
let merkleRoot = merkleRootRes.value
let proofGenRes = rlnInstance.proofGen(
data = @[],
memKeys = identityCredential,
memIndex = MembershipIndex(0),
epoch = uint64(epochTime() / 1.float64).toEpoch(),
)
assertResultOk(proofGenRes)
let
rateLimitProof = proofGenRes.value
proofVerifyRes = rlnInstance.proofVerify(
data = @[], proof = rateLimitProof, validRoots = @[merkleRoot]
)
assertResultOk(proofVerifyRes)
assert proofVerifyRes.value, "proof verification failed"
# Assert the proof fields adhere to the specified types and lengths
check:
typeEq(rateLimitProof.proof, array[256, byte])
typeEq(rateLimitProof.merkleRoot, array[32, byte])
typeEq(rateLimitProof.shareX, array[32, byte])
typeEq(rateLimitProof.shareY, array[32, byte])
typeEq(rateLimitProof.nullifier, array[32, byte])

View File

@ -1,23 +1,59 @@
import std/tempfiles
import waku/waku_rln_relay, waku/waku_rln_relay/[rln, protocol_types]
import
waku/waku_rln_relay,
waku/waku_rln_relay/[
group_manager, rln, conversion_utils, constants, protocol_types, protocol_metrics,
nonce_manager,
]
proc createRLNInstanceWrapper*(): RLNResult =
return createRlnInstance(tree_path = genTempPath("rln_tree", "waku_rln_relay"))
proc unsafeAppendRLNProof*(
rlnPeer: WakuRLNRelay, msg: var WakuMessage, senderEpochTime: float64
rlnPeer: WakuRLNRelay, msg: var WakuMessage, epoch: Epoch, messageId: MessageId
): RlnRelayResult[void] =
## this proc derived from appendRLNProof, does not perform nonce check to
## facilitate bad message id generation for testing
## Test helper derived from `appendRLNProof`.
## - Skips nonce validation to intentionally allow generating "bad" message IDs for tests.
## - Forces a real-time on-chain Merkle root refresh via `updateRoots()` and fetches Merkle
## proof elements, updating `merkleProofCache` (bypasses `trackRootsChanges`).
## WARNING: For testing only
let input = msg.toRLNSignal()
let epoch = rlnPeer.calcEpoch(senderEpochTime)
let manager = cast[OnchainGroupManager](rlnPeer.groupManager)
let rootUpdated = waitFor manager.updateRoots()
# we do not fetch a nonce from the nonce manager,
# instead we use 0 as the nonce
let proof = rlnPeer.groupManager.generateProof(input, epoch, 0).valueOr:
# Fetch Merkle proof either when a new root was detected *or* when the cache is empty.
if rootUpdated or manager.merkleProofCache.len == 0:
let proofResult = waitFor manager.fetchMerkleProofElements()
if proofResult.isErr():
error "Failed to fetch Merkle proof", error = proofResult.error
manager.merkleProofCache = proofResult.get()
let proof = manager.generateProof(msg.toRLNSignal(), epoch, messageId).valueOr:
return err("could not generate rln-v2 proof: " & $error)
msg.proof = proof.encode().buffer
return ok()
proc getWakuRlnConfig*(
manager: OnchainGroupManager,
userMessageLimit: uint64 = 1,
epochSizeSec: uint64 = 1,
treePath: string = genTempPath("rln_tree", "waku_rln_relay"),
index: MembershipIndex = MembershipIndex(0),
): WakuRlnConfig =
let wakuRlnConfig = WakuRlnConfig(
dynamic: true,
ethClientUrls: @[EthClient],
ethContractAddress: manager.ethContractAddress,
chainId: manager.chainId,
credIndex: some(index),
userMessageLimit: userMessageLimit,
epochSizeSec: epochSizeSec,
treePath: treePath,
ethPrivateKey: some(manager.ethPrivateKey.get()),
onFatalErrorAction: proc(errStr: string) =
warn "non-fatal onchain test error", errStr
,
)
return wakuRlnConfig

View File

@ -2,8 +2,6 @@
import
./test_rln_group_manager_onchain,
./test_rln_group_manager_static,
./test_waku_rln_relay,
./test_wakunode_rln_relay,
./test_rln_nonce_manager,
./test_rln_serde
./test_rln_nonce_manager

View File

@ -3,7 +3,7 @@
{.push raises: [].}
import
std/[options, sequtils, deques, random, locks],
std/[options, sequtils, deques, random, locks, osproc],
results,
stew/byteutils,
testutils/unittests,
@ -28,24 +28,16 @@ import
../testlib/wakucore,
./utils_onchain
var testLock: Lock
initLock(testLock)
suite "Onchain group manager":
var anvilProc {.threadVar.}: Process
var manager {.threadVar.}: OnchainGroupManager
setup:
# Acquire lock to ensure tests run sequentially
acquire(testLock)
let runAnvil {.used.} = runAnvil()
var manager {.threadvar.}: OnchainGroupManager
anvilProc = runAnvil()
manager = waitFor setupOnchainGroupManager()
teardown:
waitFor manager.stop()
stopAnvil(runAnvil)
# Release lock after test completes
release(testLock)
stopAnvil(anvilProc)
test "should initialize successfully":
(waitFor manager.init()).isOkOr:
@ -119,11 +111,6 @@ suite "Onchain group manager":
(waitFor manager.init()).isErrOr:
raiseAssert "Expected error when keystore file doesn't exist"
test "trackRootChanges: start tracking roots":
(waitFor manager.init()).isOkOr:
raiseAssert $error
discard manager.trackRootChanges()
test "trackRootChanges: should guard against uninitialized state":
try:
discard manager.trackRootChanges()
@ -161,10 +148,9 @@ suite "Onchain group manager":
test "trackRootChanges: should fetch history correctly":
# TODO: We can't use `trackRootChanges()` directly in this test because its current implementation
# relies on a busy loop rather than event-based monitoring. As a result, some root changes
# may be missed, leading to inconsistent test results (i.e., it may randomly return true or false).
# To ensure reliability, we use the `updateRoots()` function to validate the `validRoots` window
# after each registration.
# relies on a busy loop rather than event-based monitoring. but that busy loop fetch root every 5 seconds
# so we can't use it in this test.
const credentialCount = 6
let credentials = generateCredentials(manager.rlnInstance, credentialCount)
(waitFor manager.init()).isOkOr:

View File

@ -1,228 +0,0 @@
{.used.}
{.push raises: [].}
import
testutils/unittests,
results,
options,
waku/[
waku_rln_relay/protocol_types,
waku_rln_relay/rln,
waku_rln_relay/conversion_utils,
waku_rln_relay/group_manager/static/group_manager,
]
import chronos, libp2p/crypto/crypto, eth/keys, dnsdisc/builder
import std/tempfiles
proc generateCredentials(rlnInstance: ptr RLN): IdentityCredential =
let credRes = membershipKeyGen(rlnInstance)
return credRes.get()
proc generateCredentials(rlnInstance: ptr RLN, n: int): seq[IdentityCredential] =
var credentials: seq[IdentityCredential]
for i in 0 ..< n:
credentials.add(generateCredentials(rlnInstance))
return credentials
suite "Static group manager":
setup:
let rlnInstance = createRlnInstance(
tree_path = genTempPath("rln_tree", "group_manager_static")
).valueOr:
raiseAssert $error
let credentials = generateCredentials(rlnInstance, 10)
let manager {.used.} = StaticGroupManager(
rlnInstance: rlnInstance,
groupSize: 10,
membershipIndex: some(MembershipIndex(5)),
groupKeys: credentials,
)
asyncTest "should initialize successfully":
let merkleRootBefore = manager.rlnInstance.getMerkleRoot().valueOr:
raiseAssert $error
(await manager.init()).isOkOr:
raiseAssert $error
let merkleRootAfter = manager.rlnInstance.getMerkleRoot().valueOr:
raiseAssert $error
check:
manager.idCredentials.isSome()
manager.groupKeys.len == 10
manager.groupSize == 10
manager.membershipIndex == some(MembershipIndex(5))
manager.groupKeys[5] == manager.idCredentials.get()
manager.latestIndex == 9
merkleRootAfter.inHex() != merkleRootBefore.inHex()
asyncTest "startGroupSync: should start group sync":
(await manager.init()).isOkOr:
raiseAssert $error
require:
manager.validRoots.len() == 1
manager.rlnInstance.getMerkleRoot().get() == manager.validRoots[0]
(await manager.startGroupSync()).isOkOr:
raiseAssert $error
asyncTest "startGroupSync: should guard against uninitialized state":
let manager = StaticGroupManager(
groupSize: 0,
membershipIndex: some(MembershipIndex(0)),
groupKeys: @[],
rlnInstance: rlnInstance,
)
(await manager.startGroupSync()).isErrOr:
raiseAssert "StartGroupSync: expected error"
asyncTest "register: should guard against uninitialized state":
let manager = StaticGroupManager(
groupSize: 0,
membershipIndex: some(MembershipIndex(0)),
groupKeys: @[],
rlnInstance: rlnInstance,
)
let dummyCommitment = default(IDCommitment)
try:
await manager.register(
RateCommitment(
idCommitment: dummyCommitment, userMessageLimit: DefaultUserMessageLimit
)
)
except ValueError:
assert true
except Exception, CatchableError:
assert false, "exception raised: " & getCurrentExceptionMsg()
asyncTest "register: should register successfully":
(await manager.init()).isOkOr:
raiseAssert $error
(await manager.startGroupSync()).isOkOr:
raiseAssert $error
let idCommitment = generateCredentials(manager.rlnInstance).idCommitment
let merkleRootBefore = manager.rlnInstance.getMerkleRoot().valueOr:
raiseAssert $error
try:
await manager.register(
RateCommitment(
idCommitment: idCommitment, userMessageLimit: DefaultUserMessageLimit
)
)
except Exception, CatchableError:
assert false, "exception raised: " & getCurrentExceptionMsg()
let merkleRootAfter = manager.rlnInstance.getMerkleRoot().valueOr:
raiseAssert $error
check:
merkleRootAfter.inHex() != merkleRootBefore.inHex()
manager.latestIndex == 10
asyncTest "register: callback is called":
var callbackCalled = false
let idCommitment = generateCredentials(manager.rlnInstance).idCommitment
let fut = newFuture[void]()
proc callback(registrations: seq[Membership]): Future[void] {.async.} =
require:
registrations.len == 1
registrations[0].index == 10
registrations[0].rateCommitment ==
RateCommitment(
idCommitment: idCommitment, userMessageLimit: DefaultUserMessageLimit
)
.toLeaf()
.get()
callbackCalled = true
fut.complete()
try:
manager.onRegister(callback)
(await manager.init()).isOkOr:
raiseAssert $error
(await manager.startGroupSync()).isOkOr:
raiseAssert $error
await manager.register(
RateCommitment(
idCommitment: idCommitment, userMessageLimit: DefaultUserMessageLimit
)
)
except Exception, CatchableError:
assert false, "exception raised: " & getCurrentExceptionMsg()
await fut
check:
callbackCalled
asyncTest "withdraw: should guard against uninitialized state":
let idSecretHash = credentials[0].idSecretHash
try:
await manager.withdraw(idSecretHash)
except ValueError:
assert true
except Exception, CatchableError:
assert false, "exception raised: " & getCurrentExceptionMsg()
asyncTest "withdraw: should withdraw successfully":
(await manager.init()).isOkOr:
raiseAssert $error
(await manager.startGroupSync()).isOkOr:
raiseAssert $error
let idSecretHash = credentials[0].idSecretHash
let merkleRootBefore = manager.rlnInstance.getMerkleRoot().valueOr:
raiseAssert $error
try:
await manager.withdraw(idSecretHash)
except Exception, CatchableError:
assert false, "exception raised: " & getCurrentExceptionMsg()
let merkleRootAfter = manager.rlnInstance.getMerkleRoot().valueOr:
raiseAssert $error
check:
merkleRootAfter.inHex() != merkleRootBefore.inHex()
asyncTest "withdraw: callback is called":
var callbackCalled = false
let idSecretHash = credentials[0].idSecretHash
let idCommitment = credentials[0].idCommitment
let fut = newFuture[void]()
proc callback(withdrawals: seq[Membership]): Future[void] {.async.} =
require:
withdrawals.len == 1
withdrawals[0].index == 0
withdrawals[0].rateCommitment ==
RateCommitment(
idCommitment: idCommitment, userMessageLimit: DefaultUserMessageLimit
)
.toLeaf()
.get()
callbackCalled = true
fut.complete()
try:
manager.onWithdraw(callback)
(await manager.init()).isOkOr:
raiseAssert $error
(await manager.startGroupSync()).isOkOr:
raiseAssert $error
await manager.withdraw(idSecretHash)
except Exception, CatchableError:
assert false, "exception raised: " & getCurrentExceptionMsg()
await fut
check:
callbackCalled

View File

@ -1,68 +0,0 @@
{.used.}
{.push raises: [].}
import results
import
./rln/waku_rln_relay_utils,
waku/[
waku_keystore/protocol_types,
waku_rln_relay,
waku_rln_relay/rln,
waku_rln_relay/protocol_types,
],
../waku_keystore/utils,
testutils/unittests
from std/times import epochTime
func defaultRateCommitment*(): RateCommitment =
let idCredential = defaultIdentityCredential()
return RateCommitment(idCommitment: idCredential.idCommitment, userMessageLimit: 100)
suite "RLN Relay v2: serde":
test "toLeaf: converts a rateCommitment to a valid leaf":
# this test vector is from zerokit
let rateCommitment = defaultRateCommitment()
let leafRes = toLeaf(rateCommitment)
assert leafRes.isOk(), $leafRes.error
let expectedLeaf =
"09beac7784abfadc9958b3176b352389d0b969ccc7f8bccf3e968ed632e26eca"
check expectedLeaf == leafRes.value.inHex()
test "proofGen: generates a valid zk proof":
# this test vector is from zerokit
let rlnInstance = createRLNInstanceWrapper()
assert rlnInstance.isOk, $rlnInstance.error
let rln = rlnInstance.value
let credential = defaultIdentityCredential()
let rateCommitment = defaultRateCommitment()
let success = rln.insertMember(@(rateCommitment.toLeaf().value))
let merkleRootRes = rln.getMerkleRoot()
assert merkleRootRes.isOk, $merkleRootRes.error
let merkleRoot = merkleRootRes.value
assert success, "failed to insert member"
let proofRes = rln.proofGen(
data = @[],
membership = credential,
userMessageLimit = rateCommitment.userMessageLimit,
messageId = 0,
index = 0,
epoch = uint64(epochTime() / 1.float64).toEpoch(),
)
assert proofRes.isOk, $proofRes.error
let proof = proofRes.value
let proofVerifyRes =
rln.proofVerify(data = @[], proof = proof, validRoots = @[merkleRoot])
assert proofVerifyRes.isOk, $proofVerifyRes.error
assert proofVerifyRes.value, "proof verification failed"

View File

@ -1,7 +1,7 @@
{.used.}
import
std/[options, os, sequtils, tempfiles],
std/[options, os, sequtils, tempfiles, strutils, osproc],
stew/byteutils,
testutils/unittests,
chronos,
@ -17,11 +17,22 @@ import
waku_keystore,
],
./rln/waku_rln_relay_utils,
./utils_onchain,
../testlib/[wakucore, futures, wakunode, testutils]
from std/times import epochTime
suite "Waku rln relay":
var anvilProc {.threadVar.}: Process
var manager {.threadVar.}: OnchainGroupManager
setup:
anvilProc = runAnvil()
manager = waitFor setupOnchainGroupManager()
teardown:
stopAnvil(anvilProc)
test "key_gen Nim Wrappers":
let merkleDepth: csize_t = 20
@ -68,173 +79,6 @@ suite "Waku rln relay":
debug "the generated identity credential: ", idCredential
test "getRoot Nim binding":
# create an RLN instance which also includes an empty Merkle tree
let rlnInstance = createRLNInstanceWrapper()
require:
rlnInstance.isOk()
# read the Merkle Tree root
let
root1 {.noinit.}: Buffer = Buffer()
rootPtr1 = unsafeAddr(root1)
getRootSuccessful1 = getRoot(rlnInstance.get(), rootPtr1)
require:
getRootSuccessful1
root1.len == 32
# read the Merkle Tree root
let
root2 {.noinit.}: Buffer = Buffer()
rootPtr2 = unsafeAddr(root2)
getRootSuccessful2 = getRoot(rlnInstance.get(), rootPtr2)
require:
getRootSuccessful2
root2.len == 32
let rootValue1 = cast[ptr array[32, byte]](root1.`ptr`)
let rootHex1 = rootValue1[].inHex
let rootValue2 = cast[ptr array[32, byte]](root2.`ptr`)
let rootHex2 = rootValue2[].inHex
# the two roots must be identical
check:
rootHex1 == rootHex2
test "getMerkleRoot utils":
# create an RLN instance which also includes an empty Merkle tree
let rlnInstance = createRLNInstanceWrapper()
require:
rlnInstance.isOk()
let rln = rlnInstance.get()
# read the Merkle Tree root
let root1 = getMerkleRoot(rln)
require:
root1.isOk()
let rootHex1 = root1.value().inHex
# read the Merkle Tree root
let root2 = getMerkleRoot(rln)
require:
root2.isOk()
let rootHex2 = root2.value().inHex
# the two roots must be identical
check:
rootHex1 == rootHex2
test "update_next_member Nim Wrapper":
# create an RLN instance which also includes an empty Merkle tree
let rlnInstance = createRLNInstanceWrapper()
require:
rlnInstance.isOk()
let rln = rlnInstance.get()
# generate an identity credential
let idCredentialRes = membershipKeyGen(rln)
require:
idCredentialRes.isOk()
let idCredential = idCredentialRes.get()
let pkBuffer = toBuffer(idCredential.idCommitment)
let pkBufferPtr = unsafeAddr(pkBuffer)
# add the member to the tree
let memberAdded = updateNextMember(rln, pkBufferPtr)
check:
memberAdded
test "getMember Nim wrapper":
let rlnInstance = createRLNInstanceWrapper()
require:
rlnInstance.isOk()
let rln = rlnInstance.get()
# generate an identity credential
let idCredentialRes = membershipKeyGen(rln)
require:
idCredentialRes.isOk()
let idCredential = idCredentialRes.get()
let pkBuffer = toBuffer(idCredential.idCommitment)
let pkBufferPtr = unsafeAddr(pkBuffer)
let
root1 {.noinit.}: Buffer = Buffer()
rootPtr1 = unsafeAddr(root1)
getRootSuccessful1 = getRoot(rlnInstance.get(), rootPtr1)
# add the member to the tree
let memberAdded = updateNextMember(rln, pkBufferPtr)
require:
memberAdded
let leafRes = getMember(rln, 0)
require:
leafRes.isOk()
let leaf = leafRes.get()
let leafHex = leaf.inHex()
check:
leafHex == idCredential.idCommitment.inHex()
test "delete_member Nim wrapper":
# create an RLN instance which also includes an empty Merkle tree
let rlnInstance = createRLNInstanceWrapper()
require:
rlnInstance.isOk()
# generate an identity credential
let rln = rlnInstance.get()
let idCredentialRes = rln.membershipKeyGen()
require:
idCredentialRes.isOk()
rln.insertMember(idCredentialRes.get().idCommitment)
# delete the first member
let deletedMemberIndex = MembershipIndex(0)
let deletionSuccess = rln.deleteMember(deletedMemberIndex)
check:
deletionSuccess
test "insertMembers rln utils":
# create an RLN instance which also includes an empty Merkle tree
let rlnInstance = createRLNInstanceWrapper()
require:
rlnInstance.isOk()
let rln = rlnInstance.get()
# generate an identity credential
let idCredentialRes = rln.membershipKeyGen()
require:
idCredentialRes.isOk()
check:
rln.insertMembers(0, @[idCredentialRes.get().idCommitment])
rln.leavesSet() == 1
test "insertMember rln utils":
# create an RLN instance which also includes an empty Merkle tree
let rlnInstance = createRLNInstanceWrapper()
require:
rlnInstance.isOk()
let rln = rlnInstance.get()
# generate an identity credential
let idCredentialRes = rln.membershipKeyGen()
require:
idCredentialRes.isOk()
check:
rln.insertMember(idCredentialRes.get().idCommitment)
test "removeMember rln utils":
# create an RLN instance which also includes an empty Merkle tree
let rlnInstance = createRLNInstanceWrapper()
require:
rlnInstance.isOk()
let rln = rlnInstance.get()
let idCredentialRes = rln.membershipKeyGen()
require:
idCredentialRes.isOk()
rln.insertMember(idCredentialRes.get().idCommitment)
check:
rln.removeMember(MembershipIndex(0))
test "setMetadata rln utils":
# create an RLN instance which also includes an empty Merkle tree
let rlnInstance = createRLNInstanceWrapper()
@ -290,135 +134,6 @@ suite "Waku rln relay":
check:
metadata.isNone()
test "Merkle tree consistency check between deletion and insertion":
# create an RLN instance
let rlnInstance = createRLNInstanceWrapper()
require:
rlnInstance.isOk()
let rln = rlnInstance.get()
# read the Merkle Tree root
let
root1 {.noinit.}: Buffer = Buffer()
rootPtr1 = unsafeAddr(root1)
getRootSuccessful1 = getRoot(rln, rootPtr1)
require:
getRootSuccessful1
root1.len == 32
# generate an identity credential
let idCredentialRes = membershipKeyGen(rln)
require:
idCredentialRes.isOk()
let idCredential = idCredentialRes.get()
let pkBuffer = toBuffer(idCredential.idCommitment)
let pkBufferPtr = unsafeAddr(pkBuffer)
# add the member to the tree
let memberAdded = updateNextMember(rln, pkBufferPtr)
require:
memberAdded
# read the Merkle Tree root after insertion
let
root2 {.noinit.}: Buffer = Buffer()
rootPtr2 = unsafeAddr(root2)
getRootSuccessful = getRoot(rln, rootPtr2)
require:
getRootSuccessful
root2.len == 32
# delete the first member
let deletedMemberIndex = MembershipIndex(0)
let deletionSuccess = deleteMember(rln, deletedMemberIndex)
require:
deletionSuccess
# read the Merkle Tree root after the deletion
let
root3 {.noinit.}: Buffer = Buffer()
rootPtr3 = unsafeAddr(root3)
getRootSuccessful3 = getRoot(rln, rootPtr3)
require:
getRootSuccessful3
root3.len == 32
let rootValue1 = cast[ptr array[32, byte]](root1.`ptr`)
let rootHex1 = rootValue1[].inHex
debug "The initial root", rootHex1
let rootValue2 = cast[ptr array[32, byte]](root2.`ptr`)
let rootHex2 = rootValue2[].inHex
debug "The root after insertion", rootHex2
let rootValue3 = cast[ptr array[32, byte]](root3.`ptr`)
let rootHex3 = rootValue3[].inHex
debug "The root after deletion", rootHex3
# the root must change after the insertion
check:
not (rootHex1 == rootHex2)
## The initial root of the tree (empty tree) must be identical to
## the root of the tree after one insertion followed by a deletion
check:
rootHex1 == rootHex3
test "Merkle tree consistency check between deletion and insertion using rln utils":
# create an RLN instance
let rlnInstance = createRLNInstanceWrapper()
require:
rlnInstance.isOk()
let rln = rlnInstance.get()
# read the Merkle Tree root
let root1 = rln.getMerkleRoot()
require:
root1.isOk()
let rootHex1 = root1.value().inHex()
# generate an identity credential
let idCredentialRes = rln.membershipKeyGen()
require:
idCredentialRes.isOk()
let memberInserted = rln.insertMembers(0, @[idCredentialRes.get().idCommitment])
require:
memberInserted
# read the Merkle Tree root after insertion
let root2 = rln.getMerkleRoot()
require:
root2.isOk()
let rootHex2 = root2.value().inHex()
# delete the first member
let deletedMemberIndex = MembershipIndex(0)
let deletionSuccess = rln.removeMember(deletedMemberIndex)
require:
deletionSuccess
# read the Merkle Tree root after the deletion
let root3 = rln.getMerkleRoot()
require:
root3.isOk()
let rootHex3 = root3.value().inHex()
debug "The initial root", rootHex1
debug "The root after insertion", rootHex2
debug "The root after deletion", rootHex3
# the root must change after the insertion
check:
not (rootHex1 == rootHex2)
## The initial root of the tree (empty tree) must be identical to
## the root of the tree after one insertion followed by a deletion
check:
rootHex1 == rootHex3
test "hash Nim Wrappers":
# create an RLN instance
let rlnInstance = createRLNInstanceWrapper()
@ -488,69 +203,6 @@ suite "Waku rln relay":
"28a15a991fe3d2a014485c7fa905074bfb55c0909112f865ded2be0a26a932c3" ==
hashRes.get().inHex()
test "create a list of membership keys and construct a Merkle tree based on the list":
let rlnInstance = createRLNInstanceWrapper()
require:
rlnInstance.isOk()
let rln = rlnInstance.get()
let
groupSize = 100
memListRes = rln.createMembershipList(groupSize)
require:
memListRes.isOk()
let (list, root) = memListRes.get()
debug "created membership key list", number_of_keys = list.len
debug "Merkle tree root", size_calculated_tree_root = root.len
check:
list.len == groupSize # check the number of keys
root.len == HashHexSize # check the size of the calculated tree root
test "check correctness of toIdentityCredentials":
let groupKeys = StaticGroupKeys
# create a set of IdentityCredentials objects from groupKeys
let groupIdCredentialsRes = groupKeys.toIdentityCredentials()
require:
groupIdCredentialsRes.isOk()
let groupIdCredentials = groupIdCredentialsRes.get()
# extract the id commitments
let groupIDCommitments = groupIdCredentials.mapIt(it.idCommitment)
# calculate the Merkle tree root out of the extracted id commitments
let rlnInstance = createRLNInstanceWrapper()
require:
rlnInstance.isOk()
let rln = rlnInstance.get()
# create a Merkle tree
let rateCommitments =
groupIDCommitments.mapIt(RateCommitment(idCommitment: it, userMessageLimit: 20))
let leaves = rateCommitments.toLeaves().valueOr:
raiseAssert $error
let membersAdded = rln.insertMembers(0, leaves)
assert membersAdded, "members should be added"
let rawRoot = rln.getMerkleRoot().valueOr:
raiseAssert $error
let root = rawRoot.inHex()
debug "groupIdCredentials", num_group_id_credentials = groupIdCredentials.len
# debug "groupIDCommitments", leaving commented in case needed to debug in the future
# groupIDCommitments = groupIDCommitments.mapIt(it.inHex())
debug "root", root
check:
# check that the correct number of identity credentials is created
groupIdCredentials.len == StaticGroupSize
# compare the calculated root against the correct root
root == StaticGroupMerkleRoot
test "RateLimitProof Protobuf encode/init test":
var
proof: ZKSNARK
@ -691,49 +343,51 @@ suite "Waku rln relay":
asyncTest "validateMessageAndUpdateLog: against epoch gap":
let index = MembershipIndex(5)
let wakuRlnConfig = WakuRlnConfig(
dynamic: false,
credIndex: some(index),
userMessageLimit: 1,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "waku_rln_relay_2"),
)
let wakuRlnConfig = getWakuRlnConfig(manager = manager, index = index)
let wakuRlnRelay = (await WakuRlnRelay.new(wakuRlnConfig)).valueOr:
raiseAssert $error
let time_1 = epochTime()
let manager = cast[OnchainGroupManager](wakuRlnRelay.groupManager)
let idCredentials = generateCredentials(manager.rlnInstance)
try:
waitFor manager.register(idCredentials, UserMessageLimit(20))
except Exception, CatchableError:
assert false,
"exception raised when calling register: " & getCurrentExceptionMsg()
let epoch1 = wakuRlnRelay.getCurrentEpoch()
# Create messages from the same peer and append RLN proof to them (except wm4)
var
# create some messages from the same peer and append rln proof to them, except wm4
wm1 = WakuMessage(payload: "Valid message".toBytes(), timestamp: now())
# another message in the same epoch as wm1, it will break the messaging rate limit
# Another message in the same epoch as wm1, expected to break the rate limit
wm2 = WakuMessage(payload: "Spam message".toBytes(), timestamp: now())
await sleepAsync(1.seconds)
let time_2 = epochTime()
let epoch2 = wakuRlnRelay.getCurrentEpoch()
var
# wm3 points to the next epoch bcz of the sleep
# wm3 points to the next epoch due to the sleep
wm3 = WakuMessage(payload: "Valid message".toBytes(), timestamp: now())
wm4 = WakuMessage(payload: "Invalid message".toBytes(), timestamp: now())
wakuRlnRelay.unsafeAppendRLNProof(wm1, time_1).isOkOr:
# Append RLN proofs
wakuRlnRelay.unsafeAppendRLNProof(wm1, epoch1, MessageId(1)).isOkOr:
raiseAssert $error
wakuRlnRelay.unsafeAppendRLNProof(wm2, time_1).isOkOr:
wakuRlnRelay.unsafeAppendRLNProof(wm2, epoch1, MessageId(1)).isOkOr:
raiseAssert $error
wakuRlnRelay.unsafeAppendRLNProof(wm3, epoch2, MessageId(3)).isOkOr:
raiseAssert $error
wakuRlnRelay.unsafeAppendRLNProof(wm3, time_2).isOkOr:
raiseAssert $error
# validate messages
# Validate messages
let
msgValidate1 = wakuRlnRelay.validateMessageAndUpdateLog(wm1)
# wm2 is published within the same Epoch as wm1 and should be found as spam
# wm2 is within the same epoch as wm1 → should be spam
msgValidate2 = wakuRlnRelay.validateMessageAndUpdateLog(wm2)
# a valid message should be validated successfully
# wm3 is in the next epoch → should be valid
msgValidate3 = wakuRlnRelay.validateMessageAndUpdateLog(wm3)
# wm4 has no rln proof and should not be validated
# wm4 has no RLN proof → should be invalid
msgValidate4 = wakuRlnRelay.validateMessageAndUpdateLog(wm4)
check:
@ -745,30 +399,41 @@ suite "Waku rln relay":
asyncTest "validateMessageAndUpdateLog: against timestamp gap":
let index = MembershipIndex(5)
let wakuRlnConfig = WakuRlnConfig(
dynamic: false,
credIndex: some(index),
userMessageLimit: 10,
epochSizeSec: 10,
treePath: genTempPath("rln_tree", "waku_rln_relay_2"),
)
let wakuRlnConfig = getWakuRlnConfig(manager = manager, index = index)
let wakuRlnRelay = (await WakuRlnRelay.new(wakuRlnConfig)).valueOr:
raiseAssert $error
# usually it's 20 seconds but we set it to 2 for testing purposes which make the test faster
let manager = cast[OnchainGroupManager](wakuRlnRelay.groupManager)
let idCredentials = generateCredentials(manager.rlnInstance)
try:
waitFor manager.register(idCredentials, UserMessageLimit(20))
except Exception, CatchableError:
assert false,
"exception raised when calling register: " & getCurrentExceptionMsg()
# usually it's 20 seconds but we set it to 1 for testing purposes which make the test faster
wakuRlnRelay.rlnMaxTimestampGap = 1
var time = epochTime()
var epoch = wakuRlnRelay.getCurrentEpoch()
var
wm1 = WakuMessage(payload: "timestamp message".toBytes(), timestamp: now())
wm2 = WakuMessage(payload: "timestamp message".toBytes(), timestamp: now())
wm1 = WakuMessage(
payload: "timestamp message".toBytes(),
contentTopic: DefaultPubsubTopic,
timestamp: now(),
)
wm2 = WakuMessage(
payload: "timestamp message".toBytes(),
contentTopic: DefaultPubsubTopic,
timestamp: now(),
)
wakuRlnRelay.unsafeAppendRLNProof(wm1, time).isOkOr:
wakuRlnRelay.unsafeAppendRLNProof(wm1, epoch, MessageId(1)).isOkOr:
raiseAssert $error
wakuRlnRelay.unsafeAppendRLNProof(wm2, time).isOkOr:
wakuRlnRelay.unsafeAppendRLNProof(wm2, epoch, MessageId(2)).isOkOr:
raiseAssert $error
# validate the first message because it's timestamp is the same as the generated timestamp
@ -777,40 +442,43 @@ suite "Waku rln relay":
# wait for 2 seconds to make the timestamp different from generated timestamp
await sleepAsync(2.seconds)
# invalidate the second message because it's timestamp is different from the generated timestamp
let msgValidate2 = wakuRlnRelay.validateMessageAndUpdateLog(wm2)
check:
msgValidate1 == MessageValidationResult.Valid
msgValidate2 == MessageValidationResult.Invalid
asyncTest "validateMessageAndUpdateLog: multiple senders with same external nullifier":
asyncTest "multiple senders with same external nullifier":
let index1 = MembershipIndex(5)
let index2 = MembershipIndex(6)
let rlnConf1 = WakuRlnConfig(
dynamic: false,
credIndex: some(index1),
userMessageLimit: 1,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "waku_rln_relay_3"),
)
let rlnConf1 = getWakuRlnConfig(manager = manager, index = index1)
let wakuRlnRelay1 = (await WakuRlnRelay.new(rlnConf1)).valueOr:
raiseAssert "failed to create waku rln relay: " & $error
let rlnConf2 = WakuRlnConfig(
dynamic: false,
credIndex: some(index2),
userMessageLimit: 1,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "waku_rln_relay_4"),
)
let manager1 = cast[OnchainGroupManager](wakuRlnRelay1.groupManager)
let idCredentials1 = generateCredentials(manager1.rlnInstance)
try:
waitFor manager1.register(idCredentials1, UserMessageLimit(20))
except Exception, CatchableError:
assert false,
"exception raised when calling register: " & getCurrentExceptionMsg()
let index2 = MembershipIndex(6)
let rlnConf2 = getWakuRlnConfig(manager = manager, index = index2)
let wakuRlnRelay2 = (await WakuRlnRelay.new(rlnConf2)).valueOr:
raiseAssert "failed to create waku rln relay: " & $error
let manager2 = cast[OnchainGroupManager](wakuRlnRelay2.groupManager)
let idCredentials2 = generateCredentials(manager2.rlnInstance)
try:
waitFor manager2.register(idCredentials2, UserMessageLimit(20))
except Exception, CatchableError:
assert false,
"exception raised when calling register: " & getCurrentExceptionMsg()
# get the current epoch time
let time = epochTime()
let epoch = wakuRlnRelay1.getCurrentEpoch()
# create messages from different peers and append rln proofs to them
var
@ -820,16 +488,13 @@ suite "Waku rln relay":
wm2 =
WakuMessage(payload: "Valid message from sender 2".toBytes(), timestamp: now())
wakuRlnRelay1.appendRLNProof(wm1, time).isOkOr:
wakuRlnRelay1.unsafeAppendRLNProof(wm1, epoch, MessageId(1)).isOkOr:
raiseAssert $error
wakuRlnRelay2.appendRLNProof(wm2, time).isOkOr:
wakuRlnRelay2.unsafeAppendRLNProof(wm2, epoch, MessageId(1)).isOkOr:
raiseAssert $error
# validate messages
# validateMessage proc checks the validity of the message fields and adds it to the log (if valid)
let
msgValidate1 = wakuRlnRelay1.validateMessageAndUpdateLog(wm1)
# since this message is from a different sender, it should be validated successfully
msgValidate2 = wakuRlnRelay1.validateMessageAndUpdateLog(wm2)
check:
@ -942,12 +607,8 @@ suite "Waku rln relay":
let index = MembershipIndex(0)
proc runTestForEpochSizeSec(rlnEpochSizeSec: uint) {.async.} =
let wakuRlnConfig = WakuRlnConfig(
dynamic: false,
credIndex: some(index),
userMessageLimit: 1,
epochSizeSec: rlnEpochSizeSec,
treePath: genTempPath("rln_tree", "waku_rln_relay_4"),
let wakuRlnConfig = getWakuRlnConfig(
manager = manager, index = index, epochSizeSec = rlnEpochSizeSec.uint64
)
let wakuRlnRelay = (await WakuRlnRelay.new(wakuRlnConfig)).valueOr:

View File

@ -1,7 +1,7 @@
{.used.}
import
std/[sequtils, tempfiles],
std/[options, os, sequtils, tempfiles, strutils, osproc],
stew/byteutils,
testutils/unittests,
chronicles,
@ -11,26 +11,11 @@ import
import
waku/[waku_core, waku_node, waku_rln_relay],
../testlib/[wakucore, futures, wakunode, testutils],
./utils_onchain,
./rln/waku_rln_relay_utils
from std/times import epochTime
proc buildWakuRlnConfig(
credIndex: uint,
epochSizeSec: uint64,
treeFilename: string,
userMessageLimit: uint64 = 1,
): WakuRlnConfig =
let treePath = genTempPath("rln_tree", treeFilename)
# Off-chain
return WakuRlnConfig(
dynamic: false,
credIndex: some(credIndex.uint),
userMessageLimit: userMessageLimit,
epochSizeSec: epochSizeSec,
treePath: treePath,
)
proc waitForNullifierLog(node: WakuNode, expectedLen: int): Future[bool] {.async.} =
## Helper function
for i in 0 .. 100: # Try for up to 50 seconds (100 * 500ms)
@ -41,6 +26,16 @@ proc waitForNullifierLog(node: WakuNode, expectedLen: int): Future[bool] {.async
procSuite "WakuNode - RLN relay":
# NOTE: we set the rlnRelayUserMessageLimit to 1 to make the tests easier to reason about
var anvilProc {.threadVar.}: Process
var manager {.threadVar.}: OnchainGroupManager
setup:
anvilProc = runAnvil()
manager = waitFor setupOnchainGroupManager()
teardown:
stopAnvil(anvilProc)
asyncTest "testing rln-relay with valid proof":
let
# publisher node
@ -61,50 +56,62 @@ procSuite "WakuNode - RLN relay":
assert false, "Failed to mount relay"
# mount rlnrelay in off-chain mode
let wakuRlnConfig1 = WakuRlnConfig(
dynamic: false,
credIndex: some(1.uint),
userMessageLimit: 1,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "wakunode"),
let wakuRlnConfig1 = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "wakunode_1"),
index = MembershipIndex(1),
)
await node1.mountRlnRelay(wakuRlnConfig1)
await node1.start()
# Registration is mandatory before sending messages with rln-relay
let manager1 = cast[OnchainGroupManager](node1.wakuRlnRelay.groupManager)
let idCredentials1 = generateCredentials(manager1.rlnInstance)
try:
waitFor manager1.register(idCredentials1, UserMessageLimit(20))
except Exception, CatchableError:
assert false,
"exception raised when calling register: " & getCurrentExceptionMsg()
let rootUpdated1 = waitFor manager1.updateRoots()
debug "Updated root for node1", rootUpdated1
# node 2
(await node2.mountRelay()).isOkOr:
assert false, "Failed to mount relay"
# mount rlnrelay in off-chain mode
let wakuRlnConfig2 = WakuRlnConfig(
dynamic: false,
credIndex: some(2.uint),
userMessageLimit: 1,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "wakunode_2"),
let wakuRlnConfig2 = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "wakunode_2"),
index = MembershipIndex(2),
)
await node2.mountRlnRelay(wakuRlnConfig2)
await node2.start()
let manager2 = cast[OnchainGroupManager](node2.wakuRlnRelay.groupManager)
let rootUpdated2 = waitFor manager2.updateRoots()
debug "Updated root for node2", rootUpdated2
# node 3
(await node3.mountRelay()).isOkOr:
assert false, "Failed to mount relay"
let wakuRlnConfig3 = WakuRlnConfig(
dynamic: false,
credIndex: some(3.uint),
userMessageLimit: 1,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "wakunode_3"),
let wakuRlnConfig3 = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "wakunode_3"),
index = MembershipIndex(3),
)
await node3.mountRlnRelay(wakuRlnConfig3)
await node3.start()
let manager3 = cast[OnchainGroupManager](node3.wakuRlnRelay.groupManager)
let rootUpdated3 = waitFor manager3.updateRoots()
debug "Updated root for node3", rootUpdated3
# connect them together
await node1.connectToNodes(@[node2.switch.peerInfo.toRemotePeerInfo()])
await node3.connectToNodes(@[node2.switch.peerInfo.toRemotePeerInfo()])
@ -138,9 +145,13 @@ procSuite "WakuNode - RLN relay":
# prepare the epoch
var message =
WakuMessage(payload: @payload, contentTopic: contentTopic, timestamp: now())
doAssert(node1.wakuRlnRelay.unsafeAppendRLNProof(message, epochTime()).isOk())
doAssert(
node1.wakuRlnRelay
.unsafeAppendRLNProof(message, node1.wakuRlnRelay.getCurrentEpoch(), MessageId(0))
.isOk()
)
debug "Nodes participating in the test",
debug " Nodes participating in the test",
node1 = shortLog(node1.switch.peerInfo.peerId),
node2 = shortLog(node2.switch.peerInfo.peerId),
node3 = shortLog(node3.switch.peerInfo.peerId)
@ -149,9 +160,7 @@ procSuite "WakuNode - RLN relay":
## verifies the rate limit proof of the message and relays the message to node3
## verification at node2 occurs inside a topic validator which is installed as part of the waku-rln-relay mount proc
discard await node1.publish(some(DefaultPubsubTopic), message)
await sleepAsync(2000.millis)
assert (await completionFut.withTimeout(10.seconds)), "completionFut timed out"
assert (await completionFut.withTimeout(15.seconds)), "completionFut timed out"
await node1.stop()
await node2.stop()
@ -177,18 +186,25 @@ procSuite "WakuNode - RLN relay":
# mount rlnrelay in off-chain mode
for index, node in nodes:
let wakuRlnConfig = WakuRlnConfig(
dynamic: false,
credIndex: some(index.uint + 1),
userMessageLimit: 1,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "wakunode_" & $(index + 1)),
let wakuRlnConfig = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "wakunode_" & $(index + 1)),
index = MembershipIndex(index + 1),
)
await node.mountRlnRelay(wakuRlnConfig)
await node.start()
let manager = cast[OnchainGroupManager](node.wakuRlnRelay.groupManager)
let idCredentials = generateCredentials(manager.rlnInstance)
# start them
await allFutures(nodes.mapIt(it.start()))
try:
waitFor manager.register(idCredentials, UserMessageLimit(20))
except Exception, CatchableError:
assert false,
"exception raised when calling register: " & getCurrentExceptionMsg()
let rootUpdated = waitFor manager.updateRoots()
debug "Updated root for node", node = index + 1, rootUpdated = rootUpdated
# connect them together
await nodes[0].connectToNodes(@[nodes[1].switch.peerInfo.toRemotePeerInfo()])
@ -228,32 +244,34 @@ procSuite "WakuNode - RLN relay":
var messages1: seq[WakuMessage] = @[]
var messages2: seq[WakuMessage] = @[]
var epochTime = epochTime()
for i in 0 ..< 3:
var message = WakuMessage(
payload: ("Payload_" & $i).toBytes(),
timestamp: now(),
contentTopic: contentTopics[0],
)
nodes[0].wakuRlnRelay.unsafeAppendRLNProof(message, epochTime).isOkOr:
nodes[0].wakuRlnRelay.unsafeAppendRLNProof(
message, nodes[0].wakuRlnRelay.getCurrentEpoch(), MessageId(i.uint8)
).isOkOr:
raiseAssert $error
messages1.add(message)
epochTime = epochTime()
for i in 0 ..< 3:
var message = WakuMessage(
payload: ("Payload_" & $i).toBytes(),
timestamp: now(),
contentTopic: contentTopics[1],
)
nodes[1].wakuRlnRelay.unsafeAppendRLNProof(message, epochTime).isOkOr:
nodes[1].wakuRlnRelay.unsafeAppendRLNProof(
message, nodes[1].wakuRlnRelay.getCurrentEpoch(), MessageId(i.uint8)
).isOkOr:
raiseAssert $error
messages2.add(message)
# publish 3 messages from node[0] (last 2 are spam, window is 10 secs)
# publish 3 messages from node[1] (last 2 are spam, window is 10 secs)
# publish 3 messages from node[0] (last 2 are spam, window is 10 secs)
# publish 3 messages from node[1] (last 2 are spam, window is 10 secs)
for msg in messages1:
discard await nodes[0].publish(some($shards[0]), msg)
for msg in messages2:
@ -265,8 +283,8 @@ procSuite "WakuNode - RLN relay":
# check that node[2] got messages from both topics
# and that rln was applied (just 1 msg is rx, rest are spam)
check:
rxMessagesTopic1 == 1
rxMessagesTopic2 == 1
rxMessagesTopic1 == 3
rxMessagesTopic2 == 3
await allFutures(nodes.mapIt(it.stop()))
@ -290,49 +308,61 @@ procSuite "WakuNode - RLN relay":
assert false, "Failed to mount relay"
# mount rlnrelay in off-chain mode
let wakuRlnConfig1 = WakuRlnConfig(
dynamic: false,
credIndex: some(1.uint),
userMessageLimit: 1,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "wakunode_4"),
let wakuRlnConfig1 = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "wakunode_1"),
index = MembershipIndex(1),
)
await node1.mountRlnRelay(wakuRlnConfig1)
await node1.start()
let manager1 = cast[OnchainGroupManager](node1.wakuRlnRelay.groupManager)
let idCredentials1 = generateCredentials(manager1.rlnInstance)
try:
waitFor manager1.register(idCredentials1, UserMessageLimit(20))
except Exception, CatchableError:
assert false,
"exception raised when calling register: " & getCurrentExceptionMsg()
let rootUpdated1 = waitFor manager1.updateRoots()
debug "Updated root for node1", rootUpdated1
# node 2
(await node2.mountRelay()).isOkOr:
assert false, "Failed to mount relay"
# mount rlnrelay in off-chain mode
let wakuRlnConfig2 = WakuRlnConfig(
dynamic: false,
credIndex: some(2.uint),
userMessageLimit: 1,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "wakunode_5"),
let wakuRlnConfig2 = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "wakunode_2"),
index = MembershipIndex(2),
)
await node2.mountRlnRelay(wakuRlnConfig2)
await node2.start()
let manager2 = cast[OnchainGroupManager](node2.wakuRlnRelay.groupManager)
let rootUpdated2 = waitFor manager2.updateRoots()
debug "Updated root for node2", rootUpdated2
# node 3
(await node3.mountRelay()).isOkOr:
assert false, "Failed to mount relay"
let wakuRlnConfig3 = WakuRlnConfig(
dynamic: false,
credIndex: some(3.uint),
userMessageLimit: 1,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "wakunode_6"),
let wakuRlnConfig3 = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "wakunode_3"),
index = MembershipIndex(3),
)
await node3.mountRlnRelay(wakuRlnConfig3)
await node3.start()
let manager3 = cast[OnchainGroupManager](node3.wakuRlnRelay.groupManager)
let rootUpdated3 = waitFor manager3.updateRoots()
debug "Updated root for node3", rootUpdated3
# connect them together
await node1.connectToNodes(@[node2.switch.peerInfo.toRemotePeerInfo()])
await node3.connectToNodes(@[node2.switch.peerInfo.toRemotePeerInfo()])
@ -362,37 +392,19 @@ procSuite "WakuNode - RLN relay":
await sleepAsync(2000.millis)
# prepare the message payload
let payload = "Hello".toBytes()
let payload = "valid".toBytes()
# prepare the epoch
let epoch = node1.wakuRlnRelay.getCurrentEpoch()
# prepare the proof
let
contentTopicBytes = contentTopic.toBytes
input = concat(payload, contentTopicBytes)
extraBytes: seq[byte] = @[byte(1), 2, 3]
var message =
WakuMessage(payload: @payload, contentTopic: DefaultPubsubTopic, timestamp: now())
let nonceManager = node1.wakuRlnRelay.nonceManager
let rateLimitProofRes = node1.wakuRlnRelay.groupManager.generateProof(
concat(input, extraBytes), epoch, MessageId(0)
)
node1.wakuRlnRelay.unsafeAppendRLNProof(message, epoch, MessageId(0)).isOkOr:
assert false, "Failed to append rln proof: " & $error
assert rateLimitProofRes.isOk(), $rateLimitProofRes.error
# check the proof is generated correctly outside when block to avoid duplication
let rateLimitProof = rateLimitProofRes.get().encode().buffer
# message.payload = "Invalid".toBytes()
message.proof[0] = message.proof[0] xor 0x01
let message = WakuMessage(
payload: @payload,
contentTopic: contentTopic,
proof: rateLimitProof,
timestamp: now(),
)
## node1 publishes a message with an invalid rln proof, the message is then relayed to node2 which in turn
## attempts to verify the rate limit proof and fails hence does not relay the message to node3, thus the relayHandler of node3
## never gets called
## verification at node2 occurs inside a topic validator which is installed as part of the waku-rln-relay mount proc
discard await node1.publish(some(DefaultPubsubTopic), message)
await sleepAsync(2000.millis)
@ -424,86 +436,109 @@ procSuite "WakuNode - RLN relay":
assert false, "Failed to mount relay"
# mount rlnrelay in off-chain mode
let wakuRlnConfig1 = WakuRlnConfig(
dynamic: false,
credIndex: some(1.uint),
userMessageLimit: 1,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "wakunode_7"),
let wakuRlnConfig1 = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "wakunode_1"),
index = MembershipIndex(1),
)
await node1.mountRlnRelay(wakuRlnConfig1)
await node1.start()
# Registration is mandatory before sending messages with rln-relay
let manager1 = cast[OnchainGroupManager](node1.wakuRlnRelay.groupManager)
let idCredentials1 = generateCredentials(manager1.rlnInstance)
try:
waitFor manager1.register(idCredentials1, UserMessageLimit(20))
except Exception, CatchableError:
assert false,
"exception raised when calling register: " & getCurrentExceptionMsg()
let rootUpdated1 = waitFor manager1.updateRoots()
debug "Updated root for node1", rootUpdated1
# node 2
(await node2.mountRelay()).isOkOr:
assert false, "Failed to mount relay"
# mount rlnrelay in off-chain mode
let wakuRlnConfig2 = WakuRlnConfig(
dynamic: false,
credIndex: some(2.uint),
userMessageLimit: 1,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "wakunode_8"),
let wakuRlnConfig2 = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "wakunode_2"),
index = MembershipIndex(2),
)
await node2.mountRlnRelay(wakuRlnConfig2)
await node2.start()
# Registration is mandatory before sending messages with rln-relay
let manager2 = cast[OnchainGroupManager](node2.wakuRlnRelay.groupManager)
let rootUpdated2 = waitFor manager2.updateRoots()
debug "Updated root for node2", rootUpdated2
# node 3
(await node3.mountRelay()).isOkOr:
assert false, "Failed to mount relay"
# mount rlnrelay in off-chain mode
let wakuRlnConfig3 = WakuRlnConfig(
dynamic: false,
credIndex: some(3.uint),
userMessageLimit: 1,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "wakunode_9"),
let wakuRlnConfig3 = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "wakunode_3"),
index = MembershipIndex(3),
)
await node3.mountRlnRelay(wakuRlnConfig3)
await node3.start()
# Registration is mandatory before sending messages with rln-relay
let manager3 = cast[OnchainGroupManager](node3.wakuRlnRelay.groupManager)
let rootUpdated3 = waitFor manager3.updateRoots()
debug "Updated root for node3", rootUpdated3
# connect the nodes together node1 <-> node2 <-> node3
await node1.connectToNodes(@[node2.switch.peerInfo.toRemotePeerInfo()])
await node3.connectToNodes(@[node2.switch.peerInfo.toRemotePeerInfo()])
# get the current epoch time
let time_1 = epochTime()
let epoch_1 = node1.wakuRlnRelay.getCurrentEpoch()
# create some messages with rate limit proofs
var
wm1 = WakuMessage(
payload: "message 1".toBytes(), timestamp: now(), contentTopic: contentTopic
payload: "message 1".toBytes(),
timestamp: now(),
contentTopic: DefaultPubsubTopic,
)
# another message in the same epoch as wm1, it will break the messaging rate limit
wm2 = WakuMessage(
payload: "message 2".toBytes(), timestamp: now(), contentTopic: contentTopic
payload: "message 2".toBytes(),
timestamp: now(),
contentTopic: DefaultPubsubTopic,
)
# wm3 points to the next epoch
await sleepAsync(1000.millis)
let time_2 = epochTime()
let epoch_2 = node1.wakuRlnRelay.getCurrentEpoch()
var
wm3 = WakuMessage(
payload: "message 3".toBytes(), timestamp: now(), contentTopic: contentTopic
payload: "message 3".toBytes(),
timestamp: now(),
contentTopic: DefaultPubsubTopic,
)
wm4 = WakuMessage(
payload: "message 4".toBytes(), timestamp: now(), contentTopic: contentTopic
payload: "message 4".toBytes(),
timestamp: now(),
contentTopic: DefaultPubsubTopic,
)
node3.wakuRlnRelay.unsafeAppendRLNProof(wm1, time_1).isOkOr:
node1.wakuRlnRelay.unsafeAppendRLNProof(wm1, epoch_1, MessageId(0)).isOkOr:
raiseAssert $error
node3.wakuRlnRelay.unsafeAppendRLNProof(wm2, time_1).isOkOr:
node1.wakuRlnRelay.unsafeAppendRLNProof(wm2, epoch_1, MessageId(0)).isOkOr:
raiseAssert $error
node3.wakuRlnRelay.unsafeAppendRLNProof(wm3, time_2).isOkOr:
node1.wakuRlnRelay.unsafeAppendRLNProof(wm3, epoch_2, MessageId(2)).isOkOr:
raiseAssert $error
# relay handler for node3
@ -581,15 +616,42 @@ procSuite "WakuNode - RLN relay":
# Given both nodes mount relay and rlnrelay
(await node1.mountRelay()).isOkOr:
assert false, "Failed to mount relay"
let wakuRlnConfig1 = buildWakuRlnConfig(1, epochSizeSec, "wakunode_10")
(await node1.mountRlnRelay(wakuRlnConfig1)).isOkOr:
assert false, "Failed to mount rlnrelay"
let wakuRlnConfig1 = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "wakunode_1"),
index = MembershipIndex(1),
)
await node1.mountRlnRelay(wakuRlnConfig1)
await node1.start()
# Registration is mandatory before sending messages with rln-relay
let manager1 = cast[OnchainGroupManager](node1.wakuRlnRelay.groupManager)
let idCredentials1 = generateCredentials(manager1.rlnInstance)
try:
waitFor manager1.register(idCredentials1, UserMessageLimit(20))
except Exception, CatchableError:
assert false,
"exception raised when calling register: " & getCurrentExceptionMsg()
let rootUpdated1 = waitFor manager1.updateRoots()
debug "Updated root for node1", rootUpdated1
# Mount rlnrelay in node2 in off-chain mode
(await node2.mountRelay()).isOkOr:
assert false, "Failed to mount relay"
let wakuRlnConfig2 = buildWakuRlnConfig(2, epochSizeSec, "wakunode_11")
let wakuRlnConfig2 = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "wakunode_2"),
index = MembershipIndex(2),
)
await node2.mountRlnRelay(wakuRlnConfig2)
await node2.start()
# Registration is mandatory before sending messages with rln-relay
let manager2 = cast[OnchainGroupManager](node2.wakuRlnRelay.groupManager)
let rootUpdated2 = waitFor manager2.updateRoots()
debug "Updated root for node2", rootUpdated2
# Given the two nodes are started and connected
waitFor allFutures(node1.start(), node2.start())
@ -597,12 +659,18 @@ procSuite "WakuNode - RLN relay":
# Given some messages
var
wm1 = WakuMessage(payload: "message 1".toBytes(), contentTopic: contentTopic)
wm2 = WakuMessage(payload: "message 2".toBytes(), contentTopic: contentTopic)
wm3 = WakuMessage(payload: "message 3".toBytes(), contentTopic: contentTopic)
wm4 = WakuMessage(payload: "message 4".toBytes(), contentTopic: contentTopic)
wm5 = WakuMessage(payload: "message 5".toBytes(), contentTopic: contentTopic)
wm6 = WakuMessage(payload: "message 6".toBytes(), contentTopic: contentTopic)
wm1 =
WakuMessage(payload: "message 1".toBytes(), contentTopic: DefaultPubsubTopic)
wm2 =
WakuMessage(payload: "message 2".toBytes(), contentTopic: DefaultPubsubTopic)
wm3 =
WakuMessage(payload: "message 3".toBytes(), contentTopic: DefaultPubsubTopic)
wm4 =
WakuMessage(payload: "message 4".toBytes(), contentTopic: DefaultPubsubTopic)
wm5 =
WakuMessage(payload: "message 5".toBytes(), contentTopic: DefaultPubsubTopic)
wm6 =
WakuMessage(payload: "message 6".toBytes(), contentTopic: DefaultPubsubTopic)
# And node2 mounts a relay handler that completes the respective future when a message is received
var
@ -635,14 +703,26 @@ procSuite "WakuNode - RLN relay":
# Given all messages have an rln proof and are published by the node 1
let publishSleepDuration: Duration = 5000.millis
let startTime = epochTime()
let epoch_1 = node1.wakuRlnRelay.calcEpoch(epochTime().float64)
let epoch_2 = node1.wakuRlnRelay.calcEpoch(
epochTime().float64 + node1.wakuRlnRelay.rlnEpochSizeSec.float64 * 1
)
let epoch_3 = node1.wakuRlnRelay.calcEpoch(
epochTime().float64 + node1.wakuRlnRelay.rlnEpochSizeSec.float64 * 2
)
let epoch_4 = node1.wakuRlnRelay.calcEpoch(
epochTime().float64 + node1.wakuRlnRelay.rlnEpochSizeSec.float64 * 3
)
let epoch_5 = node1.wakuRlnRelay.calcEpoch(
epochTime().float64 + node1.wakuRlnRelay.rlnEpochSizeSec.float64 * 4
)
# Epoch 1
node1.wakuRlnRelay.unsafeAppendRLNProof(wm1, startTime).isOkOr:
node1.wakuRlnRelay.unsafeAppendRLNProof(wm1, epoch_1, MessageId(0)).isOkOr:
raiseAssert $error
# Message wm2 is published in the same epoch as wm1, so it'll be considered spam
node1.wakuRlnRelay.unsafeAppendRLNProof(wm2, startTime).isOkOr:
node1.wakuRlnRelay.unsafeAppendRLNProof(wm2, epoch_1, MessageId(0)).isOkOr:
raiseAssert $error
discard await node1.publish(some(DefaultPubsubTopic), wm1)
@ -654,7 +734,7 @@ procSuite "WakuNode - RLN relay":
# Epoch 2
node1.wakuRlnRelay.unsafeAppendRLNProof(wm3, startTime + float(1 * epochSizeSec)).isOkOr:
node1.wakuRlnRelay.unsafeAppendRLNProof(wm3, epoch_2, MessageId(0)).isOkOr:
raiseAssert $error
discard await node1.publish(some(DefaultPubsubTopic), wm3)
@ -666,7 +746,7 @@ procSuite "WakuNode - RLN relay":
await node2.waitForNullifierLog(2)
# Epoch 3
node1.wakuRlnRelay.unsafeAppendRLNProof(wm4, startTime + float(2 * epochSizeSec)).isOkOr:
node1.wakuRlnRelay.unsafeAppendRLNProof(wm4, epoch_3, MessageId(0)).isOkOr:
raiseAssert $error
discard await node1.publish(some(DefaultPubsubTopic), wm4)
@ -676,7 +756,7 @@ procSuite "WakuNode - RLN relay":
await node2.waitForNullifierLog(3)
# Epoch 4
node1.wakuRlnRelay.unsafeAppendRLNProof(wm5, startTime + float(3 * epochSizeSec)).isOkOr:
node1.wakuRlnRelay.unsafeAppendRLNProof(wm5, epoch_4, MessageId(0)).isOkOr:
raiseAssert $error
discard await node1.publish(some(DefaultPubsubTopic), wm5)
@ -686,7 +766,7 @@ procSuite "WakuNode - RLN relay":
await node2.waitForNullifierLog(4)
# Epoch 5
node1.wakuRlnRelay.unsafeAppendRLNProof(wm6, startTime + float(4 * epochSizeSec)).isOkOr:
node1.wakuRlnRelay.unsafeAppendRLNProof(wm6, epoch_5, MessageId(0)).isOkOr:
raiseAssert $error
discard await node1.publish(some(DefaultPubsubTopic), wm6)
@ -707,78 +787,3 @@ procSuite "WakuNode - RLN relay":
# Cleanup
waitFor allFutures(node1.stop(), node2.stop())
asyncTest "Spam Detection and Slashing (currently gossipsub score decrease)":
# Given two nodes
let
contentTopic = ContentTopic("/waku/2/default-content/proto")
shardSeq = @[DefaultRelayShard]
nodeKey1 = generateSecp256k1Key()
node1 = newTestWakuNode(nodeKey1, parseIpAddress("0.0.0.0"), Port(0))
nodeKey2 = generateSecp256k1Key()
node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), Port(0))
epochSizeSec: uint64 = 5 # This means rlnMaxEpochGap = 4
# Given both nodes mount relay and rlnrelay
# Mount rlnrelay in node1 in off-chain mode
(await node1.mountRelay()).isOkOr:
assert false, "Failed to mount relay"
let wakuRlnConfig1 = buildWakuRlnConfig(1, epochSizeSec, "wakunode_10")
await node1.mountRlnRelay(wakuRlnConfig1)
# Mount rlnrelay in node2 in off-chain mode
(await node2.mountRelay()).isOkOr:
assert false, "Failed to mount relay"
let wakuRlnConfig2 = buildWakuRlnConfig(2, epochSizeSec, "wakunode_11")
await node2.mountRlnRelay(wakuRlnConfig2)
proc simpleHandler(
topic: PubsubTopic, msg: WakuMessage
): Future[void] {.async, gcsafe.} =
await sleepAsync(0.milliseconds)
node1.subscribe((kind: PubsubSub, topic: DefaultPubsubTopic), simpleHandler).isOkOr:
assert false, "Failed to subscribe to pubsub topic in node2: " & $error
node2.subscribe((kind: PubsubSub, topic: DefaultPubsubTopic), simpleHandler).isOkOr:
assert false, "Failed to subscribe to pubsub topic in node1: " & $error
# Given the two nodes are started and connected
waitFor allFutures(node1.start(), node2.start())
await node1.connectToNodes(@[node2.switch.peerInfo.toRemotePeerInfo()])
# Given some messages with rln proofs
let time = epochTime()
var
msg1 = WakuMessage(
payload: "message 1".toBytes(), timestamp: now(), contentTopic: contentTopic
)
msg2 = WakuMessage(
payload: "message 2".toBytes(), timestamp: now(), contentTopic: contentTopic
)
node1.wakuRlnRelay.unsafeAppendRLNProof(msg1, time).isOkOr:
raiseAssert $error
# Message wm2 is published in the same epoch as wm1, so it'll be considered spam
node1.wakuRlnRelay.unsafeAppendRLNProof(msg2, time).isOkOr:
raiseAssert $error
# When publishing the first message (valid)
discard await node1.publish(some(DefaultPubsubTopic), msg1)
await sleepAsync(FUTURE_TIMEOUT_SCORING) # Wait for scoring
# Then the score of node2 should increase
check:
node1.wakuRelay.peerStats[node2.switch.peerInfo.peerId].score == 0.1
node2.wakuRelay.peerStats[node1.switch.peerInfo.peerId].score == 1.1
# When publishing the second message (spam)
discard await node1.publish(some(DefaultPubsubTopic), msg2)
await sleepAsync(FUTURE_TIMEOUT_SCORING)
# Then the score of node2 should decrease
check:
node1.wakuRelay.peerStats[node2.switch.peerInfo.peerId].score == 0.1
node2.wakuRelay.peerStats[node1.switch.peerInfo.peerId].score == -99.4
await node1.stop()
await node2.stop()

View File

@ -1,7 +1,7 @@
{.used.}
import
std/tempfiles,
std/[tempfiles, osproc],
testutils/unittests,
presto,
presto/client as presto_client,
@ -23,7 +23,8 @@ import
],
../testlib/common,
../testlib/wakucore,
../testlib/wakunode
../testlib/wakunode,
../waku_rln_relay/[rln/waku_rln_relay_utils, utils_onchain]
proc testWakuNode(): WakuNode =
let
@ -36,6 +37,16 @@ proc testWakuNode(): WakuNode =
suite "Waku v2 REST API - health":
# TODO: better test for health
var anvilProc {.threadVar.}: Process
var manager {.threadVar.}: OnchainGroupManager
setup:
anvilProc = runAnvil()
manager = waitFor setupOnchainGroupManager()
teardown:
stopAnvil(anvilProc)
asyncTest "Get node health info - GET /health":
# Given
let node = testWakuNode()
@ -67,11 +78,10 @@ suite "Waku v2 REST API - health":
# now kick in rln (currently the only check for health)
await node.mountRlnRelay(
WakuRlnConfig(
dynamic: false,
credIndex: some(1.uint),
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "wakunode"),
getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "wakunode"),
index = MembershipIndex(1),
)
)

View File

@ -1,7 +1,7 @@
{.used.}
import
std/[sequtils, strformat, tempfiles],
std/[sequtils, strformat, tempfiles, osproc],
stew/byteutils,
testutils/unittests,
presto,
@ -24,7 +24,8 @@ import
],
../testlib/wakucore,
../testlib/wakunode,
../resources/payloads
../resources/payloads,
../waku_rln_relay/[rln/waku_rln_relay_utils, utils_onchain]
proc testWakuNode(): WakuNode =
let
@ -36,6 +37,16 @@ proc testWakuNode(): WakuNode =
newTestWakuNode(privkey, bindIp, port, some(extIp), some(port))
suite "Waku v2 Rest API - Relay":
var anvilProc {.threadVar.}: Process
var manager {.threadVar.}: OnchainGroupManager
setup:
anvilProc = runAnvil()
manager = waitFor setupOnchainGroupManager()
teardown:
stopAnvil(anvilProc)
asyncTest "Subscribe a node to an array of pubsub topics - POST /relay/v1/subscriptions":
# Given
let node = testWakuNode()
@ -246,18 +257,33 @@ suite "Waku v2 Rest API - Relay":
## "Relay API: publish and subscribe/unsubscribe":
# Given
let node = testWakuNode()
await node.start()
(await node.mountRelay()).isOkOr:
assert false, "Failed to mount relay"
let wakuRlnConfig = WakuRlnConfig(
dynamic: false,
credIndex: some(1.uint),
userMessageLimit: 20,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "wakunode_1"),
let wakuRlnConfig = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "waku_rest_relay_1"),
index = MembershipIndex(1),
)
await node.mountRlnRelay(wakuRlnConfig)
await node.start()
# Registration is mandatory before sending messages with rln-relay
let manager = cast[OnchainGroupManager](node.wakuRlnRelay.groupManager)
let idCredentials = generateCredentials(manager.rlnInstance)
try:
waitFor manager.register(idCredentials, UserMessageLimit(20))
except Exception, CatchableError:
assert false,
"exception raised when calling register: " & getCurrentExceptionMsg()
let rootUpdated = waitFor manager.updateRoots()
debug "Updated root for node", rootUpdated
let proofRes = waitFor manager.fetchMerkleProofElements()
if proofRes.isErr():
assert false, "failed to fetch merkle proof: " & proofRes.error
manager.merkleProofCache = proofRes.get()
# RPC server setup
var restPort = Port(0)
@ -484,20 +510,35 @@ suite "Waku v2 Rest API - Relay":
## "Relay API: publish and subscribe/unsubscribe":
# Given
let node = testWakuNode()
await node.start()
(await node.mountRelay()).isOkOr:
assert false, "Failed to mount relay"
require node.mountAutoSharding(1, 8).isOk
let wakuRlnConfig = WakuRlnConfig(
dynamic: false,
credIndex: some(1.uint),
userMessageLimit: 20,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "wakunode_1"),
let wakuRlnConfig = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "waku_rest_relay_1"),
index = MembershipIndex(1),
)
await node.mountRlnRelay(wakuRlnConfig)
await node.start()
# Registration is mandatory before sending messages with rln-relay
let manager = cast[OnchainGroupManager](node.wakuRlnRelay.groupManager)
let idCredentials = generateCredentials(manager.rlnInstance)
try:
waitFor manager.register(idCredentials, UserMessageLimit(20))
except Exception, CatchableError:
assert false,
"exception raised when calling register: " & getCurrentExceptionMsg()
let rootUpdated = waitFor manager.updateRoots()
debug "Updated root for node", rootUpdated
let proofRes = waitFor manager.fetchMerkleProofElements()
if proofRes.isErr():
assert false, "failed to fetch merkle proof: " & proofRes.error
manager.merkleProofCache = proofRes.get()
# RPC server setup
var restPort = Port(0)
@ -545,20 +586,35 @@ suite "Waku v2 Rest API - Relay":
## "Relay API: publish and subscribe/unsubscribe":
# Given
let node = testWakuNode()
await node.start()
(await node.mountRelay()).isOkOr:
assert false, "Failed to mount relay"
require node.mountAutoSharding(1, 8).isOk
let wakuRlnConfig = WakuRlnConfig(
dynamic: false,
credIndex: some(1.uint),
userMessageLimit: 20,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "wakunode_1"),
let wakuRlnConfig = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "waku_rest_relay_1"),
index = MembershipIndex(1),
)
await node.mountRlnRelay(wakuRlnConfig)
await node.start()
# Registration is mandatory before sending messages with rln-relay
let manager = cast[OnchainGroupManager](node.wakuRlnRelay.groupManager)
let idCredentials = generateCredentials(manager.rlnInstance)
try:
waitFor manager.register(idCredentials, UserMessageLimit(20))
except Exception, CatchableError:
assert false,
"exception raised when calling register: " & getCurrentExceptionMsg()
let rootUpdated = waitFor manager.updateRoots()
debug "Updated root for node", rootUpdated
let proofRes = waitFor manager.fetchMerkleProofElements()
if proofRes.isErr():
assert false, "failed to fetch merkle proof: " & proofRes.error
manager.merkleProofCache = proofRes.get()
# RPC server setup
var restPort = Port(0)
@ -598,18 +654,33 @@ suite "Waku v2 Rest API - Relay":
asyncTest "Post a message larger than maximum size - POST /relay/v1/messages/{topic}":
# Given
let node = testWakuNode()
await node.start()
(await node.mountRelay()).isOkOr:
assert false, "Failed to mount relay"
let wakuRlnConfig = WakuRlnConfig(
dynamic: false,
credIndex: some(1.uint),
userMessageLimit: 20,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "wakunode_1"),
let wakuRlnConfig = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "waku_rest_relay_1"),
index = MembershipIndex(1),
)
await node.mountRlnRelay(wakuRlnConfig)
await node.start()
# Registration is mandatory before sending messages with rln-relay
let manager = cast[OnchainGroupManager](node.wakuRlnRelay.groupManager)
let idCredentials = generateCredentials(manager.rlnInstance)
try:
waitFor manager.register(idCredentials, UserMessageLimit(20))
except Exception, CatchableError:
assert false,
"exception raised when calling register: " & getCurrentExceptionMsg()
let rootUpdated = waitFor manager.updateRoots()
debug "Updated root for node", rootUpdated
let proofRes = waitFor manager.fetchMerkleProofElements()
if proofRes.isErr():
assert false, "failed to fetch merkle proof: " & proofRes.error
manager.merkleProofCache = proofRes.get()
# RPC server setup
var restPort = Port(0)
@ -660,20 +731,35 @@ suite "Waku v2 Rest API - Relay":
asyncTest "Post a message larger than maximum size - POST /relay/v1/auto/messages/{topic}":
# Given
let node = testWakuNode()
await node.start()
(await node.mountRelay()).isOkOr:
assert false, "Failed to mount relay"
require node.mountAutoSharding(1, 8).isOk
let wakuRlnConfig = WakuRlnConfig(
dynamic: false,
credIndex: some(1.uint),
userMessageLimit: 20,
epochSizeSec: 1,
treePath: genTempPath("rln_tree", "wakunode_1"),
let wakuRlnConfig = getWakuRlnConfig(
manager = manager,
treePath = genTempPath("rln_tree", "waku_rest_relay_1"),
index = MembershipIndex(1),
)
await node.mountRlnRelay(wakuRlnConfig)
await node.start()
# Registration is mandatory before sending messages with rln-relay
let manager = cast[OnchainGroupManager](node.wakuRlnRelay.groupManager)
let idCredentials = generateCredentials(manager.rlnInstance)
try:
waitFor manager.register(idCredentials, UserMessageLimit(20))
except Exception, CatchableError:
assert false,
"exception raised when calling register: " & getCurrentExceptionMsg()
let rootUpdated = waitFor manager.updateRoots()
debug "Updated root for node", rootUpdated
let proofRes = waitFor manager.fetchMerkleProofElements()
if proofRes.isErr():
assert false, "failed to fetch merkle proof: " & proofRes.error
manager.merkleProofCache = proofRes.get()
# RPC server setup
var restPort = Port(0)

View File

@ -18,7 +18,7 @@ proc doInspectRlnDb*(conf: InspectRlnDbConf) =
trace "configuration", conf = $conf
# 2. initialize rlnInstance
let rlnInstance = createRLNInstance(d = 20, tree_path = conf.treePath).valueOr:
let rlnInstance = createRLNInstance(d = 20).valueOr:
error "failure while creating RLN instance", error
quit(1)
@ -38,29 +38,4 @@ proc doInspectRlnDb*(conf: InspectRlnDbConf) =
contractAddress = metadata.contractAddress,
validRoots = metadata.validRoots.mapIt(it.inHex())
var index: uint = 0
var hits: uint = 0
var zeroLeafIndices: seq[uint] = @[]
var assumeEmptyAfter: uint = 10
while true:
let leaf = rlnInstance.getMember(index).valueOr:
error "failure while getting RLN leaf", error
quit(1)
if leaf.inHex() == "0000000000000000000000000000000000000000000000000000000000000000":
zeroLeafIndices.add(index)
hits = hits + 1
else:
hits = 0
if hits > assumeEmptyAfter:
info "reached end of RLN tree", index = index - assumeEmptyAfter
# remove zeroLeafIndices that are not at the end of the tree
zeroLeafIndices = zeroLeafIndices.filterIt(it < index - assumeEmptyAfter)
break
index = index + 1
info "zero leaf indices", zeroLeafIndices
quit(0)

View File

@ -31,9 +31,7 @@ proc doRlnKeystoreGenerator*(conf: RlnKeystoreGeneratorConf) =
trace "configuration", conf = $conf
# 2. initialize rlnInstance
let rlnInstanceRes = createRLNInstance(
d = 20, tree_path = genTempPath("rln_tree", "rln_keystore_generator")
)
let rlnInstanceRes = createRLNInstance(d = 20)
if rlnInstanceRes.isErr():
error "failure while creating RLN instance", error = rlnInstanceRes.error
quit(1)

View File

@ -116,7 +116,7 @@ task wakunode2, "Build Waku v2 cli node":
task benchmarks, "Some benchmarks":
let name = "benchmarks"
buildBinary name, "apps/benchmarks/"
buildBinary name, "apps/benchmarks/", "-p:../.."
task wakucanary, "Build waku-canary tool":
let name = "wakucanary"

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,3 @@
import group_manager/[static, on_chain]
import group_manager/[on_chain]
export static, on_chain
export on_chain

View File

@ -119,58 +119,23 @@ method stop*(g: GroupManager): Future[void] {.base, async.} =
method onWithdraw*(g: GroupManager, cb: OnWithdrawCallback) {.base, gcsafe.} =
g.withdrawCb = some(cb)
proc slideRootQueue*(
rootQueue: var Deque[MerkleNode], root: MerkleNode
): seq[MerkleNode] =
## updates the root queue with the latest root and pops the oldest one when the capacity of `AcceptableRootWindowSize` is reached
let overflowCount = rootQueue.len - AcceptableRootWindowSize + 1
var overflowedRoots = newSeq[MerkleNode]()
if overflowCount > 0:
# Delete the oldest `overflowCount` roots in the deque (index 0..`overflowCount`)
# insert into overflowedRoots seq and return
for i in 0 ..< overflowCount:
overFlowedRoots.add(rootQueue.popFirst())
# Push the next root into the queue
rootQueue.addLast(root)
return overFlowedRoots
method indexOfRoot*(
g: GroupManager, root: MerkleNode
): int {.base, gcsafe, raises: [].} =
## returns the index of the root in the merkle tree.
## returns -1 if the root is not found
## returns the index of the root in the merkle tree and returns -1 if the root is not found
return g.validRoots.find(root)
method validateRoot*(
g: GroupManager, root: MerkleNode
): bool {.base, gcsafe, raises: [].} =
## validates the root against the valid roots queue
if g.indexOfRoot(root) >= 0:
return true
return false
template slideRootQueue*(g: GroupManager): untyped =
let rootRes = g.rlnInstance.getMerkleRoot()
if rootRes.isErr():
raise newException(ValueError, "failed to get merkle root")
let rootAfterUpdate = rootRes.get()
var rootBuffer: Deque[MerkleNode]
let overflowedRoots = slideRootQueue(g.validRoots, rootAfterUpdate)
if overflowedRoots.len > 0:
for root in overflowedRoots:
discard rootBuffer.slideRootQueue(root)
rootBuffer
return g.indexOfRoot(root) >= 0
method verifyProof*(
g: GroupManager, input: openArray[byte], proof: RateLimitProof
g: GroupManager, input: seq[byte], proof: RateLimitProof
): GroupManagerResult[bool] {.base, gcsafe, raises: [].} =
## verifies the proof against the input and the current merkle root
let proofVerifyRes =
g.rlnInstance.proofVerify(input, proof, g.validRoots.items().toSeq())
if proofVerifyRes.isErr():
return err("proof verification failed: " & $proofVerifyRes.error())
return ok(proofVerifyRes.value())
## Dummy implementation for verifyProof
return err("verifyProof is not implemented")
method generateProof*(
g: GroupManager,
@ -179,29 +144,8 @@ method generateProof*(
messageId: MessageId,
rlnIdentifier = DefaultRlnIdentifier,
): GroupManagerResult[RateLimitProof] {.base, gcsafe, raises: [].} =
var lastProcessedEpoch {.global.}: Epoch
## generates a proof for the given data and epoch
## the proof is generated using the current merkle root
if g.idCredentials.isNone():
return err("identity credentials are not set")
if g.membershipIndex.isNone():
return err("membership index is not set")
if g.userMessageLimit.isNone():
return err("user message limit is not set")
waku_rln_proof_generation_duration_seconds.nanosecondTime:
let proof = proofGen(
rlnInstance = g.rlnInstance,
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)
## Dummy implementation for generateProof
return err("generateProof is not implemented")
method isReady*(g: GroupManager): Future[bool] {.base, async.} =
raise newException(

View File

@ -173,11 +173,6 @@ template retryWrapper(
retryWrapper(res, RetryStrategy.new(), errStr, g.onFatalErrorAction):
body
method validateRoot*(g: OnchainGroupManager, root: MerkleNode): bool =
if g.validRoots.find(root) >= 0:
return true
return false
proc updateRoots*(g: OnchainGroupManager): Future[bool] {.async.} =
let rootRes = await g.fetchMerkleRoot()
if rootRes.isErr():
@ -200,21 +195,17 @@ proc updateRoots*(g: OnchainGroupManager): Future[bool] {.async.} =
proc trackRootChanges*(g: OnchainGroupManager) {.async: (raises: [CatchableError]).} =
try:
initializedGuard(g)
let ethRpc = g.ethRpc.get()
let wakuRlnContract = g.wakuRlnContract.get()
const rpcDelay = 5.seconds
while true:
await sleepAsync(rpcDelay)
let rootUpdated = await g.updateRoots()
if rootUpdated:
if g.membershipIndex.isNone():
error "membershipIndex is not set; skipping proof update"
let proofResult = await g.fetchMerkleProofElements()
if proofResult.isErr():
error "Failed to fetch Merkle proof", error = proofResult.error
else:
let proofResult = await g.fetchMerkleProofElements()
if proofResult.isErr():
error "Failed to fetch Merkle proof", error = proofResult.error
g.merkleProofCache = proofResult.get()
let nextFreeIndex = await g.fetchNextFreeIndex()
@ -226,8 +217,6 @@ proc trackRootChanges*(g: OnchainGroupManager) {.async: (raises: [CatchableError
let memberCount = cast[int64](nextFreeIndex.get())
waku_rln_number_registered_memberships.set(float64(memberCount))
await sleepAsync(rpcDelay)
except CatchableError:
error "Fatal error in trackRootChanges", error = getCurrentExceptionMsg()
@ -457,7 +446,7 @@ method generateProof*(
nullifier: nullifier,
)
debug "Proof generated successfully"
debug "Proof generated successfully", proof = output
waku_rln_remaining_proofs_per_epoch.dec()
waku_rln_total_generated_proofs.inc()
@ -493,7 +482,7 @@ method verifyProof*(
if not ffiOk:
return err("could not verify the proof")
else:
trace "Proof verified successfully !"
debug "Proof verified successfully"
return ok(validProof)

View File

@ -1,3 +0,0 @@
import static/group_manager
export group_manager

View File

@ -1,144 +0,0 @@
import ../group_manager_base, ../../rln, std/sequtils, ../../constants
export group_manager_base
type StaticGroupManager* = ref object of GroupManager
groupKeys*: seq[IdentityCredential]
groupSize*: uint
template initializedGuard*(g: StaticGroupManager): untyped =
if not g.initialized:
raise newException(ValueError, "StaticGroupManager is not initialized")
proc resultifiedInitGuard(g: StaticGroupManager): GroupManagerResult[void] =
try:
initializedGuard(g)
return ok()
except CatchableError:
return err("StaticGroupManager is not initialized")
method init*(g: StaticGroupManager): Future[GroupManagerResult[void]] {.async.} =
let
groupSize = g.groupSize
groupKeys = g.groupKeys
membershipIndex =
if g.membershipIndex.isSome():
g.membershipIndex.get()
else:
return err("membershipIndex is not set")
if membershipIndex < MembershipIndex(0) or
membershipIndex >= MembershipIndex(groupSize):
return err(
"Invalid membership index. Must be within 0 and " & $(groupSize - 1) & "but was " &
$membershipIndex
)
g.userMessageLimit = some(DefaultUserMessageLimit)
g.idCredentials = some(groupKeys[membershipIndex])
# Seed the received commitments into the merkle tree
let rateCommitments = groupKeys.mapIt(
RateCommitment(
idCommitment: it.idCommitment, userMessageLimit: g.userMessageLimit.get()
)
)
let leaves = rateCommitments.toLeaves().valueOr:
return err("Failed to convert rate commitments to leaves: " & $error)
let membersInserted = g.rlnInstance.insertMembers(g.latestIndex, leaves)
discard g.slideRootQueue()
g.latestIndex += MembershipIndex(groupKeys.len - 1)
g.initialized = true
return ok()
method startGroupSync*(
g: StaticGroupManager
): Future[GroupManagerResult[void]] {.async.} =
?g.resultifiedInitGuard()
# No-op
return ok()
method register*(
g: StaticGroupManager, rateCommitment: RateCommitment
): Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
let leaf = rateCommitment.toLeaf().get()
await g.registerBatch(@[leaf])
method registerBatch*(
g: StaticGroupManager, rateCommitments: seq[RawRateCommitment]
): Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
let membersInserted = g.rlnInstance.insertMembers(g.latestIndex + 1, rateCommitments)
if not membersInserted:
raise newException(ValueError, "Failed to insert members into the merkle tree")
if g.registerCb.isSome():
var memberSeq = newSeq[Membership]()
for i in 0 ..< rateCommitments.len:
memberSeq.add(
Membership(
rateCommitment: rateCommitments[i],
index: g.latestIndex + MembershipIndex(i) + 1,
)
)
await g.registerCb.get()(memberSeq)
discard g.slideRootQueue()
g.latestIndex += MembershipIndex(rateCommitments.len)
return
method withdraw*(
g: StaticGroupManager, idSecretHash: IdentitySecretHash
): Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
let groupKeys = g.groupKeys
for i in 0 ..< groupKeys.len:
if groupKeys[i].idSecretHash == idSecretHash:
let idCommitment = groupKeys[i].idCommitment
let index = MembershipIndex(i)
let rateCommitment = RateCommitment(
idCommitment: idCommitment, userMessageLimit: g.userMessageLimit.get()
).toLeaf().valueOr:
raise newException(ValueError, "Failed to parse rateCommitment")
let memberRemoved = g.rlnInstance.removeMember(index)
if not memberRemoved:
raise newException(ValueError, "Failed to remove member from the merkle tree")
if g.withdrawCb.isSome():
let withdrawCb = g.withdrawCb.get()
await withdrawCb(@[Membership(rateCommitment: rateCommitment, index: index)])
return
method withdrawBatch*(
g: StaticGroupManager, idSecretHashes: seq[IdentitySecretHash]
): Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
# call withdraw on each idSecretHash
for idSecretHash in idSecretHashes:
await g.withdraw(idSecretHash)
method onRegister*(g: StaticGroupManager, cb: OnRegisterCallback) {.gcsafe.} =
g.registerCb = some(cb)
method onWithdraw*(g: StaticGroupManager, cb: OnWithdrawCallback) {.gcsafe.} =
g.withdrawCb = some(cb)
method stop*(g: StaticGroupManager): Future[void] {.async.} =
initializedGuard(g)
# No-op
method isReady*(g: StaticGroupManager): Future[bool] {.async.} =
initializedGuard(g)
return true

View File

@ -21,81 +21,6 @@ proc toBuffer*(x: openArray[byte]): Buffer =
## RLN Zerokit module APIs
######################################################################
#------------------------------ Merkle Tree operations -----------------------------------------
proc update_next_member*(
ctx: ptr RLN, input_buffer: ptr Buffer
): bool {.importc: "set_next_leaf".}
## adds an element in the merkle tree to the next available position
## input_buffer points to the id commitment byte seq
## the return bool value indicates the success or failure of the operation
proc delete_member*(ctx: ptr RLN, index: uint): bool {.importc: "delete_leaf".}
## index is the position of the id commitment key to be deleted from the tree
## the deleted id commitment key is replaced with a zero leaf
## the return bool value indicates the success or failure of the operation
proc get_root*(ctx: ptr RLN, output_buffer: ptr Buffer): bool {.importc: "get_root".}
## get_root populates the passed pointer output_buffer with the current tree root
## the output_buffer holds the Merkle tree root of size 32 bytes
## the return bool value indicates the success or failure of the operation
proc get_merkle_proof*(
ctx: ptr RLN, index: uint, output_buffer: ptr Buffer
): bool {.importc: "get_proof".}
## populates the passed pointer output_buffer with the merkle proof for the leaf at position index in the tree stored by ctx
## the output_buffer holds a serialized Merkle proof (vector of 32 bytes nodes)
## the return bool value indicates the success or failure of the operation
proc set_leaf*(
ctx: ptr RLN, index: uint, input_buffer: ptr Buffer
): bool {.importc: "set_leaf".}
## sets the leaf at position index in the tree stored by ctx to the value passed by input_buffer
## the input_buffer holds a serialized leaf of 32 bytes
## the return bool value indicates the success or failure of the operation
proc get_leaf*(
ctx: ptr RLN, index: uint, output_buffer: ptr Buffer
): bool {.importc: "get_leaf".}
## gets the leaf at position index in the tree stored by ctx
## the output_buffer holds a serialized leaf of 32 bytes
## the return bool value indicates the success or failure of the operation
proc leaves_set*(ctx: ptr RLN): uint {.importc: "leaves_set".}
## gets the number of leaves set in the tree stored by ctx
## the return uint value indicates the number of leaves set in the tree
proc init_tree_with_leaves*(
ctx: ptr RLN, input_buffer: ptr Buffer
): bool {.importc: "init_tree_with_leaves".}
## sets multiple leaves in the tree stored by ctx to the value passed by input_buffer
## the input_buffer holds a serialized vector of leaves (32 bytes each)
## the input_buffer size is prefixed by a 8 bytes integer indicating the number of leaves
## leaves are set one after each other starting from index 0
## the return bool value indicates the success or failure of the operation
proc atomic_write*(
ctx: ptr RLN, index: uint, leaves_buffer: ptr Buffer, indices_buffer: ptr Buffer
): bool {.importc: "atomic_operation".}
## sets multiple leaves, and zeroes out indices in the tree stored by ctx to the value passed by input_buffer
## the leaves_buffer holds a serialized vector of leaves (32 bytes each)
## the leaves_buffer size is prefixed by a 8 bytes integer indicating the number of leaves
## the indices_bufffer holds a serialized vector of indices (8 bytes each)
## the indices_buffer size is prefixed by a 8 bytes integer indicating the number of indices
## leaves are set one after each other starting from index `index`
## the return bool value indicates the success or failure of the operation
proc reset_tree*(ctx: ptr RLN, tree_height: uint): bool {.importc: "set_tree".}
## resets the tree stored by ctx to the empty tree (all leaves set to 0) of height tree_height
## the return bool value indicates the success or failure of the operation
#----------------------------------------------------------------------------------------------
#-------------------------------- zkSNARKs operations -----------------------------------------
proc key_gen*(
ctx: ptr RLN, output_buffer: ptr Buffer

View File

@ -98,7 +98,7 @@ proc createRLNInstanceLocal(
mode: "high_throughput",
compression: false,
flush_every_ms: 500,
path: if tree_path != "": tree_path else: DefaultRlnTreePath,
path: tree_path,
),
)
@ -201,214 +201,6 @@ proc extractMetadata*(proof: RateLimitProof): RlnRelayResult[ProofMetadata] =
)
)
proc proofGen*(
rlnInstance: ptr RLN,
data: openArray[byte],
membership: IdentityCredential,
userMessageLimit: UserMessageLimit,
messageId: MessageId,
index: MembershipIndex,
epoch: Epoch,
rlnIdentifier = DefaultRlnIdentifier,
): RateLimitProofResult =
# obtain the external nullifier
let externalNullifierRes = poseidon(@[@(epoch), @(rlnIdentifier)])
if externalNullifierRes.isErr():
return err("could not construct the external nullifier")
# serialize inputs
let serializedInputs = serialize(
idSecretHash = membership.idSecretHash,
memIndex = index,
userMessageLimit = userMessageLimit,
messageId = messageId,
externalNullifier = externalNullifierRes.get(),
msg = data,
)
var inputBuffer = toBuffer(serializedInputs)
debug "input buffer ", inputBuffer = repr(inputBuffer)
# generate the proof
var proof: Buffer
let proofIsSuccessful = generate_proof(rlnInstance, addr inputBuffer, addr proof)
# check whether the generate_proof call is done successfully
if not proofIsSuccessful:
return err("could not generate the proof")
var proofValue = cast[ptr array[320, byte]](proof.`ptr`)
let proofBytes: array[320, byte] = proofValue[]
debug "proof content", proofHex = proofValue[].toHex
## parse the proof as [ proof<128> | root<32> | external_nullifier<32> | share_x<32> | share_y<32> | nullifier<32> ]
let
proofOffset = 128
rootOffset = proofOffset + 32
externalNullifierOffset = rootOffset + 32
shareXOffset = externalNullifierOffset + 32
shareYOffset = shareXOffset + 32
nullifierOffset = shareYOffset + 32
var
zkproof: ZKSNARK
proofRoot, shareX, shareY: MerkleNode
externalNullifier: ExternalNullifier
nullifier: Nullifier
discard zkproof.copyFrom(proofBytes[0 .. proofOffset - 1])
discard proofRoot.copyFrom(proofBytes[proofOffset .. rootOffset - 1])
discard
externalNullifier.copyFrom(proofBytes[rootOffset .. externalNullifierOffset - 1])
discard shareX.copyFrom(proofBytes[externalNullifierOffset .. shareXOffset - 1])
discard shareY.copyFrom(proofBytes[shareXOffset .. shareYOffset - 1])
discard nullifier.copyFrom(proofBytes[shareYOffset .. nullifierOffset - 1])
let output = RateLimitProof(
proof: zkproof,
merkleRoot: proofRoot,
externalNullifier: externalNullifier,
epoch: epoch,
rlnIdentifier: rlnIdentifier,
shareX: shareX,
shareY: shareY,
nullifier: nullifier,
)
return ok(output)
# validRoots should contain a sequence of roots in the acceptable windows.
# As default, it is set to an empty sequence of roots. This implies that the validity check for the proof's root is skipped
proc proofVerify*(
rlnInstance: ptr RLN,
data: openArray[byte],
proof: RateLimitProof,
validRoots: seq[MerkleNode] = @[],
): RlnRelayResult[bool] =
## verifies the proof, returns an error if the proof verification fails
## returns true if the proof is valid
var normalizedProof = proof
# when we do this, we ensure that we compute the proof for the derived value
# of the externalNullifier. The proof verification will fail if a malicious peer
# attaches invalid epoch+rlnidentifier pair
normalizedProof.externalNullifier = poseidon(
@[@(proof.epoch), @(proof.rlnIdentifier)]
).valueOr:
return err("could not construct the external nullifier")
var
proofBytes = serialize(normalizedProof, data)
proofBuffer = proofBytes.toBuffer()
validProof: bool
rootsBytes = serialize(validRoots)
rootsBuffer = rootsBytes.toBuffer()
trace "serialized proof", proof = byteutils.toHex(proofBytes)
let verifyIsSuccessful =
verify_with_roots(rlnInstance, addr proofBuffer, addr rootsBuffer, addr validProof)
if not verifyIsSuccessful:
# something went wrong in verification call
warn "could not verify validity of the proof", proof = proof
return err("could not verify the proof")
if not validProof:
return ok(false)
else:
return ok(true)
proc insertMember*(rlnInstance: ptr RLN, idComm: IDCommitment): bool =
## inserts a member to the tree
## returns true if the member is inserted successfully
## returns false if the member could not be inserted
var pkBuffer = toBuffer(idComm)
let pkBufferPtr = addr pkBuffer
# add the member to the tree
let memberAdded = update_next_member(rlnInstance, pkBufferPtr)
return memberAdded
proc getMember*(
rlnInstance: ptr RLN, index: MembershipIndex
): RlnRelayResult[IDCommitment] =
## returns the member at the given index
## returns an error if the index is out of bounds
## returns the member if the index is valid
var
idCommitment {.noinit.}: Buffer = Buffer()
idCommitmentPtr = addr(idCommitment)
memberRetrieved = get_leaf(rlnInstance, index, idCommitmentPtr)
if not memberRetrieved:
return err("could not get the member")
if not idCommitment.len == 32:
return err("wrong output size")
let idCommitmentValue = (cast[ptr array[32, byte]](idCommitment.`ptr`))[]
return ok(@idCommitmentValue)
proc atomicWrite*(
rlnInstance: ptr RLN,
index = none(MembershipIndex),
idComms = newSeq[IDCommitment](),
toRemoveIndices = newSeq[MembershipIndex](),
): bool =
## Insert multiple members i.e., identity commitments, and remove multiple members
## returns true if the operation is successful
## returns false if the operation fails
let startIndex =
if index.isNone():
MembershipIndex(0)
else:
index.get()
# serialize the idComms
let idCommsBytes = serialize(idComms)
var idCommsBuffer = idCommsBytes.toBuffer()
let idCommsBufferPtr = addr idCommsBuffer
# serialize the toRemoveIndices
let indicesBytes = serialize(toRemoveIndices)
var indicesBuffer = indicesBytes.toBuffer()
let indicesBufferPtr = addr indicesBuffer
let operationSuccess =
atomic_write(rlnInstance, startIndex, idCommsBufferPtr, indicesBufferPtr)
return operationSuccess
proc insertMembers*(
rlnInstance: ptr RLN, index: MembershipIndex, idComms: seq[IDCommitment]
): bool =
## Insert multiple members i.e., identity commitments
## returns true if the insertion is successful
## returns false if any of the insertions fails
## Note: This proc is atomic, i.e., if any of the insertions fails, all the previous insertions are rolled back
return atomicWrite(rlnInstance, some(index), idComms)
proc removeMember*(rlnInstance: ptr RLN, index: MembershipIndex): bool =
let deletionSuccess = delete_member(rlnInstance, index)
return deletionSuccess
proc removeMembers*(rlnInstance: ptr RLN, indices: seq[MembershipIndex]): bool =
return atomicWrite(rlnInstance, idComms = @[], toRemoveIndices = indices)
proc getMerkleRoot*(rlnInstance: ptr RLN): MerkleNodeResult =
# read the Merkle Tree root after insertion
var
root {.noinit.}: Buffer = Buffer()
rootPtr = addr(root)
getRootSuccessful = getRoot(rlnInstance, rootPtr)
if not getRootSuccessful:
return err("could not get the root")
if not root.len == 32:
return err("wrong output size")
var rootValue = cast[ptr MerkleNode](root.`ptr`)[]
return ok(rootValue)
type RlnMetadata* = object
lastProcessedBlock*: uint64
chainId*: UInt256

View File

@ -49,45 +49,11 @@ type RlnRelayConf* = object of RootObj
treePath*: string
epochSizeSec*: uint64
userMessageLimit*: uint64
ethPrivateKey*: Option[string]
type WakuRlnConfig* = object of RlnRelayConf
onFatalErrorAction*: OnFatalErrorHandler
proc createMembershipList*(
rln: ptr RLN, n: int
): RlnRelayResult[(seq[RawMembershipCredentials], string)] =
## createMembershipList produces a sequence of identity credentials in the form of (identity trapdoor, identity nullifier, identity secret hash, id commitment) in the hexadecimal format
## this proc also returns the root of a Merkle tree constructed out of the identity commitment keys of the generated list
## the output of this proc is used to initialize a static group keys (to test waku-rln-relay in the off-chain mode)
## Returns an error if it cannot create the membership list
var output = newSeq[RawMembershipCredentials]()
var idCommitments = newSeq[IDCommitment]()
for i in 0 .. n - 1:
# generate an identity credential
let idCredentialRes = rln.membershipKeyGen()
if idCredentialRes.isErr():
return
err("could not generate an identity credential: " & idCredentialRes.error())
let idCredential = idCredentialRes.get()
let idTuple = (
idCredential.idTrapdoor.inHex(),
idCredential.idNullifier.inHex(),
idCredential.idSecretHash.inHex(),
idCredential.idCommitment.inHex(),
)
output.add(idTuple)
idCommitments.add(idCredential.idCommitment)
# Insert members into tree
let membersAdded = rln.insertMembers(0, idCommitments)
if not membersAdded:
return err("could not insert members into the tree")
let root = rln.getMerkleRoot().value().inHex()
return ok((output, root))
type WakuRLNRelay* = ref object of RootObj
# the log of nullifiers and Shamir shares of the past messages grouped per epoch
nullifierLog*: OrderedTable[Epoch, Table[Nullifier, ProofMetadata]]
@ -176,7 +142,6 @@ proc updateLog*(
err("the epoch was not found: " & getCurrentExceptionMsg()) # should never happen
proc getCurrentEpoch*(rlnPeer: WakuRLNRelay): Epoch =
## gets the current rln Epoch time
return rlnPeer.calcEpoch(epochTime())
proc absDiff*(e1, e2: Epoch): uint64 =
@ -194,6 +159,16 @@ proc absDiff*(e1, e2: Epoch): uint64 =
else:
return epoch2 - epoch1
proc toRLNSignal*(wakumessage: WakuMessage): seq[byte] =
## it is a utility proc that prepares the `data` parameter of the proof generation procedure i.e., `proofGen` that resides in the current module
## it extracts the `contentTopic`, `timestamp` and the `payload` of the supplied `wakumessage` and serializes them into a byte sequence
let
contentTopicBytes = toBytes(wakumessage.contentTopic)
timestampBytes = toBytes(wakumessage.timestamp.uint64)
output = concat(wakumessage.payload, contentTopicBytes, @(timestampBytes))
return output
proc validateMessage*(
rlnPeer: WakuRLNRelay, msg: WakuMessage
): MessageValidationResult =
@ -251,7 +226,8 @@ proc validateMessage*(
waku_rln_proof_verification_total.inc()
waku_rln_proof_verification_duration_seconds.nanosecondTime:
let proofVerificationRes = rlnPeer.groupManager.verifyProof(input, proof)
let proofVerificationRes =
rlnPeer.groupManager.verifyProof(msg.toRLNSignal(), proof)
if proofVerificationRes.isErr():
waku_rln_errors_total.inc(labelValues = ["proof_verification"])
@ -309,16 +285,6 @@ proc validateMessageAndUpdateLog*(
return isValidMessage
proc toRLNSignal*(wakumessage: WakuMessage): seq[byte] =
## it is a utility proc that prepares the `data` parameter of the proof generation procedure i.e., `proofGen` that resides in the current module
## it extracts the `contentTopic`, `timestamp` and the `payload` of the supplied `wakumessage` and serializes them into a byte sequence
let
contentTopicBytes = toBytes(wakumessage.contentTopic)
timestampBytes = toBytes(wakumessage.timestamp.uint64)
output = concat(wakumessage.payload, contentTopicBytes, @(timestampBytes))
return output
proc appendRLNProof*(
rlnPeer: WakuRLNRelay, msg: var WakuMessage, senderEpochTime: float64
): RlnRelayResult[void] =
@ -445,38 +411,25 @@ proc mount(
let rlnInstance = createRLNInstance(tree_path = conf.treePath).valueOr:
return err("could not create RLN instance: " & $error)
if not conf.dynamic:
# static setup
let parsedGroupKeys = StaticGroupKeys.toIdentityCredentials().valueOr:
return err("could not parse static group keys: " & $error)
let (rlnRelayCredPath, rlnRelayCredPassword) =
if conf.creds.isSome:
(some(conf.creds.get().path), some(conf.creds.get().password))
else:
(none(string), none(string))
groupManager = StaticGroupManager(
groupSize: StaticGroupSize,
groupKeys: parsedGroupKeys,
membershipIndex: conf.credIndex,
rlnInstance: rlnInstance,
onFatalErrorAction: conf.onFatalErrorAction,
)
# we don't persist credentials in static mode since they exist in ./constants.nim
else:
let (rlnRelayCredPath, rlnRelayCredPassword) =
if conf.creds.isSome:
(some(conf.creds.get().path), some(conf.creds.get().password))
else:
(none(string), none(string))
groupManager = OnchainGroupManager(
userMessageLimit: some(conf.userMessageLimit),
ethClientUrls: conf.ethClientUrls,
ethContractAddress: $conf.ethContractAddress,
chainId: conf.chainId,
rlnInstance: rlnInstance,
registrationHandler: registrationHandler,
keystorePath: rlnRelayCredPath,
keystorePassword: rlnRelayCredPassword,
membershipIndex: conf.credIndex,
onFatalErrorAction: conf.onFatalErrorAction,
)
groupManager = OnchainGroupManager(
userMessageLimit: some(conf.userMessageLimit),
ethClientUrls: conf.ethClientUrls,
ethContractAddress: $conf.ethContractAddress,
chainId: conf.chainId,
rlnInstance: rlnInstance,
registrationHandler: registrationHandler,
keystorePath: rlnRelayCredPath,
keystorePassword: rlnRelayCredPassword,
ethPrivateKey: conf.ethPrivateKey,
membershipIndex: conf.credIndex,
onFatalErrorAction: conf.onFatalErrorAction,
)
# Initialize the groupManager
(await groupManager.init()).isOkOr: