chore(rln-relay): integrate waku rln registry (#1943)

This commit is contained in:
Aaryamann Challani 2023-08-25 22:48:52 +05:30 committed by GitHub
parent 32aa1c5b61
commit cc9f8d4254
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 80 additions and 130 deletions

View File

@ -60,26 +60,26 @@ proc uploadRLNContract*(ethClientAddress: string): Future[Address] {.async.} =
debug "hasher address: ", hasherAddress debug "hasher address: ", hasherAddress
# encode membership contract inputs to 32 bytes zero-padded # encode registry contract inputs to 32 bytes zero-padded
let let
membershipFeeEncoded = encode(MembershipFee).data
depthEncoded = encode(MerkleTreeDepth.u256).data
hasherAddressEncoded = encode(hasherAddress).data hasherAddressEncoded = encode(hasherAddress).data
# this is the contract constructor input # this is the contract constructor input
contractInput = membershipFeeEncoded & depthEncoded & hasherAddressEncoded contractInput = hasherAddressEncoded
debug "encoded membership fee: ", membershipFeeEncoded
debug "encoded depth: ", depthEncoded
debug "encoded hasher address: ", hasherAddressEncoded debug "encoded hasher address: ", hasherAddressEncoded
debug "encoded contract input:", contractInput debug "encoded contract input:", contractInput
# deploy membership contract with its constructor inputs # deploy registry contract with its constructor inputs
let receipt = await web3.deployContract(MembershipContractCode, let receipt = await web3.deployContract(RegistryContractCode,
contractInput = contractInput) contractInput = contractInput)
let contractAddress = receipt.contractAddress.get let contractAddress = receipt.contractAddress.get()
debug "Address of the deployed membership contract: ", contractAddress debug "Address of the deployed registry contract: ", contractAddress
let registryContract = web3.contractSender(WakuRlnRegistry, contractAddress)
let newStorageReceipt = await registryContract.newStorage().send()
debug "Receipt of the newStorage transaction: ", newStorageReceipt
let newBalance = await web3.provider.eth_getBalance(web3.defaultAccount, "latest") let newBalance = await web3.provider.eth_getBalance(web3.defaultAccount, "latest")
debug "Account balance after the contract deployment: ", newBalance debug "Account balance after the contract deployment: ", newBalance
@ -149,14 +149,11 @@ proc stopGanache(runGanache: Process) {.used.} =
try: try:
# We terminate Ganache daemon by sending a SIGTERM signal to the runGanache PID to trigger RPC server termination and clean-up # We terminate Ganache daemon by sending a SIGTERM signal to the runGanache PID to trigger RPC server termination and clean-up
discard startProcess("pkill", args = ["-f", "ganache"], options = {poUsePath}) discard startProcess("pkill", args = ["-f", "ganache"], options = {poUsePath})
# NOTE: the below line must remain commented out, otherwise it will cause a deadlocked state
# ref: https://nim-lang.org/docs/osproc.html#waitForExit%2CProcess%2Cint
# debug "ganache logs", logs=runGanache.outputstream.readAll()
debug "Sent SIGTERM to Ganache", ganachePID=ganachePID debug "Sent SIGTERM to Ganache", ganachePID=ganachePID
except: except:
error "Ganache daemon termination failed: ", err = getCurrentExceptionMsg() error "Ganache daemon termination failed: ", err = getCurrentExceptionMsg()
proc setup(signer = true): Future[OnchainGroupManager] {.async.} = proc setup(): Future[OnchainGroupManager] {.async.} =
let rlnInstanceRes = createRlnInstance(tree_path = genTempPath("rln_tree", "group_manager_onchain")) let rlnInstanceRes = createRlnInstance(tree_path = genTempPath("rln_tree", "group_manager_onchain"))
require: require:
rlnInstanceRes.isOk() rlnInstanceRes.isOk()
@ -168,18 +165,16 @@ proc setup(signer = true): Future[OnchainGroupManager] {.async.} =
let web3 = await newWeb3(EthClient) let web3 = await newWeb3(EthClient)
let accounts = await web3.provider.eth_accounts() let accounts = await web3.provider.eth_accounts()
web3.defaultAccount = accounts[1] web3.defaultAccount = accounts[0]
var pk = none(string) var pk = none(string)
if signer: let (privateKey, _) = await createEthAccount()
let (privateKey, _) = await createEthAccount() pk = some($privateKey)
pk = some($privateKey)
let manager = OnchainGroupManager(ethClientUrl: EthClient, let manager = OnchainGroupManager(ethClientUrl: EthClient,
ethContractAddress: $contractAddress, ethContractAddress: $contractAddress,
ethPrivateKey: pk, ethPrivateKey: pk,
rlnInstance: rlnInstance, rlnInstance: rlnInstance)
saveKeystore: false)
return manager return manager
@ -211,13 +206,12 @@ suite "Onchain group manager":
metadata.contractAddress == manager.ethContractAddress metadata.contractAddress == manager.ethContractAddress
await manager.stop() await manager.stop()
let differentContractAddress = await uploadRLNContract(manager.ethClientUrl)
# simulating a change in the contractAddress # simulating a change in the contractAddress
let manager2 = OnchainGroupManager(ethClientUrl: EthClient, let manager2 = OnchainGroupManager(ethClientUrl: EthClient,
ethContractAddress: "0x0000000000000000000000000000000000000000", ethContractAddress: $differentContractAddress,
ethPrivateKey: manager.ethPrivateKey, rlnInstance: manager.rlnInstance)
rlnInstance: manager.rlnInstance,
saveKeystore: false)
expect(ValueError): await manager2.init() expect(ValueError): await manager2.init()
asyncTest "startGroupSync: should start group sync": asyncTest "startGroupSync: should start group sync":
@ -234,7 +228,7 @@ suite "Onchain group manager":
asyncTest "startGroupSync: should sync to the state of the group": asyncTest "startGroupSync: should sync to the state of the group":
let manager = await setup() let manager = await setup()
let credentials = generateCredentials(manager.rlnInstance)
await manager.init() await manager.init()
let merkleRootBeforeRes = manager.rlnInstance.getMerkleRoot() let merkleRootBeforeRes = manager.rlnInstance.getMerkleRoot()
@ -248,13 +242,14 @@ suite "Onchain group manager":
proc callback(registrations: seq[Membership]): Future[void] {.async.} = proc callback(registrations: seq[Membership]): Future[void] {.async.} =
require: require:
registrations.len == 1 registrations.len == 1
registrations[0].idCommitment == manager.idCredentials.get().idCommitment registrations[0].idCommitment == credentials.idCommitment
registrations[0].index == 0 registrations[0].index == 1
fut.complete() fut.complete()
return callback return callback
manager.onRegister(generateCallback(fut)) manager.onRegister(generateCallback(fut))
await manager.register(credentials)
await manager.startGroupSync() await manager.startGroupSync()
await fut await fut
@ -291,7 +286,6 @@ suite "Onchain group manager":
futs[futureIndex].complete() futs[futureIndex].complete()
futureIndex += 1 futureIndex += 1
return callback return callback
manager.onRegister(generateCallback(futures, credentials)) manager.onRegister(generateCallback(futures, credentials))
await manager.startGroupSync() await manager.startGroupSync()
@ -317,7 +311,7 @@ suite "Onchain group manager":
await manager.register(dummyCommitment) await manager.register(dummyCommitment)
asyncTest "register: should register successfully": asyncTest "register: should register successfully":
let manager = await setup(false) let manager = await setup()
await manager.init() await manager.init()
await manager.startGroupSync() await manager.startGroupSync()
@ -336,7 +330,7 @@ suite "Onchain group manager":
manager.latestIndex == 1 manager.latestIndex == 1
asyncTest "register: callback is called": asyncTest "register: callback is called":
let manager = await setup(false) let manager = await setup()
let idCommitment = generateCredentials(manager.rlnInstance).idCommitment let idCommitment = generateCredentials(manager.rlnInstance).idCommitment
@ -366,19 +360,24 @@ suite "Onchain group manager":
asyncTest "validateRoot: should validate good root": asyncTest "validateRoot: should validate good root":
let manager = await setup() let manager = await setup()
let credentials = generateCredentials(manager.rlnInstance)
await manager.init() await manager.init()
let fut = newFuture[void]() let fut = newFuture[void]()
proc callback(registrations: seq[Membership]): Future[void] {.async.} = proc callback(registrations: seq[Membership]): Future[void] {.async.} =
if registrations.len == 1 and if registrations.len == 1 and
registrations[0].idCommitment == manager.idCredentials.get().idCommitment and registrations[0].idCommitment == credentials.idCommitment and
registrations[0].index == 0: registrations[0].index == 1:
manager.idCredentials = some(credentials)
manager.membershipIndex = some(registrations[0].index)
fut.complete() fut.complete()
manager.onRegister(callback) manager.onRegister(callback)
await manager.startGroupSync() await manager.startGroupSync()
await manager.register(credentials)
await fut await fut
let messageBytes = "Hello".toBytes() let messageBytes = "Hello".toBytes()
@ -405,10 +404,10 @@ suite "Onchain group manager":
await manager.init() await manager.init()
await manager.startGroupSync() await manager.startGroupSync()
let idCredential = generateCredentials(manager.rlnInstance) let credentials = generateCredentials(manager.rlnInstance)
## Assume the registration occured out of band ## Assume the registration occured out of band
manager.idCredentials = some(idCredential) manager.idCredentials = some(credentials)
manager.membershipIndex = some(MembershipIndex(0)) manager.membershipIndex = some(MembershipIndex(0))
let messageBytes = "Hello".toBytes() let messageBytes = "Hello".toBytes()
@ -432,19 +431,23 @@ suite "Onchain group manager":
asyncTest "verifyProof: should verify valid proof": asyncTest "verifyProof: should verify valid proof":
let manager = await setup() let manager = await setup()
let credentials = generateCredentials(manager.rlnInstance)
await manager.init() await manager.init()
let fut = newFuture[void]() let fut = newFuture[void]()
proc callback(registrations: seq[Membership]): Future[void] {.async.} = proc callback(registrations: seq[Membership]): Future[void] {.async.} =
if registrations.len == 1 and if registrations.len == 1 and
registrations[0].idCommitment == manager.idCredentials.get().idCommitment and registrations[0].idCommitment == credentials.idCommitment and
registrations[0].index == 0: registrations[0].index == 1:
manager.idCredentials = some(credentials)
manager.membershipIndex = some(registrations[0].index)
fut.complete() fut.complete()
manager.onRegister(callback) manager.onRegister(callback)
await manager.startGroupSync() await manager.startGroupSync()
await manager.register(credentials)
await fut await fut
let messageBytes = "Hello".toBytes() let messageBytes = "Hello".toBytes()

View File

@ -62,9 +62,7 @@ when isMainModule:
rlnInstance: rlnInstance, rlnInstance: rlnInstance,
keystorePath: none(string), keystorePath: none(string),
keystorePassword: none(string), keystorePassword: none(string),
ethPrivateKey: some(conf.rlnRelayEthPrivateKey), ethPrivateKey: some(conf.rlnRelayEthPrivateKey))
# saveKeystore = false, since we're managing it
saveKeystore: false)
try: try:
waitFor groupManager.init() waitFor groupManager.init()
except CatchableError: except CatchableError:

