diff --git a/CHANGELOG.md b/CHANGELOG.md
index cea6a11bb..b5842b63a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,35 @@
-## v0.29.0 (2024-06-11)
+## v0.30.0 (2024-07-01)
+
+### Notes
+
+* Before upgrading to this version, if you are currently using RLN, make sure to remove your existing `keystore` folder and `rln_tree`
+and start your installation from scratch, as
+explained in [nwaku-compose](https://github.com/waku-org/nwaku-compose/blob/1b56575df9ddb904af0941a19ea1df3d36bfddfa/README.md).
+
+### Release highlights
+
+* RLN_v2 is used. The maximum rate can be set to
+`N` messages per epoch, instead of just one message per epoch. See [this](https://github.com/waku-org/nwaku/issues/2345) for more details.
+
+### Changes
+
+- rln-relay: add chain-id flag to wakunode and restrict usage if mismatches rpc provider ([#2858](https://github.com/waku-org/nwaku/pull/2858))
+- rln: fix nullifierlog vulnerability ([#2855](https://github.com/waku-org/nwaku/pull/2855))
+- chore: add TWN parameters for RLNv2 ([#2843](https://github.com/waku-org/nwaku/pull/2843))
+- fix(rln-relay): clear nullifier log only if length is over max epoch gap ([#2836](https://github.com/waku-org/nwaku/pull/2836))
+- rlnv2: clean fork of rlnv2 ([#2828](https://github.com/waku-org/nwaku/issues/2828)) ([a02832fe](https://github.com/waku-org/nwaku/commit/a02832fe))
+- zerokit: bump submodule ([#2830](https://github.com/waku-org/nwaku/issues/2830)) ([bd064882](https://github.com/waku-org/nwaku/commit/bd064882))
+
+This release supports the following [libp2p protocols](https://docs.libp2p.io/concepts/protocols/):
+| Protocol | Spec status | Protocol id |
+| ---: | :---: | :--- |
+| [`11/WAKU2-RELAY`](https://github.com/vacp2p/rfc-index/blob/main/waku/standards/core/11/relay.md) | `stable` | `/vac/waku/relay/2.0.0` |
+| [`12/WAKU2-FILTER`](https://github.com/vacp2p/rfc-index/blob/main/waku/standards/core/12/filter.md) | `draft` | `/vac/waku/filter/2.0.0-beta1`
`/vac/waku/filter-subscribe/2.0.0-beta1`
`/vac/waku/filter-push/2.0.0-beta1` |
+| [`13/WAKU2-STORE`](https://github.com/vacp2p/rfc-index/blob/main/waku/standards/core/13/store.md) | `draft` | `/vac/waku/store/2.0.0-beta4` |
+| [`19/WAKU2-LIGHTPUSH`](https://github.com/vacp2p/rfc-index/blob/main/waku/standards/core/19/lightpush.md) | `draft` | `/vac/waku/lightpush/2.0.0-beta1` |
+| [`66/WAKU2-METADATA`](https://github.com/waku-org/specs/blob/master/standards/core/metadata.md) | `raw` | `/vac/waku/metadata/1.0.0` |
+
+## v0.29.0 (2024-06-19)
## What's Changed
diff --git a/tests/waku_rln_relay/test_rln_group_manager_onchain.nim b/tests/waku_rln_relay/test_rln_group_manager_onchain.nim
index c6e29e5bd..b839a2f7a 100644
--- a/tests/waku_rln_relay/test_rln_group_manager_onchain.nim
+++ b/tests/waku_rln_relay/test_rln_group_manager_onchain.nim
@@ -23,6 +23,8 @@ import
../testlib/common,
./utils
+const CHAIN_ID = 1337
+
proc generateCredentials(rlnInstance: ptr RLN): IdentityCredential =
let credRes = membershipKeyGen(rlnInstance)
return credRes.get()
@@ -137,7 +139,7 @@ proc runAnvil(): Process =
anvilPath,
args = [
"--port", "8540", "--gas-limit", "300000000000000", "--balance", "1000000000",
- "--chain-id", "1337",
+ "--chain-id", $CHAIN_ID,
],
options = {poUsePath},
)
@@ -192,6 +194,7 @@ proc setup(): Future[OnchainGroupManager] {.async.} =
let manager = OnchainGroupManager(
ethClientUrl: EthClient,
ethContractAddress: $contractAddress,
+ chainId: CHAIN_ID,
ethPrivateKey: pk,
rlnInstance: rlnInstance,
)
@@ -215,6 +218,20 @@ suite "Onchain group manager":
await manager.stop()
+ asyncTest "should error on initialization when chainId does not match":
+ let manager = await setup()
+ manager.chainId = CHAIN_ID + 1
+
+ (await manager.init()).isErrOr:
+ raiseAssert "Expected error when chainId does not match"
+
+ asyncTest "should initialize when chainId is set to 0":
+ let manager = await setup()
+ manager.chainId = 0
+
+ (await manager.init()).isOkOr:
+ raiseAssert $error
+
asyncTest "should error on initialization when loaded metadata does not match":
let manager = await setup()
(await manager.init()).isOkOr:
diff --git a/tests/waku_rln_relay/test_waku_rln_relay.nim b/tests/waku_rln_relay/test_waku_rln_relay.nim
index c8f655ad7..454ec10a5 100644
--- a/tests/waku_rln_relay/test_waku_rln_relay.nim
+++ b/tests/waku_rln_relay/test_waku_rln_relay.nim
@@ -886,3 +886,40 @@ suite "Waku rln relay":
check:
buckets.len == 5
buckets == [2.0, 4.0, 6.0, 8.0, 10.0]
+
+ asyncTest "nullifierLog clearing only after epoch has passed":
+ let index = MembershipIndex(0)
+
+ proc runTestForEpochSizeSec(rlnEpochSizeSec: uint) {.async.} =
+ let wakuRlnConfig = WakuRlnConfig(
+ rlnRelayDynamic: false,
+ rlnRelayCredIndex: some(index),
+ rlnRelayUserMessageLimit: 1,
+ rlnEpochSizeSec: rlnEpochSizeSec,
+ rlnRelayTreePath: genTempPath("rln_tree", "waku_rln_relay_4"),
+ )
+
+ let wakuRlnRelay = (await WakuRlnRelay.new(wakuRlnConfig)).valueOr:
+ raiseAssert $error
+
+ let rlnMaxEpochGap = wakuRlnRelay.rlnMaxEpochGap
+ let testProofMetadata = default(ProofMetadata)
+ let testProofMetadataTable = {testProofMetadata.nullifier: testProofMetadata}.toTable()
+
+ for i in 0..rlnMaxEpochGap:
+ # we add epochs to the nullifierLog
+ let testEpoch = wakuRlnRelay.calcEpoch(epochTime() + float(rlnEpochSizeSec * i))
+ wakuRlnRelay.nullifierLog[testEpoch] = testProofMetadataTable
+ check: wakuRlnRelay.nullifierLog.len().uint == i + 1
+
+ check: wakuRlnRelay.nullifierLog.len().uint == rlnMaxEpochGap + 1
+
+ # clearing it now will remove 1 epoch
+ wakuRlnRelay.clearNullifierLog()
+
+ check: wakuRlnRelay.nullifierLog.len().uint == rlnMaxEpochGap
+
+ var testEpochSizes: seq[uint] = @[1,5,10,30,60,600]
+ for i in testEpochSizes:
+ await runTestForEpochSizeSec(i)
+
diff --git a/tools/rln_keystore_generator/rln_keystore_generator.nim b/tools/rln_keystore_generator/rln_keystore_generator.nim
index 6c64ed6d2..2157b1c1b 100644
--- a/tools/rln_keystore_generator/rln_keystore_generator.nim
+++ b/tools/rln_keystore_generator/rln_keystore_generator.nim
@@ -76,7 +76,7 @@ proc doRlnKeystoreGenerator*(conf: WakuNodeConf) =
debug "Transaction hash", txHash = groupManager.registrationTxHash.get()
info "Your membership has been registered on-chain.",
- chainId = $groupManager.chainId.get(),
+ chainId = $groupManager.chainId,
contractAddress = conf.rlnRelayEthContractAddress,
membershipIndex = groupManager.membershipIndex.get()
info "Your user message limit is", userMessageLimit = conf.rlnRelayUserMessageLimit
@@ -84,7 +84,7 @@ proc doRlnKeystoreGenerator*(conf: WakuNodeConf) =
# 6. write to keystore
let keystoreCred = KeystoreMembership(
membershipContract: MembershipContract(
- chainId: $groupManager.chainId.get(), address: conf.rlnRelayEthContractAddress
+ chainId: $groupManager.chainId, address: conf.rlnRelayEthContractAddress
),
treeIndex: groupManager.membershipIndex.get(),
identityCredential: credential,
diff --git a/waku/factory/external_config.nim b/waku/factory/external_config.nim
index bc3c08bb1..a4d487e6a 100644
--- a/waku/factory/external_config.nim
+++ b/waku/factory/external_config.nim
@@ -76,6 +76,12 @@ type WakuNodeConf* = object
name: "rln-relay-eth-contract-address"
.}: string
+ rlnRelayChainId* {.
+ desc: "Chain ID of the provided contract (optional, will fetch from RPC provider if not used)",
+ defaultValue: 0,
+ name: "rln-relay-chain-id"
+ .}: uint
+
rlnRelayCredPassword* {.
desc: "Password for encrypting RLN credentials",
defaultValue: "",
diff --git a/waku/factory/networks_config.nim b/waku/factory/networks_config.nim
index b31aec3c4..49c6dfd9a 100644
--- a/waku/factory/networks_config.nim
+++ b/waku/factory/networks_config.nim
@@ -5,6 +5,7 @@ type ClusterConf* = object
clusterId*: uint16
rlnRelay*: bool
rlnRelayEthContractAddress*: string
+ rlnRelayChainId*: uint
rlnRelayDynamic*: bool
rlnRelayBandwidthThreshold*: int
rlnEpochSizeSec*: uint64
@@ -32,12 +33,12 @@ proc TheWakuNetworkConf*(T: type ClusterConf): ClusterConf =
maxMessageSize: "150KiB",
clusterId: 1,
rlnRelay: true,
- rlnRelayEthContractAddress: "0xF471d71E9b1455bBF4b85d475afb9BB0954A29c4",
+ rlnRelayEthContractAddress: "0x4976Df0f61135EF3E5720D92eadE2e5F47A68Ef9",
rlnRelayDynamic: true,
+ rlnRelayChainId: 2442, # https://chainlist.org/chain/2442
rlnRelayBandwidthThreshold: 0,
- rlnEpochSizeSec: 1,
- # parameter to be defined with rln_v2
- rlnRelayUserMessageLimit: 1,
+ rlnEpochSizeSec: 600,
+ rlnRelayUserMessageLimit: 20,
pubsubTopics:
@[
"/waku/2/rs/1/0", "/waku/2/rs/1/1", "/waku/2/rs/1/2", "/waku/2/rs/1/3",
diff --git a/waku/factory/node_factory.nim b/waku/factory/node_factory.nim
index e2202261c..ad9719ec9 100644
--- a/waku/factory/node_factory.nim
+++ b/waku/factory/node_factory.nim
@@ -203,6 +203,7 @@ proc setupProtocols(
rlnRelayDynamic: conf.rlnRelayDynamic,
rlnRelayCredIndex: conf.rlnRelayCredIndex,
rlnRelayEthContractAddress: conf.rlnRelayEthContractAddress,
+ rlnRelayChainId: conf.rlnRelayChainId,
rlnRelayEthClientAddress: string(conf.rlnRelayethClientAddress),
rlnRelayCredPath: conf.rlnRelayCredPath,
rlnRelayCredPassword: conf.rlnRelayCredPassword,
diff --git a/waku/factory/waku.nim b/waku/factory/waku.nim
index 3c8862093..709be62c2 100644
--- a/waku/factory/waku.nim
+++ b/waku/factory/waku.nim
@@ -113,6 +113,7 @@ proc init*(T: type Waku, conf: WakuNodeConf): Result[Waku, string] =
confCopy.clusterId = twnClusterConf.clusterId
confCopy.rlnRelay = twnClusterConf.rlnRelay
confCopy.rlnRelayEthContractAddress = twnClusterConf.rlnRelayEthContractAddress
+ confCopy.rlnRelayChainId = twnClusterConf.rlnRelayChainId
confCopy.rlnRelayDynamic = twnClusterConf.rlnRelayDynamic
confCopy.rlnRelayBandwidthThreshold = twnClusterConf.rlnRelayBandwidthThreshold
confCopy.discv5Discovery = twnClusterConf.discv5Discovery
diff --git a/waku/waku_rln_relay/group_manager/on_chain/group_manager.nim b/waku/waku_rln_relay/group_manager/on_chain/group_manager.nim
index 1d9935c54..5cc89a6f7 100644
--- a/waku/waku_rln_relay/group_manager/on_chain/group_manager.nim
+++ b/waku/waku_rln_relay/group_manager/on_chain/group_manager.nim
@@ -58,7 +58,7 @@ type
wakuRlnContract*: Option[WakuRlnContractWithSender]
latestProcessedBlock*: BlockNumber
registrationTxHash*: Option[TxHash]
- chainId*: Option[Quantity]
+ chainId*: uint
keystorePath*: Option[string]
keystorePassword*: Option[string]
registrationHandler*: Option[RegistrationHandler]
@@ -103,7 +103,7 @@ proc setMetadata*(
let metadataSetRes = g.rlnInstance.setMetadata(
RlnMetadata(
lastProcessedBlock: normalizedBlock,
- chainId: uint64(g.chainId.get()),
+ chainId: g.chainId,
contractAddress: g.ethContractAddress,
validRoots: g.validRoots.toSeq(),
)
@@ -537,11 +537,18 @@ method init*(g: OnchainGroupManager): Future[GroupManagerResult[void]] {.async.}
g.retryWrapper(ethRpc, "Failed to connect to the Ethereum client"):
await newWeb3(g.ethClientUrl)
+ var fetchedChainId: uint
+ g.retryWrapper(fetchedChainId, "Failed to get the chain id"):
+ uint(await ethRpc.provider.eth_chainId())
+
# Set the chain id
- var chainId: Quantity
- g.retryWrapper(chainId, "Failed to get the chain id"):
- await ethRpc.provider.eth_chainId()
- g.chainId = some(chainId)
+ if g.chainId == 0:
+ warn "Chain ID not set in config, using RPC Provider's Chain ID", providerChainId = fetchedChainId
+
+ if g.chainId != 0 and g.chainId != fetchedChainId:
+ return err("The RPC Provided a Chain ID which is different than the provided Chain ID: provided = " & $g.chainId & ", actual = " & $fetchedChainId)
+
+ g.chainId = fetchedChainId
if g.ethPrivateKey.isSome():
let pk = g.ethPrivateKey.get()
@@ -564,7 +571,7 @@ method init*(g: OnchainGroupManager): Future[GroupManagerResult[void]] {.async.}
var keystoreQuery = KeystoreMembership(
membershipContract:
- MembershipContract(chainId: $g.chainId.get(), address: g.ethContractAddress)
+ MembershipContract(chainId: $g.chainId, address: g.ethContractAddress)
)
if g.membershipIndex.isSome():
keystoreQuery.treeIndex = MembershipIndex(g.membershipIndex.get())
@@ -596,7 +603,7 @@ method init*(g: OnchainGroupManager): Future[GroupManagerResult[void]] {.async.}
warn "could not initialize with persisted rln metadata"
elif metadataGetOptRes.get().isSome():
let metadata = metadataGetOptRes.get().get()
- if metadata.chainId != uint64(g.chainId.get()):
+ if metadata.chainId != uint(g.chainId):
return err("persisted data: chain id mismatch")
if metadata.contractAddress != g.ethContractAddress.toLower():
diff --git a/waku/waku_rln_relay/rln_relay.nim b/waku/waku_rln_relay/rln_relay.nim
index e4aeb9552..2cc0cc8c9 100644
--- a/waku/waku_rln_relay/rln_relay.nim
+++ b/waku/waku_rln_relay/rln_relay.nim
@@ -1,3 +1,4 @@
+
{.push raises: [].}
import
@@ -37,6 +38,7 @@ type WakuRlnConfig* = object
rlnRelayCredIndex*: Option[uint]
rlnRelayEthContractAddress*: string
rlnRelayEthClientAddress*: string
+ rlnRelayChainId*: uint
rlnRelayCredPath*: string
rlnRelayCredPassword*: string
rlnRelayTreePath*: string
@@ -141,7 +143,7 @@ proc updateLog*(
try:
# check if an identical record exists
if rlnPeer.nullifierLog[epoch].hasKeyOrPut(proofMetadata.nullifier, proofMetadata):
- # the above condition could be `discarded` but it is kept for clarity, that slashing will
+ # the above condition could be `discarded` but it is kept for clarity, that slashing will
# be implemented here
# TODO: slashing logic
return ok()
@@ -276,8 +278,10 @@ proc validateMessageAndUpdateLog*(
if proofMetadataRes.isErr():
return MessageValidationResult.Invalid
- # insert the message to the log (never errors)
- discard rlnPeer.updateLog(msgProof.epoch, proofMetadataRes.get())
+ # insert the message to the log (never errors) only if the
+ # message is valid.
+ if isValidMessage == MessageValidationResult.Valid:
+ discard rlnPeer.updateLog(msgProof.epoch, proofMetadataRes.get())
return isValidMessage
@@ -305,21 +309,25 @@ proc appendRLNProof*(
let proof = rlnPeer.groupManager.generateProof(input, epoch, nonce).valueOr:
return err("could not generate rln-v2 proof: " & $error)
-
msg.proof = proof.encode().buffer
return ok()
-proc clearNullifierLog(rlnPeer: WakuRlnRelay) =
+proc clearNullifierLog*(rlnPeer: WakuRlnRelay) =
# clear the first MaxEpochGap epochs of the nullifer log
# if more than MaxEpochGap epochs are in the log
- # note: the epochs are ordered ascendingly
- if rlnPeer.nullifierLog.len().uint < rlnPeer.rlnMaxEpochGap:
- return
+ let currentEpoch = fromEpoch(rlnPeer.getCurrentEpoch())
- trace "clearing epochs from the nullifier log", count = rlnPeer.rlnMaxEpochGap
- let epochsToClear = rlnPeer.nullifierLog.keys().toSeq()[0 ..< rlnPeer.rlnMaxEpochGap]
- for epoch in epochsToClear:
- rlnPeer.nullifierLog.del(epoch)
+ var epochsToRemove: seq[Epoch] = @[]
+ for epoch in rlnPeer.nullifierLog.keys():
+ let epochInt = fromEpoch(epoch)
+
+ # clean all epochs that are +- rlnMaxEpochGap from the current epoch
+ if (currentEpoch+rlnPeer.rlnMaxEpochGap) <= epochInt or epochInt <= (currentEpoch-rlnPeer.rlnMaxEpochGap):
+ epochsToRemove.add(epoch)
+
+ for epochRemove in epochsToRemove:
+ trace "clearing epochs from the nullifier log", currentEpoch = currentEpoch, cleanedEpoch = fromEpoch(epochRemove)
+ rlnPeer.nullifierLog.del(epochRemove)
proc generateRlnValidator*(
wakuRlnRelay: WakuRLNRelay, spamHandler = none(SpamHandler)
@@ -420,6 +428,7 @@ proc mount(
groupManager = OnchainGroupManager(
ethClientUrl: string(conf.rlnRelayethClientAddress),
ethContractAddress: $conf.rlnRelayEthContractAddress,
+ chainId: conf.rlnRelayChainId,
rlnInstance: rlnInstance,
registrationHandler: registrationHandler,
keystorePath: rlnRelayCredPath,