improve recents roots retrieval and logs

This commit is contained in:
stubbsta 2026-05-28 13:32:46 +02:00
parent 3ba630daad
commit 02fc4d4889
No known key found for this signature in database
2 changed files with 66 additions and 64 deletions

View File

@ -97,7 +97,8 @@ suite "Onchain group manager":
check: check:
merkleRootBefore != merkleRootAfter 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 # 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 # 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. # so we can't use it in this test.
@ -123,6 +124,32 @@ suite "Onchain group manager":
merkleRootBefore != merkleRootAfter merkleRootBefore != merkleRootAfter
manager.validRoots.len() == credentialCount 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": test "trackRootChanges: oldest roots are evicted once the window is exceeded":
const const
initialCount = 5 initialCount = 5
@ -135,7 +162,7 @@ suite "Onchain group manager":
for i in 0 ..< initialCount: for i in 0 ..< initialCount:
(waitFor manager.register(credentials[i], UserMessageLimit(20))).isOkOr: (waitFor manager.register(credentials[i], UserMessageLimit(20))).isOkOr:
assert false, "Failed to register credential " & $i & ": " & error assert false, "Failed to register credential " & $i & ": " & error
discard waitFor manager.updateRoots() discard waitFor manager.updateRecentRoots()
check manager.validRoots.len() >= 3 check manager.validRoots.len() >= 3
let firstThreeBefore = let firstThreeBefore =
@ -145,7 +172,7 @@ suite "Onchain group manager":
for i in initialCount ..< credentials.len(): for i in initialCount ..< credentials.len():
(waitFor manager.register(credentials[i], UserMessageLimit(20))).isOkOr: (waitFor manager.register(credentials[i], UserMessageLimit(20))).isOkOr:
assert false, "Failed to register credential " & $i & ": " & error assert false, "Failed to register credential " & $i & ": " & error
discard waitFor manager.updateRoots() discard waitFor manager.updateRecentRoots()
let rootsAfter = manager.validRoots.items().toSeq() let rootsAfter = manager.validRoots.items().toSeq()
@ -250,7 +277,7 @@ suite "Onchain group manager":
waitFor fut waitFor fut
let rootUpdated = waitFor manager.updateRoots() let rootUpdated = waitFor manager.updateRecentRoots()
if rootUpdated: if rootUpdated:
let proofResult = waitFor manager.fetchMerkleProofElements() let proofResult = waitFor manager.fetchMerkleProofElements()
@ -332,7 +359,7 @@ suite "Onchain group manager":
assert false, "error returned when calling register: " & error assert false, "error returned when calling register: " & error
waitFor fut waitFor fut
let rootUpdated = waitFor manager.updateRoots() let rootUpdated = waitFor manager.updateRecentRoots()
if rootUpdated: if rootUpdated:
let proofResult = waitFor manager.fetchMerkleProofElements() let proofResult = waitFor manager.fetchMerkleProofElements()
@ -369,7 +396,7 @@ suite "Onchain group manager":
let messageBytes = "Hello".toBytes() let messageBytes = "Hello".toBytes()
let rootUpdated = waitFor manager.updateRoots() let rootUpdated = waitFor manager.updateRecentRoots()
manager.merkleProofCache = newSeq[byte](640) manager.merkleProofCache = newSeq[byte](640)
for i in 0 ..< 640: for i in 0 ..< 640:
@ -398,7 +425,7 @@ suite "Onchain group manager":
verified == false verified == false
test "root queue should be updated correctly": test "root queue should be updated correctly":
const credentialCount = 12 const credentialCount = 9
let credentials = generateCredentials(credentialCount) let credentials = generateCredentials(credentialCount)
(waitFor manager.init()).isOkOr: (waitFor manager.init()).isOkOr:
raiseAssert $error raiseAssert $error
@ -427,7 +454,7 @@ suite "Onchain group manager":
for i in 0 ..< credentials.len(): for i in 0 ..< credentials.len():
(waitFor manager.register(credentials[i], UserMessageLimit(20))).isOkOr: (waitFor manager.register(credentials[i], UserMessageLimit(20))).isOkOr:
assert false, "Failed to register credential " & $i & ": " & error assert false, "Failed to register credential " & $i & ": " & error
discard waitFor manager.updateRoots() discard waitFor manager.updateRecentRoots()
waitFor allFutures(futures) waitFor allFutures(futures)
@ -472,7 +499,7 @@ suite "Onchain group manager":
(waitFor manager.register(credentials, UserMessageLimit(20))).isOkOr: (waitFor manager.register(credentials, UserMessageLimit(20))).isOkOr:
assert false, "register failed: " & error assert false, "register failed: " & error
discard waitFor manager.updateRoots() discard waitFor manager.updateRecentRoots()
let roots = manager.validRoots.items().toSeq() let roots = manager.validRoots.items().toSeq()
require: require:
roots.len > 0 roots.len > 0