File diff suppressed because one or more lines are too long

View File

@ -27,24 +27,30 @@ export group_manager_base
logScope: logScope:
topics = "waku rln_relay onchain_group_manager" topics = "waku rln_relay onchain_group_manager"
contract(WakuRlnRegistry):
proc usingStorageIndex(): Uint16 {.pure.}
proc storages(index: Uint16): Address {.pure.}
proc register(storageIndex: Uint16, idCommitment: Uint256)
proc newStorage()
# membership contract interface # membership contract interface
contract(RlnContract): contract(RlnStorage):
proc register(idCommitment: Uint256) {.payable.} # external payable
proc MemberRegistered(idCommitment: Uint256, index: Uint256) {.event.} proc MemberRegistered(idCommitment: Uint256, index: Uint256) {.event.}
proc MEMBERSHIP_DEPOSIT(): Uint256 proc MEMBERSHIP_DEPOSIT(): Uint256 {.pure.}
# TODO the following are to be supported proc members(idCommitment: Uint256): Uint256 {.view.}
# proc registerBatch(pubkeys: seq[Uint256]) # external payable proc idCommitmentIndex(): Uint256 {.view.}
# proc withdraw(secret: Uint256, pubkeyIndex: Uint256, receiver: Address)
# proc withdrawBatch( secrets: seq[Uint256], pubkeyIndex: seq[Uint256], receiver: seq[Address])
type type
RlnContractWithSender = Sender[RlnContract] RegistryContractWithSender = Sender[WakuRlnRegistry]
RlnContractWithSender = Sender[RlnStorage]
OnchainGroupManager* = ref object of GroupManager OnchainGroupManager* = ref object of GroupManager
ethClientUrl*: string ethClientUrl*: string
ethPrivateKey*: Option[string] ethPrivateKey*: Option[string]
ethContractAddress*: string ethContractAddress*: string
ethRpc*: Option[Web3] ethRpc*: Option[Web3]
rlnContract*: Option[RlnContractWithSender] rlnContract*: Option[RlnContractWithSender]
registryContract*: Option[RegistryContractWithSender]
usingStorageIndex: Option[Uint16]
membershipFee*: Option[Uint256] membershipFee*: Option[Uint256]
latestProcessedBlock*: Option[BlockNumber] latestProcessedBlock*: Option[BlockNumber]
registrationTxHash*: Option[TxHash] registrationTxHash*: Option[TxHash]
@ -53,7 +59,6 @@ type
keystoreIndex*: uint keystoreIndex*: uint
membershipGroupIndex*: uint membershipGroupIndex*: uint
keystorePassword*: Option[string] keystorePassword*: Option[string]
saveKeystore*: bool
registrationHandler*: Option[RegistrationHandler] registrationHandler*: Option[RegistrationHandler]
# this buffer exists to backfill appropriate roots for the merkle tree, # this buffer exists to backfill appropriate roots for the merkle tree,
# in event of a reorg. we store 5 in the buffer. Maybe need to revisit this, # in event of a reorg. we store 5 in the buffer. Maybe need to revisit this,
@ -99,7 +104,6 @@ method register*(g: OnchainGroupManager, idCommitment: IDCommitment): Future[voi
await g.registerBatch(@[idCommitment]) await g.registerBatch(@[idCommitment])
method registerBatch*(g: OnchainGroupManager, idCommitments: seq[IDCommitment]): Future[void] {.async.} = method registerBatch*(g: OnchainGroupManager, idCommitments: seq[IDCommitment]): Future[void] {.async.} =
initializedGuard(g) initializedGuard(g)
@ -111,7 +115,7 @@ method register*(g: OnchainGroupManager, identityCredentials: IdentityCredential
initializedGuard(g) initializedGuard(g)
let ethRpc = g.ethRpc.get() let ethRpc = g.ethRpc.get()
let rlnContract = g.rlnContract.get() let registryContract = g.registryContract.get()
let membershipFee = g.membershipFee.get() let membershipFee = g.membershipFee.get()
let gasPrice = int(await ethRpc.provider.eth_gasPrice()) * 2 let gasPrice = int(await ethRpc.provider.eth_gasPrice()) * 2
@ -119,16 +123,16 @@ method register*(g: OnchainGroupManager, identityCredentials: IdentityCredential
var txHash: TxHash var txHash: TxHash
try: # send the registration transaction and check if any error occurs try: # send the registration transaction and check if any error occurs
txHash = await rlnContract.register(idCommitment).send(value = membershipFee, let storageIndex = g.usingStorageIndex.get()
gasPrice = gasPrice, debug "registering the member", idCommitment = idCommitment, storageIndex = storageIndex
gas = 100000'u64) txHash = await registryContract.register(storageIndex, idCommitment).send(gasPrice = gasPrice)
except ValueError as e: except CatchableError:
error "error while registering the member", msg = e.msg error "error while registering the member", msg = getCurrentExceptionMsg()
raise newException(ValueError, "could not register the member: " & e.msg) raise newException(CatchableError, "could not register the member: " & getCurrentExceptionMsg())
# wait for the transaction to be mined # wait for the transaction to be mined
let tsReceipt = await ethRpc.getMinedTransactionReceipt(txHash) let tsReceipt = await ethRpc.getMinedTransactionReceipt(txHash)
debug "registration transaction mined", txHash = txHash
g.registrationTxHash = some(txHash) g.registrationTxHash = some(txHash)
# the receipt topic holds the hash of signature of the raised events # the receipt topic holds the hash of signature of the raised events
# TODO: make this robust. search within the event list for the event # TODO: make this robust. search within the event list for the event
@ -382,70 +386,13 @@ proc startOnchainSync(g: OnchainGroupManager): Future[void] {.async.} =
except CatchableError: except CatchableError:
raise newException(ValueError, "failed to start listening to events: " & getCurrentExceptionMsg()) raise newException(ValueError, "failed to start listening to events: " & getCurrentExceptionMsg())
proc persistCredentials(g: OnchainGroupManager): GroupManagerResult[void] =
if not g.saveKeystore:
return ok()
if g.idCredentials.isNone():
return err("no credentials to persist")
let index = g.membershipIndex.get()
let idCredential = g.idCredentials.get()
var path = DefaultKeystorePath
var password = DefaultKeystorePassword
if g.keystorePath.isSome():
path = g.keystorePath.get()
else:
warn "keystore: no credentials path set, using default path", path=DefaultKeystorePath
if g.keystorePassword.isSome():
password = g.keystorePassword.get()
else:
warn "keystore: no credentials password set, using default password", password=DefaultKeystorePassword
let keystoreCred = MembershipCredentials(
identityCredential: idCredential,
membershipGroups: @[MembershipGroup(
membershipContract: MembershipContract(
chainId: $g.chainId.get(),
address: g.ethContractAddress
),
treeIndex: index
)]
)
let persistRes = addMembershipCredentials(path, @[keystoreCred], password, RLNAppInfo)
if persistRes.isErr():
error "keystore: failed to persist credentials", error=persistRes.error()
return ok()
method startGroupSync*(g: OnchainGroupManager): Future[void] {.async.} = method startGroupSync*(g: OnchainGroupManager): Future[void] {.async.} =
initializedGuard(g) initializedGuard(g)
# Get archive history # Get archive history
try: try:
await startOnchainSync(g) await startOnchainSync(g)
except CatchableError: except CatchableError:
raise newException(ValueError, "failed to start onchain sync service: " & getCurrentExceptionMsg()) raise newException(CatchableError, "failed to start onchain sync service: " & getCurrentExceptionMsg())
if g.ethPrivateKey.isSome() and g.idCredentials.isNone():
let idCredentialRes = g.rlnInstance.membershipKeyGen()
if idCredentialRes.isErr():
raise newException(CatchableError, "Identity credential generation failed")
let idCredential = idCredentialRes.get()
g.idCredentials = some(idCredential)
debug "registering commitment on contract"
await g.register(idCredential)
if g.registrationHandler.isSome():
# We need to callback with the tx hash
let handler = g.registrationHandler.get()
handler($g.registrationTxHash.get())
let persistRes = g.persistCredentials()
if persistRes.isErr():
error "failed to persist credentials", error=persistRes.error()
return return
method onRegister*(g: OnchainGroupManager, cb: OnRegisterCallback) {.gcsafe.} = method onRegister*(g: OnchainGroupManager, cb: OnRegisterCallback) {.gcsafe.} =
@ -456,7 +403,6 @@ method onWithdraw*(g: OnchainGroupManager, cb: OnWithdrawCallback) {.gcsafe.} =
method init*(g: OnchainGroupManager): Future[void] {.async.} = method init*(g: OnchainGroupManager): Future[void] {.async.} =
var ethRpc: Web3 var ethRpc: Web3
var contract: RlnContractWithSender
# check if the Ethereum client is reachable # check if the Ethereum client is reachable
try: try:
ethRpc = await newWeb3(g.ethClientUrl) ethRpc = await newWeb3(g.ethClientUrl)
@ -475,12 +421,18 @@ method init*(g: OnchainGroupManager): Future[void] {.async.} =
ethRpc.privateKey = some(pkParseRes.get()) ethRpc.privateKey = some(pkParseRes.get())
ethRpc.defaultAccount = ethRpc.privateKey.get().toPublicKey().toCanonicalAddress().Address ethRpc.defaultAccount = ethRpc.privateKey.get().toPublicKey().toCanonicalAddress().Address
let registryAddress = web3.fromHex(web3.Address, g.ethContractAddress)
let registryContract = ethRpc.contractSender(WakuRlnRegistry, registryAddress)
let contractAddress = web3.fromHex(web3.Address, g.ethContractAddress) # get the current storage index
contract = ethRpc.contractSender(RlnContract, contractAddress) let usingStorageIndex = await registryContract.usingStorageIndex().call()
g.usingStorageIndex = some(usingStorageIndex)
let rlnContractAddress = await registryContract.storages(usingStorageIndex).call()
let rlnContract = ethRpc.contractSender(RlnStorage, rlnContractAddress)
g.ethRpc = some(ethRpc) g.ethRpc = some(ethRpc)
g.rlnContract = some(contract) g.rlnContract = some(rlnContract)
g.registryContract = some(registryContract)
if g.keystorePath.isSome() and g.keystorePassword.isSome(): if g.keystorePath.isSome() and g.keystorePassword.isSome():
waku_rln_membership_credentials_import_duration_seconds.nanosecondTime: waku_rln_membership_credentials_import_duration_seconds.nanosecondTime:
@ -513,7 +465,7 @@ method init*(g: OnchainGroupManager): Future[void] {.async.} =
# check if the contract exists by calling a static function # check if the contract exists by calling a static function
var membershipFee: Uint256 var membershipFee: Uint256
try: try:
membershipFee = await contract.MEMBERSHIP_DEPOSIT().call() membershipFee = await rlnContract.MEMBERSHIP_DEPOSIT().call()
except CatchableError: except CatchableError:
raise newException(ValueError, raise newException(ValueError,
"could not get the membership deposit: " & getCurrentExceptionMsg()) "could not get the membership deposit: " & getCurrentExceptionMsg())

View File

@ -344,7 +344,6 @@ proc mount(conf: WakuRlnConfig,
var var
groupManager: GroupManager groupManager: GroupManager
credentials: MembershipCredentials credentials: MembershipCredentials
persistCredentials = false
# create an RLN instance # create an RLN instance
let rlnInstanceRes = createRLNInstance(tree_path = conf.rlnRelayTreePath) let rlnInstanceRes = createRLNInstance(tree_path = conf.rlnRelayTreePath)
if rlnInstanceRes.isErr(): if rlnInstanceRes.isErr():
@ -374,9 +373,7 @@ proc mount(conf: WakuRlnConfig,
keystorePath: rlnRelayCredPath, keystorePath: rlnRelayCredPath,
keystorePassword: rlnRelayCredentialsPassword, keystorePassword: rlnRelayCredentialsPassword,
keystoreIndex: conf.rlnRelayCredIndex, keystoreIndex: conf.rlnRelayCredIndex,
membershipGroupIndex: conf.rlnRelayMembershipGroupIndex, membershipGroupIndex: conf.rlnRelayMembershipGroupIndex)
saveKeystore: true)
# Initialize the groupManager # Initialize the groupManager
await groupManager.init() await groupManager.init()
# Start the group sync # Start the group sync