From ddb2b9b82e5fa4182c81de9602cb80460221e92c Mon Sep 17 00:00:00 2001 From: Sanaz Taheri Boshrooyeh <35961250+staheri14@users.noreply.github.com> Date: Mon, 30 May 2022 12:14:07 -0700 Subject: [PATCH] Feat (Rln relay): adds utility procs to listen to the registration events emitted by the rln membership contract (#976) * adds the contract handler file * adds integration test for the group listening * adds groupManagement proc * deletes rln relay contract handler file * brings back all the tests * replaces toUINT256 with getIdCommitment proc * replaces individual futures with an array of futures * adds code documentation * asyncSpawn instead of await * adds untitest for toIDCommitment and toUInt256 * reorganizes the test and add rlnInstance * mounts handleGroupUpdates on the rln peer * asyncSpawn to await * implements toIDCommitment * updates the unittest * improves the code documentation * removes unused tests * removes registration of the dynamic group management handler * adds a comment * adds a comment * adds a TODO * removes getIdCommitment --- tests/v2/test_waku_rln_relay.nim | 18 +++++ tests/v2/test_waku_rln_relay_onchain.nim | 81 ++++++++++++++++++- waku/v2/node/wakunode2.nim | 4 +- .../waku_rln_relay/waku_rln_relay_utils.nim | 44 +++++++++- 4 files changed, 138 insertions(+), 9 deletions(-) diff --git a/tests/v2/test_waku_rln_relay.nim b/tests/v2/test_waku_rln_relay.nim index 5de242a8d..eeda9e7e1 100644 --- a/tests/v2/test_waku_rln_relay.nim +++ b/tests/v2/test_waku_rln_relay.nim @@ -703,4 +703,22 @@ suite "Waku rln relay": msgValidate2 == MessageValidationResult.Spam msgValidate3 == MessageValidationResult.Valid msgValidate4 == MessageValidationResult.Invalid + test "toIDCommitment and toUInt256": + # create an instance of rln + var rlnInstance = createRLNInstance() + check: + rlnInstance.isOk == true + + # create a key pair + var keypair = rlnInstance.value.membershipKeyGen() + check: + keypair.isSome() + # convert the idCommitment to UInt256 + let idCUInt = keypair.get().idCommitment.toUInt256() + # convert the UInt256 back to ICommitment + let idCommitment = toIDCommitment(idCUInt) + + # check that the conversion has not distorted the original value + check: + keypair.get().idCommitment == idCommitment diff --git a/tests/v2/test_waku_rln_relay_onchain.nim b/tests/v2/test_waku_rln_relay_onchain.nim index 49ef77270..04001659e 100644 --- a/tests/v2/test_waku_rln_relay_onchain.nim +++ b/tests/v2/test_waku_rln_relay_onchain.nim @@ -102,7 +102,7 @@ procSuite "Waku-rln-relay": let membershipKeyPair = membershipKeyGen(rlnInstance.value) check: membershipKeyPair.isSome - let pk = membershipKeyPair.get().idCommitment.toUInt256() + let pk = membershipKeyPair.get().idCommitment.toUInt256() debug "membership commitment key", pk = pk # test ------------------------------ @@ -128,6 +128,80 @@ procSuite "Waku-rln-relay": # wait for the event to be received await fut + # release resources ----------------------- + await web3.close() + asyncTest "dynamic group management": + # preparation ------------------------------ + debug "ethereum client address", ETH_CLIENT + let contractAddress = await uploadRLNContract(ETH_CLIENT) + # connect to the eth client + let web3 = await newWeb3(ETH_CLIENT) + debug "web3 connected to", ETH_CLIENT + + # fetch the list of registered accounts + let accounts = await web3.provider.eth_accounts() + web3.defaultAccount = accounts[1] + debug "contract deployer account address ", + defaultAccount = web3.defaultAccount + + # prepare a contract sender to interact with it + var contractObj = web3.contractSender(MembershipContract, + contractAddress) # creates a Sender object with a web3 field and contract address of type Address + + # test ------------------------------ + # create an RLN instance + var rlnInstance = createRLNInstance() + check: + rlnInstance.isOk == true + var rln = rlnInstance.value + + # create rln membership key pair + let keyPair = rln.membershipKeyGen() + check: + keyPair.isSome + let pk = keyPair.get().idCommitment.toUInt256() + debug "membership commitment key", pk = pk + + # initialize the WakuRLNRelay + var rlnPeer = WakuRLNRelay(membershipKeyPair: keyPair.get(), + membershipIndex: MembershipIndex(0), + ethClientAddress: ETH_CLIENT, + ethAccountAddress: accounts[0], + membershipContractAddress: contractAddress, + rlnInstance: rln) + + # generate another membership key pair + let keyPair2 = rln.membershipKeyGen() + check: + keyPair2.isSome + let pk2 = keyPair2.get().idCommitment.toUInt256() + debug "membership commitment key", pk2 = pk2 + + var events = [newFuture[void](), newFuture[void]()] + proc handler(pubkey: Uint256, index: Uint256) = + debug "handler is called", pubkey = pubkey, index = index + if pubkey == pk: + events[0].complete() + if pubkey == pk2: + events[1].complete() + let isSuccessful = rlnPeer.rlnInstance.insertMember(pubkey.toIDCommitment()) + check: + isSuccessful + + # mount the handler for listening to the contract events + await rlnPeer.handleGroupUpdates(handler) + + # register a member to the contract + let tx = await contractObj.register(pk).send(value = MEMBERSHIP_FEE) + debug "a member is registered", tx = tx + + # register another member to the contract + let tx2 = await contractObj.register(pk2).send(value = MEMBERSHIP_FEE) + debug "a member is registered", tx2 = tx2 + + # wait for all the events to be received by the rlnPeer + await all(events) + # release resources ----------------------- await web3.close() @@ -215,7 +289,7 @@ procSuite "Waku-rln-relay": web3 = await newWeb3(ETH_CLIENT) accounts = await web3.provider.eth_accounts() # choose one of the existing account for the rln-relay peer - ethAccountAddress = accounts[9] + ethAccountAddress = accounts[0] await web3.close() # create current peer's pk @@ -267,5 +341,4 @@ procSuite "Waku-rln-relay": check: expectedRoot == calculatedRoot - await node.stop() - + await node.stop() \ No newline at end of file diff --git a/waku/v2/node/wakunode2.nim b/waku/v2/node/wakunode2.nim index 4508e3cfb..818af937a 100644 --- a/waku/v2/node/wakunode2.nim +++ b/waku/v2/node/wakunode2.nim @@ -636,9 +636,9 @@ when defined(rln): if onchainMode: # register the rln-relay peer to the membership contract - let is_successful = await rlnPeer.register() + let isSuccessful = await rlnPeer.register() # check whether registration is done - doAssert(is_successful) + doAssert(isSuccessful) debug "peer is successfully registered into the membership contract" # adds a topic validator for the supplied pubsub topic at the relay protocol diff --git a/waku/v2/protocol/waku_rln_relay/waku_rln_relay_utils.nim b/waku/v2/protocol/waku_rln_relay/waku_rln_relay_utils.nim index d7261b717..4e7bbb5c9 100644 --- a/waku/v2/protocol/waku_rln_relay/waku_rln_relay_utils.nim +++ b/waku/v2/protocol/waku_rln_relay/waku_rln_relay_utils.nim @@ -3,7 +3,7 @@ import std/sequtils, tables, times, chronicles, options, chronos, stint, - web3, + web3, json, stew/results, stew/[byteutils, arrayops, endians2], rln, @@ -21,8 +21,12 @@ type SpamHandler* = proc(wakuMessage: WakuMessage): void {.gcsafe, closure, # membership contract interface contract(MembershipContract): - # TODO define a return type of bool for register method to signify a successful registration proc register(pubkey: Uint256) # external payable + proc MemberRegistered(pubkey: Uint256, index: Uint256) {.event.} + # TODO the followings are to be supported + # proc registerBatch(pubkeys: seq[Uint256]) # external payable + # proc withdraw(secret: Uint256, pubkeyIndex: Uint256, receiver: Address) + # proc withdrawBatch( secrets: seq[Uint256], pubkeyIndex: seq[Uint256], receiver: seq[Address]) proc createRLNInstance*(d: int = MERKLE_TREE_DEPTH): RLNResult {.raises: [Defect, IOError].} = @@ -98,6 +102,10 @@ proc toUInt256*(idCommitment: IDCommitment): UInt256 = let pk = cast[UInt256](idCommitment) return pk +proc toIDCommitment*(idCommitment: UInt256): IDCommitment = + let pk = cast[IDCommitment](idCommitment) + return pk + proc register*(rlnPeer: WakuRLNRelay): Future[bool] {.async.} = ## registers the public key of the rlnPeer which is rlnPeer.membershipKeyPair.publicKey ## into the membership contract whose address is in rlnPeer.membershipContractAddress @@ -108,7 +116,7 @@ proc register*(rlnPeer: WakuRLNRelay): Future[bool] {.async.} = web3.privateKey = rlnPeer.ethAccountPrivateKey var sender = web3.contractSender(MembershipContract, rlnPeer.membershipContractAddress) # creates a Sender object with a web3 field and contract address of type Address - let pk = toUInt256(rlnPeer.membershipKeyPair.idCommitment) + let pk = rlnPeer.membershipKeyPair.idCommitment.toUInt256() discard await sender.register(pk).send(MEMBERSHIP_FEE) debug "pk", pk = pk # TODO check the receipt and then return true/false @@ -548,3 +556,33 @@ proc addAll*(rlnInstance: RLN[Bn256], list: seq[IDCommitment]): bool = if not member_is_added: return false return true + +# the types of inputs to this handler matches the MemberRegistered event/proc defined in the MembershipContract interface +type RegistrationEventHandler = proc(pubkey: Uint256, index: Uint256): void {.gcsafe, closure, raises: [Defect].} + + +proc subscribeToGroupEvents(ethClientUri: string, contractAddress: Address, blockNumber: string = "0x0", handler: RegistrationEventHandler) {.async, gcsafe.} = + ## connects to the eth client whose URI is supplied as `ethClientUri` + ## subscribes to the `MemberRegistered` event emitted from the `MembershipContract` which is available on the supplied `contractAddress` + ## it collects all the events starting from the given `blockNumber` + ## for every received event, it calls the `handler` + + # connect to the eth client + let web3 = await newWeb3(ETH_CLIENT) + # prepare a contract sender to interact with it + var contractObj = web3.contractSender(MembershipContract, contractAddress) + + # subscribe to the MemberRegistered events + # TODO can do similarly for deletion events, though it is not yet supported + discard await contractObj.subscribe(MemberRegistered, %*{"fromBlock": blockNumber, "address": contractAddress}) do(pubkey: Uint256, index: Uint256){.raises: [Defect], gcsafe.}: + try: + debug "onRegister", pubkey = pubkey, index = index + handler(pubkey, index) + except Exception as err: + doAssert false, err.msg + do (err: CatchableError): + echo "Error from subscription: ", err.msg + +proc handleGroupUpdates*(rlnPeer: WakuRLNRelay, handler: RegistrationEventHandler) {.async, gcsafe.} = + # mounts the supplied handler for the registration events emitting from the membership contract + await subscribeToGroupEvents(ethClientUri = rlnPeer.ethClientAddress, contractAddress = rlnPeer.membershipContractAddress, handler = handler) \ No newline at end of file