Feat(rln-relay): enables two modes of onchain and offchain (#992)

* 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

* WIP

* uncomments the tests

* defines a new mountRlnRelayStatic proc

* splits mountRlnRelay into two procs for dynamic and static group management

* adds a config for off-chain and on-chain rln-relay

* runs dynamic or static mode of rln-relay based on the input config

* adds Eth private key and account configs

* reads Eth private key and account the configs

* comments put the second register proc

* add proper call to the rlnrelay dynamic mode

* adds todo

* adds new rln relay configs

* splits register into two procs

* makes eth account private key non-optional

* removes getIdCommitment and edits the register proc

* removes getIdCommitment calls

* uncomments the commented tests

* fixes a bug

* removes contract deployment for the offchain test

* fixes a bug, edits comments

* removes custom types  without proper parsing and serialization routines from the configs

* fixes a bug

* switches to stew byte utils

* removes log decoding

* WIP

* updates register proc

* edits test titles

* removes eth private key config

* changes the output of register proc to return the registered index

* integrates the registration process into mountRlnRelayDynamic

* integration test for the register proc

* brings back the onchain tests

* updates comments

* cleans up

* disambiguates the Address type namespace

* fixes type ambiguities

* adds default values for rln key and index

* updates config descriptions

* adds type conversion from hex to MembershipKeyPair

* adds more code documentation

* passed the group value instead of option  to the mount proc

* fix a bug

* a minor input type fix for rln chat2

* groups let declarations

* adds default values for addresses

* logs registered keys
This commit is contained in:
Sanaz Taheri Boshrooyeh 2022-06-17 15:00:19 -07:00 committed by GitHub
parent d754444f45
commit 6c8ab0ab0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 402 additions and 172 deletions

View File

@ -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

View File

@ -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)

View File

@ -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
@ -342,3 +329,144 @@ procSuite "Waku-rln-relay":
expectedRoot == calculatedRoot
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()

View File

@ -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()

View File

@ -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]

View File

@ -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,15 +1226,15 @@ 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) = 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)
# 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)
info "membership id key", idkey=memKeyPairOpt.get().idKey.toHex
info "membership id commitment key", idCommitmentkey=memKeyPairOpt.get().idCommitment.toHex
@ -1230,6 +1249,28 @@ when isMainModule:
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:
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
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)

View File

@ -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

View File

@ -4,6 +4,7 @@ import
std/sequtils, tables, times,
chronicles, options, chronos, stint,
web3, json,
eth/keys,
stew/results,
stew/[byteutils, arrayops, endians2],
rln,
@ -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