fix(rln-relay): make nullifier log abide by epoch ordering (#2508)

* fix(rln-relay): nullifier log abide by epoch ordering

* fix: cleaner hasKey method, test

* chore: idiomatic usage of results, error handling

Co-authored-by: Ivan FB <128452529+Ivansete-status@users.noreply.github.com>

---------

Co-authored-by: Ivan FB <128452529+Ivansete-status@users.noreply.github.com>
This commit is contained in:
Aaryamann Challani 2024-03-06 23:59:07 +05:30 committed by GitHub
parent a9d0e48164
commit beba14dcaa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 77 additions and 45 deletions

View File

@ -632,30 +632,25 @@ suite "Waku rln relay":
# check whether hasDuplicate correctly finds records with the same nullifiers but different secret shares # check whether hasDuplicate correctly finds records with the same nullifiers but different secret shares
# no duplicate for proof1 should be found, since the log is empty # no duplicate for proof1 should be found, since the log is empty
let result1 = wakurlnrelay.hasDuplicate(proof1.extractMetadata().tryGet()) let result1 = wakurlnrelay.hasDuplicate(epoch, proof1.extractMetadata().tryGet())
require: assert result1.isOk(), $result1.error
result1.isOk() assert result1.value == false, "no duplicate should be found"
# no duplicate is found
result1.value == false
# add it to the log # add it to the log
discard wakurlnrelay.updateLog(proof1.extractMetadata().tryGet()) discard wakurlnrelay.updateLog(epoch, proof1.extractMetadata().tryGet())
# # no duplicate for proof2 should be found, its nullifier differs from proof1 # no duplicate for proof2 should be found, its nullifier differs from proof1
let result2 = wakurlnrelay.hasDuplicate(proof2.extractMetadata().tryGet()) let result2 = wakurlnrelay.hasDuplicate(epoch, proof2.extractMetadata().tryGet())
require: assert result2.isOk(), $result2.error
result2.isOk() # no duplicate is found
# no duplicate is found assert result2.value == false, "no duplicate should be found"
result2.value == false
# add it to the log # add it to the log
discard wakurlnrelay.updateLog(proof2.extractMetadata().tryGet()) discard wakurlnrelay.updateLog(epoch, proof2.extractMetadata().tryGet())
# proof3 has the same nullifier as proof1 but different secret shares, it should be detected as duplicate # proof3 has the same nullifier as proof1 but different secret shares, it should be detected as duplicate
let result3 = wakurlnrelay.hasDuplicate(proof3.extractMetadata().tryGet()) let result3 = wakurlnrelay.hasDuplicate(epoch, proof3.extractMetadata().tryGet())
require: assert result3.isOk(), $result3.error
result3.isOk() # it is a duplicate
check: assert result3.value, "duplicate should be found"
# it is a duplicate
result3.value == true
asyncTest "validateMessageAndUpdateLog test": asyncTest "validateMessageAndUpdateLog test":
let index = MembershipIndex(5) let index = MembershipIndex(5)
@ -710,6 +705,52 @@ suite "Waku rln relay":
msgValidate3 == MessageValidationResult.Valid msgValidate3 == MessageValidationResult.Valid
msgValidate4 == MessageValidationResult.Invalid msgValidate4 == MessageValidationResult.Invalid
asyncTest "validateMessageAndUpdateLog: multiple senders with same external nullifier":
let index1 = MembershipIndex(5)
let index2 = MembershipIndex(6)
let rlnConf1 = WakuRlnConfig(rlnRelayDynamic: false,
rlnRelayCredIndex: some(index1),
rlnEpochSizeSec: 1,
rlnRelayTreePath: genTempPath("rln_tree", "waku_rln_relay_3"))
let wakuRlnRelay1 = (await WakuRlnRelay.new(rlnConf1)).valueOr:
raiseAssert "failed to create waku rln relay: " & $error
let rlnConf2 = WakuRlnConfig(rlnRelayDynamic: false,
rlnRelayCredIndex: some(index2),
rlnEpochSizeSec: 1,
rlnRelayTreePath: genTempPath("rln_tree", "waku_rln_relay_4"))
let wakuRlnRelay2 = (await WakuRlnRelay.new(rlnConf2)).valueOr:
raiseAssert "failed to create waku rln relay: " & $error
# get the current epoch time
let time = epochTime()
# create messages from different peers and append rln proofs to them
var
wm1 = WakuMessage(payload: "Valid message from sender 1".toBytes())
# another message in the same epoch as wm1, it will break the messaging rate limit
wm2 = WakuMessage(payload: "Valid message from sender 2".toBytes())
let
proofAdded1 = wakuRlnRelay1.appendRLNProof(wm1, time)
proofAdded2 = wakuRlnRelay2.appendRLNProof(wm2, time)
# ensure proofs are added
assert proofAdded1.isOk(), "failed to append rln proof: " & $proofAdded1.error
assert proofAdded2.isOk(), "failed to append rln proof: " & $proofAdded2.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, some(time))
# since this message is from a different sender, it should be validated successfully
msgValidate2 = wakuRlnRelay1.validateMessageAndUpdateLog(wm2, some(time))
check:
msgValidate1 == MessageValidationResult.Valid
msgValidate2 == MessageValidationResult.Valid
test "toIDCommitment and toUInt256": test "toIDCommitment and toUInt256":
# create an instance of rln # create an instance of rln
let rlnInstance = createRLNInstanceWrapper() let rlnInstance = createRLNInstanceWrapper()

