diff --git a/tests/waku_rln_relay/test_rln_group_manager_onchain.nim b/tests/waku_rln_relay/test_rln_group_manager_onchain.nim index 1c2807d31..1123ba0a9 100644 --- a/tests/waku_rln_relay/test_rln_group_manager_onchain.nim +++ b/tests/waku_rln_relay/test_rln_group_manager_onchain.nim @@ -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. @@ -123,6 +124,32 @@ suite "Onchain group manager": merkleRootBefore != merkleRootAfter manager.validRoots.len() == credentialCount + test "trackRootChanges: should fetch history correctly: fetch root cache": + # 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 = 6 + let credentials = generateCredentials(credentialCount) + (waitFor manager.init()).isOkOr: + raiseAssert $error + + let merkleRootCacheBefore = (waitFor manager.fetchMerkleRootsCache()).valueOr: + raiseAssert "Failed to fetch merkle root before: " & error + + 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 after: " & error + + check: + merkleRootCacheBefore != merkleRootCacheAfter + manager.validRoots.len() == credentialCount + test "trackRootChanges: oldest roots are evicted once the window is exceeded": const initialCount = 5 @@ -135,7 +162,7 @@ suite "Onchain group manager": for i in 0 ..< initialCount: (waitFor manager.register(credentials[i], UserMessageLimit(20))).isOkOr: assert false, "Failed to register credential " & $i & ": " & error - discard waitFor manager.updateRoots() + discard waitFor manager.updateRecentRoots() check manager.validRoots.len() >= 3 let firstThreeBefore = @@ -145,7 +172,7 @@ suite "Onchain group manager": 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.updateRoots() + discard waitFor manager.updateRecentRoots() let rootsAfter = manager.validRoots.items().toSeq() @@ -250,7 +277,7 @@ suite "Onchain group manager": waitFor fut - let rootUpdated = waitFor manager.updateRoots() + let rootUpdated = waitFor manager.updateRecentRoots() if rootUpdated: let proofResult = waitFor manager.fetchMerkleProofElements() @@ -332,7 +359,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() @@ -369,7 +396,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: @@ -398,7 +425,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 @@ -427,7 +454,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) @@ -472,7 +499,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 diff --git a/waku/waku_rln_relay/group_manager/on_chain/group_manager.nim b/waku/waku_rln_relay/group_manager/on_chain/group_manager.nim index 363b86b31..25ae11f96 100644 --- a/waku/waku_rln_relay/group_manager/on_chain/group_manager.nim +++ b/waku/waku_rln_relay/group_manager/on_chain/group_manager.nim @@ -86,7 +86,7 @@ proc fetchMerkleRoot*( error "Failed to fetch Merkle root", error = getCurrentExceptionMsg() return err("Failed to fetch merkle root: " & getCurrentExceptionMsg()) -proc fetchMerkleRoot*( +proc fetchMerkleRootsCache*( g: OnchainGroupManager ): Future[Result[seq[byte], string]] {.async.} = let @@ -148,72 +148,49 @@ proc checkInitialized(g: OnchainGroupManager): Result[void, string] = return err("OnchainGroupManager is not initialized") return ok() -# proc updateRoots*(g: OnchainGroupManager): Future[bool] {.async.} = -# let rootRes = await g.fetchMerkleRoot() -# if rootRes.isErr(): -# return false - -# let merkleRoot = UInt256ToField(rootRes.get()) - -# if g.validRoots.len == 0: -# g.validRoots.addLast(merkleRoot) -# return true - -# if g.validRoots[g.validRoots.len - 1] != merkleRoot: -# if g.validRoots.len > AcceptableRootWindowSize: -# discard g.validRoots.popFirst() -# g.validRoots.addLast(merkleRoot) -# return true - -# return false - proc updateRoots*(g: OnchainGroupManager): Future[bool] {.async.} = let rootRes = await g.fetchMerkleRoot() - # let rootRes = await g.fetchMerkleRecentRoots() + if rootRes.isErr(): + return false + + let merkleRoot = UInt256ToField(rootRes.get()) + + if g.validRoots.len == 0: + g.validRoots.addLast(merkleRoot) + return true + + if g.validRoots[g.validRoots.len - 1] != merkleRoot: + if g.validRoots.len > AcceptableRootWindowSize: + discard g.validRoots.popFirst() + g.validRoots.addLast(merkleRoot) + return true + + return false + +proc updateRecentRoots*(g: OnchainGroupManager): Future[bool] {.async.} = + let rootRes = await g.fetchMerkleRootsCache() if rootRes.isErr(): error "Failed to fetch recent roots", error = rootRes.error return false let bytes = rootRes.get() - debug "recent roots raw bytes received", length = bytes.len if (bytes.len mod 32) != 0: error "Invalid recent roots payload length", length = bytes.len return false let chunkCount = bytes.len div 32 - debug "recent roots parsed chunk count", chunks = chunkCount if chunkCount != 5: warn "Unexpected number of recent roots returned; proceeding anyway", count = chunkCount - # Parse 32-byte chunks into MerkleNode values (UInt256 -> Field bytes) - var newRoots: seq[MerkleNode] = @[] # newest-first order from contract - var zeroIndices: seq[int] = @[] - for i in 0 ..< chunkCount: - let startIdx = i * 32 - let endIdx = (i + 1) * 32 - 1 - var allZero = true - for b in bytes[startIdx .. endIdx]: - if b != 0'u8: - allZero = false - break - if allZero: - zeroIndices.add(i) - let u = UInt256.fromBytesBE(bytes[startIdx .. endIdx]) - let node = UInt256ToField(u) - newRoots.add(node) - - if zeroIndices.len > 0: - debug "zero or empty roots detected in recent roots", - zeroRootsCount = zeroIndices.len, indices = zeroIndices - - # Convert to deque order: oldest-first (so newest ends up at tail) - # Skip zero roots so they are not added to the validRoots list + # Parse 32-byte chunks (contract returns newest-first) into MerkleNode values, + # reversing to oldest-first and skipping zero roots. var newRootsDequeOrder: seq[MerkleNode] = @[] - for i in countdown(newRoots.len - 1, 0): - if zeroIndices.contains(i): + for startIdx in countdown(bytes.len - 32, 0, 32): + let u = UInt256.fromBytesBE(bytes.toOpenArray(startIdx, startIdx + 31)) + if u.isZero: continue - newRootsDequeOrder.add(newRoots[i]) + newRootsDequeOrder.add(UInt256ToField(u)) if newRootsDequeOrder.len == 0: debug "no non-zero recent roots to add; skipping update" @@ -236,24 +213,22 @@ proc updateRoots*(g: OnchainGroupManager): Future[bool] {.async.} = let toAdd = newRootsDequeOrder[matchLen ..< newRootsDequeOrder.len] if toAdd.len == 0: - debug "recent roots already present up-to-date; skipping update" return false # If this is the first time, just seed the deque with all provided roots if g.validRoots.len == 0: for r in newRootsDequeOrder: - debug "adding new recent root", root = r g.validRoots.addLast(r) + debug "seeded recent roots", count = newRootsDequeOrder.len return true - # Add all new roots to the "top" (tail) and drop the bottom 5 + # Add all new roots to the "top" (tail) and trim to AcceptableRootWindowSize for r in toAdd: - debug "adding new recent root", root = r g.validRoots.addLast(r) + debug "appended recent roots", count = toAdd.len, roots = toAdd - # Only trim the deque if it exceeds the acceptable window size while g.validRoots.len > AcceptableRootWindowSize: - debug "removing old recent root", root = g.validRoots[0] + trace "removing old merkle root", root = g.validRoots[0] discard g.validRoots.popFirst() return true