Merge 16e17b4a9d76ef496ccd57c61d26e603d4c6af78 into 64a0ed7d967454d9c3b345023719e6ca5d73f129

This commit is contained in:
Tanya S 2026-06-03 14:23:24 +02:00 committed by GitHub
commit 552d9419e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 174 additions and 25 deletions

View File

@ -97,7 +97,8 @@ suite "Onchain group manager":
check:
merkleRootBefore != merkleRootAfter
test "trackRootChanges: should fetch history correctly":
test "trackRootChanges: should fetch history correctly: fetch single root()":
# basic check for the soon to be deprecated root contract function, is replaced by getRecentRoots()
# TODO: We can't use `trackRootChanges()` directly in this test because its current implementation
# 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.
@ -107,7 +108,8 @@ suite "Onchain group manager":
(waitFor manager.init()).isOkOr:
raiseAssert $error
let merkleRootBefore = waitFor manager.fetchMerkleRoot()
let merkleRootBefore = (waitFor manager.fetchMerkleRoot()).valueOr:
raiseAssert "Failed to fetch merkle root before: " & error
for i in 0 ..< credentials.len():
info "Registering credential", index = i, credential = credentials[i]
@ -115,12 +117,82 @@ suite "Onchain group manager":
assert false, "Failed to register credential " & $i & ": " & error
discard waitFor manager.updateRoots()
let merkleRootAfter = waitFor manager.fetchMerkleRoot()
let merkleRootAfter = (waitFor manager.fetchMerkleRoot()).valueOr:
raiseAssert "Failed to fetch merkle root after: " & error
check:
merkleRootBefore != merkleRootAfter
manager.validRoots.len() == credentialCount
test "trackRootChanges: should fetch history correctly: fetch root cache":
# Verify that the group_manager list of valid roots is updated correctly from the recent roots
# cache as new credentials are registered.
# TODO: We can't use `trackRootChanges()` directly in this test because its current implementation
# 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 = 5
let credentials = generateCredentials(credentialCount)
(waitFor manager.init()).isOkOr:
raiseAssert $error
let merkleRootCacheBefore = (waitFor manager.fetchMerkleRootsCache()).valueOr:
raiseAssert "Failed to fetch merkle root cache before: " & error
check:
merkleRootCacheBefore.len == 5 * 32
merkleRootCacheBefore.allIt(it == 0'u8)
manager.validRoots.len() == 0
for i in 0 ..< credentials.len():
info "Registering credential", index = i, credential = credentials[i]
(waitFor manager.register(credentials[i], UserMessageLimit(20))).isOkOr:
assert false, "Failed to register credential " & $i & ": " & error
discard waitFor manager.updateRecentRoots()
let merkleRootCacheAfter = (waitFor manager.fetchMerkleRootsCache()).valueOr:
raiseAssert "Failed to fetch merkle root cache after: " & error
check:
merkleRootCacheAfter.len == 5 * 32
not merkleRootCacheAfter.allIt(it == 0'u8)
manager.validRoots.len() == credentialCount
manager.validRoots.items().toSeq().allIt(it != default(MerkleNode))
test "trackRootChanges: oldest roots are evicted once the window is exceeded":
const
initialCount = 5
additionalCount = 6
let credentials = generateCredentials(initialCount + additionalCount)
(waitFor manager.init()).isOkOr:
raiseAssert $error
# Register the first 5 credentials and snapshot the 3 oldest roots.
for i in 0 ..< initialCount:
(waitFor manager.register(credentials[i], UserMessageLimit(20))).isOkOr:
assert false, "Failed to register credential " & $i & ": " & error
discard waitFor manager.updateRecentRoots()
check manager.validRoots.len() >= 3
let firstThreeBefore =
@[manager.validRoots[0], manager.validRoots[1], manager.validRoots[2]]
# Register the remaining credentials, pushing the deque past AcceptableRootWindowSize.
for i in initialCount ..< credentials.len():
(waitFor manager.register(credentials[i], UserMessageLimit(20))).isOkOr:
assert false, "Failed to register credential " & $i & ": " & error
discard waitFor manager.updateRecentRoots()
let rootsAfter = manager.validRoots.items().toSeq()
# 51 registrations into a window of 50 evicts exactly the single oldest root,
# so only the first of the original three is gone; the other two remain.
check:
manager.validRoots.len() == AcceptableRootWindowSize
firstThreeBefore[0] notin rootsAfter
firstThreeBefore[1] in rootsAfter
firstThreeBefore[2] in rootsAfter
test "register: should guard against uninitialized state":
let dummyCommitment = default(IDCommitment)
@ -214,7 +286,7 @@ suite "Onchain group manager":
waitFor fut
let rootUpdated = waitFor manager.updateRoots()
let rootUpdated = waitFor manager.updateRecentRoots()
if rootUpdated:
let proofResult = waitFor manager.fetchMerkleProofElements()
@ -296,7 +368,7 @@ suite "Onchain group manager":
assert false, "error returned when calling register: " & error
waitFor fut
let rootUpdated = waitFor manager.updateRoots()
let rootUpdated = waitFor manager.updateRecentRoots()
if rootUpdated:
let proofResult = waitFor manager.fetchMerkleProofElements()
@ -333,7 +405,7 @@ suite "Onchain group manager":
let messageBytes = "Hello".toBytes()
let rootUpdated = waitFor manager.updateRoots()
let rootUpdated = waitFor manager.updateRecentRoots()
manager.merkleProofCache = newSeq[byte](640)
for i in 0 ..< 640:
@ -362,7 +434,7 @@ suite "Onchain group manager":
verified == false
test "root queue should be updated correctly":
const credentialCount = 12
const credentialCount = 9
let credentials = generateCredentials(credentialCount)
(waitFor manager.init()).isOkOr:
raiseAssert $error
@ -391,7 +463,7 @@ suite "Onchain group manager":
for i in 0 ..< credentials.len():
(waitFor manager.register(credentials[i], UserMessageLimit(20))).isOkOr:
assert false, "Failed to register credential " & $i & ": " & error
discard waitFor manager.updateRoots()
discard waitFor manager.updateRecentRoots()
waitFor allFutures(futures)
@ -436,7 +508,7 @@ suite "Onchain group manager":
(waitFor manager.register(credentials, UserMessageLimit(20))).isOkOr:
assert false, "register failed: " & error
discard waitFor manager.updateRoots()
discard waitFor manager.updateRecentRoots()
let roots = manager.validRoots.items().toSeq()
require:
roots.len > 0

View File

@ -62,10 +62,10 @@ proc fetchMerkleProofElements*(
let response = await sendEthCallWithParams(
ethRpc = g.ethRpc.get(),
functionSignature = methodSig,
params = paddedParam,
fromAddress = g.ethRpc.get().defaultAccount,
toAddress = fromHex(Address, g.ethContractAddress),
chainId = g.chainId,
params = paddedParam,
)
return response
@ -73,14 +73,32 @@ proc fetchMerkleProofElements*(
proc fetchMerkleRoot*(
g: OnchainGroupManager
): Future[Result[UInt256, string]] {.async.} =
let merkleRoot = await sendEthCallWithoutParams(
ethRpc = g.ethRpc.get(),
functionSignature = "root()",
fromAddress = g.ethRpc.get().defaultAccount,
toAddress = fromHex(Address, g.ethContractAddress),
chainId = g.chainId,
)
return merkleRoot
try:
let merkleRoot = await sendEthCallWithoutParams(
ethRpc = g.ethRpc.get(),
functionSignature = "root()",
fromAddress = g.ethRpc.get().defaultAccount,
toAddress = fromHex(Address, g.ethContractAddress),
chainId = g.chainId,
)
return merkleRoot
except CatchableError:
error "Failed to fetch Merkle root", error = getCurrentExceptionMsg()
return err("Failed to fetch merkle root: " & getCurrentExceptionMsg())
proc fetchMerkleRootsCache*(
g: OnchainGroupManager
): Future[Result[seq[byte], string]] {.async.} =
let
# using sendEthCallWithParams to get return type of seq[bytes] for getRecentRoots() function which returns an array of bytes32
merkleRoots = await sendEthCallWithParams(
ethRpc = g.ethRpc.get(),
functionSignature = "getRecentRoots()",
fromAddress = g.ethRpc.get().defaultAccount,
toAddress = fromHex(Address, g.ethContractAddress),
chainId = g.chainId,
)
return merkleRoots
proc fetchNextFreeIndex*(
g: OnchainGroupManager
@ -102,10 +120,10 @@ proc fetchMembershipStatus*(
await sendEthCallWithParams(
ethRpc = g.ethRpc.get(),
functionSignature = "isInMembershipSet(uint256)",
params = params,
fromAddress = g.ethRpc.get().defaultAccount,
toAddress = fromHex(Address, g.ethContractAddress),
chainId = g.chainId,
params = params,
)
).valueOr:
return err("Failed to check membership: " & error)
@ -148,14 +166,69 @@ proc updateRoots*(g: OnchainGroupManager): Future[bool] {.async.} =
return false
proc updateRecentRoots*(g: OnchainGroupManager): Future[bool] {.async.} =
let bytes = (await g.fetchMerkleRoot()).valueOr:
error "Failed to fetch current Merkle root", error = error
return false
if (bytes.len mod 32) != 0:
error "Invalid recent roots payload length", length = bytes.len
return false
let chunkCount = bytes.len div 32
if chunkCount != 5:
warn "Unexpected number of recent roots returned; proceeding anyway",
count = chunkCount
# Parse 32-byte chunks (contract returns newest-first) into MerkleNode values,
# reversing to oldest-first and skipping zero roots.
var newRootsDequeOrder: seq[MerkleNode] = @[]
for startIdx in countdown(bytes.len - 32, 0, 32):
let u = UInt256.fromBytesBE(bytes.toOpenArray(startIdx, startIdx + 31))
if u.isZero:
continue
newRootsDequeOrder.add(UInt256ToField(u))
if newRootsDequeOrder.len == 0:
debug "no non-zero recent roots to add; skipping update"
return false
# Determine overlap with existing tail so we only append truly new roots
var overlap = min(g.validRoots.len, newRootsDequeOrder.len)
var matchLen = 0
# Find the largest n (<= overlap) such that last n of validRoots == first n of newRootsDequeOrder
for n in countdown(overlap, 1):
var ok = true
let startIdx = g.validRoots.len - n
for i in 0 ..< n:
if g.validRoots[startIdx + i] != newRootsDequeOrder[i]:
ok = false
break
if ok:
matchLen = n
break
let toAdd = newRootsDequeOrder[matchLen ..< newRootsDequeOrder.len]
if toAdd.len == 0:
return false
# Append new roots to the tail; trim happens below if we exceed the window.
toAdd.mapIt(g.validRoots.addLast(it))
debug "appended recent roots", count = toAdd.len, roots = toAdd
while g.validRoots.len > AcceptableRootWindowSize:
trace "removing old merkle root", root = g.validRoots[0]
discard g.validRoots.popFirst()
return true
proc trackRootChanges*(g: OnchainGroupManager): Future[Result[void, string]] {.async.} =
?checkInitialized(g)
const rpcDelay = 5.seconds
const rpcDelay = 10.seconds
while true:
await sleepAsync(rpcDelay)
let rootUpdated = await g.updateRoots()
let rootUpdated = await g.updateRecentRoots()
if rootUpdated:
## The membership set on-chain has changed (some new members have joined or some members have left)
@ -174,6 +247,7 @@ proc trackRootChanges*(g: OnchainGroupManager): Future[Result[void, string]] {.a
let memberCount = cast[int64](nextFreeIndex)
waku_rln_number_registered_memberships.set(float64(memberCount))
await sleepAsync(rpcDelay)
method register*(
g: OnchainGroupManager, rateCommitment: RateCommitment
@ -393,8 +467,11 @@ method generateProof*(
external_nullifier: extNullifier,
)
let output = generateRlnProofWithWitness(g.rlnInstance, witness, epoch, rlnIdentifier).valueOr:
return err("Failed to generate proof: " & error)
waku_rln_proof_generation_duration_seconds.nanosecondTime:
let output = generateRlnProofWithWitness(
g.rlnInstance, witness, epoch, rlnIdentifier
).valueOr:
return err("Failed to generate proof: " & error)
info "Proof generated successfully", proof = output

View File

@ -76,10 +76,10 @@ proc sendEthCallWithoutParams*(
proc sendEthCallWithParams*(
ethRpc: Web3,
functionSignature: string,
params: seq[byte],
fromAddress: Address,
toAddress: Address,
chainId: UInt256,
params: seq[byte] = @[],
): Future[Result[seq[byte], string]] {.async.} =
## Workaround for web3 chainId=null issue with parameterized contract calls
let functionHash =