diff --git a/examples/v2/chat2.nim b/examples/v2/chat2.nim index ec77ffd2a..a255b2a70 100644 --- a/examples/v2/chat2.nim +++ b/examples/v2/chat2.nim @@ -511,12 +511,12 @@ proc processInput(rfd: AsyncFD, rng: ref BrHmacDrbgContext) {.async.} = showChatPrompt(chat) # set up rln relay inputs - let (groupOpt, memKeyPairOpt, memIndexOpt) = rlnRelaySetUp(conf.rlnRelayMemIndex) + let (groupOpt, memKeyPairOpt, memIndexOpt) = rlnRelayStaticSetUp(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, spamHandler = some(spamHandler)) + node.mountRlnRelayStatic(group = groupOpt.get(), memKeyPair = memKeyPairOpt.get(), memIndex = memIndexOpt.get(), pubsubTopic = conf.rlnRelayPubsubTopic, contentTopic = conf.rlnRelayContentTopic, spamHandler = some(spamHandler)) debug "membership id key", idkey=memKeyPairOpt.get().idKey.toHex debug "membership id commitment key", idCommitmentkey=memKeyPairOpt.get().idCommitment.toHex diff --git a/tests/v2/test_waku_rln_relay.nim b/tests/v2/test_waku_rln_relay.nim index eeda9e7e1..23d62b9e4 100644 --- a/tests/v2/test_waku_rln_relay.nim +++ b/tests/v2/test_waku_rln_relay.nim @@ -45,10 +45,9 @@ procSuite "Waku rln relay": # -------- mount rln-relay in the off-chain mode node.mountRelay(@[RLNRELAY_PUBSUB_TOPIC]) - await node.mountRlnRelay(groupOpt = some(groupIDCommitments), - memKeyPairOpt = some(groupKeyPairs[index]), - memIndexOpt = some(index), - onchainMode = false, + node.mountRlnRelayStatic(group = groupIDCommitments, + memKeyPair = groupKeyPairs[index], + memIndex = index, pubsubTopic = RLNRELAY_PUBSUB_TOPIC, contentTopic = RLNRELAY_CONTENT_TOPIC) diff --git a/tests/v2/test_waku_rln_relay_onchain.nim b/tests/v2/test_waku_rln_relay_onchain.nim index 04001659e..e6c2634df 100644 --- a/tests/v2/test_waku_rln_relay_onchain.nim +++ b/tests/v2/test_waku_rln_relay_onchain.nim @@ -155,7 +155,6 @@ procSuite "Waku-rln-relay": rlnInstance.isOk == true var rln = rlnInstance.value - # create rln membership key pair let keyPair = rln.membershipKeyGen() check: keyPair.isSome @@ -273,7 +272,8 @@ procSuite "Waku-rln-relay": check: is_successful - asyncTest "mounting waku rln-relay": + + asyncTest "mounting waku rln-relay: check correct Merkle tree construction in the static/off-chain group management": # preparation ------------------------------ let nodeKey = crypto.PrivateKey.random(Secp256k1, rng[])[] @@ -281,17 +281,6 @@ procSuite "Waku-rln-relay": Port(60000)) await node.start() - # deploy the contract - let membershipContractAddress = await uploadRLNContract(ETH_CLIENT) - - # prepare rln-relay inputs - let - web3 = await newWeb3(ETH_CLIENT) - accounts = await web3.provider.eth_accounts() - # choose one of the existing account for the rln-relay peer - ethAccountAddress = accounts[0] - await web3.close() - # create current peer's pk var rlnInstance = createRLNInstance() check: @@ -321,18 +310,16 @@ procSuite "Waku-rln-relay": member_is_added = rln.insertMember(memberKeypair.get().idCommitment) doAssert(member_is_added) debug "member key", key = memberKeypair.get().idCommitment.toHex + let expectedRoot = rln.getMerkleRoot().value().toHex debug "expected root ", expectedRoot # test ------------------------------ # start rln-relay node.mountRelay(@[RLNRELAY_PUBSUB_TOPIC]) - await node.mountRlnRelay(ethClientAddrOpt = some(EthClient), - ethAccAddrOpt = some(ethAccountAddress), - memContractAddOpt = some(membershipContractAddress), - groupOpt = some(group), - memKeyPairOpt = some(keypair.get()), - memIndexOpt = some(index), + node.mountRlnRelayStatic(group = group, + memKeyPair = keypair.get(), + memIndex = index, pubsubTopic = RLNRELAY_PUBSUB_TOPIC, contentTopic = RLNRELAY_CONTENT_TOPIC) let calculatedRoot = node.wakuRlnRelay.rlnInstance.getMerkleRoot().value().toHex @@ -341,4 +328,145 @@ procSuite "Waku-rln-relay": check: expectedRoot == calculatedRoot - await node.stop() \ No newline at end of file + await node.stop() + + asyncTest "mounting waku rln-relay: check correct Merkle tree construction in the dynamic/onchain group management": + # preparation ------------------------------ + let + nodeKey = crypto.PrivateKey.random(Secp256k1, rng[])[] + node = WakuNode.new(nodeKey, ValidIpAddress.init("0.0.0.0"), Port(60000)) + await node.start() + + # deploy the contract + let contractAddress = await uploadRLNContract(ETH_CLIENT) + + # prepare rln-relay inputs + let + web3 = await newWeb3(ETH_CLIENT) + accounts = await web3.provider.eth_accounts() + # choose one of the existing accounts for the rln-relay peer + ethAccountAddress = accounts[0] + web3.defaultAccount = accounts[0] + + # create an rln instance + var rlnInstance = createRLNInstance() + check: + rlnInstance.isOk == true + var rln = rlnInstance.value + + # create two rln key pairs + let + keyPair1 = rln.membershipKeyGen() + keyPair2 = rln.membershipKeyGen() + check: + keyPair1.isSome + keyPair2.isSome + let + pk1 = keyPair1.get().idCommitment.toUInt256() + pk2 = keyPair2.get().idCommitment.toUInt256() + debug "member key1", key = keyPair1.get().idCommitment.toHex + debug "member key2", key = keyPair2.get().idCommitment.toHex + + # add the rln keys to the Merkle tree + let + member_is_added1 = rln.insertMember(keyPair1.get().idCommitment) + member_is_added2 = rln.insertMember(keyPair2.get().idCommitment) + doAssert(member_is_added1) + doAssert(member_is_added2) + + # get the Merkle root + let expectedRoot = rln.getMerkleRoot().value().toHex + + # 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 + + # register the members to the contract + let tx1Hash = await contractObj.register(pk1).send(value = MEMBERSHIP_FEE) + debug "a member is registered", tx1 = tx1Hash + + # register another member to the contract + let tx2Hash = await contractObj.register(pk2).send(value = MEMBERSHIP_FEE) + debug "a member is registered", tx2 = tx2Hash + + # test ------------------------------ + # start rln-relay + node.mountRelay(@[RLNRELAY_PUBSUB_TOPIC]) + await node.mountRlnRelayDynamic(ethClientAddr = EthClient, + ethAccAddr = ethAccountAddress, + memContractAddr = contractAddress, + memKeyPair = keyPair1, + memIndex = some(MembershipIndex(0)), + pubsubTopic = RLNRELAY_PUBSUB_TOPIC, + contentTopic = RLNRELAY_CONTENT_TOPIC) + + await sleepAsync(2000) # wait for the event to reach the group handler + + # rln pks are inserted into the rln peer's Merkle tree and the resulting root + # is expected to be the same as the calculatedRoot i.e., the one calculated outside of the mountRlnRelayDynamic proc + let calculatedRoot = node.wakuRlnRelay.rlnInstance.getMerkleRoot().value().toHex + debug "calculated root ", calculatedRoot=calculatedRoot + debug "expected root ", expectedRoot=expectedRoot + + check: + expectedRoot == calculatedRoot + + + await web3.close() + await node.stop() + + asyncTest "mounting waku rln-relay: check correct registration of peers without rln-relay credentials in dynamic/on-chain mode": + # deploy the contract + let contractAddress = await uploadRLNContract(ETH_CLIENT) + + # prepare rln-relay inputs + let + web3 = await newWeb3(ETH_CLIENT) + accounts = await web3.provider.eth_accounts() + # choose two of the existing accounts for the rln-relay peers + ethAccountAddress1 = accounts[0] + ethAccountAddress2 = accounts[1] + await web3.close() + + # prepare two nodes + let + nodeKey = crypto.PrivateKey.random(Secp256k1, rng[])[] + node = WakuNode.new(nodeKey, ValidIpAddress.init("0.0.0.0"), Port(60000)) + await node.start() + + let + nodeKey2 = crypto.PrivateKey.random(Secp256k1, rng[])[] + node2 = WakuNode.new(nodeKey2, ValidIpAddress.init("0.0.0.0"), Port(60001)) + await node2.start() + + # start rln-relay on the first node, leave rln-relay credentials empty + node.mountRelay(@[RLNRELAY_PUBSUB_TOPIC]) + await node.mountRlnRelayDynamic(ethClientAddr = EthClient, + ethAccAddr = ethAccountAddress1, + memContractAddr = contractAddress, + memKeyPair = none(MembershipKeyPair), + memIndex = none(MembershipIndex), + pubsubTopic = RLNRELAY_PUBSUB_TOPIC, + contentTopic = RLNRELAY_CONTENT_TOPIC) + + + + # start rln-relay on the second node, leave rln-relay credentials empty + node2.mountRelay(@[RLNRELAY_PUBSUB_TOPIC]) + await node2.mountRlnRelayDynamic(ethClientAddr = EthClient, + ethAccAddr = ethAccountAddress2, + memContractAddr = contractAddress, + memKeyPair = none(MembershipKeyPair), + memIndex = none(MembershipIndex), + pubsubTopic = RLNRELAY_PUBSUB_TOPIC, + contentTopic = RLNRELAY_CONTENT_TOPIC) + + # the two nodes should be registered into the contract + # since nodes are spun up sequentially + # the first node has index 0 whereas the second node gets index 1 + check: + node.wakuRlnRelay.membershipIndex == MembershipIndex(0) + node2.wakuRlnRelay.membershipIndex == MembershipIndex(1) + + await node.stop() + await node2.stop() \ No newline at end of file diff --git a/tests/v2/test_wakunode.nim b/tests/v2/test_wakunode.nim index 559284567..a36c56678 100644 --- a/tests/v2/test_wakunode.nim +++ b/tests/v2/test_wakunode.nim @@ -725,36 +725,33 @@ procSuite "WakuNode": # set up three nodes # node1 node1.mountRelay(@[rlnRelayPubSubTopic]) - let (groupOpt1, memKeyPairOpt1, memIndexOpt1) = rlnRelaySetUp(1) # set up rln relay inputs + let (groupOpt1, memKeyPairOpt1, memIndexOpt1) = rlnRelayStaticSetUp(1) # set up rln relay inputs # mount rlnrelay in off-chain mode - waitFor node1.mountRlnRelay(groupOpt = groupOpt1, - memKeyPairOpt = memKeyPairOpt1, - memIndexOpt= memIndexOpt1, - onchainMode = false, + node1.mountRlnRelayStatic(group = groupOpt1.get(), + memKeyPair = memKeyPairOpt1.get(), + memIndex = memIndexOpt1.get(), pubsubTopic = rlnRelayPubSubTopic, contentTopic = contentTopic) await node1.start() # node 2 node2.mountRelay(@[rlnRelayPubSubTopic]) - let (groupOpt2, memKeyPairOpt2, memIndexOpt2) = rlnRelaySetUp(2) # set up rln relay inputs + let (groupOpt2, memKeyPairOpt2, memIndexOpt2) = rlnRelayStaticSetUp(2) # set up rln relay inputs # mount rlnrelay in off-chain mode - waitFor node2.mountRlnRelay(groupOpt = groupOpt2, - memKeyPairOpt = memKeyPairOpt2, - memIndexOpt= memIndexOpt2, - onchainMode = false, + node2.mountRlnRelayStatic(group = groupOpt2.get(), + memKeyPair = memKeyPairOpt2.get(), + memIndex = memIndexOpt2.get(), pubsubTopic = rlnRelayPubSubTopic, contentTopic = contentTopic) await node2.start() # node 3 node3.mountRelay(@[rlnRelayPubSubTopic]) - let (groupOpt3, memKeyPairOpt3, memIndexOpt3) = rlnRelaySetUp(3) # set up rln relay inputs + let (groupOpt3, memKeyPairOpt3, memIndexOpt3) = rlnRelayStaticSetUp(3) # set up rln relay inputs # mount rlnrelay in off-chain mode - waitFor node3.mountRlnRelay(groupOpt = groupOpt3, - memKeyPairOpt = memKeyPairOpt3, - memIndexOpt= memIndexOpt3, - onchainMode = false, + node3.mountRlnRelayStatic(group = groupOpt3.get(), + memKeyPair = memKeyPairOpt3.get(), + memIndex = memIndexOpt3.get(), pubsubTopic = rlnRelayPubSubTopic, contentTopic = contentTopic) await node3.start() @@ -818,36 +815,33 @@ procSuite "WakuNode": # set up three nodes # node1 node1.mountRelay(@[rlnRelayPubSubTopic]) - let (groupOpt1, memKeyPairOpt1, memIndexOpt1) = rlnRelaySetUp(1) # set up rln relay inputs + let (groupOpt1, memKeyPairOpt1, memIndexOpt1) = rlnRelayStaticSetUp(1) # set up rln relay inputs # mount rlnrelay in off-chain mode - waitFor node1.mountRlnRelay(groupOpt = groupOpt1, - memKeyPairOpt = memKeyPairOpt1, - memIndexOpt= memIndexOpt1, - onchainMode = false, + node1.mountRlnRelayStatic(group = groupOpt1.get(), + memKeyPair = memKeyPairOpt1.get(), + memIndex = memIndexOpt1.get(), pubsubTopic = rlnRelayPubSubTopic, contentTopic = contentTopic) await node1.start() # node 2 node2.mountRelay(@[rlnRelayPubSubTopic]) - let (groupOpt2, memKeyPairOpt2, memIndexOpt2) = rlnRelaySetUp(2) # set up rln relay inputs + let (groupOpt2, memKeyPairOpt2, memIndexOpt2) = rlnRelayStaticSetUp(2) # set up rln relay inputs # mount rlnrelay in off-chain mode - waitFor node2.mountRlnRelay(groupOpt = groupOpt2, - memKeyPairOpt = memKeyPairOpt2, - memIndexOpt= memIndexOpt2, - onchainMode = false, + node2.mountRlnRelayStatic(group = groupOpt2.get(), + memKeyPair = memKeyPairOpt2.get(), + memIndex = memIndexOpt2.get(), pubsubTopic = rlnRelayPubSubTopic, contentTopic = contentTopic) await node2.start() # node 3 node3.mountRelay(@[rlnRelayPubSubTopic]) - let (groupOpt3, memKeyPairOpt3, memIndexOpt3) = rlnRelaySetUp(3) # set up rln relay inputs + let (groupOpt3, memKeyPairOpt3, memIndexOpt3) = rlnRelayStaticSetUp(3) # set up rln relay inputs # mount rlnrelay in off-chain mode - waitFor node3.mountRlnRelay(groupOpt = groupOpt3, - memKeyPairOpt = memKeyPairOpt3, - memIndexOpt= memIndexOpt3, - onchainMode = false, + node3.mountRlnRelayStatic(group = groupOpt3.get(), + memKeyPair = memKeyPairOpt3.get(), + memIndex= memIndexOpt3.get(), pubsubTopic = rlnRelayPubSubTopic, contentTopic = contentTopic) await node3.start() @@ -926,36 +920,33 @@ procSuite "WakuNode": # set up three nodes # node1 node1.mountRelay(@[rlnRelayPubSubTopic]) - let (groupOpt1, memKeyPairOpt1, memIndexOpt1) = rlnRelaySetUp(1) # set up rln relay inputs + let (groupOpt1, memKeyPairOpt1, memIndexOpt1) = rlnRelayStaticSetUp(1) # set up rln relay inputs # mount rlnrelay in off-chain mode - waitFor node1.mountRlnRelay(groupOpt = groupOpt1, - memKeyPairOpt = memKeyPairOpt1, - memIndexOpt= memIndexOpt1, - onchainMode = false, + node1.mountRlnRelayStatic(group = groupOpt1.get(), + memKeyPair = memKeyPairOpt1.get(), + memIndex = memIndexOpt1.get(), pubsubTopic = rlnRelayPubSubTopic, contentTopic = contentTopic) await node1.start() # node 2 node2.mountRelay(@[rlnRelayPubSubTopic]) - let (groupOpt2, memKeyPairOpt2, memIndexOpt2) = rlnRelaySetUp(2) # set up rln relay inputs + let (groupOpt2, memKeyPairOpt2, memIndexOpt2) = rlnRelayStaticSetUp(2) # set up rln relay inputs # mount rlnrelay in off-chain mode - waitFor node2.mountRlnRelay(groupOpt = groupOpt2, - memKeyPairOpt = memKeyPairOpt2, - memIndexOpt= memIndexOpt2, - onchainMode = false, + node2.mountRlnRelayStatic(group = groupOpt2.get(), + memKeyPair = memKeyPairOpt2.get(), + memIndex = memIndexOpt2.get(), pubsubTopic = rlnRelayPubSubTopic, contentTopic = contentTopic) await node2.start() # node 3 node3.mountRelay(@[rlnRelayPubSubTopic]) - let (groupOpt3, memKeyPairOpt3, memIndexOpt3) = rlnRelaySetUp(3) # set up rln relay inputs + let (groupOpt3, memKeyPairOpt3, memIndexOpt3) = rlnRelayStaticSetUp(3) # set up rln relay inputs # mount rlnrelay in off-chain mode - waitFor node3.mountRlnRelay(groupOpt = groupOpt3, - memKeyPairOpt = memKeyPairOpt3, - memIndexOpt= memIndexOpt3, - onchainMode = false, + node3.mountRlnRelayStatic(group = groupOpt3.get(), + memKeyPair = memKeyPairOpt3.get(), + memIndex = memIndexOpt3.get(), pubsubTopic = rlnRelayPubSubTopic, contentTopic = contentTopic) await node3.start() diff --git a/vendor/nim-libbacktrace/vendor/libbacktrace-upstream/libtool b/vendor/nim-libbacktrace/vendor/libbacktrace-upstream/libtool index e5e88f7f5..ff932c6fe 100755 --- a/vendor/nim-libbacktrace/vendor/libbacktrace-upstream/libtool +++ b/vendor/nim-libbacktrace/vendor/libbacktrace-upstream/libtool @@ -2,7 +2,7 @@ # libtool - Provide generalized library-building support services. # Generated automatically by config.status (libbacktrace) version-unused -# Libtool was configured on host fv-az447-649: +# Libtool was configured on host fv-az241-328: # NOTE: Changes made to this file will be lost: look at ltmain.sh. # # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005, diff --git a/waku/v2/node/config.nim b/waku/v2/node/config.nim index 3814f0c6c..7d3000dbf 100644 --- a/waku/v2/node/config.nim +++ b/waku/v2/node/config.nim @@ -8,6 +8,7 @@ import libp2p/crypto/secp, nimcrypto/utils, eth/keys, + web3, ../protocol/waku_rln_relay/waku_rln_relay_types, ../protocol/waku_message @@ -129,6 +130,36 @@ type defaultValue: "/toy-chat/2/luzhou/proto" name: "rln-relay-content-topic" }: ContentTopic + rlnRelayDynamic* {. + desc: "Enable waku-rln-relay with on-chain dynamic group management: true|false", + defaultValue: false + name: "rln-relay-dynamic" }: bool + + rlnRelayIdKey* {. + desc: "Rln relay identity secret key as a Hex string", + defaultValue: "" + name: "rln-relay-id" }: string + + rlnRelayIdCommitmentKey* {. + desc: "Rln relay identity commitment key as a Hex string", + defaultValue: "" + name: "rln-relay-id-commitment" }: string + + rlnRelayEthAccount* {. + desc: "Ethereum testnet account address", + defaultValue: "" + name: "eth-account-address" }: string + + rlnRelayEthClientAddress* {. + desc: "Ethereum testnet client address e.g., ws://localhost:8540/", + defaultValue: "ws://localhost:8540/" + name: "eth-client-address" }: string + + rlnRelayEthMemContractAddress* {. + desc: "Address of membership contract on an Ethereum testnet", + defaultValue: "" + name: "eth-mem-contract-address" }: string + staticnodes* {. desc: "Peer multiaddr to directly connect with. Argument may be repeated." name: "staticnode" }: seq[string] diff --git a/waku/v2/node/wakunode2.nim b/waku/v2/node/wakunode2.nim index a26dfc57d..fa2bda29d 100644 --- a/waku/v2/node/wakunode2.nim +++ b/waku/v2/node/wakunode2.nim @@ -6,6 +6,7 @@ import stew/shims/net as stewNet, stew/byteutils, eth/keys, + nimcrypto, eth/p2p/discoveryv5/enr, libp2p/crypto/crypto, libp2p/protocols/ping, @@ -38,7 +39,7 @@ when defined(rln): import libp2p/protocols/pubsub/rpc/messages, libp2p/protocols/pubsub/pubsub, - web3, + web3, web3/ethtypes, ../protocol/waku_rln_relay/[rln, waku_rln_relay_utils] declarePublicCounter waku_node_messages, "number of messages received", ["type"] @@ -543,20 +544,17 @@ when defined(rln): let pb = PubSub(node.wakuRelay) pb.addValidator(pubsubTopic, validator) - proc mountRlnRelay*(node: WakuNode, - ethClientAddrOpt: Option[string] = none(string), - ethAccAddrOpt: Option[web3.Address] = none(web3.Address), - memContractAddOpt: Option[web3.Address] = none(web3.Address), - groupOpt: Option[seq[IDCommitment]] = none(seq[IDCommitment]), - memKeyPairOpt: Option[MembershipKeyPair] = none(MembershipKeyPair), - memIndexOpt: Option[MembershipIndex] = none(MembershipIndex), - onchainMode: bool = true, + proc mountRlnRelayStatic*(node: WakuNode, + group: seq[IDCommitment], + memKeyPair: MembershipKeyPair, + memIndex: MembershipIndex, pubsubTopic: string, contentTopic: ContentTopic, - spamHandler: Option[SpamHandler] = none(SpamHandler)) {.async.} = + spamHandler: Option[SpamHandler] = none(SpamHandler)) {.raises: [Defect, IOError].}= # TODO return a bool value to indicate the success of the call - # check whether inputs are provided + debug "mounting rln-relay in off-chain/static mode" + # check whether inputs are provided # relay protocol is the prerequisite of rln-relay if node.wakuRelay.isNil: error "Failed to mount WakuRLNRelay. Relay protocol is not mounted." @@ -565,41 +563,8 @@ when defined(rln): if pubsubTopic notin node.wakuRelay.defaultTopics: error "Failed to mount WakuRLNRelay. The relay protocol does not support the configured pubsub topic.", pubsubTopic=pubsubTopic return - if onchainMode: - if memContractAddOpt.isNone(): - error "failed to mount rln relay: membership contract address is not provided" - return - if ethClientAddrOpt.isNone(): - error "failed to mount rln relay: Ethereum client address is not provided" - return - if ethAccAddrOpt.isNone(): - error "failed to mount rln relay: Ethereum account address is not provided" - return - else: - if groupOpt.isNone(): - error "failed to mount rln relay: group information is not provided" - return - if memKeyPairOpt.isNone(): - error "failed to mount rln relay: membership key of the node is not provided" - return - if memIndexOpt.isNone(): - error "failed to mount rln relay: membership index is not provided" - return - - var - ethClientAddr: string - ethAccAddr: web3.Address - memContractAdd: web3.Address - if onchainMode: - ethClientAddr = ethClientAddrOpt.get() - ethAccAddr = ethAccAddrOpt.get() - memContractAdd = memContractAddOpt.get() - - let - group = groupOpt.get() - memKeyPair = memKeyPairOpt.get() - memIndex = memIndexOpt.get() + debug "rln-relay input validation passed" # check the peer's index and the inclusion of user's identity commitment in the group doAssert((memKeyPair.idCommitment) == group[int(memIndex)]) @@ -609,40 +574,94 @@ when defined(rln): doAssert(rlnInstance.isOk) var rln = rlnInstance.value - # generate the membership keys if none is provided - # in a happy path, this condition never gets through for a static group of users - # the node should pass its keys i.e., memKeyPairOpt to the function - if not memKeyPairOpt.isSome: - let membershipKeyPair = rln.membershipKeyGen() - # check whether keys are generated - doAssert(membershipKeyPair.isSome()) - debug "the membership key for the rln relay is generated", idKey=membershipKeyPair.get().idKey.toHex, idCommitment=membershipKeyPair.get().idCommitment.toHex - - # add members to the Merkle tree for index in 0..group.len-1: let member = group[index] let member_is_added = rln.insertMember(member) doAssert(member_is_added) - # create the WakuRLNRelay var rlnPeer = WakuRLNRelay(membershipKeyPair: memKeyPair, membershipIndex: memIndex, - membershipContractAddress: memContractAdd, + rlnInstance: rln, + pubsubTopic: pubsubTopic, + contentTopic: contentTopic) + + # 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 + node.addRLNRelayValidator(pubsubTopic, contentTopic, spamHandler) + debug "rln relay topic validator is mounted successfully", pubsubTopic=pubsubTopic, contentTopic=contentTopic + + node.wakuRlnRelay = rlnPeer + + + proc mountRlnRelayDynamic*(node: WakuNode, + ethClientAddr: string = "", + ethAccAddr: web3.Address, + memContractAddr: web3.Address, + memKeyPair: Option[MembershipKeyPair] = none(MembershipKeyPair), + memIndex: Option[MembershipIndex] = none(MembershipIndex), + pubsubTopic: string, + contentTopic: ContentTopic, + spamHandler: Option[SpamHandler] = none(SpamHandler)) {.async.} = + debug "mounting rln-relay in on-chain/dynamic mode" + # TODO return a bool value to indicate the success of the call + # relay protocol is the prerequisite of rln-relay + if node.wakuRelay.isNil: + error "Failed to mount WakuRLNRelay. Relay protocol is not mounted." + return + # check whether the pubsub topic is supported at the relay level + if pubsubTopic notin node.wakuRelay.defaultTopics: + error "Failed to mount WakuRLNRelay. The relay protocol does not support the configured pubsub topic.", pubsubTopic=pubsubTopic + return + debug "rln-relay input validation passed" + + # create an RLN instance + var rlnInstance = createRLNInstance() + doAssert(rlnInstance.isOk) + var rln = rlnInstance.value + + # prepare rln membership key pair + var + keyPair: MembershipKeyPair + rlnIndex: MembershipIndex + if memKeyPair.isNone: # if non provided, create one and register to the contract + trace "no rln-relay key is provided, generating one" + let keyPairOpt = rln.membershipKeyGen() + doAssert(keyPairOpt.isSome) + keyPair = keyPairOpt.get() + # register the rln-relay peer to the membership contract + let regIndexRes = await register(idComm = keyPair.idCommitment, ethAccountAddress = ethAccAddr, ethClientAddress = ethClientAddr, membershipContractAddress = memContractAddr) + # check whether registration is done + doAssert(regIndexRes.isOk()) + rlnIndex = regIndexRes.value + debug "peer is successfully registered into the membership contract", rlnIndex=rlnIndex, idComm=keyPair.idCommitment.toHex(), idKey=keyPair.idKey.toHex() + else: + keyPair = memKeyPair.get() + rlnIndex = memIndex.get() + + # create the WakuRLNRelay + var rlnPeer = WakuRLNRelay(membershipKeyPair: keyPair, + membershipIndex: rlnIndex, + membershipContractAddress: memContractAddr, ethClientAddress: ethClientAddr, ethAccountAddress: ethAccAddr, rlnInstance: rln, pubsubTopic: pubsubTopic, contentTopic: contentTopic) - if onchainMode: - # register the rln-relay peer to the membership contract - let isSuccessful = await rlnPeer.register() - # check whether registration is done - doAssert(isSuccessful) - debug "peer is successfully registered into the membership contract" + proc handler(pubkey: Uint256, index: Uint256) = + debug "a new key is added", pubkey=pubkey + # assuming all the members arrive in order + let pk = pubkey.toIDCommitment() + let isSuccessful = rlnPeer.rlnInstance.insertMember(pk) + debug "received pk", pk=pk.toHex, index =index + doAssert(isSuccessful) + + asyncSpawn rlnPeer.handleGroupUpdates(handler) + debug "dynamic group management is started" # 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 @@ -1207,29 +1226,51 @@ when isMainModule: when defined(rln): if conf.rlnRelay: - info "WakuRLNRelay is enabled" + if not conf.rlnRelayDynamic: + info " setting up waku-rln-relay in on-chain mode... " + # set up rln relay inputs + let (groupOpt, memKeyPairOpt, memIndexOpt) = rlnRelayStaticSetUp(conf.rlnRelayMemIndex) + if memIndexOpt.isNone: + error "failed to mount WakuRLNRelay" + else: + # mount rlnrelay in off-chain mode with a static group of users + node.mountRlnRelayStatic(group = groupOpt.get(), memKeyPair = memKeyPairOpt.get(), memIndex= memIndexOpt.get(), pubsubTopic = conf.rlnRelayPubsubTopic, contentTopic = conf.rlnRelayContentTopic) - # set up rln relay inputs - let (groupOpt, memKeyPairOpt, memIndexOpt) = rlnRelaySetUp(conf.rlnRelayMemIndex) - if memIndexOpt.isNone: - error "failed to mount WakuRLNRelay" + info "membership id key", idkey=memKeyPairOpt.get().idKey.toHex + info "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 + # TODO have added this check to account for unseen corner cases, will remove it later + let + root = node.wakuRlnRelay.rlnInstance.getMerkleRoot.value.toHex() + expectedRoot = STATIC_GROUP_MERKLE_ROOT + if root != expectedRoot: + error "root mismatch: something went wrong not in Merkle tree construction" + debug "the calculated root", root + info "WakuRLNRelay is mounted successfully", pubsubtopic=conf.rlnRelayPubsubTopic, contentTopic=conf.rlnRelayContentTopic 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) - - info "membership id key", idkey=memKeyPairOpt.get().idKey.toHex - info "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 - # TODO have added this check to account for unseen corner cases, will remove it later + info " setting up waku-rln-relay in on-chain mode... " + + # read related inputs to run rln-relay in on-chain mode and do type conversion when needed let - root = node.wakuRlnRelay.rlnInstance.getMerkleRoot.value.toHex() - expectedRoot = STATIC_GROUP_MERKLE_ROOT - if root != expectedRoot: - error "root mismatch: something went wrong not in Merkle tree construction" - debug "the calculated root", root - info "WakuRLNRelay is mounted successfully", pubsubtopic=conf.rlnRelayPubsubTopic, contentTopic=conf.rlnRelayContentTopic + ethAccountAddr = web3.fromHex(web3.Address, conf.rlnRelayEthAccount) + ethClientAddr = conf.rlnRelayEthClientAddress + ethMemContractAddress = web3.fromHex(web3.Address, conf.rlnRelayEthMemContractAddress) + rlnRelayId = conf.rlnRelayIdKey + rlnRelayIdCommitmentKey = conf.rlnRelayIdCommitmentKey + rlnRelayIndex = conf.rlnRelayMemIndex + # check if the peer has provided its rln credentials + if rlnRelayIdCommitmentKey != "" and rlnRelayId != "": + # type conversation from hex strings to MembershipKeyPair + let keyPair = @[(rlnRelayId, rlnRelayIdCommitmentKey)] + let memKeyPair = keyPair.toMembershipKeyPairs()[0] + # mount the rln relay protocol in the on-chain/dynamic mode + waitFor node.mountRlnRelayDynamic(memContractAddr = ethMemContractAddress, ethClientAddr = ethClientAddr, memKeyPair = some(memKeyPair), memIndex = some(rlnRelayIndex), ethAccAddr = ethAccountAddr, pubsubTopic = conf.rlnRelayPubsubTopic, contentTopic = conf.rlnRelayContentTopic) + else: + # no rln credential is provided + # mount the rln relay protocol in the on-chain/dynamic mode + waitFor node.mountRlnRelayDynamic(memContractAddr = ethMemContractAddress, ethClientAddr = ethClientAddr, ethAccAddr = ethAccountAddr, pubsubTopic = conf.rlnRelayPubsubTopic, contentTopic = conf.rlnRelayContentTopic) if conf.swap: mountSwap(node) diff --git a/waku/v2/protocol/waku_rln_relay/waku_rln_relay_types.nim b/waku/v2/protocol/waku_rln_relay/waku_rln_relay_types.nim index 776284975..6f53de6e0 100644 --- a/waku/v2/protocol/waku_rln_relay/waku_rln_relay_types.nim +++ b/waku/v2/protocol/waku_rln_relay/waku_rln_relay_types.nim @@ -74,7 +74,7 @@ type WakuRLNRelay* = ref object # this field is required for signing transactions # TODO may need to erase this ethAccountPrivateKey when is not used # TODO may need to make ethAccountPrivateKey mandatory - ethAccountPrivateKey*: Option[PrivateKey] + ethAccountPrivateKey*: PrivateKey rlnInstance*: RLN[Bn256] pubsubTopic*: string # the pubsub topic for which rln relay is mounted # contentTopic should be of type waku_message.ContentTopic, however, due to recursive module dependency, the underlying type of ContentTopic is used instead 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 4e7bbb5c9..de9189650 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 @@ -4,9 +4,10 @@ import std/sequtils, tables, times, chronicles, options, chronos, stint, web3, json, + eth/keys, stew/results, stew/[byteutils, arrayops, endians2], - rln, + rln, waku_rln_relay_types, ../waku_message @@ -65,6 +66,7 @@ proc createRLNInstance*(d: int = MERKLE_TREE_DEPTH): RLNResult return err("error in parameters generation") return ok(rlnInstance) + proc membershipKeyGen*(ctxPtr: RLN[Bn256]): Option[MembershipKeyPair] = ## generates a MembershipKeyPair that can be used for the registration into the rln membership contract @@ -99,28 +101,65 @@ proc membershipKeyGen*(ctxPtr: RLN[Bn256]): Option[MembershipKeyPair] = return some(keypair) proc toUInt256*(idCommitment: IDCommitment): UInt256 = - let pk = cast[UInt256](idCommitment) + let pk = UInt256.fromBytesBE(idCommitment) return pk -proc toIDCommitment*(idCommitment: UInt256): IDCommitment = - let pk = cast[IDCommitment](idCommitment) +proc toIDCommitment*(idCommitmentUint: UInt256): IDCommitment = + let pk = IDCommitment(idCommitmentUint.toBytesBE()) return pk +proc toMembershipIndex(v: UInt256): MembershipIndex = + let result: MembershipIndex = cast[MembershipIndex](v) + return result + +proc register*(idComm: IDCommitment, ethAccountAddress: Address, ethClientAddress: string, membershipContractAddress: Address): Future[Result[MembershipIndex, string]] {.async.} = + # TODO may need to also get eth Account Private Key as PrivateKey + ## registers the idComm into the membership contract whose address is in rlnPeer.membershipContractAddress + let web3 = await newWeb3(ethClientAddress) + web3.defaultAccount = ethAccountAddress + + # when the private key is set in a web3 instance, the send proc (sender.register(pk).send(MEMBERSHIP_FEE)) + # does the signing using the provided key + # web3.privateKey = some(ethAccountPrivateKey) + var sender = web3.contractSender(MembershipContract, membershipContractAddress) # creates a Sender object with a web3 field and contract address of type Address + + debug "registering an id commitment", idComm=idComm + let + pk = idComm.toUInt256() + txHash = await sender.register(pk).send(MEMBERSHIP_FEE) + tsReceipt = await web3.getMinedTransactionReceipt(txHash) + + # the receipt topic holds the hash of signature of the raised events + 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[0..65] != "0x5a92c2530f207992057b9c3e544108ffce3beda4a63719f316967c49bf6159d2": + return err("invalid event signature hash") + + # 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.hexToSeqByte() + eventIdCommUint = UInt256.fromBytesBE(argumentsBytes[0..31]) + eventIndex = UInt256.fromBytesBE(argumentsBytes[32..^1]) + eventIdComm = eventIdCommUint.toIDCommitment() + debug "the identity commitment key extracted from tx log", eventIdComm=eventIdComm + debug "the index of registered identity commitment key", eventIndex=eventIndex + + if eventIdComm != idComm: + return err("invalid id commitment key") + + + await web3.close() + return ok(toMembershipIndex(eventIndex)) + 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 - let web3 = await newWeb3(rlnPeer.ethClientAddress) - web3.defaultAccount = rlnPeer.ethAccountAddress - # when the private key is set in a web3 instance, the send proc (sender.register(pk).send(MEMBERSHIP_FEE)) - # does the signing using the provided key - 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 = 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 - await web3.close() + let pk = rlnPeer.membershipKeyPair.idCommitment + discard await register(idComm = pk, ethAccountAddress = rlnPeer.ethAccountAddress, ethClientAddress = rlnPeer.ethClientAddress, membershipContractAddress = rlnPeer.membershipContractAddress ) + return true proc appendLength*(input: openArray[byte]): seq[byte] = @@ -343,7 +382,7 @@ proc createMembershipList*(n: int): (seq[(string, string)], string) {.raises: [ let root = rln.getMerkleRoot().value.toHex return (output, root) -proc rlnRelaySetUp*(rlnRelayMemIndex: MembershipIndex): (Option[seq[ +proc rlnRelayStaticSetUp*(rlnRelayMemIndex: MembershipIndex): (Option[seq[ IDCommitment]], Option[MembershipKeyPair], Option[ MembershipIndex]) {.raises: [Defect, ValueError].} = let @@ -579,6 +618,7 @@ proc subscribeToGroupEvents(ethClientUri: string, contractAddress: Address, bloc debug "onRegister", pubkey = pubkey, index = index handler(pubkey, index) except Exception as err: + # chronos still raises exceptions which inherit directly from Exception doAssert false, err.msg do (err: CatchableError): echo "Error from subscription: ", err.msg