mirror of https://github.com/waku-org/nwaku.git
chore(rln-relay): integrate waku rln registry (#1943)
This commit is contained in:
parent
32aa1c5b61
commit
cc9f8d4254
|
@ -60,26 +60,26 @@ proc uploadRLNContract*(ethClientAddress: string): Future[Address] {.async.} =
|
|||
debug "hasher address: ", hasherAddress
|
||||
|
||||
|
||||
# encode membership contract inputs to 32 bytes zero-padded
|
||||
# encode registry contract inputs to 32 bytes zero-padded
|
||||
let
|
||||
membershipFeeEncoded = encode(MembershipFee).data
|
||||
depthEncoded = encode(MerkleTreeDepth.u256).data
|
||||
hasherAddressEncoded = encode(hasherAddress).data
|
||||
# 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 contract input:", contractInput
|
||||
|
||||
# deploy membership contract with its constructor inputs
|
||||
let receipt = await web3.deployContract(MembershipContractCode,
|
||||
contractInput = contractInput)
|
||||
let contractAddress = receipt.contractAddress.get
|
||||
debug "Address of the deployed membership contract: ", contractAddress
|
||||
# deploy registry contract with its constructor inputs
|
||||
let receipt = await web3.deployContract(RegistryContractCode,
|
||||
contractInput = contractInput)
|
||||
let contractAddress = receipt.contractAddress.get()
|
||||
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")
|
||||
debug "Account balance after the contract deployment: ", newBalance
|
||||
|
||||
|
@ -149,14 +149,11 @@ proc stopGanache(runGanache: Process) {.used.} =
|
|||
try:
|
||||
# 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})
|
||||
# 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
|
||||
except:
|
||||
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"))
|
||||
require:
|
||||
rlnInstanceRes.isOk()
|
||||
|
@ -168,18 +165,16 @@ proc setup(signer = true): Future[OnchainGroupManager] {.async.} =
|
|||
let web3 = await newWeb3(EthClient)
|
||||
|
||||
let accounts = await web3.provider.eth_accounts()
|
||||
web3.defaultAccount = accounts[1]
|
||||
web3.defaultAccount = accounts[0]
|
||||
|
||||
var pk = none(string)
|
||||
if signer:
|
||||
let (privateKey, _) = await createEthAccount()
|
||||
pk = some($privateKey)
|
||||
let (privateKey, _) = await createEthAccount()
|
||||
pk = some($privateKey)
|
||||
|
||||
let manager = OnchainGroupManager(ethClientUrl: EthClient,
|
||||
ethContractAddress: $contractAddress,
|
||||
ethPrivateKey: pk,
|
||||
rlnInstance: rlnInstance,
|
||||
saveKeystore: false)
|
||||
rlnInstance: rlnInstance)
|
||||
|
||||
return manager
|
||||
|
||||
|
@ -211,13 +206,12 @@ suite "Onchain group manager":
|
|||
metadata.contractAddress == manager.ethContractAddress
|
||||
|
||||
await manager.stop()
|
||||
|
||||
|
||||
let differentContractAddress = await uploadRLNContract(manager.ethClientUrl)
|
||||
# simulating a change in the contractAddress
|
||||
let manager2 = OnchainGroupManager(ethClientUrl: EthClient,
|
||||
ethContractAddress: "0x0000000000000000000000000000000000000000",
|
||||
ethPrivateKey: manager.ethPrivateKey,
|
||||
rlnInstance: manager.rlnInstance,
|
||||
saveKeystore: false)
|
||||
ethContractAddress: $differentContractAddress,
|
||||
rlnInstance: manager.rlnInstance)
|
||||
expect(ValueError): await manager2.init()
|
||||
|
||||
asyncTest "startGroupSync: should start group sync":
|
||||
|
@ -234,7 +228,7 @@ suite "Onchain group manager":
|
|||
|
||||
asyncTest "startGroupSync: should sync to the state of the group":
|
||||
let manager = await setup()
|
||||
|
||||
let credentials = generateCredentials(manager.rlnInstance)
|
||||
await manager.init()
|
||||
|
||||
let merkleRootBeforeRes = manager.rlnInstance.getMerkleRoot()
|
||||
|
@ -248,13 +242,14 @@ suite "Onchain group manager":
|
|||
proc callback(registrations: seq[Membership]): Future[void] {.async.} =
|
||||
require:
|
||||
registrations.len == 1
|
||||
registrations[0].idCommitment == manager.idCredentials.get().idCommitment
|
||||
registrations[0].index == 0
|
||||
registrations[0].idCommitment == credentials.idCommitment
|
||||
registrations[0].index == 1
|
||||
fut.complete()
|
||||
return callback
|
||||
|
||||
manager.onRegister(generateCallback(fut))
|
||||
|
||||
await manager.register(credentials)
|
||||
await manager.startGroupSync()
|
||||
|
||||
await fut
|
||||
|
@ -291,7 +286,6 @@ suite "Onchain group manager":
|
|||
futs[futureIndex].complete()
|
||||
futureIndex += 1
|
||||
return callback
|
||||
|
||||
manager.onRegister(generateCallback(futures, credentials))
|
||||
await manager.startGroupSync()
|
||||
|
||||
|
@ -317,7 +311,7 @@ suite "Onchain group manager":
|
|||
await manager.register(dummyCommitment)
|
||||
|
||||
asyncTest "register: should register successfully":
|
||||
let manager = await setup(false)
|
||||
let manager = await setup()
|
||||
await manager.init()
|
||||
await manager.startGroupSync()
|
||||
|
||||
|
@ -336,7 +330,7 @@ suite "Onchain group manager":
|
|||
manager.latestIndex == 1
|
||||
|
||||
asyncTest "register: callback is called":
|
||||
let manager = await setup(false)
|
||||
let manager = await setup()
|
||||
|
||||
let idCommitment = generateCredentials(manager.rlnInstance).idCommitment
|
||||
|
||||
|
@ -366,19 +360,24 @@ suite "Onchain group manager":
|
|||
|
||||
asyncTest "validateRoot: should validate good root":
|
||||
let manager = await setup()
|
||||
let credentials = generateCredentials(manager.rlnInstance)
|
||||
await manager.init()
|
||||
|
||||
|
||||
let fut = newFuture[void]()
|
||||
|
||||
proc callback(registrations: seq[Membership]): Future[void] {.async.} =
|
||||
if registrations.len == 1 and
|
||||
registrations[0].idCommitment == manager.idCredentials.get().idCommitment and
|
||||
registrations[0].index == 0:
|
||||
registrations[0].idCommitment == credentials.idCommitment and
|
||||
registrations[0].index == 1:
|
||||
manager.idCredentials = some(credentials)
|
||||
manager.membershipIndex = some(registrations[0].index)
|
||||
fut.complete()
|
||||
|
||||
manager.onRegister(callback)
|
||||
|
||||
await manager.startGroupSync()
|
||||
await manager.register(credentials)
|
||||
await fut
|
||||
|
||||
let messageBytes = "Hello".toBytes()
|
||||
|
@ -405,10 +404,10 @@ suite "Onchain group manager":
|
|||
await manager.init()
|
||||
await manager.startGroupSync()
|
||||
|
||||
let idCredential = generateCredentials(manager.rlnInstance)
|
||||
let credentials = generateCredentials(manager.rlnInstance)
|
||||
|
||||
## Assume the registration occured out of band
|
||||
manager.idCredentials = some(idCredential)
|
||||
manager.idCredentials = some(credentials)
|
||||
manager.membershipIndex = some(MembershipIndex(0))
|
||||
|
||||
let messageBytes = "Hello".toBytes()
|
||||
|
@ -432,19 +431,23 @@ suite "Onchain group manager":
|
|||
|
||||
asyncTest "verifyProof: should verify valid proof":
|
||||
let manager = await setup()
|
||||
let credentials = generateCredentials(manager.rlnInstance)
|
||||
await manager.init()
|
||||
|
||||
let fut = newFuture[void]()
|
||||
|
||||
proc callback(registrations: seq[Membership]): Future[void] {.async.} =
|
||||
if registrations.len == 1 and
|
||||
registrations[0].idCommitment == manager.idCredentials.get().idCommitment and
|
||||
registrations[0].index == 0:
|
||||
registrations[0].idCommitment == credentials.idCommitment and
|
||||
registrations[0].index == 1:
|
||||
manager.idCredentials = some(credentials)
|
||||
manager.membershipIndex = some(registrations[0].index)
|
||||
fut.complete()
|
||||
|
||||
manager.onRegister(callback)
|
||||
|
||||
await manager.startGroupSync()
|
||||
await manager.register(credentials)
|
||||
await fut
|
||||
|
||||
let messageBytes = "Hello".toBytes()
|
||||
|
|
|
@ -62,9 +62,7 @@ when isMainModule:
|
|||
rlnInstance: rlnInstance,
|
||||
keystorePath: none(string),
|
||||
keystorePassword: none(string),
|
||||
ethPrivateKey: some(conf.rlnRelayEthPrivateKey),
|
||||
# saveKeystore = false, since we're managing it
|
||||
saveKeystore: false)
|
||||
ethPrivateKey: some(conf.rlnRelayEthPrivateKey))
|
||||
try:
|
||||
waitFor groupManager.init()
|
||||
except CatchableError:
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -27,24 +27,30 @@ export group_manager_base
|
|||
logScope:
|
||||
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
|
||||
contract(RlnContract):
|
||||
proc register(idCommitment: Uint256) {.payable.} # external payable
|
||||
contract(RlnStorage):
|
||||
proc MemberRegistered(idCommitment: Uint256, index: Uint256) {.event.}
|
||||
proc MEMBERSHIP_DEPOSIT(): Uint256
|
||||
# TODO the following are to be supported
|
||||
# proc registerBatch(pubkeys: seq[Uint256]) # external payable
|
||||
# proc withdraw(secret: Uint256, pubkeyIndex: Uint256, receiver: Address)
|
||||
# proc withdrawBatch( secrets: seq[Uint256], pubkeyIndex: seq[Uint256], receiver: seq[Address])
|
||||
proc MEMBERSHIP_DEPOSIT(): Uint256 {.pure.}
|
||||
proc members(idCommitment: Uint256): Uint256 {.view.}
|
||||
proc idCommitmentIndex(): Uint256 {.view.}
|
||||
|
||||
type
|
||||
RlnContractWithSender = Sender[RlnContract]
|
||||
RegistryContractWithSender = Sender[WakuRlnRegistry]
|
||||
RlnContractWithSender = Sender[RlnStorage]
|
||||
OnchainGroupManager* = ref object of GroupManager
|
||||
ethClientUrl*: string
|
||||
ethPrivateKey*: Option[string]
|
||||
ethContractAddress*: string
|
||||
ethRpc*: Option[Web3]
|
||||
rlnContract*: Option[RlnContractWithSender]
|
||||
registryContract*: Option[RegistryContractWithSender]
|
||||
usingStorageIndex: Option[Uint16]
|
||||
membershipFee*: Option[Uint256]
|
||||
latestProcessedBlock*: Option[BlockNumber]
|
||||
registrationTxHash*: Option[TxHash]
|
||||
|
@ -53,7 +59,6 @@ type
|
|||
keystoreIndex*: uint
|
||||
membershipGroupIndex*: uint
|
||||
keystorePassword*: Option[string]
|
||||
saveKeystore*: bool
|
||||
registrationHandler*: Option[RegistrationHandler]
|
||||
# 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,
|
||||
|
@ -99,7 +104,6 @@ method register*(g: OnchainGroupManager, idCommitment: IDCommitment): Future[voi
|
|||
await g.registerBatch(@[idCommitment])
|
||||
|
||||
|
||||
|
||||
method registerBatch*(g: OnchainGroupManager, idCommitments: seq[IDCommitment]): Future[void] {.async.} =
|
||||
initializedGuard(g)
|
||||
|
||||
|
@ -111,7 +115,7 @@ method register*(g: OnchainGroupManager, identityCredentials: IdentityCredential
|
|||
initializedGuard(g)
|
||||
|
||||
let ethRpc = g.ethRpc.get()
|
||||
let rlnContract = g.rlnContract.get()
|
||||
let registryContract = g.registryContract.get()
|
||||
let membershipFee = g.membershipFee.get()
|
||||
|
||||
let gasPrice = int(await ethRpc.provider.eth_gasPrice()) * 2
|
||||
|
@ -119,16 +123,16 @@ method register*(g: OnchainGroupManager, identityCredentials: IdentityCredential
|
|||
|
||||
var txHash: TxHash
|
||||
try: # send the registration transaction and check if any error occurs
|
||||
txHash = await rlnContract.register(idCommitment).send(value = membershipFee,
|
||||
gasPrice = gasPrice,
|
||||
gas = 100000'u64)
|
||||
except ValueError as e:
|
||||
error "error while registering the member", msg = e.msg
|
||||
raise newException(ValueError, "could not register the member: " & e.msg)
|
||||
let storageIndex = g.usingStorageIndex.get()
|
||||
debug "registering the member", idCommitment = idCommitment, storageIndex = storageIndex
|
||||
txHash = await registryContract.register(storageIndex, idCommitment).send(gasPrice = gasPrice)
|
||||
except CatchableError:
|
||||
error "error while registering the member", msg = getCurrentExceptionMsg()
|
||||
raise newException(CatchableError, "could not register the member: " & getCurrentExceptionMsg())
|
||||
|
||||
# wait for the transaction to be mined
|
||||
let tsReceipt = await ethRpc.getMinedTransactionReceipt(txHash)
|
||||
|
||||
debug "registration transaction mined", txHash = txHash
|
||||
g.registrationTxHash = some(txHash)
|
||||
# the receipt topic holds the hash of signature of the raised events
|
||||
# TODO: make this robust. search within the event list for the event
|
||||
|
@ -382,70 +386,13 @@ proc startOnchainSync(g: OnchainGroupManager): Future[void] {.async.} =
|
|||
except CatchableError:
|
||||
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.} =
|
||||
initializedGuard(g)
|
||||
# Get archive history
|
||||
try:
|
||||
await startOnchainSync(g)
|
||||
except CatchableError:
|
||||
raise newException(ValueError, "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()
|
||||
|
||||
raise newException(CatchableError, "failed to start onchain sync service: " & getCurrentExceptionMsg())
|
||||
return
|
||||
|
||||
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.} =
|
||||
var ethRpc: Web3
|
||||
var contract: RlnContractWithSender
|
||||
# check if the Ethereum client is reachable
|
||||
try:
|
||||
ethRpc = await newWeb3(g.ethClientUrl)
|
||||
|
@ -475,12 +421,18 @@ method init*(g: OnchainGroupManager): Future[void] {.async.} =
|
|||
ethRpc.privateKey = some(pkParseRes.get())
|
||||
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)
|
||||
contract = ethRpc.contractSender(RlnContract, contractAddress)
|
||||
# get the current storage index
|
||||
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.rlnContract = some(contract)
|
||||
g.rlnContract = some(rlnContract)
|
||||
g.registryContract = some(registryContract)
|
||||
|
||||
if g.keystorePath.isSome() and g.keystorePassword.isSome():
|
||||
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
|
||||
var membershipFee: Uint256
|
||||
try:
|
||||
membershipFee = await contract.MEMBERSHIP_DEPOSIT().call()
|
||||
membershipFee = await rlnContract.MEMBERSHIP_DEPOSIT().call()
|
||||
except CatchableError:
|
||||
raise newException(ValueError,
|
||||
"could not get the membership deposit: " & getCurrentExceptionMsg())
|
||||
|
|
|
@ -344,7 +344,6 @@ proc mount(conf: WakuRlnConfig,
|
|||
var
|
||||
groupManager: GroupManager
|
||||
credentials: MembershipCredentials
|
||||
persistCredentials = false
|
||||
# create an RLN instance
|
||||
let rlnInstanceRes = createRLNInstance(tree_path = conf.rlnRelayTreePath)
|
||||
if rlnInstanceRes.isErr():
|
||||
|
@ -374,9 +373,7 @@ proc mount(conf: WakuRlnConfig,
|
|||
keystorePath: rlnRelayCredPath,
|
||||
keystorePassword: rlnRelayCredentialsPassword,
|
||||
keystoreIndex: conf.rlnRelayCredIndex,
|
||||
membershipGroupIndex: conf.rlnRelayMembershipGroupIndex,
|
||||
saveKeystore: true)
|
||||
|
||||
membershipGroupIndex: conf.rlnRelayMembershipGroupIndex)
|
||||
# Initialize the groupManager
|
||||
await groupManager.init()
|
||||
# Start the group sync
|
||||
|
|
Loading…
Reference in New Issue