chore: allow multiple rln eth clients (#3402)

* use of multiple Eth clients instead of just one
* config_chat2 enhance param comment
* group_manager: raise exception if could not connect to any of the eth clients
This commit is contained in:
Ivan FB 2025-05-12 10:57:13 +02:00 committed by GitHub
parent d86babac3a
commit 42ab866f2c
14 changed files with 80 additions and 52 deletions

View File

@ -560,8 +560,7 @@ proc processInput(rfd: AsyncFD, rng: ref HmacDrbgContext) {.async.} =
dynamic: conf.rlnRelayDynamic,
credIndex: conf.rlnRelayCredIndex,
chainId: conf.rlnRelayChainId,
ethContractAddress: conf.rlnRelayEthContractAddress,
ethClientAddress: string(conf.rlnRelayethClientAddress),
ethClientUrls: conf.ethClientUrls.mapIt(string(it)),
creds: some(
RlnRelayCreds(
path: conf.rlnRelayCredPath, password: conf.rlnRelayCredPassword

View File

@ -18,7 +18,8 @@ type
prod
test
EthRpcUrl = distinct string
EthRpcUrl* = distinct string
Chat2Conf* = object ## General node config
logLevel* {.
desc: "Sets the log level.", defaultValue: LogLevel.INFO, name: "log-level"
@ -248,11 +249,12 @@ type
name: "rln-relay-id-commitment-key"
.}: string
rlnRelayEthClientAddress* {.
desc: "HTTP address of an Ethereum testnet client e.g., http://localhost:8540/",
defaultValue: "http://localhost:8540/",
ethClientUrls* {.
desc:
"HTTP address of an Ethereum testnet client e.g., http://localhost:8540/. Argument may be repeated.",
defaultValue: newSeq[EthRpcUrl](0),
name: "rln-relay-eth-client-address"
.}: EthRpcUrl
.}: seq[EthRpcUrl]
rlnRelayEthContractAddress* {.
desc: "Address of membership contract on an Ethereum testnet",

View File

@ -638,7 +638,7 @@ when isMainModule:
dynamic: conf.rlnRelayDynamic,
credIndex: some(uint(0)),
ethContractAddress: conf.rlnRelayEthContractAddress,
ethClientAddress: string(conf.rlnRelayethClientAddress),
ethClientUrls: conf.ethClientUrls.mapIt(string(it)),
treePath: conf.rlnRelayTreePath,
epochSizeSec: conf.rlnEpochSizeSec,
creds: none(RlnRelayCreds),

View File

@ -8,7 +8,7 @@ import
stew/shims/net,
regex
type EthRpcUrl = distinct string
type EthRpcUrl* = distinct string
type NetworkMonitorConf* = object
logLevel* {.
@ -82,11 +82,12 @@ type NetworkMonitorConf* = object
name: "rln-relay-tree-path"
.}: string
rlnRelayEthClientAddress* {.
desc: "HTTP address of an Ethereum testnet client e.g., http://localhost:8540/",
defaultValue: "http://localhost:8540/",
ethClientUrls* {.
desc:
"HTTP address of an Ethereum testnet client e.g., http://localhost:8540/. Argument may be repeated.",
defaultValue: newSeq[EthRpcUrl](0),
name: "rln-relay-eth-client-address"
.}: EthRpcUrl
.}: seq[EthRpcUrl]
rlnRelayEthContractAddress* {.
desc: "Address of membership contract on an Ethereum testnet",

View File

@ -26,7 +26,7 @@ suite "Waku config - apply preset":
cmd: noCommand,
preset: "twn",
relay: true,
rlnRelayEthClientAddress: "http://someaddress".EthRpcUrl,
ethClientUrls: @["http://someaddress".EthRpcUrl],
rlnRelayTreePath: "/tmp/sometreepath",
)
@ -109,7 +109,7 @@ suite "Waku config - apply preset":
cmd: noCommand,
clusterId: 1.uint16,
relay: true,
rlnRelayEthClientAddress: "http://someaddress".EthRpcUrl,
ethClientUrls: @["http://someaddress".EthRpcUrl],
rlnRelayTreePath: "/tmp/sometreepath",
)

View File

@ -77,10 +77,10 @@ suite "Onchain group manager":
assert metadata.contractAddress == manager.ethContractAddress,
"contractAddress is not equal to " & manager.ethContractAddress
let differentContractAddress = await uploadRLNContract(manager.ethClientUrl)
let differentContractAddress = await uploadRLNContract(manager.ethClientUrls[0])
# simulating a change in the contractAddress
let manager2 = OnchainGroupManager(
ethClientUrl: EthClient,
ethClientUrls: @[EthClient],
ethContractAddress: $differentContractAddress,
rlnInstance: manager.rlnInstance,
onFatalErrorAction: proc(errStr: string) =

View File

@ -250,7 +250,7 @@ proc stopAnvil*(runAnvil: Process) {.used.} =
error "Anvil daemon termination failed: ", err = getCurrentExceptionMsg()
proc setupOnchainGroupManager*(
ethClientAddress: string = EthClient, amountEth: UInt256 = 10.u256
ethClientUrl: string = EthClient, amountEth: UInt256 = 10.u256
): Future[OnchainGroupManager] {.async.} =
let rlnInstanceRes =
createRlnInstance(tree_path = genTempPath("rln_tree", "group_manager_onchain"))
@ -259,9 +259,9 @@ proc setupOnchainGroupManager*(
let rlnInstance = rlnInstanceRes.get()
let contractAddress = await uploadRLNContract(ethClientAddress)
let contractAddress = await uploadRLNContract(ethClientUrl)
# connect to the eth client
let web3 = await newWeb3(ethClientAddress)
let web3 = await newWeb3(ethClientUrl)
let accounts = await web3.provider.eth_accounts()
web3.defaultAccount = accounts[0]
@ -275,7 +275,7 @@ proc setupOnchainGroupManager*(
)
let manager = OnchainGroupManager(
ethClientUrl: ethClientAddress,
ethClientUrls: @[ethClientUrl],
ethContractAddress: $contractAddress,
chainId: CHAIN_ID,
ethPrivateKey: some($privateKey),

View File

@ -3,7 +3,7 @@ when (NimMajor, NimMinor) < (1, 4):
else:
{.push raises: [].}
import chronicles, results, std/tempfiles
import chronicles, results, std/[tempfiles, sequtils]
import
waku/[
@ -19,7 +19,7 @@ logScope:
type RlnKeystoreGeneratorConf* = object
execute*: bool
ethContractAddress*: string
ethClientAddress*: string
ethClientUrls*: seq[string]
chainId*: uint
credPath*: string
credPassword*: string
@ -65,7 +65,7 @@ proc doRlnKeystoreGenerator*(conf: RlnKeystoreGeneratorConf) =
# 4. initialize OnchainGroupManager
let groupManager = OnchainGroupManager(
ethClientUrl: string(conf.ethClientAddress),
ethClientUrls: conf.ethClientUrls,
chainId: conf.chainId,
ethContractAddress: conf.ethContractAddress,
rlnInstance: rlnInstance,

View File

@ -9,9 +9,8 @@ logScope:
##############################
type RlnRelayConfBuilder* = object
enabled*: Option[bool]
chainId*: Option[uint]
ethClientAddress*: Option[string]
ethClientUrls*: Option[seq[string]]
ethContractAddress*: Option[string]
credIndex*: Option[uint]
credPassword*: Option[string]
@ -42,8 +41,8 @@ proc withCredPath*(b: var RlnRelayConfBuilder, credPath: string) =
proc withDynamic*(b: var RlnRelayConfBuilder, dynamic: bool) =
b.dynamic = some(dynamic)
proc withEthClientAddress*(b: var RlnRelayConfBuilder, ethClientAddress: string) =
b.ethClientAddress = some(ethClientAddress)
proc withEthClientUrls*(b: var RlnRelayConfBuilder, ethClientUrls: seq[string]) =
b.ethClientUrls = some(ethClientUrls)
proc withEthContractAddress*(b: var RlnRelayConfBuilder, ethContractAddress: string) =
b.ethContractAddress = some(ethContractAddress)
@ -76,8 +75,8 @@ proc build*(b: RlnRelayConfBuilder): Result[Option[RlnRelayConf], string] =
if b.dynamic.isNone():
return err("rlnRelay.dynamic is not specified")
if b.ethClientAddress.get("") == "":
return err("rlnRelay.ethClientAddress is not specified")
if b.ethClientUrls.get(newSeq[string](0)).len == 0:
return err("rlnRelay.ethClientUrls is not specified")
if b.ethContractAddress.get("") == "":
return err("rlnRelay.ethContractAddress is not specified")
if b.epochSizeSec.isNone():
@ -94,7 +93,7 @@ proc build*(b: RlnRelayConfBuilder): Result[Option[RlnRelayConf], string] =
credIndex: b.credIndex,
creds: creds,
dynamic: b.dynamic.get(),
ethClientAddress: b.ethClientAddress.get(),
ethClientUrls: b.ethClientUrls.get(),
ethContractAddress: b.ethContractAddress.get(),
epochSizeSec: b.epochSizeSec.get(),
userMessageLimit: b.userMessageLimit.get(),

View File

@ -1,5 +1,5 @@
import
std/[strutils, strformat],
std/[strutils, strformat, sequtils],
results,
chronicles,
chronos,
@ -75,11 +75,12 @@ type WakuNodeConf* = object
name: "rln-relay-cred-path"
.}: string
rlnRelayEthClientAddress* {.
desc: "HTTP address of an Ethereum testnet client e.g., http://localhost:8540/",
defaultValue: "http://localhost:8540/",
ethClientUrls* {.
desc:
"HTTP address of an Ethereum testnet client e.g., http://localhost:8540/. Argument may be repeated.",
defaultValue: @[EthRpcUrl("http://localhost:8540/")],
name: "rln-relay-eth-client-address"
.}: EthRpcUrl
.}: seq[EthRpcUrl]
rlnRelayEthContractAddress* {.
desc: "Address of membership contract on an Ethereum testnet.",
@ -867,7 +868,7 @@ proc toKeystoreGeneratorConf*(n: WakuNodeConf): RlnKeystoreGeneratorConf =
RlnKeystoreGeneratorConf(
execute: n.execute,
chainId: n.rlnRelayChainId,
ethClientAddress: n.rlnRelayEthClientAddress.string,
ethClientUrls: n.ethClientUrls.mapIt(string(it)),
ethContractAddress: n.rlnRelayEthContractAddress,
userMessageLimit: n.rlnRelayUserMessageLimit,
ethPrivateKey: n.rlnRelayEthPrivateKey,
@ -907,8 +908,8 @@ proc toWakuConf*(n: WakuNodeConf): ConfResult[WakuConf] =
b.rlnRelayConf.withCredPath(n.rlnRelayCredPath)
if n.rlnRelayCredPassword != "":
b.rlnRelayConf.withCredPassword(n.rlnRelayCredPassword)
if n.rlnRelayEthClientAddress.string != "":
b.rlnRelayConf.withEthClientAddress(n.rlnRelayEthClientAddress.string)
if n.ethClientUrls.len > 0:
b.rlnRelayConf.withEthClientUrls(n.ethClientUrls.mapIt(string(it)))
if n.rlnRelayEthContractAddress != "":
b.rlnRelayConf.withEthContractAddress(n.rlnRelayEthContractAddress)

View File

@ -354,7 +354,7 @@ proc setupProtocols(
credIndex: rlnRelayConf.credIndex,
ethContractAddress: rlnRelayConf.ethContractAddress,
chainId: rlnRelayConf.chainId,
ethClientAddress: rlnRelayConf.ethClientAddress,
ethClientUrls: rlnRelayConf.ethClientUrls,
creds: rlnRelayConf.creds,
treePath: rlnRelayConf.treePath,
userMessageLimit: rlnRelayConf.userMessageLimit,

View File

@ -160,7 +160,7 @@ proc logConf*(conf: WakuConf) =
maxMessageSize = conf.maxMessageSizeBytes,
rlnEpochSizeSec = rlnRelayConf.epochSizeSec,
rlnRelayUserMessageLimit = rlnRelayConf.userMessageLimit,
rlnRelayEthClientAddress = string(rlnRelayConf.ethClientAddress)
ethClientUrls = rlnRelayConf.ethClientUrls
proc validateNodeKey(wakuConf: WakuConf): Result[void, string] =
wakuConf.nodeKey.getPublicKey().isOkOr:
@ -224,8 +224,8 @@ proc validateNoEmptyStrings(wakuConf: WakuConf): Result[void, string] =
if isEmptyOrWhiteSpace(rlnRelayConf.treePath):
return err("rlnRelayConf.treepath is an empty string")
if isEmptyOrWhiteSpace(rlnRelayConf.ethClientAddress):
return err("rlnRelayConf.ethClientAddress is an empty string")
if rlnRelayConf.ethClientUrls.len == 0:
return err("rlnRelayConf.ethClientUrls is empty")
if isEmptyOrWhiteSpace(rlnRelayConf.ethContractAddress):
return err("rlnRelayConf.ethContractAddress is an empty string")

View File

@ -51,7 +51,7 @@ contract(WakuRlnContract):
type
WakuRlnContractWithSender = Sender[WakuRlnContract]
OnchainGroupManager* = ref object of GroupManager
ethClientUrl*: string
ethClientUrls*: seq[string]
ethPrivateKey*: Option[string]
ethContractAddress*: string
ethRpc*: Option[Web3]
@ -458,11 +458,35 @@ method onRegister*(g: OnchainGroupManager, cb: OnRegisterCallback) {.gcsafe.} =
method onWithdraw*(g: OnchainGroupManager, cb: OnWithdrawCallback) {.gcsafe.} =
g.withdrawCb = some(cb)
proc establishConnection(
g: OnchainGroupManager
): Future[GroupManagerResult[Web3]] {.async.} =
var ethRpc: Web3
g.retryWrapper(ethRpc, "Failed to connect to the Ethereum client"):
var innerEthRpc: Web3
var connected = false
for clientUrl in g.ethClientUrls:
## We give a chance to the user to provide multiple clients
## and we try to connect to each of them
try:
innerEthRpc = await newWeb3(clientUrl)
connected = true
break
except CatchableError:
error "failed connect Eth client", error = getCurrentExceptionMsg()
if not connected:
raise newException(CatchableError, "all failed")
innerEthRpc
return ok(ethRpc)
method init*(g: OnchainGroupManager): Future[GroupManagerResult[void]] {.async.} =
# check if the Ethereum client is reachable
var ethRpc: Web3
g.retryWrapper(ethRpc, "Failed to connect to the Ethereum client"):
await newWeb3(g.ethClientUrl)
let ethRpc: Web3 = (await establishConnection(g)).valueOr:
return err("failed to connect to Ethereum clients: " & $error)
var fetchedChainId: uint
g.retryWrapper(fetchedChainId, "Failed to get the chain id"):
@ -544,9 +568,11 @@ method init*(g: OnchainGroupManager): Future[GroupManagerResult[void]] {.async.}
proc onDisconnect() {.async.} =
error "Ethereum client disconnected"
var newEthRpc: Web3
g.retryWrapper(newEthRpc, "Failed to reconnect with the Ethereum client"):
await newWeb3(g.ethClientUrl)
var newEthRpc: Web3 = (await g.establishConnection()).valueOr:
g.onFatalErrorAction("failed to connect to Ethereum clients onDisconnect")
return
newEthRpc.ondisconnect = ethRpc.ondisconnect
g.ethRpc = some(newEthRpc)

View File

@ -43,7 +43,7 @@ type RlnRelayConf* = object of RootObj
dynamic*: bool
credIndex*: Option[uint]
ethContractAddress*: string
ethClientAddress*: string
ethClientUrls*: seq[string]
chainId*: uint
creds*: Option[RlnRelayCreds]
treePath*: string
@ -455,7 +455,7 @@ proc mount(
(none(string), none(string))
groupManager = OnchainGroupManager(
ethClientUrl: string(conf.ethClientAddress),
ethClientUrls: conf.ethClientUrls,
ethContractAddress: $conf.ethContractAddress,
chainId: conf.chainId,
rlnInstance: rlnInstance,