View File

@ -86,7 +86,7 @@ proc fetchMerkleRoot*(
error "Failed to fetch Merkle root", error = getCurrentExceptionMsg() error "Failed to fetch Merkle root", error = getCurrentExceptionMsg()
return err("Failed to fetch merkle root: " & getCurrentExceptionMsg()) return err("Failed to fetch merkle root: " & getCurrentExceptionMsg())
proc fetchMerkleRoot*( proc fetchMerkleRootsCache*(
g: OnchainGroupManager g: OnchainGroupManager
): Future[Result[seq[byte], string]] {.async.} = ): Future[Result[seq[byte], string]] {.async.} =
let let
@ -148,72 +148,49 @@ proc checkInitialized(g: OnchainGroupManager): Result[void, string] =
return err("OnchainGroupManager is not initialized") return err("OnchainGroupManager is not initialized")
return ok() 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.} = proc updateRoots*(g: OnchainGroupManager): Future[bool] {.async.} =
let rootRes = await g.fetchMerkleRoot() 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(): if rootRes.isErr():
error "Failed to fetch recent roots", error = rootRes.error error "Failed to fetch recent roots", error = rootRes.error
return false return false
let bytes = rootRes.get() let bytes = rootRes.get()
debug "recent roots raw bytes received", length = bytes.len
if (bytes.len mod 32) != 0: if (bytes.len mod 32) != 0:
error "Invalid recent roots payload length", length = bytes.len error "Invalid recent roots payload length", length = bytes.len
return false return false
let chunkCount = bytes.len div 32 let chunkCount = bytes.len div 32
debug "recent roots parsed chunk count", chunks = chunkCount
if chunkCount != 5: if chunkCount != 5:
warn "Unexpected number of recent roots returned; proceeding anyway", warn "Unexpected number of recent roots returned; proceeding anyway",
count = chunkCount count = chunkCount
# Parse 32-byte chunks into MerkleNode values (UInt256 -> Field bytes) # Parse 32-byte chunks (contract returns newest-first) into MerkleNode values,
var newRoots: seq[MerkleNode] = @[] # newest-first order from contract # reversing to oldest-first and skipping zero roots.
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
var newRootsDequeOrder: seq[MerkleNode] = @[] var newRootsDequeOrder: seq[MerkleNode] = @[]
for i in countdown(newRoots.len - 1, 0): for startIdx in countdown(bytes.len - 32, 0, 32):
if zeroIndices.contains(i): let u = UInt256.fromBytesBE(bytes.toOpenArray(startIdx, startIdx + 31))
if u.isZero:
continue continue
newRootsDequeOrder.add(newRoots[i]) newRootsDequeOrder.add(UInt256ToField(u))
if newRootsDequeOrder.len == 0: if newRootsDequeOrder.len == 0:
debug "no non-zero recent roots to add; skipping update" 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] let toAdd = newRootsDequeOrder[matchLen ..< newRootsDequeOrder.len]
if toAdd.len == 0: if toAdd.len == 0:
debug "recent roots already present up-to-date; skipping update"
return false return false
# If this is the first time, just seed the deque with all provided roots # If this is the first time, just seed the deque with all provided roots
if g.validRoots.len == 0: if g.validRoots.len == 0:
for r in newRootsDequeOrder: for r in newRootsDequeOrder:
debug "adding new recent root", root = r
g.validRoots.addLast(r) g.validRoots.addLast(r)
debug "seeded recent roots", count = newRootsDequeOrder.len
return true 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: for r in toAdd:
debug "adding new recent root", root = r
g.validRoots.addLast(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: 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() discard g.validRoots.popFirst()
return true return true