mirror of https://github.com/waku-org/nwaku.git
Updates rln-chat2 interface (#846)
* adds ProofMetadata * adds EPOCH_INTERVAL * adds messageLog field * adds updateLog, toEpoch, fromEpoch, getEpoch, compareTo * adds unit test for toEpoch and fromEpoch * adds unit test for Epoch comparison * adds result codes for updateLog * adds unit test for update log * renames epoch related consts * modifies updateLog with new return type and new logic of spam detection * adds unit text for the modified updateLog * changes max epoch gap type size * splits updateLog into two procs isSpam and updateLog * updates unittests * fixes a bug, returns false when the message is not spam * renames messageLog to nullifierLog * renames isSpam to hasDuplicate * updates the rln validator, adds comments * adds appendRLNProof proc plus some code beatification * unit test for validate message * adds unhappy test to validateMessage unit test * renames EPOCH_UNIT_SECONDS * renames MAX_CLOCK_GAP_SECONDS * WIP: integration test * fixes compile errors * sets a real epoch value * updates on old unittests * adds comments to the rln relay tests * adds more comments * makes rln import conditional * adds todos * adds more todos * adds rln-relay mount process into chat2 * further todos * logs contentTopic * introduces rln relay configs * changes default pubsub topic * adds contentTopic config * imports rln relay dependencies * consolidates imports * removes module identifier from ContentTopic * adds contentTopic field * adds contentTopic argument to mountRlnRelay calls * appends rln proof to chat2 messages * changes the default chat2 contentTopic * adds missing content topic fields * fixes a bug * adds a new logic about empty content topics * appends proof only when rln flag is active * removes unnecessary todos * fixes an indentation issue * adds log messages * verifies the proof against the concatenation of msg payload and content topic * a bug fix * WIP * removes duplicate epoch time calculation * converts echo to log * invokes handler * bug fix * prints calculated epoch * changes the format of printed epoch * updates log levels * logs the input buffer supplied to the generate_proof * replaces echos with logs * changes log level to trace * resets the log level of chat2 to INFO * upgrades log level to debug * exports toRLNSignal and adds a doc string * updates log level * enables all test2 * removes an echo statement * modifies a comment * further updates on the log level * a minor update * invokes the spam handler when provided * checks for payload version * deletes a redundant check * deletes a rendant check * updates default rln-relay cht2 content topic * adds a todo and log * changes the case of testnet content topic * removes a flaky check
This commit is contained in:
parent
e1254c20ae
commit
d851d48424
|
@ -129,6 +129,32 @@ proc showChatPrompt(c: Chat) =
|
|||
except IOError:
|
||||
discard
|
||||
|
||||
proc getChatLine(c: Chat, msg:WakuMessage): Result[string, string]=
|
||||
when PayloadV1:
|
||||
# Use Waku v1 payload encoding/encryption
|
||||
let
|
||||
keyInfo = KeyInfo(kind: Symmetric, symKey: c.symKey)
|
||||
decodedPayload = decodePayload(decoded.get(), keyInfo)
|
||||
|
||||
if decodedPayload.isOK():
|
||||
let
|
||||
pb = Chat2Message.init(decodedPayload.get().payload)
|
||||
chatLine = if pb.isOk: pb[].toString()
|
||||
else: string.fromBytes(decodedPayload.get().payload)
|
||||
return ok(chatLine)
|
||||
else:
|
||||
debug "Invalid encoded WakuMessage payload",
|
||||
error = decodedPayload.error
|
||||
return err("Invalid encoded WakuMessage payload")
|
||||
else:
|
||||
# No payload encoding/encryption from Waku
|
||||
let
|
||||
pb = Chat2Message.init(msg.payload)
|
||||
chatLine = if pb.isOk: pb[].toString()
|
||||
else: string.fromBytes(msg.payload)
|
||||
return ok(chatline)
|
||||
|
||||
|
||||
proc printReceivedMessage(c: Chat, msg: WakuMessage) =
|
||||
when PayloadV1:
|
||||
# Use Waku v1 payload encoding/encryption
|
||||
|
@ -225,6 +251,8 @@ proc publish(c: Chat, line: string) =
|
|||
debug "could not append rate limit proof to the message", success=success
|
||||
else:
|
||||
debug "rate limit proof is appended to the message", success=success
|
||||
# TODO move it to log after doogfooding
|
||||
echo "--rln epoch: ", fromEpoch(message.proof.epoch)
|
||||
if not c.node.wakuLightPush.isNil():
|
||||
# Attempt lightpush
|
||||
asyncSpawn c.node.lightpush(DefaultTopic, message, handler)
|
||||
|
@ -245,6 +273,7 @@ proc publish(c: Chat, line: string) =
|
|||
debug "could not append rate limit proof to the message", success=success
|
||||
else:
|
||||
debug "rate limit proof is appended to the message", success=success
|
||||
echo "--rln epoch: ", fromEpoch(message.proof.epoch)
|
||||
|
||||
if not c.node.wakuLightPush.isNil():
|
||||
# Attempt lightpush
|
||||
|
@ -372,7 +401,6 @@ proc processInput(rfd: AsyncFD, rng: ref BrHmacDrbgContext) {.async.} =
|
|||
prompt: false,
|
||||
contentTopic: conf.contentTopic,
|
||||
symKey: generateSymKey(conf.contentTopic))
|
||||
|
||||
if conf.staticnodes.len > 0:
|
||||
await connectToNodes(chat, conf.staticnodes)
|
||||
elif conf.dnsDiscovery and conf.dnsDiscoveryUrl != "":
|
||||
|
@ -495,16 +523,24 @@ proc processInput(rfd: AsyncFD, rng: ref BrHmacDrbgContext) {.async.} =
|
|||
if conf.rlnRelay:
|
||||
info "WakuRLNRelay is enabled"
|
||||
|
||||
proc spamHandler(wakuMessage: WakuMessage) {.gcsafe, closure.} =
|
||||
debug "spam handler is called"
|
||||
let chatLineResult = chat.getChatLine(wakuMessage)
|
||||
if chatLineResult.isOk():
|
||||
echo "A spam message is found and discarded : ", chatLineResult.value
|
||||
else:
|
||||
echo "A spam message is found and discarded"
|
||||
|
||||
# set up rln relay inputs
|
||||
let (groupOpt, memKeyPairOpt, memIndexOpt) = rlnRelaySetUp(conf.rlnRelayMemIndex)
|
||||
if memIndexOpt.isNone:
|
||||
error "failed to mount WakuRLNRelay"
|
||||
else:
|
||||
# mount rlnrelay in offline mode (for now)
|
||||
waitFor node.mountRlnRelay(groupOpt = groupOpt, memKeyPairOpt = memKeyPairOpt, memIndexOpt= memIndexOpt, onchainMode = false, pubsubTopic = conf.rlnRelayPubsubTopic, contentTopic = conf.rlnRelayContentTopic)
|
||||
waitFor node.mountRlnRelay(groupOpt = groupOpt, memKeyPairOpt = memKeyPairOpt, memIndexOpt= memIndexOpt, onchainMode = false, pubsubTopic = conf.rlnRelayPubsubTopic, contentTopic = conf.rlnRelayContentTopic, spamHandler = some(spamHandler))
|
||||
|
||||
trace "membership id key", idkey=memKeyPairOpt.get().idKey.toHex
|
||||
trace "membership id commitment key", idCommitmentkey=memKeyPairOpt.get().idCommitment.toHex
|
||||
debug "membership id key", idkey=memKeyPairOpt.get().idKey.toHex
|
||||
debug "membership id commitment key", idCommitmentkey=memKeyPairOpt.get().idCommitment.toHex
|
||||
|
||||
# check the correct construction of the tree by comparing the calculated root against the expected root
|
||||
# no error should happen as it is already captured in the unit tests
|
||||
|
@ -514,8 +550,8 @@ proc processInput(rfd: AsyncFD, rng: ref BrHmacDrbgContext) {.async.} =
|
|||
expectedRoot = STATIC_GROUP_MERKLE_ROOT
|
||||
if root != expectedRoot:
|
||||
error "root mismatch: something went wrong not in Merkle tree construction"
|
||||
trace "the calculated root", root
|
||||
trace "WakuRLNRelay is mounted successfully", pubsubtopic=conf.rlnRelayPubsubTopic, contentTopic=conf.rlnRelayContentTopic
|
||||
debug "the calculated root", root
|
||||
debug "WakuRLNRelay is mounted successfully", pubsubtopic=conf.rlnRelayPubsubTopic, contentTopic=conf.rlnRelayContentTopic
|
||||
|
||||
|
||||
await chat.readWriteLoop()
|
||||
|
|
|
@ -241,7 +241,7 @@ type
|
|||
|
||||
rlnRelayContentTopic* {.
|
||||
desc: "the pubsub topic for which rln-relay gets enabled",
|
||||
defaultValue: "waku/2/rln-relay/proto"
|
||||
defaultValue: "/toy-chat/2/luzhou/proto"
|
||||
name: "rln-relay-content-topic" }: ContentTopic
|
||||
|
||||
rlnRelayPubsubTopic* {.
|
||||
|
|
|
@ -911,12 +911,12 @@ procSuite "WakuNode":
|
|||
wm1 = WakuMessage(payload: "message 1".toBytes(), contentTopic: contentTopic)
|
||||
proofAdded1 = node3.wakuRlnRelay.appendRLNProof(wm1, time)
|
||||
# another message in the same epoch as wm1, it will break the messaging rate limit
|
||||
wm2 = WakuMessage(payload: "message2".toBytes(), contentTopic: contentTopic)
|
||||
wm2 = WakuMessage(payload: "message 2".toBytes(), contentTopic: contentTopic)
|
||||
proofAdded2 = node3.wakuRlnRelay.appendRLNProof(wm2, time)
|
||||
# wm3 points to the next epoch
|
||||
wm3 = WakuMessage(payload: "message 3".toBytes(), contentTopic: contentTopic)
|
||||
proofAdded3 = node3.wakuRlnRelay.appendRLNProof(wm3, time+EPOCH_UNIT_SECONDS)
|
||||
wm4 = WakuMessage(payload: "message4".toBytes(), contentTopic: contentTopic)
|
||||
wm4 = WakuMessage(payload: "message 4".toBytes(), contentTopic: contentTopic)
|
||||
|
||||
# check proofs are added correctly
|
||||
check:
|
||||
|
@ -966,9 +966,7 @@ procSuite "WakuNode":
|
|||
res2 = await completionFut2.withTimeout(10.seconds)
|
||||
|
||||
check:
|
||||
res1 or res2 == true # either of the wm1 and wm2 is relayed
|
||||
(res1 and res2) == false # either of the wm1 and wm2 is found as spam hence not relayed
|
||||
(await completionFut2.withTimeout(10.seconds)) == true
|
||||
(await completionFut3.withTimeout(10.seconds)) == true
|
||||
(await completionFut4.withTimeout(10.seconds)) == false
|
||||
|
||||
|
|
|
@ -470,30 +470,34 @@ proc mountStore*(node: WakuNode, store: MessageStore = nil, persistMessages: boo
|
|||
node.switch.mount(node.wakuStore, protocolMatcher(WakuStoreCodec))
|
||||
|
||||
when defined(rln):
|
||||
proc addRLNRelayValidator*(node: WakuNode, pubsubTopic: string, contentTopic: ContentTopic) =
|
||||
proc addRLNRelayValidator*(node: WakuNode, pubsubTopic: string, contentTopic: ContentTopic, spamHandler: Option[SpamHandler] = none(SpamHandler)) =
|
||||
## this procedure is a thin wrapper for the pubsub addValidator method
|
||||
## it sets a validator for the waku messages published on the supplied pubsubTopic and contentTopic
|
||||
## if contentTopic is empty, then validation takes place for All the messages published on the given pubsubTopic
|
||||
## the message validation logic is according to https://rfc.vac.dev/spec/17/
|
||||
proc validator(topic: string, message: messages.Message): Future[pubsub.ValidationResult] {.async.} =
|
||||
debug "rln-relay topic validator is called"
|
||||
let msg = WakuMessage.init(message.data)
|
||||
if msg.isOk():
|
||||
let wakumessage = msg.value()
|
||||
# check the contentTopic
|
||||
if (wakumessage.contentTopic != "") and (contentTopic != "") and (wakumessage.contentTopic != contentTopic):
|
||||
trace "content topic did not match:", contentTopic=wakumessage.contentTopic, payload=string.fromBytes(wakumessage.payload)
|
||||
debug "content topic did not match:", contentTopic=wakumessage.contentTopic, payload=string.fromBytes(wakumessage.payload)
|
||||
return pubsub.ValidationResult.Accept
|
||||
# validate the message
|
||||
let validationRes = node.wakuRlnRelay.validateMessage(wakumessage)
|
||||
case validationRes:
|
||||
of Valid:
|
||||
trace "message validity is verified, relaying:", wakumessage=wakumessage, payload=string.fromBytes(wakumessage.payload)
|
||||
debug "message validity is verified, relaying:", wakumessage=wakumessage, payload=string.fromBytes(wakumessage.payload)
|
||||
return pubsub.ValidationResult.Accept
|
||||
of Invalid:
|
||||
trace "message validity could not be verified, discarding:", wakumessage=wakumessage, payload=string.fromBytes(wakumessage.payload)
|
||||
debug "message validity could not be verified, discarding:", wakumessage=wakumessage, payload=string.fromBytes(wakumessage.payload)
|
||||
return pubsub.ValidationResult.Reject
|
||||
of Spam:
|
||||
trace "A spam message is found! yay! discarding:", wakumessage=wakumessage, payload=string.fromBytes(wakumessage.payload)
|
||||
debug "A spam message is found! yay! discarding:", wakumessage=wakumessage, payload=string.fromBytes(wakumessage.payload)
|
||||
if spamHandler.isSome:
|
||||
let handler = spamHandler.get
|
||||
handler(wakumessage)
|
||||
return pubsub.ValidationResult.Reject
|
||||
# set a validator for the supplied pubsubTopic
|
||||
let pb = PubSub(node.wakuRelay)
|
||||
|
@ -508,7 +512,8 @@ when defined(rln):
|
|||
memIndexOpt: Option[MembershipIndex] = none(MembershipIndex),
|
||||
onchainMode: bool = true,
|
||||
pubsubTopic: string,
|
||||
contentTopic: ContentTopic) {.async.} =
|
||||
contentTopic: ContentTopic,
|
||||
spamHandler: Option[SpamHandler] = none(SpamHandler)) {.async.} =
|
||||
# TODO return a bool value to indicate the success of the call
|
||||
# check whether inputs are provided
|
||||
|
||||
|
@ -601,8 +606,8 @@ when defined(rln):
|
|||
# adds a topic validator for the supplied pubsub topic at the relay protocol
|
||||
# messages published on this pubsub topic will be relayed upon a successful validation, otherwise they will be dropped
|
||||
# the topic validator checks for the correct non-spamming proof of the message
|
||||
addRLNRelayValidator(node, pubsubTopic, contentTopic)
|
||||
debug "rln relay topic validator is mounted successfully", pubsubTopic=pubsubTopic
|
||||
addRLNRelayValidator(node, pubsubTopic, contentTopic, spamHandler)
|
||||
debug "rln relay topic validator is mounted successfully", pubsubTopic=pubsubTopic, contentTopic=contentTopic
|
||||
|
||||
node.wakuRlnRelay = rlnPeer
|
||||
|
||||
|
|
|
@ -116,8 +116,8 @@ const
|
|||
# the root is created locally, using createMembershipList proc from waku_rln_relay_utils module, and the result is hardcoded in here
|
||||
STATIC_GROUP_MERKLE_ROOT* = "a1877a553eff12e1b21632a0545a916a5c5b8060ad7cc6c69956741134397b2d"
|
||||
|
||||
const EPOCH_UNIT_SECONDS* = float64(2)
|
||||
const MAX_CLOCK_GAP_SECONDS* = 20.0 # the maximum clock difference between peers
|
||||
const EPOCH_UNIT_SECONDS* = float64(10) # the rln-relay epoch length in seconds
|
||||
const MAX_CLOCK_GAP_SECONDS* = 20.0 # the maximum clock difference between peers in seconds
|
||||
# maximum allowed gap between the epochs of messages' RateLimitProofs
|
||||
const MAX_EPOCH_GAP* = int64(MAX_CLOCK_GAP_SECONDS/EPOCH_UNIT_SECONDS)
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@ logScope:
|
|||
type RLNResult* = Result[RLN[Bn256], string]
|
||||
type MerkleNodeResult* = Result[MerkleNode, string]
|
||||
type RateLimitProofResult* = Result[RateLimitProof, string]
|
||||
type SpamHandler* = proc(wakuMessage: WakuMessage): void {.gcsafe, closure, raises: [Defect].}
|
||||
|
||||
# membership contract interface
|
||||
contract(MembershipContract):
|
||||
# TODO define a return type of bool for register method to signify a successful registration
|
||||
|
@ -157,6 +159,8 @@ proc proofGen*(rlnInstance: RLN[Bn256], data: openArray[byte], memKeys: Membersh
|
|||
msg = data)
|
||||
var inputBuffer = toBuffer(serializedInputs)
|
||||
|
||||
debug "input buffer ", inputBuffer
|
||||
|
||||
# generate the proof
|
||||
var proof: Buffer
|
||||
let proofIsSuccessful = generate_proof(rlnInstance, addr inputBuffer, addr proof)
|
||||
|
@ -445,16 +449,19 @@ proc validateMessage*(rlnPeer: WakuRLNRelay, msg: WakuMessage, timeOption: Optio
|
|||
# get current rln epoch
|
||||
epoch = getCurrentEpoch()
|
||||
|
||||
debug "current epoch", currentEpoch=fromEpoch(epoch)
|
||||
let
|
||||
msgEpoch = msg.proof.epoch
|
||||
# calculate the gaps
|
||||
gap = compare(epoch, msgEpoch)
|
||||
|
||||
|
||||
debug "message epoch", msgEpoch=fromEpoch(msgEpoch)
|
||||
|
||||
# validate the epoch
|
||||
if abs(gap) >= MAX_EPOCH_GAP:
|
||||
# message's epoch is too old or too ahead
|
||||
# accept messages whose epoch is within +-MAX_EPOCH_GAP from the current epoch
|
||||
debug "invalid message: epoch gap exceeds a threshold",gap=gap
|
||||
debug "invalid message: epoch gap exceeds a threshold",gap=gap, payload=string.fromBytes(msg.payload)
|
||||
return MessageValidationResult.Invalid
|
||||
|
||||
# verify the proof
|
||||
|
@ -463,31 +470,39 @@ proc validateMessage*(rlnPeer: WakuRLNRelay, msg: WakuMessage, timeOption: Optio
|
|||
input = concat(msg.payload, contentTopicBytes)
|
||||
if not rlnPeer.rlnInstance.proofVerify(input, msg.proof):
|
||||
# invalid proof
|
||||
debug "invalid message: invalid proof"
|
||||
debug "invalid message: invalid proof", payload=string.fromBytes(msg.payload)
|
||||
return MessageValidationResult.Invalid
|
||||
|
||||
# check if double messaging has happened
|
||||
let hasDup = rlnPeer.hasDuplicate(msg)
|
||||
if hasDup.isOk and hasDup.value == true:
|
||||
debug "invalid message: message is a spam"
|
||||
debug "invalid message: message is a spam", payload=string.fromBytes(msg.payload)
|
||||
return MessageValidationResult.Spam
|
||||
|
||||
# insert the message to the log
|
||||
# the result of `updateLog` is discarded because message insertion is guaranteed by the implementation i.e.,
|
||||
# it will never error out
|
||||
discard rlnPeer.updateLog(msg)
|
||||
debug "message is valid", payload=string.fromBytes(msg.payload)
|
||||
return MessageValidationResult.Valid
|
||||
|
||||
|
||||
proc toRLNSignal*(wakumessage: WakuMessage): seq[byte] =
|
||||
## it is a utility proc that prepares the `data` parameter of the proof generation procedure i.e., `proofGen` that resides in the current module
|
||||
## it extracts the `contentTopic` and the `payload` of the supplied `wakumessage` and serializes them into a byte sequence
|
||||
let
|
||||
contentTopicBytes = wakumessage.contentTopic.toBytes
|
||||
output = concat(wakumessage.payload, contentTopicBytes)
|
||||
return output
|
||||
|
||||
|
||||
proc appendRLNProof*(rlnPeer: WakuRLNRelay, msg: var WakuMessage, senderEpochTime: float64): bool =
|
||||
## returns true if it can create and append a `RateLimitProof` to the supplied `msg`
|
||||
## returns false otherwise
|
||||
## `senderEpochTime` indicates the number of seconds passed since Unix epoch. The fractional part holds sub-seconds.
|
||||
## The `epoch` field of `RateLimitProof` is derived from the provided `senderEpochTime` (using `calcEpoch()`)
|
||||
|
||||
let
|
||||
contentTopicBytes = msg.contentTopic.toBytes
|
||||
input = concat(msg.payload, contentTopicBytes)
|
||||
let input = msg.toRLNSignal()
|
||||
|
||||
var proof: RateLimitProofResult = proofGen(rlnInstance = rlnPeer.rlnInstance, data = input,
|
||||
memKeys = rlnPeer.membershipKeyPair,
|
||||
|
|
Loading…
Reference in New Issue