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
# no duplicate for proof1 should be found, since the log is empty
let result1 = wakurlnrelay.hasDuplicate(proof1.extractMetadata().tryGet())
require:
result1.isOk()
# no duplicate is found
result1.value == false
let result1 = wakurlnrelay.hasDuplicate(epoch, proof1.extractMetadata().tryGet())
assert result1.isOk(), $result1.error
assert result1.value == false, "no duplicate should be found"
# 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
let result2 = wakurlnrelay.hasDuplicate(proof2.extractMetadata().tryGet())
require:
result2.isOk()
# no duplicate is found
result2.value == false
# no duplicate for proof2 should be found, its nullifier differs from proof1
let result2 = wakurlnrelay.hasDuplicate(epoch, proof2.extractMetadata().tryGet())
assert result2.isOk(), $result2.error
# no duplicate is found
assert result2.value == false, "no duplicate should be found"
# 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
let result3 = wakurlnrelay.hasDuplicate(proof3.extractMetadata().tryGet())
require:
result3.isOk()
check:
# it is a duplicate
result3.value == true
let result3 = wakurlnrelay.hasDuplicate(epoch, proof3.extractMetadata().tryGet())
assert result3.isOk(), $result3.error
# it is a duplicate
assert result3.value, "duplicate should be found"
asyncTest "validateMessageAndUpdateLog test":
let index = MembershipIndex(5)
@ -710,6 +705,52 @@ suite "Waku rln relay":
msgValidate3 == MessageValidationResult.Valid
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":
# create an instance of rln
let rlnInstance = createRLNInstanceWrapper()

View File

@ -79,7 +79,7 @@ proc createMembershipList*(rln: ptr RLN, n: int): RlnRelayResult[(
type WakuRLNRelay* = ref object of RootObj
# 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
rlnEpochSizeSec*: uint64
rlnMaxEpochGap*: uint64
@ -103,58 +103,49 @@ proc stop*(rlnPeer: WakuRLNRelay) {.async: (raises: [Exception]).} =
await rlnPeer.groupManager.stop()
proc hasDuplicate*(rlnPeer: WakuRLNRelay,
epoch: Epoch,
proofMetadata: ProofMetadata): RlnRelayResult[bool] =
## 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
## otherwise, returns false
## Returns an error if it cannot check for duplicates
let externalNullifier = proofMetadata.externalNullifier
# check if the epoch exists
if not rlnPeer.nullifierLog.hasKey(externalNullifier):
let nullifier = proofMetadata.nullifier
if not rlnPeer.nullifierLog.hasKey(epoch):
return ok(false)
try:
if rlnPeer.nullifierLog[externalNullifier].contains(proofMetadata):
if rlnPeer.nullifierLog[epoch].hasKey(nullifier):
# there is an identical record, mark it as spam
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
return ok(false)
except KeyError as e:
return err("the epoch was not found")
except KeyError:
return err("the epoch was not found: " & getCurrentExceptionMsg())
proc updateLog*(rlnPeer: WakuRLNRelay,
epoch: Epoch,
proofMetadata: ProofMetadata): RlnRelayResult[void] =
## saves supplied proofMetadata `proofMetadata`
## in the `nullifierLog` of the `rlnPeer`
## Returns an error if it cannot update the log
let externalNullifier = proofMetadata.externalNullifier
# check if the externalNullifier exists
if not rlnPeer.nullifierLog.hasKey(externalNullifier):
rlnPeer.nullifierLog[externalNullifier] = @[proofMetadata]
# check if the epoch exists
if not rlnPeer.nullifierLog.hasKeyOrPut(epoch, { proofMetadata.nullifier: proofMetadata }.toTable()):
return ok()
try:
# 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
return ok()
# add proofMetadata to the log
rlnPeer.nullifierLog[externalNullifier].add(proofMetadata)
return ok()
except KeyError as e:
return err("the external nullifier was not found") # should never happen
except KeyError:
return err("the epoch was not found: " & getCurrentExceptionMsg()) # should never happen
proc getCurrentEpoch*(rlnPeer: WakuRLNRelay): Epoch =
## gets the current rln Epoch time
@ -249,7 +240,7 @@ proc validateMessage*(rlnPeer: WakuRLNRelay,
if proofMetadataRes.isErr():
waku_rln_errors_total.inc(labelValues=["proof_metadata_extraction"])
return MessageValidationResult.Invalid
let hasDup = rlnPeer.hasDuplicate(proofMetadataRes.get())
let hasDup = rlnPeer.hasDuplicate(msgEpoch, proofMetadataRes.get())
if hasDup.isErr():
waku_rln_errors_total.inc(labelValues=["duplicate_check"])
elif hasDup.value == true:
@ -282,7 +273,7 @@ proc validateMessageAndUpdateLog*(
return MessageValidationResult.Invalid
# insert the message to the log (never errors)
discard rlnPeer.updateLog(proofMetadataRes.get())
discard rlnPeer.updateLog(msgProof.epoch, proofMetadataRes.get())
return result