View File

@ -79,7 +79,7 @@ proc createMembershipList*(rln: ptr RLN, n: int): RlnRelayResult[(
type WakuRLNRelay* = ref object of RootObj type WakuRLNRelay* = ref object of RootObj
# the log of nullifiers and Shamir shares of the past messages grouped per epoch # the log of nullifiers and Shamir shares of the past messages grouped per epoch
nullifierLog*: OrderedTable[Epoch, seq[ProofMetadata]] nullifierLog*: OrderedTable[Epoch, Table[Nullifier, ProofMetadata]]
lastEpoch*: Epoch # the epoch of the last published rln message lastEpoch*: Epoch # the epoch of the last published rln message
rlnEpochSizeSec*: uint64 rlnEpochSizeSec*: uint64
rlnMaxEpochGap*: uint64 rlnMaxEpochGap*: uint64
@ -103,58 +103,49 @@ proc stop*(rlnPeer: WakuRLNRelay) {.async: (raises: [Exception]).} =
await rlnPeer.groupManager.stop() await rlnPeer.groupManager.stop()
proc hasDuplicate*(rlnPeer: WakuRLNRelay, proc hasDuplicate*(rlnPeer: WakuRLNRelay,
epoch: Epoch,
proofMetadata: ProofMetadata): RlnRelayResult[bool] = proofMetadata: ProofMetadata): RlnRelayResult[bool] =
## returns true if there is another message in the `nullifierLog` of the `rlnPeer` with the same ## returns true if there is another message in the `nullifierLog` of the `rlnPeer` with the same
## epoch and nullifier as `proofMetadata`'s epoch and nullifier ## epoch and nullifier as `proofMetadata`'s epoch and nullifier
## otherwise, returns false ## otherwise, returns false
## Returns an error if it cannot check for duplicates ## Returns an error if it cannot check for duplicates
let externalNullifier = proofMetadata.externalNullifier
# check if the epoch exists # check if the epoch exists
if not rlnPeer.nullifierLog.hasKey(externalNullifier): let nullifier = proofMetadata.nullifier
if not rlnPeer.nullifierLog.hasKey(epoch):
return ok(false) return ok(false)
try: try:
if rlnPeer.nullifierLog[externalNullifier].contains(proofMetadata): if rlnPeer.nullifierLog[epoch].hasKey(nullifier):
# there is an identical record, mark it as spam # there is an identical record, mark it as spam
return ok(true) return ok(true)
# check for a message with the same nullifier but different secret shares
let matched = rlnPeer.nullifierLog[externalNullifier].filterIt((
it.nullifier == proofMetadata.nullifier) and ((it.shareX != proofMetadata.shareX) or
(it.shareY != proofMetadata.shareY)))
if matched.len != 0:
# there is a duplicate
return ok(true)
# there is no duplicate # there is no duplicate
return ok(false) return ok(false)
except KeyError as e: except KeyError:
return err("the epoch was not found") return err("the epoch was not found: " & getCurrentExceptionMsg())
proc updateLog*(rlnPeer: WakuRLNRelay, proc updateLog*(rlnPeer: WakuRLNRelay,
epoch: Epoch,
proofMetadata: ProofMetadata): RlnRelayResult[void] = proofMetadata: ProofMetadata): RlnRelayResult[void] =
## saves supplied proofMetadata `proofMetadata` ## saves supplied proofMetadata `proofMetadata`
## in the `nullifierLog` of the `rlnPeer` ## in the `nullifierLog` of the `rlnPeer`
## Returns an error if it cannot update the log ## Returns an error if it cannot update the log
let externalNullifier = proofMetadata.externalNullifier # check if the epoch exists
# check if the externalNullifier exists if not rlnPeer.nullifierLog.hasKeyOrPut(epoch, { proofMetadata.nullifier: proofMetadata }.toTable()):
if not rlnPeer.nullifierLog.hasKey(externalNullifier):
rlnPeer.nullifierLog[externalNullifier] = @[proofMetadata]
return ok() return ok()
try: try:
# check if an identical record exists # check if an identical record exists
if rlnPeer.nullifierLog[externalNullifier].contains(proofMetadata): if rlnPeer.nullifierLog[epoch].hasKeyOrPut(proofMetadata.nullifier, proofMetadata):
# the above condition could be `discarded` but it is kept for clarity, that slashing will
# be implemented here
# TODO: slashing logic # TODO: slashing logic
return ok() return ok()
# add proofMetadata to the log
rlnPeer.nullifierLog[externalNullifier].add(proofMetadata)
return ok() return ok()
except KeyError as e: except KeyError:
return err("the external nullifier was not found") # should never happen return err("the epoch was not found: " & getCurrentExceptionMsg()) # should never happen
proc getCurrentEpoch*(rlnPeer: WakuRLNRelay): Epoch = proc getCurrentEpoch*(rlnPeer: WakuRLNRelay): Epoch =
## gets the current rln Epoch time ## gets the current rln Epoch time
@ -249,7 +240,7 @@ proc validateMessage*(rlnPeer: WakuRLNRelay,
if proofMetadataRes.isErr(): if proofMetadataRes.isErr():
waku_rln_errors_total.inc(labelValues=["proof_metadata_extraction"]) waku_rln_errors_total.inc(labelValues=["proof_metadata_extraction"])
return MessageValidationResult.Invalid return MessageValidationResult.Invalid
let hasDup = rlnPeer.hasDuplicate(proofMetadataRes.get()) let hasDup = rlnPeer.hasDuplicate(msgEpoch, proofMetadataRes.get())
if hasDup.isErr(): if hasDup.isErr():
waku_rln_errors_total.inc(labelValues=["duplicate_check"]) waku_rln_errors_total.inc(labelValues=["duplicate_check"])
elif hasDup.value == true: elif hasDup.value == true:
@ -282,7 +273,7 @@ proc validateMessageAndUpdateLog*(
return MessageValidationResult.Invalid return MessageValidationResult.Invalid
# insert the message to the log (never errors) # insert the message to the log (never errors)
discard rlnPeer.updateLog(proofMetadataRes.get()) discard rlnPeer.updateLog(msgProof.epoch, proofMetadataRes.get())
return result return result