From 2d46c35117e2a4bb61c07f28d116ca51c4dc4f3d Mon Sep 17 00:00:00 2001 From: Aaryamann Challani <43716372+rymnc@users.noreply.github.com> Date: Fri, 9 Feb 2024 16:31:45 +0530 Subject: [PATCH] feat(rln-relay-v2): rln-keystore-generator updates (#2392) * chore: init rln-v2 in OnchainGroupManager * chore: update wrappers * fix: units for userMessageLimit * valueOr for error handling * fix: len usage --- apps/wakunode2/external_config.nim | 5 + .../rln_keystore_generator.nim | 34 +- waku/waku_keystore/protocol_types.nim | 42 +- waku/waku_rln_relay/conversion_utils.nim | 4 + .../group_manager/group_manager_base.nim | 132 ++++--- .../group_manager/on_chain/group_manager.nim | 374 ++++++++++++------ .../group_manager/static/group_manager.nim | 105 +++-- waku/waku_rln_relay/protocol_types.nim | 4 + waku/waku_rln_relay/rln/wrappers.nim | 32 +- waku/waku_rln_relay/rln_relay.nim | 7 +- 10 files changed, 515 insertions(+), 224 deletions(-) diff --git a/apps/wakunode2/external_config.nim b/apps/wakunode2/external_config.nim index 1aa1305d3c..907f42e6de 100644 --- a/apps/wakunode2/external_config.nim +++ b/apps/wakunode2/external_config.nim @@ -81,6 +81,11 @@ type desc: "Private key for broadcasting transactions", defaultValue: "", name: "rln-relay-eth-private-key" }: string + + rlnRelayUserMessageLimit* {. + desc: "Set a user message limit for the rln membership registration. Must be a positive integer. Default is 1.", + defaultValue: 1, + name: "rln-relay-user-message-limit" .}: uint64 maxMessageSize* {. desc: "Maximum message size. Accepted units: KiB, KB, and B. e.g. 1024KiB; 1500 B; etc." diff --git a/tools/rln_keystore_generator/rln_keystore_generator.nim b/tools/rln_keystore_generator/rln_keystore_generator.nim index 5c312575ee..5742eedc29 100644 --- a/tools/rln_keystore_generator/rln_keystore_generator.nim +++ b/tools/rln_keystore_generator/rln_keystore_generator.nim @@ -63,7 +63,10 @@ proc doRlnKeystoreGenerator*(conf: WakuNodeConf) = # 5. register on-chain try: - waitFor groupManager.register(credential) + when defined(rln_v2): + waitFor groupManager.register(credential, conf.rlnRelayUserMessageLimit) + else: + waitFor groupManager.register(credential) except Exception, CatchableError: error "failure while registering credentials on-chain", error=getCurrentExceptionMsg() quit(1) @@ -73,16 +76,29 @@ proc doRlnKeystoreGenerator*(conf: WakuNodeConf) = info "Your membership has been registered on-chain.", chainId = $groupManager.chainId.get(), contractAddress = conf.rlnRelayEthContractAddress, membershipIndex = groupManager.membershipIndex.get() + when defined(rln_v2): + info "Your user message limit is", userMessageLimit = conf.rlnRelayUserMessageLimit # 6. write to keystore - let keystoreCred = KeystoreMembership( - membershipContract: MembershipContract( - chainId: $groupManager.chainId.get(), - address: conf.rlnRelayEthContractAddress, - ), - treeIndex: groupManager.membershipIndex.get(), - identityCredential: credential, - ) + when defined(rln_v2): + let keystoreCred = KeystoreMembership( + membershipContract: MembershipContract( + chainId: $groupManager.chainId.get(), + address: conf.rlnRelayEthContractAddress, + ), + treeIndex: groupManager.membershipIndex.get(), + identityCredential: credential, + userMessageLimit: conf.rlnRelayUserMessageLimit, + ) + else: + let keystoreCred = KeystoreMembership( + membershipContract: MembershipContract( + chainId: $groupManager.chainId.get(), + address: conf.rlnRelayEthContractAddress, + ), + treeIndex: groupManager.membershipIndex.get(), + identityCredential: credential, + ) let persistRes = addMembershipCredentials(conf.rlnRelayCredPath, keystoreCred, diff --git a/waku/waku_keystore/protocol_types.nim b/waku/waku_keystore/protocol_types.nim index c1a5babd76..7f2b25a282 100644 --- a/waku/waku_keystore/protocol_types.nim +++ b/waku/waku_keystore/protocol_types.nim @@ -42,8 +42,7 @@ proc toIDCommitment*(idCommitmentUint: UInt256): IDCommitment = type MembershipIndex* = uint proc toMembershipIndex*(v: UInt256): MembershipIndex = - let membershipIndex: MembershipIndex = cast[MembershipIndex](v) - return membershipIndex + return cast[MembershipIndex](v) # Converts a sequence of tuples containing 4 string (i.e. identity trapdoor, nullifier, secret hash and commitment) to an IndentityCredential type RawMembershipCredentials* = (string, string, string, string) @@ -93,18 +92,35 @@ type KeystoreMembership* = ref object of RootObj membershipContract*: MembershipContract treeIndex*: MembershipIndex identityCredential*: IdentityCredential + when defined(rln_v2): + userMessageLimit*: uint64 -proc `$`*(m: KeystoreMembership): string = - return "KeystoreMembership(chainId: " & m.membershipContract.chainId & ", contractAddress: " & m.membershipContract.address & ", treeIndex: " & $m.treeIndex & ", identityCredential: " & $m.identityCredential & ")" - -proc `==`*(x, y: KeystoreMembership): bool = - return x.membershipContract.chainId == y.membershipContract.chainId and - x.membershipContract.address == y.membershipContract.address and - x.treeIndex == y.treeIndex and - x.identityCredential.idTrapdoor == y.identityCredential.idTrapdoor and - x.identityCredential.idNullifier == y.identityCredential.idNullifier and - x.identityCredential.idSecretHash == y.identityCredential.idSecretHash and - x.identityCredential.idCommitment == y.identityCredential.idCommitment +when defined(rln_v2): + proc `$`*(m: KeystoreMembership): string = + return "KeystoreMembership(chainId: " & m.membershipContract.chainId & ", contractAddress: " & m.membershipContract.address & ", treeIndex: " & $m.treeIndex & ", userMessageLimit: " & $m.userMessageLimit & ", identityCredential: " & $m.identityCredential & ")" +else: + proc `$`*(m: KeystoreMembership): string = + return "KeystoreMembership(chainId: " & m.membershipContract.chainId & ", contractAddress: " & m.membershipContract.address & ", treeIndex: " & $m.treeIndex & ", identityCredential: " & $m.identityCredential & ")" + +when defined(rln_v2): + proc `==`*(x, y: KeystoreMembership): bool = + return x.membershipContract.chainId == y.membershipContract.chainId and + x.membershipContract.address == y.membershipContract.address and + x.treeIndex == y.treeIndex and + x.userMessageLimit == y.userMessageLimit and + x.identityCredential.idTrapdoor == y.identityCredential.idTrapdoor and + x.identityCredential.idNullifier == y.identityCredential.idNullifier and + x.identityCredential.idSecretHash == y.identityCredential.idSecretHash and + x.identityCredential.idCommitment == y.identityCredential.idCommitment +else: + proc `==`*(x, y: KeystoreMembership): bool = + return x.membershipContract.chainId == y.membershipContract.chainId and + x.membershipContract.address == y.membershipContract.address and + x.treeIndex == y.treeIndex and + x.identityCredential.idTrapdoor == y.identityCredential.idTrapdoor and + x.identityCredential.idNullifier == y.identityCredential.idNullifier and + x.identityCredential.idSecretHash == y.identityCredential.idSecretHash and + x.identityCredential.idCommitment == y.identityCredential.idCommitment proc hash*(m: KeystoreMembership): string = # hash together the chainId, address and treeIndex diff --git a/waku/waku_rln_relay/conversion_utils.nim b/waku/waku_rln_relay/conversion_utils.nim index 45201f805d..c00e6a50d6 100644 --- a/waku/waku_rln_relay/conversion_utils.nim +++ b/waku/waku_rln_relay/conversion_utils.nim @@ -41,6 +41,10 @@ proc inHex*(value: IdentityTrapdoor or valueHex = "0" & valueHex return toLowerAscii(valueHex) +when defined(rln_v2): + proc toUserMessageLimit*(v: UInt256): UserMessageLimit = + return cast[UserMessageLimit](v) + proc encodeLengthPrefix*(input: openArray[byte]): seq[byte] = ## returns length prefixed version of the input ## with the following format [len<8>|input] diff --git a/waku/waku_rln_relay/group_manager/group_manager_base.nim b/waku/waku_rln_relay/group_manager/group_manager_base.nim index 947097af36..2fb68df220 100644 --- a/waku/waku_rln_relay/group_manager/group_manager_base.nim +++ b/waku/waku_rln_relay/group_manager/group_manager_base.nim @@ -23,8 +23,11 @@ export # It should also be used to sync the group state with the rest of the group members type Membership* = object - idCommitment*: IDCommitment index*: MembershipIndex + when defined(rln_v2): + rateCommitment*: RateCommitment + else: + idCommitment*: IDCommitment type OnRegisterCallback* = proc (registrations: seq[Membership]): Future[void] {.gcsafe.} type OnWithdrawCallback* = proc (withdrawals: seq[Membership]): Future[void] {.gcsafe.} @@ -41,6 +44,8 @@ type initialized*: bool latestIndex*: MembershipIndex validRoots*: Deque[MerkleNode] + when defined(rln_v2): + userMessageLimit*: Option[UserMessageLimit] # This proc is used to initialize the group manager # Any initialization logic should be implemented here @@ -55,20 +60,35 @@ method startGroupSync*(g: GroupManager): Future[void] {.base, async: (raises: [E # This proc is used to register a new identity commitment into the merkle tree # The user may or may not have the identity secret to this commitment # It should be used when detecting new members in the group, and syncing the group state -method register*(g: GroupManager, idCommitment: IDCommitment): Future[void] {.base,async: (raises: [Exception]).} = - raise newException(CatchableError, "register proc for " & $g.type & " is not implemented yet") +when defined(rln_v2): + method register*(g: GroupManager, + rateCommitment: RateCommitment): Future[void] {.base,async: (raises: [Exception]).} = + raise newException(CatchableError, "register proc for " & $g.type & " is not implemented yet") +else: + method register*(g: GroupManager, idCommitment: IDCommitment): Future[void] {.base,async: (raises: [Exception]).} = + raise newException(CatchableError, "register proc for " & $g.type & " is not implemented yet") # This proc is used to register a new identity commitment into the merkle tree # The user should have the identity secret to this commitment # It should be used when the user wants to join the group -method register*(g: GroupManager, credentials: IdentityCredential): Future[void] {.base,async: (raises: [Exception]).} = - raise newException(CatchableError, "register proc for " & $g.type & " is not implemented yet") +when defined(rln_v2): + method register*(g: GroupManager, + credentials: IdentityCredential, + userMessageLimit: UserMessageLimit): Future[void] {.base,async: (raises: [Exception]).} = + raise newException(CatchableError, "register proc for " & $g.type & " is not implemented yet") +else: + method register*(g: GroupManager, credentials: IdentityCredential): Future[void] {.base,async: (raises: [Exception]).} = + raise newException(CatchableError, "register proc for " & $g.type & " is not implemented yet") # This proc is used to register a batch of new identity commitments into the merkle tree # The user may or may not have the identity secret to these commitments # It should be used when detecting a batch of new members in the group, and syncing the group state -method registerBatch*(g: GroupManager, idCommitments: seq[IDCommitment]): Future[void] {.base,async: (raises: [Exception]).} = - raise newException(CatchableError, "registerBatch proc for " & $g.type & " is not implemented yet") +when defined(rln_v2): + method registerBatch*(g: GroupManager, rateCommitments: seq[RateCommitment]): Future[void] {.base,async: (raises: [Exception]).} = + raise newException(CatchableError, "registerBatch proc for " & $g.type & " is not implemented yet") +else: + method registerBatch*(g: GroupManager, idCommitments: seq[IDCommitment]): Future[void] {.base,async: (raises: [Exception]).} = + raise newException(CatchableError, "registerBatch proc for " & $g.type & " is not implemented yet") # This proc is used to set a callback that will be called when a new identity commitment is registered # The callback may be called multiple times, and should be used to for any post processing @@ -86,8 +106,16 @@ method withdrawBatch*(g: GroupManager, identitySecretHashes: seq[IdentitySecretH raise newException(CatchableError, "withdrawBatch proc for " & $g.type & " is not implemented yet") # This proc is used to insert and remove a set of commitments from the merkle tree -method atomicBatch*(g: GroupManager, idCommitments: seq[IDCommitment], toRemoveIndices: seq[MembershipIndex]): Future[void] {.base,async: (raises: [Exception]).} = - raise newException(CatchableError, "atomicBatch proc for " & $g.type & " is not implemented yet") +when defined(rln_v2): + method atomicBatch*(g: GroupManager, + rateCommitments: seq[RateCommitment], + toRemoveIndices: seq[MembershipIndex]): Future[void] {.base,async: (raises: [Exception]).} = + raise newException(CatchableError, "atomicBatch proc for " & $g.type & " is not implemented yet") +else: + method atomicBatch*(g: GroupManager, + idCommitments: seq[IDCommitment], + toRemoveIndices: seq[MembershipIndex]): Future[void] {.base,async: (raises: [Exception]).} = + raise newException(CatchableError, "atomicBatch proc for " & $g.type & " is not implemented yet") method stop*(g: GroupManager): Future[void] {.base,async.} = raise newException(CatchableError, "stop proc for " & $g.type & " is not implemented yet") @@ -99,7 +127,7 @@ method onWithdraw*(g: GroupManager, cb: OnWithdrawCallback) {.base,gcsafe.} = proc slideRootQueue*(rootQueue: var Deque[MerkleNode], root: MerkleNode): seq[MerkleNode] = ## updates the root queue with the latest root and pops the oldest one when the capacity of `AcceptableRootWindowSize` is reached - let overflowCount = rootQueue.len() - AcceptableRootWindowSize + 1 + let overflowCount = rootQueue.len - AcceptableRootWindowSize + 1 var overflowedRoots = newSeq[MerkleNode]() if overflowCount > 0: # Delete the oldest `overflowCount` roots in the deque (index 0..`overflowCount`) @@ -135,45 +163,55 @@ template slideRootQueue*(g: GroupManager): untyped = discard rootBuffer.slideRootQueue(root) rootBuffer +method verifyProof*(g: GroupManager, + input: openArray[byte], + proof: RateLimitProof): GroupManagerResult[bool] {.base,gcsafe,raises:[].} = + ## verifies the proof against the input and the current merkle root + let proofVerifyRes = g.rlnInstance.proofVerify(input, proof, g.validRoots.items().toSeq()) + if proofVerifyRes.isErr(): + return err("proof verification failed: " & $proofVerifyRes.error()) + return ok(proofVerifyRes.value()) + when defined(rln_v2): - method verifyProof*(g: GroupManager, - input: openArray[byte], - proof: RateLimitProof): GroupManagerResult[bool] {.base,gcsafe,raises:[].} = - ## verifies the proof against the input and the current merkle root - ## TODO: verify the external nullifier with provided RateLimitProof - let proofVerifyRes = g.rlnInstance.proofVerify(input, RateLimitProof(proof), g.validRoots.items().toSeq()) - if proofVerifyRes.isErr(): - return err("proof verification failed: " & $proofVerifyRes.error()) - return ok(proofVerifyRes.value()) + method generateProof*(g: GroupManager, + data: openArray[byte], + epoch: Epoch, + messageId: MessageId, + rlnIdentifier = DefaultRlnIdentifier): GroupManagerResult[RateLimitProof] {.base,gcsafe,raises:[].} = + ## generates a proof for the given data and epoch + ## the proof is generated using the current merkle root + if g.idCredentials.isNone(): + return err("identity credentials are not set") + if g.membershipIndex.isNone(): + return err("membership index is not set") + if g.userMessageLimit.isNone(): + return err("user message limit is not set") + waku_rln_proof_generation_duration_seconds.nanosecondTime: + let proof = proofGen(rlnInstance = g.rlnInstance, + data = data, + memKeys = g.idCredentials.get(), + memIndex = g.membershipIndex.get(), + epoch = epoch).valueOr: + return err("proof generation failed: " & $error) + return ok(proof) else: - method verifyProof*(g: GroupManager, - input: openArray[byte], - proof: RateLimitProof): GroupManagerResult[bool] {.base,gcsafe,raises:[].} = - ## verifies the proof against the input and the current merkle root - let proofVerifyRes = g.rlnInstance.proofVerify(input, proof, g.validRoots.items().toSeq()) - if proofVerifyRes.isErr(): - return err("proof verification failed: " & $proofVerifyRes.error()) - return ok(proofVerifyRes.value()) - - -method generateProof*(g: GroupManager, - data: openArray[byte], - epoch: Epoch): GroupManagerResult[RateLimitProof] {.base,gcsafe,raises:[].} = - ## generates a proof for the given data and epoch - ## the proof is generated using the current merkle root - if g.idCredentials.isNone(): - return err("identity credentials are not set") - if g.membershipIndex.isNone(): - return err("membership index is not set") - waku_rln_proof_generation_duration_seconds.nanosecondTime: - let proofGenRes = proofGen(rlnInstance = g.rlnInstance, - data = data, - memKeys = g.idCredentials.get(), - memIndex = g.membershipIndex.get(), - epoch = epoch) - if proofGenRes.isErr(): - return err("proof generation failed: " & $proofGenRes.error()) - return ok(proofGenRes.value()) + method generateProof*(g: GroupManager, + data: openArray[byte], + epoch: Epoch): GroupManagerResult[RateLimitProof] {.base,gcsafe,raises:[].} = + ## generates a proof for the given data and epoch + ## the proof is generated using the current merkle root + if g.idCredentials.isNone(): + return err("identity credentials are not set") + if g.membershipIndex.isNone(): + return err("membership index is not set") + waku_rln_proof_generation_duration_seconds.nanosecondTime: + let proof = proofGen(rlnInstance = g.rlnInstance, + data = data, + memKeys = g.idCredentials.get(), + memIndex = g.membershipIndex.get(), + epoch = epoch).valueOr: + return err("proof generation failed: " & $error) + return ok(proof) method isReady*(g: GroupManager): Future[bool] {.base,async.} = raise newException(CatchableError, "isReady proc for " & $g.type & " is not implemented yet") 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 53059251da..d393b4ec13 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 @@ -8,6 +8,7 @@ import web3/ethtypes, eth/keys as keys, chronicles, + nimcrypto/keccak, stint, json, std/tables, @@ -28,28 +29,54 @@ export group_manager_base logScope: topics = "waku rln_relay onchain_group_manager" -contract(WakuRlnRegistry): - # this describes the storage slot to use - proc usingStorageIndex(): Uint16 {.pure.} - # this map contains the address of a given storage slot - proc storages(index: Uint16): Address {.pure.} - # this serves as an entrypoint into the rln storage contract - proc register(storageIndex: Uint16, idCommitment: Uint256) - # this creates a new storage on the rln registry - proc newStorage() - -# membership contract interface -contract(RlnStorage): - # this event is raised when a new member is registered - proc MemberRegistered(idCommitment: Uint256, index: Uint256) {.event.} - # this constant contains the membership deposit of the contract - proc MEMBERSHIP_DEPOSIT(): Uint256 {.pure.} - # this map denotes existence of a given user - proc memberExists(idCommitment: Uint256): Uint256 {.view.} - # this constant describes the next index of a new member - proc idCommitmentIndex(): Uint256 {.view.} - # this constant describes the block number this contract was deployed on - proc deployedBlockNumber(): Uint256 {.view.} +# using the when predicate does not work within the contract macro, hence need to dupe +when defined(rln_v2): + contract(WakuRlnRegistry): + # this describes the storage slot to use + proc usingStorageIndex(): Uint16 {.pure.} + # this map contains the address of a given storage slot + proc storages(index: Uint16): Address {.pure.} + # this serves as an entrypoint into the rln storage contract + proc register(storageIndex: Uint16, idCommitment: Uint256, userMessageLimit: Uint256) + # this creates a new storage on the rln registry + proc newStorage() + + # membership contract interface + contract(RlnStorage): + # this event is raised when a new member is registered + proc MemberRegistered(idCommitment: Uint256, userMessageLimit: Uint256, index: Uint256) {.event.} + # this constant contains the membership deposit of the contract + proc MEMBERSHIP_DEPOSIT(): Uint256 {.pure.} + # this map denotes existence of a given user + proc memberExists(idCommitment: Uint256): Uint256 {.view.} + # this constant describes the next index of a new member + proc idCommitmentIndex(): Uint256 {.view.} + # this constant describes the block number this contract was deployed on + proc deployedBlockNumber(): Uint256 {.view.} +else: + contract(WakuRlnRegistry): + # this describes the storage slot to use + proc usingStorageIndex(): Uint16 {.pure.} + # this map contains the address of a given storage slot + proc storages(index: Uint16): Address {.pure.} + # this serves as an entrypoint into the rln storage contract + proc register(storageIndex: Uint16, idCommitment: Uint256) + # this creates a new storage on the rln registry + proc newStorage() + + # membership contract interface + contract(RlnStorage): + # this event is raised when a new member is registered + proc MemberRegistered(idCommitment: Uint256, index: Uint256) {.event.} + # this constant contains the membership deposit of the contract + proc MEMBERSHIP_DEPOSIT(): Uint256 {.pure.} + # this map denotes existence of a given user + proc memberExists(idCommitment: Uint256): Uint256 {.view.} + # this constant describes the next index of a new member + proc idCommitmentIndex(): Uint256 {.view.} + # this constant describes the block number this contract was deployed on + proc deployedBlockNumber(): Uint256 {.view.} + type RegistryContractWithSender = Sender[WakuRlnRegistry] @@ -100,96 +127,197 @@ proc setMetadata*(g: OnchainGroupManager): RlnRelayResult[void] = return err("failed to persist rln metadata: " & getCurrentExceptionMsg()) return ok() -method atomicBatch*(g: OnchainGroupManager, - start: MembershipIndex, - idCommitments = newSeq[IDCommitment](), - toRemoveIndices = newSeq[MembershipIndex]()): - Future[void] {.async: (raises: [Exception]).} = - initializedGuard(g) - - waku_rln_membership_insertion_duration_seconds.nanosecondTime: - let operationSuccess = g.rlnInstance.atomicWrite(some(start), idCommitments, toRemoveIndices) - if not operationSuccess: - raise newException(ValueError, "atomic batch operation failed") - # TODO: when slashing is enabled, we need to track slashed members - waku_rln_number_registered_memberships.set(int64(g.rlnInstance.leavesSet())) - if g.registerCb.isSome(): - var membersSeq = newSeq[Membership]() - for i in 0 ..< idCommitments.len(): - var index = start + MembershipIndex(i) - trace "registering member", idCommitment = idCommitments[i], index = index - let member = Membership(idCommitment: idCommitments[i], index: index) - membersSeq.add(member) - await g.registerCb.get()(membersSeq) - - g.validRootBuffer = g.slideRootQueue() - let setMetadataRes = g.setMetadata() - if setMetadataRes.isErr(): - error "failed to persist rln metadata", error=setMetadataRes.error - -method register*(g: OnchainGroupManager, idCommitment: IDCommitment): - Future[void] {.async: (raises: [Exception]).} = - initializedGuard(g) - - await g.registerBatch(@[idCommitment]) - - -method registerBatch*(g: OnchainGroupManager, idCommitments: seq[IDCommitment]): +when defined(rln_v2): + method atomicBatch*(g: OnchainGroupManager, + start: MembershipIndex, + rateCommitments = newSeq[RateCommitment](), + toRemoveIndices = newSeq[MembershipIndex]()): Future[void] {.async: (raises: [Exception]).} = - initializedGuard(g) + initializedGuard(g) + + # convert the rateCommitment struct to a leaf value + let leavesRes = rateCommitments.toLeaves() + if leavesRes.isErr(): + raise newException(CatchableError, "failed to convert rateCommitments to leaves: " & leavesRes.error) + let leaves = cast[seq[seq[byte]]](leavesRes.get()) + + waku_rln_membership_insertion_duration_seconds.nanosecondTime: + let operationSuccess = g.rlnInstance.atomicWrite(some(start), + leaves, + toRemoveIndices) + if not operationSuccess: + raise newException(CatchableError, "atomic batch operation failed") + # TODO: when slashing is enabled, we need to track slashed members + waku_rln_number_registered_memberships.set(int64(g.rlnInstance.leavesSet())) + + if g.registerCb.isSome(): + var membersSeq = newSeq[Membership]() + for i in 0 ..< rateCommitments.len: + var index = start + MembershipIndex(i) + trace "registering member", rateCommitment = rateCommitments[i], index = index + let member = Membership(rateCommitment: rateCommitments[i], index: index) + membersSeq.add(member) + await g.registerCb.get()(membersSeq) + + g.validRootBuffer = g.slideRootQueue() + let setMetadataRes = g.setMetadata() + if setMetadataRes.isErr(): + error "failed to persist rln metadata", error=setMetadataRes.error +else: + method atomicBatch*(g: OnchainGroupManager, + start: MembershipIndex, + idCommitments = newSeq[IDCommitment](), + toRemoveIndices = newSeq[MembershipIndex]()): + Future[void] {.async: (raises: [Exception]).} = + initializedGuard(g) + + waku_rln_membership_insertion_duration_seconds.nanosecondTime: + let operationSuccess = g.rlnInstance.atomicWrite(some(start), idCommitments, toRemoveIndices) + if not operationSuccess: + raise newException(ValueError, "atomic batch operation failed") + # TODO: when slashing is enabled, we need to track slashed members + waku_rln_number_registered_memberships.set(int64(g.rlnInstance.leavesSet())) + + if g.registerCb.isSome(): + var membersSeq = newSeq[Membership]() + for i in 0 ..< idCommitments.len: + var index = start + MembershipIndex(i) + trace "registering member", idCommitment = idCommitments[i], index = index + let member = Membership(idCommitment: idCommitments[i], index: index) + membersSeq.add(member) + await g.registerCb.get()(membersSeq) + + g.validRootBuffer = g.slideRootQueue() + let setMetadataRes = g.setMetadata() + if setMetadataRes.isErr(): + error "failed to persist rln metadata", error=setMetadataRes.error + +when defined(rln_v2): + method register*(g: OnchainGroupManager, + rateCommitment: RateCommitment): Future[void] {.async: (raises: [Exception]).} = + initializedGuard(g) + + await g.registerBatch(@[rateCommitment]) +else: + method register*(g: OnchainGroupManager, + idCommitment: IDCommitment): Future[void] {.async: (raises: [Exception]).} = + initializedGuard(g) - await g.atomicBatch(g.latestIndex, idCommitments) - g.latestIndex += MembershipIndex(idCommitments.len()) + await g.registerBatch(@[idCommitment]) -method register*(g: OnchainGroupManager, identityCredentials: IdentityCredential): - Future[void] {.async: (raises: [Exception]).} = - initializedGuard(g) +when defined(rln_v2): + method registerBatch*(g: OnchainGroupManager, + rateCommitments: seq[RateCommitment]): Future[void] {.async: (raises: [Exception]).} = + initializedGuard(g) - let ethRpc = g.ethRpc.get() - let registryContract = g.registryContract.get() - let membershipFee = g.membershipFee.get() - - var gasPrice: int - retryWrapper(gasPrice, RetryStrategy.new(), "Failed to get gas price"): - int(await ethRpc.provider.eth_gasPrice()) * 2 - let idCommitment = identityCredentials.idCommitment.toUInt256() - - var txHash: TxHash - let storageIndex = g.usingStorageIndex.get() - debug "registering the member", idCommitment = idCommitment, storageIndex = storageIndex - retryWrapper(txHash, RetryStrategy.new(), "Failed to register the member"): - await registryContract.register(storageIndex, idCommitment).send(gasPrice = gasPrice) - - # wait for the transaction to be mined - var tsReceipt: ReceiptObject - retryWrapper(tsReceipt, RetryStrategy.new(), "Failed to get the transaction receipt"): - await ethRpc.getMinedTransactionReceipt(txHash) - debug "registration transaction mined", txHash = txHash - g.registrationTxHash = some(txHash) - # the receipt topic holds the hash of signature of the raised events - # TODO: make this robust. search within the event list for the event - let firstTopic = tsReceipt.logs[0].topics[0] - # the hash of the signature of MemberRegistered(uint256,uint256) event is equal to the following hex value - if firstTopic != cast[FixedBytes[32]](hexToByteArray[32]( - "0x5a92c2530f207992057b9c3e544108ffce3beda4a63719f316967c49bf6159d2" - )): - raise newException(ValueError, "unexpected event signature") - - # the arguments of the raised event i.e., MemberRegistered are encoded inside the data field - # data = pk encoded as 256 bits || index encoded as 256 bits - let arguments = tsReceipt.logs[0].data - debug "tx log data", arguments=arguments - let - argumentsBytes = arguments - # In TX log data, uints are encoded in big endian - eventIndex = UInt256.fromBytesBE(argumentsBytes[32..^1]) - - g.membershipIndex = some(eventIndex.toMembershipIndex()) - - # don't handle member insertion into the tree here, it will be handled by the event listener - return + await g.atomicBatch(g.latestIndex, rateCommitments) + g.latestIndex += MembershipIndex(rateCommitments.len) +else: + method registerBatch*(g: OnchainGroupManager, + idCommitments: seq[IDCommitment]): Future[void] {.async: (raises: [Exception]).} = + initializedGuard(g) + + await g.atomicBatch(g.latestIndex, idCommitments) + g.latestIndex += MembershipIndex(idCommitments.len) + + +when defined(rln_v2): + method register*(g: OnchainGroupManager, + identityCredential: IdentityCredential, + userMessageLimit: UserMessageLimit): Future[void] {.async: (raises: [Exception]).} = + initializedGuard(g) + + let ethRpc = g.ethRpc.get() + let registryContract = g.registryContract.get() + let membershipFee = g.membershipFee.get() + + var gasPrice: int + retryWrapper(gasPrice, RetryStrategy.new(), "Failed to get gas price"): + int(await ethRpc.provider.eth_gasPrice()) * 2 + let idCommitment = identityCredential.idCommitment.toUInt256() + + var txHash: TxHash + let storageIndex = g.usingStorageIndex.get() + debug "registering the member", idCommitment = idCommitment, storageIndex = storageIndex, userMessageLimit = userMessageLimit + retryWrapper(txHash, RetryStrategy.new(), "Failed to register the member"): + await registryContract.register(storageIndex, idCommitment, u256(userMessageLimit)).send(gasPrice = gasPrice) + + # wait for the transaction to be mined + var tsReceipt: ReceiptObject + retryWrapper(tsReceipt, RetryStrategy.new(), "Failed to get the transaction receipt"): + await ethRpc.getMinedTransactionReceipt(txHash) + debug "registration transaction mined", txHash = txHash + g.registrationTxHash = some(txHash) + # the receipt topic holds the hash of signature of the raised events + # TODO: make this robust. search within the event list for the event + let firstTopic = tsReceipt.logs[0].topics[0] + # the hash of the signature of MemberRegistered(uint256,uint256,uint256) event is equal to the following hex value + if firstTopic != cast[FixedBytes[32]](keccak256.digest("MemberRegistered(uint256,uint256,uint256)").data): + raise newException(ValueError, "unexpected event signature") + + # the arguments of the raised event i.e., MemberRegistered are encoded inside the data field + # data = pk encoded as 256 bits || index encoded as 256 bits || userMessageLimit encoded as 256 bits + let arguments = tsReceipt.logs[0].data + debug "tx log data", arguments=arguments + let + argumentsBytes = arguments + # In TX log data, uints are encoded in big endian + userMessageLimit = UInt256.fromBytesBE(argumentsBytes[32..64]) + membershipIndex = UInt256.fromBytesBE(argumentsBytes[64..^1]) + + g.membershipIndex = some(membershipIndex.toMembershipIndex()) + g.userMessageLimit = some(userMessageLimit.toUserMessageLimit()) + + # don't handle member insertion into the tree here, it will be handled by the event listener + return +else: + method register*(g: OnchainGroupManager, + credentials: IdentityCredential): Future[void] {.async: (raises: [Exception]).} = + initializedGuard(g) + + let ethRpc = g.ethRpc.get() + let registryContract = g.registryContract.get() + let membershipFee = g.membershipFee.get() + + var gasPrice: int + retryWrapper(gasPrice, RetryStrategy.new(), "Failed to get gas price"): + int(await ethRpc.provider.eth_gasPrice()) * 2 + let idCommitment = credentials.idCommitment.toUInt256() + + var txHash: TxHash + let storageIndex = g.usingStorageIndex.get() + debug "registering the member", idCommitment = idCommitment, storageIndex = storageIndex + retryWrapper(txHash, RetryStrategy.new(), "Failed to register the member"): + await registryContract.register(storageIndex, idCommitment).send(gasPrice = gasPrice) + + # wait for the transaction to be mined + var tsReceipt: ReceiptObject + retryWrapper(tsReceipt, RetryStrategy.new(), "Failed to get the transaction receipt"): + await ethRpc.getMinedTransactionReceipt(txHash) + debug "registration transaction mined", txHash = txHash + g.registrationTxHash = some(txHash) + # the receipt topic holds the hash of signature of the raised events + # TODO: make this robust. search within the event list for the event + let firstTopic = tsReceipt.logs[0].topics[0] + # the hash of the signature of MemberRegistered(uint256,uint256) event is equal to the following hex value + if firstTopic != cast[FixedBytes[32]](keccak256.digest("MemberRegistered(uint256,uint256)").data): + raise newException(ValueError, "unexpected event signature") + + # the arguments of the raised event i.e., MemberRegistered are encoded inside the data field + # data = pk encoded as 256 bits || index encoded as 256 bits + let arguments = tsReceipt.logs[0].data + debug "tx log data", arguments=arguments + let + argumentsBytes = arguments + # In TX log data, uints are encoded in big endian + eventIndex = UInt256.fromBytesBE(argumentsBytes[32..^1]) + + g.membershipIndex = some(eventIndex.toMembershipIndex()) + + # don't handle member insertion into the tree here, it will be handled by the event listener + return method withdraw*(g: OnchainGroupManager, idCommitment: IDCommitment): Future[void] {.async: (raises: [Exception]).} = @@ -209,6 +337,8 @@ proc parseEvent(event: type MemberRegistered, ## returns an error if it cannot parse the `data` parameter var idComm: UInt256 var index: UInt256 + when defined(rln_v2): + var userMessageLimit: UInt256 var data: string # Remove the 0x prefix try: @@ -221,7 +351,15 @@ proc parseEvent(event: type MemberRegistered, offset += decode(data, offset, idComm) # Parse the index offset += decode(data, offset, index) - return ok(Membership(idCommitment: idComm.toIDCommitment(), index: index.toMembershipIndex())) + when defined(rln_v2): + # Parse the userMessageLimit + offset += decode(data, offset, userMessageLimit) + when defined(rln_v2): + return ok(Membership(rateCommitment: RateCommitment(idCommitment: idComm.toIDCommitment(), + userMessageLimit: userMessageLimit.toUserMessageLimit()), + index: index.toMembershipIndex())) + else: + return ok(Membership(idCommitment: idComm.toIDCommitment(), index: index.toMembershipIndex())) except CatchableError: return err("failed to parse the data field of the MemberRegistered event") @@ -294,15 +432,23 @@ proc handleEvents(g: OnchainGroupManager, try: let startIndex = blockTable[blockNumber].filterIt(not it[1])[0][0].index let removalIndices = members.filterIt(it[1]).mapIt(it[0].index) - let idCommitments = members.mapIt(it[0].idCommitment) - await g.atomicBatch(start = startIndex, - idCommitments = idCommitments, - toRemoveIndices = removalIndices) - g.latestIndex = startIndex + MembershipIndex(idCommitments.len()) + when defined(rln_v2): + let rateCommitments = members.mapIt(it[0].rateCommitment) + await g.atomicBatch(start = startIndex, + rateCommitments = rateCommitments, + toRemoveIndices = removalIndices) + g.latestIndex = startIndex + MembershipIndex(rateCommitments.len) + trace "new members added to the Merkle tree", commitments=rateCommitments + else: + let idCommitments = members.mapIt(it[0].idCommitment) + await g.atomicBatch(start = startIndex, + idCommitments = idCommitments, + toRemoveIndices = removalIndices) + g.latestIndex = startIndex + MembershipIndex(idCommitments.len) + trace "new members added to the Merkle tree", commitments=idCommitments except CatchableError: error "failed to insert members into the tree", error=getCurrentExceptionMsg() raise newException(ValueError, "failed to insert members into the tree") - trace "new members added to the Merkle tree", commitments=members.mapIt(it[0].idCommitment.inHex()) return @@ -494,6 +640,8 @@ method init*(g: OnchainGroupManager): Future[void] {.async.} = raise newException(CatchableError, "could not parse the keystore: " & $keystoreCredRes.error) let keystoreCred = keystoreCredRes.get() g.membershipIndex = some(keystoreCred.treeIndex) + when defined(rln_v2): + g.userMessageLimit = some(keystoreCred.userMessageLimit) # now we check on the contract if the commitment actually has a membership try: let membershipExists = await rlnContract.memberExists(keystoreCred diff --git a/waku/waku_rln_relay/group_manager/static/group_manager.nim b/waku/waku_rln_relay/group_manager/static/group_manager.nim index 135796a660..3b5af900ab 100644 --- a/waku/waku_rln_relay/group_manager/static/group_manager.nim +++ b/waku/waku_rln_relay/group_manager/static/group_manager.nim @@ -34,7 +34,7 @@ method init*(g: StaticGroupManager): Future[void] {.async.} = discard g.slideRootQueue() - g.latestIndex += MembershipIndex(idCommitments.len() - 1) + g.latestIndex += MembershipIndex(idCommitments.len - 1) g.initialized = true @@ -44,51 +44,90 @@ method startGroupSync*(g: StaticGroupManager): Future[void] {.async: (raises: [E initializedGuard(g) # No-op -method register*(g: StaticGroupManager, idCommitment: IDCommitment): - Future[void] {.async: (raises: [Exception]).} = - initializedGuard(g) +when defined(rln_v2): + method register*(g: StaticGroupManager, + rateCommitment: RateCommitment): Future[void] {.async: (raises: [Exception]).} = + initializedGuard(g) - await g.registerBatch(@[idCommitment]) + await g.registerBatch(@[rateCommitment]) +else: + method register*(g: StaticGroupManager, idCommitment: IDCommitment): + Future[void] {.async: (raises: [Exception]).} = + initializedGuard(g) -method registerBatch*(g: StaticGroupManager, idCommitments: seq[IDCommitment]): - Future[void] {.async: (raises: [Exception]).} = - initializedGuard(g) + await g.registerBatch(@[idCommitment]) - let membersInserted = g.rlnInstance.insertMembers(g.latestIndex + 1, idCommitments) - if not membersInserted: - raise newException(ValueError, "Failed to insert members into the merkle tree") +when defined(rln_v2): + method registerBatch*(g: StaticGroupManager, + rateCommitments: seq[RateCommitment]): Future[void] {.async: (raises: [Exception]).} = + initializedGuard(g) - if g.registerCb.isSome(): - var memberSeq = newSeq[Membership]() - for i in 0..