mirror of
https://github.com/logos-messaging/logos-delivery.git
synced 2026-08-04 10:53:19 +00:00
Add root poll if root invalid and retry
This commit is contained in:
parent
2f0f346d9e
commit
4fbe2c81c3
@ -307,7 +307,7 @@ suite "Onchain group manager":
|
||||
validProofRes.isOk()
|
||||
let validProof = validProofRes.get()
|
||||
|
||||
let validated = manager.validateRoot(validProof.merkleRoot)
|
||||
let validated = waitFor manager.validateRoot(validProof.merkleRoot)
|
||||
|
||||
check:
|
||||
validated
|
||||
@ -343,11 +343,90 @@ suite "Onchain group manager":
|
||||
validProofRes.isOk()
|
||||
let validProof = validProofRes.get()
|
||||
|
||||
let validated = manager.validateRoot(validProof.merkleRoot)
|
||||
let validated = waitFor manager.validateRoot(validProof.merkleRoot)
|
||||
|
||||
check:
|
||||
validated == false
|
||||
|
||||
test "validateRoot: refreshes from on-chain when root is unknown locally":
|
||||
# Without an explicit updateRecentRoots, validateRoot must detect a
|
||||
# missing root, refresh the local window from the contract cache, and
|
||||
# find the root there.
|
||||
const credentialCount = 3
|
||||
let credentials = generateCredentials(credentialCount)
|
||||
(waitFor manager.init()).isOkOr:
|
||||
raiseAssert $error
|
||||
|
||||
for i in 0 ..< credentials.len():
|
||||
(waitFor manager.register(credentials[i], UserMessageLimit(20))).isOkOr:
|
||||
assert false, "Failed to register credential " & $i & ": " & error
|
||||
|
||||
let currentRoot = (waitFor manager.fetchMerkleRoot()).valueOr:
|
||||
raiseAssert "Failed to fetch merkle root: " & error
|
||||
let rootField = UInt256ToField(currentRoot)
|
||||
|
||||
check manager.validRoots.len() == 0
|
||||
|
||||
let validated = waitFor manager.validateRoot(rootField)
|
||||
|
||||
check:
|
||||
validated
|
||||
manager.validRoots.len() > 0
|
||||
|
||||
test "validateRoot: debounces refresh requests within the interval":
|
||||
# After one refresh, a second miss within RootRefreshDebounceInterval
|
||||
# must return false without issuing another RPC — observable via
|
||||
# lastRefreshAt staying pinned to the first refresh's timestamp.
|
||||
let credentials = generateCredentials()
|
||||
(waitFor manager.init()).isOkOr:
|
||||
raiseAssert $error
|
||||
|
||||
(waitFor manager.register(credentials, UserMessageLimit(20))).isOkOr:
|
||||
assert false, "register failed: " & error
|
||||
|
||||
var bogusRoot: MerkleNode
|
||||
for i in 0 ..< bogusRoot.len:
|
||||
bogusRoot[i] = 0xAB'u8
|
||||
|
||||
let firstResult = waitFor manager.validateRoot(bogusRoot)
|
||||
let firstRefreshAt = manager.lastRefreshAt
|
||||
|
||||
let secondResult = waitFor manager.validateRoot(bogusRoot)
|
||||
|
||||
check:
|
||||
not firstResult
|
||||
not secondResult
|
||||
manager.lastRefreshAt == firstRefreshAt
|
||||
|
||||
test "validateRoot: concurrent misses share a single in-flight refresh":
|
||||
# Two validateRoot calls kicked off before the first's refresh resolves
|
||||
# must both end up `true` for the new root. Without coalescing, the
|
||||
# second call would fall through to the debounce gate and return false.
|
||||
const credentialCount = 3
|
||||
let credentials = generateCredentials(credentialCount)
|
||||
(waitFor manager.init()).isOkOr:
|
||||
raiseAssert $error
|
||||
|
||||
for i in 0 ..< credentials.len():
|
||||
(waitFor manager.register(credentials[i], UserMessageLimit(20))).isOkOr:
|
||||
assert false, "Failed to register credential " & $i & ": " & error
|
||||
|
||||
let currentRoot = (waitFor manager.fetchMerkleRoot()).valueOr:
|
||||
raiseAssert "Failed to fetch merkle root: " & error
|
||||
let rootField = UInt256ToField(currentRoot)
|
||||
|
||||
check manager.validRoots.len() == 0
|
||||
|
||||
let f1 = manager.validateRoot(rootField)
|
||||
let f2 = manager.validateRoot(rootField)
|
||||
|
||||
waitFor allFutures(f1, f2)
|
||||
|
||||
check:
|
||||
f1.read()
|
||||
f2.read()
|
||||
manager.validRoots.len() > 0
|
||||
|
||||
test "verifyProof: should verify valid proof":
|
||||
let credentials = generateCredentials()
|
||||
(waitFor manager.init()).isOkOr:
|
||||
|
||||
@ -269,13 +269,13 @@ suite "Waku rln relay":
|
||||
|
||||
# Validate messages
|
||||
let
|
||||
msgValidate1 = wakuRlnRelay.validateMessageAndUpdateLog(wm1)
|
||||
msgValidate1 = await wakuRlnRelay.validateMessageAndUpdateLog(wm1)
|
||||
# wm2 is within the same epoch as wm1 → should be spam
|
||||
msgValidate2 = wakuRlnRelay.validateMessageAndUpdateLog(wm2)
|
||||
msgValidate2 = await wakuRlnRelay.validateMessageAndUpdateLog(wm2)
|
||||
# wm3 is in the next epoch → should be valid
|
||||
msgValidate3 = wakuRlnRelay.validateMessageAndUpdateLog(wm3)
|
||||
msgValidate3 = await wakuRlnRelay.validateMessageAndUpdateLog(wm3)
|
||||
# wm4 has no RLN proof → should be invalid
|
||||
msgValidate4 = wakuRlnRelay.validateMessageAndUpdateLog(wm4)
|
||||
msgValidate4 = await wakuRlnRelay.validateMessageAndUpdateLog(wm4)
|
||||
|
||||
check:
|
||||
msgValidate1 == MessageValidationResult.Valid
|
||||
@ -323,12 +323,12 @@ suite "Waku rln relay":
|
||||
raiseAssert $error
|
||||
|
||||
# validate the first message because it's timestamp is the same as the generated timestamp
|
||||
let msgValidate1 = wakuRlnRelay.validateMessageAndUpdateLog(wm1)
|
||||
let msgValidate1 = await wakuRlnRelay.validateMessageAndUpdateLog(wm1)
|
||||
|
||||
# wait for 2 seconds to make the timestamp different from generated timestamp
|
||||
await sleepAsync(2.seconds)
|
||||
|
||||
let msgValidate2 = wakuRlnRelay.validateMessageAndUpdateLog(wm2)
|
||||
let msgValidate2 = await wakuRlnRelay.validateMessageAndUpdateLog(wm2)
|
||||
|
||||
check:
|
||||
msgValidate1 == MessageValidationResult.Valid
|
||||
@ -378,8 +378,8 @@ suite "Waku rln relay":
|
||||
raiseAssert $error
|
||||
|
||||
let
|
||||
msgValidate1 = wakuRlnRelay1.validateMessageAndUpdateLog(wm1)
|
||||
msgValidate2 = wakuRlnRelay1.validateMessageAndUpdateLog(wm2)
|
||||
msgValidate1 = await wakuRlnRelay1.validateMessageAndUpdateLog(wm1)
|
||||
msgValidate2 = await wakuRlnRelay1.validateMessageAndUpdateLog(wm2)
|
||||
|
||||
check:
|
||||
msgValidate1 == MessageValidationResult.Valid
|
||||
|
||||
@ -121,9 +121,7 @@ method indexOfRoot*(
|
||||
## 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: [].} =
|
||||
method validateRoot*(g: GroupManager, root: MerkleNode): Future[bool] {.base, async.} =
|
||||
## validates the root against the valid roots queue
|
||||
return g.indexOfRoot(root) >= 0
|
||||
|
||||
|
||||
@ -28,6 +28,10 @@ export group_manager_base
|
||||
logScope:
|
||||
topics = "waku rln_relay onchain_group_manager"
|
||||
|
||||
const RootRefreshDebounceInterval* = 1.seconds
|
||||
## Minimum gap between on-demand recent-roots refreshes triggered by
|
||||
## `validateRoot` misses, to bound contract-call rate under spam.
|
||||
|
||||
type
|
||||
WakuRlnContractWithSender = Sender[WakuRlnContract]
|
||||
OnchainGroupManager* = ref object of GroupManager
|
||||
@ -43,6 +47,10 @@ type
|
||||
registrationHandler*: Option[RegistrationHandler]
|
||||
latestProcessedBlock*: BlockNumber
|
||||
merkleProofCache*: seq[byte]
|
||||
lastRefreshAt*: Moment
|
||||
pendingRefresh*: Future[bool]
|
||||
## Non-nil while an on-demand recent-roots refresh is in flight, so that
|
||||
## concurrent `validateRoot` misses can ride along on a single RPC.
|
||||
|
||||
# The below code is not working with the latest web3 version due to chainId being null (specifically on linea-sepolia)
|
||||
# TODO: find better solution than this custom sendEthCallWithoutParams call
|
||||
@ -474,6 +482,31 @@ method generateProof*(
|
||||
waku_rln_total_generated_proofs.inc()
|
||||
return ok(output)
|
||||
|
||||
method validateRoot*(g: OnchainGroupManager, root: MerkleNode): Future[bool] {.async.} =
|
||||
## Validates the root against the local valid roots window. If the root is
|
||||
## not found, refresh the local window from the on-chain recent-roots cache
|
||||
## and re-check once before giving up. Concurrent misses share a single
|
||||
## in-flight refresh; new refreshes are debounced by
|
||||
## `RootRefreshDebounceInterval` to bound contract-call rate.
|
||||
if g.indexOfRoot(root) >= 0:
|
||||
return true
|
||||
|
||||
# Coalesce: if a refresh is already in flight, ride along instead of starting a new one.
|
||||
if not g.pendingRefresh.isNil and not g.pendingRefresh.finished:
|
||||
discard await g.pendingRefresh
|
||||
return g.indexOfRoot(root) >= 0
|
||||
|
||||
# Debounce: don't queue another refresh too soon after the previous one.
|
||||
let now = Moment.now()
|
||||
if now - g.lastRefreshAt < RootRefreshDebounceInterval:
|
||||
return false
|
||||
|
||||
g.lastRefreshAt = now
|
||||
g.pendingRefresh = g.updateRecentRoots()
|
||||
discard await g.pendingRefresh
|
||||
|
||||
return g.indexOfRoot(root) >= 0
|
||||
|
||||
method verifyProof*(
|
||||
g: OnchainGroupManager, input: seq[byte], proof: RateLimitProof
|
||||
): GroupManagerResult[bool] {.gcsafe.} =
|
||||
|
||||
@ -177,7 +177,7 @@ proc toRLNSignal*(wakumessage: WakuMessage): seq[byte] =
|
||||
|
||||
proc validateMessage*(
|
||||
rlnPeer: WakuRLNRelay, msg: WakuMessage
|
||||
): MessageValidationResult =
|
||||
): Future[MessageValidationResult] {.async.} =
|
||||
## validate the supplied `msg` based on the waku-rln-relay routing protocol i.e.,
|
||||
## the `msg`'s epoch is within MaxEpochGap of the current epoch
|
||||
## the `msg` has valid rate limit proof
|
||||
@ -217,7 +217,7 @@ proc validateMessage*(
|
||||
waku_rln_invalid_messages_total.inc(labelValues = ["timestamp_mismatch"])
|
||||
return MessageValidationResult.Invalid
|
||||
|
||||
let rootValidationRes = rlnPeer.groupManager.validateRoot(proof.merkleRoot)
|
||||
let rootValidationRes = await rlnPeer.groupManager.validateRoot(proof.merkleRoot)
|
||||
if not rootValidationRes:
|
||||
warn "invalid message: provided root does not belong to acceptable window of roots",
|
||||
provided = proof.merkleRoot.inHex(),
|
||||
@ -272,11 +272,11 @@ proc validateMessage*(
|
||||
|
||||
proc validateMessageAndUpdateLog*(
|
||||
rlnPeer: WakuRLNRelay, msg: WakuMessage
|
||||
): MessageValidationResult =
|
||||
): Future[MessageValidationResult] {.async.} =
|
||||
## validates the message and updates the log to prevent double messaging
|
||||
## in future messages
|
||||
|
||||
let isValidMessage = rlnPeer.validateMessage(msg)
|
||||
let isValidMessage = await rlnPeer.validateMessage(msg)
|
||||
|
||||
let msgProof = RateLimitProof.init(msg.proof).valueOr:
|
||||
return MessageValidationResult.Invalid
|
||||
@ -353,7 +353,7 @@ proc generateRlnValidator*(
|
||||
return pubsub.ValidationResult.Reject
|
||||
|
||||
# validate the message and update log
|
||||
let validationRes = wakuRlnRelay.validateMessageAndUpdateLog(message)
|
||||
let validationRes = await wakuRlnRelay.validateMessageAndUpdateLog(message)
|
||||
|
||||
let
|
||||
proof = byteutils.toHex(msgProof.proof)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user