From 1e1d89ec932e1c5b08bf2d611282b6bd590ff87e Mon Sep 17 00:00:00 2001 From: Sergei Tikhomirov Date: Sun, 22 Jun 2025 15:31:01 +0200 Subject: [PATCH] pass eligibilityEnabled as config parameter --- waku/factory/builder.nim | 9 ++- .../conf_builder/eligibility_conf_builder.nim | 60 +++++++++++++++++++ .../conf_builder/waku_conf_builder.nim | 9 ++- waku/factory/external_config.nim | 40 +++++++++++-- waku/factory/node_factory.nim | 9 +++ waku/factory/waku_conf.nim | 52 +++++++++++----- waku/node/waku_node.nim | 36 ++++++----- 7 files changed, 179 insertions(+), 36 deletions(-) create mode 100644 waku/factory/conf_builder/eligibility_conf_builder.nim diff --git a/waku/factory/builder.nim b/waku/factory/builder.nim index 4af6fc658..13c6b4ae2 100644 --- a/waku/factory/builder.nim +++ b/waku/factory/builder.nim @@ -46,6 +46,10 @@ type #Rate limit configs for non-relay req-resp protocols rateLimitSettings: Option[seq[string]] + # Eligibility enabled + # FIXME: is this the right way to propagate eligibility-related config items? + eligibilityEnabled: bool + WakuNodeBuilderResult* = Result[void, string] ## Init @@ -113,6 +117,7 @@ proc withPeerManagerConfig*( maxConnections: int, relayServiceRatio: string, shardAware = false, + eligibilityEnabled = false, ) = let (relayRatio, serviceRatio) = parseRelayServiceRatio(relayServiceRatio).get() var relayPeers = int(ceil(float(maxConnections) * relayRatio)) @@ -121,6 +126,7 @@ proc withPeerManagerConfig*( builder.maxServicePeers = servicePeers builder.maxRelayPeers = relayPeers builder.shardAware = shardAware + builder.eligibilityEnabled = eligibilityEnabled proc withColocationLimit*(builder: var WakuNodeBuilder, colocationLimit: int) = builder.colocationLimit = colocationLimit @@ -206,7 +212,8 @@ proc build*(builder: WakuNodeBuilder): Result[WakuNode, string] = colocationLimit = builder.colocationLimit, shardedPeerManagement = builder.shardAware, dnsNameServers = netConfig.dnsNameServers, - eligibilityEnabled = true, # FIXME: i13n: read config or something instead + # FIXME: should eligibilityEnabled be part of some eligibilityConfig within WakuNodeBuilder here? + eligibilityEnabled = builder.eligibilityEnabled #reputationEnabled = true, # FIXME: i13n: read config or something instead ) diff --git a/waku/factory/conf_builder/eligibility_conf_builder.nim b/waku/factory/conf_builder/eligibility_conf_builder.nim new file mode 100644 index 000000000..615e73175 --- /dev/null +++ b/waku/factory/conf_builder/eligibility_conf_builder.nim @@ -0,0 +1,60 @@ +import chronicles, std/options, results +import ../waku_conf +import eth/common as eth_common + +logScope: + topics = "waku conf builder eligibility" + +type EligibilityConfBuilder* = object + enabled*: Option[bool] + receiverAddress*: Option[string] + paymentAmountWei*: Option[uint32] + ethClientUrls*: Option[seq[string]] + +proc init*(T: type EligibilityConfBuilder): EligibilityConfBuilder = + EligibilityConfBuilder() + +proc withEnabled*(b: var EligibilityConfBuilder, enabled: bool) = + b.enabled = some(enabled) + +proc withReceiverAddress*(b: var EligibilityConfBuilder, receiverAddress: string) = + b.receiverAddress = some(receiverAddress) + +proc withPaymentAmountWei*(b: var EligibilityConfBuilder, amount: uint32) = + b.paymentAmountWei = some(amount) + +proc withEthClientUrls*(b: var EligibilityConfBuilder, urls: seq[string]) = + b.ethClientUrls = some(urls) + +proc build*(b: EligibilityConfBuilder): Result[Option[EligibilityConf], string] = + if not b.enabled.get(false): + debug "eligibility: EligibilityConf not enabled" + return ok(none(EligibilityConf)) + + # Validation + if b.receiverAddress.isNone() or b.paymentAmountWei.isNone(): + debug "eligibility: EligibilityConf validation failed - missing address or amount" + return err("Eligibility: receiver address and payment amount must be specified") + + # FIXME: add validation check that receiver address is validly formed + + if b.paymentAmountWei.get() == 0: + debug "eligibility: EligibilityConf validation failed - payment amount is zero" + return err("Eligibility: payment amount must be above zero") + + let urls = b.ethClientUrls.get(@[]) + if urls.len == 0: + debug "eligibility: EligibilityConf validation failed - no eth rpc urls" + return err("Eligibility: eligibility-eth-client-address is not specified") + + debug "eligibility: EligibilityConf created" + return ok( + some( + EligibilityConf( + enabled: true, + receiverAddress: b.receiverAddress.get(), + paymentAmountWei: b.paymentAmountWei.get(), + ethClientUrls: urls, + ) + ) + ) diff --git a/waku/factory/conf_builder/waku_conf_builder.nim b/waku/factory/conf_builder/waku_conf_builder.nim index 44cb706af..92a51fcf1 100644 --- a/waku/factory/conf_builder/waku_conf_builder.nim +++ b/waku/factory/conf_builder/waku_conf_builder.nim @@ -22,7 +22,8 @@ import ./discv5_conf_builder, ./web_socket_conf_builder, ./metrics_server_conf_builder, - ./rln_relay_conf_builder + ./rln_relay_conf_builder, + ./eligibility_conf_builder logScope: topics = "waku conf builder" @@ -72,6 +73,7 @@ type WakuConfBuilder* = object rlnRelayConf*: RlnRelayConfBuilder storeServiceConf*: StoreServiceConfBuilder webSocketConf*: WebSocketConfBuilder + eligibilityConf*: EligibilityConfBuilder # End conf builders relay: Option[bool] lightPush: Option[bool] @@ -135,6 +137,7 @@ proc init*(T: type WakuConfBuilder): WakuConfBuilder = rlnRelayConf: RlnRelayConfBuilder.init(), storeServiceConf: StoreServiceConfBuilder.init(), webSocketConf: WebSocketConfBuilder.init(), + eligibilityConf: EligibilityConfBuilder.init(), ) proc withClusterConf*(b: var WakuConfBuilder, clusterConf: ClusterConf) = @@ -473,6 +476,9 @@ proc build*( let webSocketConf = builder.webSocketConf.build().valueOr: return err("WebSocket Conf building failed: " & $error) + + let eligibilityConf = builder.eligibilityConf.build().valueOr: + return err("Eligibility Conf building failed: " & $error) # End - Build sub-configs let logLevel = @@ -596,6 +602,7 @@ proc build*( metricsServerConf: metricsServerConf, restServerConf: restServerConf, dnsDiscoveryConf: dnsDiscoveryConf, + eligibilityConf: eligibilityConf, # end confs nodeKey: nodeKey, clusterId: clusterId, diff --git a/waku/factory/external_config.nim b/waku/factory/external_config.nim index 075b132ab..df9962abf 100644 --- a/waku/factory/external_config.nim +++ b/waku/factory/external_config.nim @@ -27,7 +27,8 @@ import ../node/peer_manager, ../waku_core/topics/pubsub_topic, ../../tools/rln_keystore_generator/rln_keystore_generator, - ../../tools/rln_db_inspector/rln_db_inspector + ../../tools/rln_db_inspector/rln_db_inspector, + ./conf_builder/eligibility_conf_builder include ../waku_core/message/default_values @@ -266,8 +267,8 @@ type WakuNodeConf* = object isRelayClient* {. desc: """Set the node as a relay-client. -Set it to true for nodes that run behind a NAT or firewall and -hence would have reachability issues.""", + Set it to true for nodes that run behind a NAT or firewall and + hence would have reachability issues.""", defaultValue: false, name: "relay-client" .}: bool @@ -489,12 +490,30 @@ hence would have reachability issues.""", ## Eligibility config ## Is eligibility check enabled or not eligibilityEnabled* {. - desc: - "Enable server-side eligibility (proof-of-payment) check for light protocols: true|false", + desc: "Enable server-side eligibility (proof-of-payment) check for light protocols: true|false", defaultValue: false, - name: "eligibility" + name: "eligibility-enabled" .}: bool + ## The expected blockchain address receiving payments for eligibility + eligibilityReceiverAddress* {. + desc: "Blockchain address receiving payment for eligibility from light protocol clients", + defaultValue: "", + name: "eligibility-receiver-address" + .}: string + + ## The expected amount for eligibility payments + eligibilityPaymentAmountWei* {. + desc: "The expected payment amount from light protocol clients", + defaultValue: 0, + name: "eligibility-payment-amount-wei" + .}: uint32 + + eligibilityEthRpcUrl* {. + desc: "HTTP address of an Ethereum client for eligibility checks. Argument may be repeated.", + defaultValue: @[EthRpcUrl("http://localhost:8540/")], + name: "eligibility-eth-client-address" + .}: seq[EthRpcUrl] ## Reliability config reliabilityEnabled* {. @@ -1064,4 +1083,13 @@ proc toWakuConf*(n: WakuNodeConf): ConfResult[WakuConf] = b.withRateLimits(n.rateLimits) + # Setup eligibility configuration + b.eligibilityConf.withEnabled(n.eligibilityEnabled) + if n.eligibilityReceiverAddress != "": + b.eligibilityConf.withReceiverAddress(n.eligibilityReceiverAddress) + if n.eligibilityPaymentAmountWei != 0: + b.eligibilityConf.withPaymentAmountWei(n.eligibilityPaymentAmountWei) + if n.eligibilityEthRpcUrl.len > 0: + b.eligibilityConf.withEthClientUrls(n.eligibilityEthRpcUrl.mapIt(string(it))) + return b.build() diff --git a/waku/factory/node_factory.nim b/waku/factory/node_factory.nim index fc53ea81f..8ad019812 100644 --- a/waku/factory/node_factory.nim +++ b/waku/factory/node_factory.nim @@ -109,6 +109,12 @@ proc initNode( ) builder.withColocationLimit(conf.colocationLimit) + let eligibilityEnabled = + if conf.eligibilityConf.isSome(): + conf.eligibilityConf.get().enabled + else: + false + if conf.maxRelayPeers.isSome(): let maxRelayPeers = conf.maxRelayPeers.get() @@ -117,10 +123,12 @@ proc initNode( relayRatio = (maxRelayPeers.float / maxConnections.float) * 100 serviceRatio = 100 - relayRatio + # FIXME: DRY builder.withPeerManagerConfig( maxConnections = conf.maxConnections, relayServiceRatio = $relayRatio & ":" & $serviceRatio, shardAware = conf.relayShardedPeerManagement, + eligibilityEnabled = eligibilityEnabled, ) error "maxRelayPeers is deprecated. It is recommended to use relayServiceRatio instead. If relayServiceRatio is not set, it will be automatically calculated based on maxConnections and maxRelayPeers." else: @@ -128,6 +136,7 @@ proc initNode( maxConnections = conf.maxConnections, relayServiceRatio = conf.relayServiceRatio, shardAware = conf.relayShardedPeerManagement, + eligibilityEnabled = eligibilityEnabled, ) builder.withRateLimit(conf.rateLimits) builder.withCircuitRelay(relay) diff --git a/waku/factory/waku_conf.nim b/waku/factory/waku_conf.nim index 94b89a26e..ba81a57c4 100644 --- a/waku/factory/waku_conf.nim +++ b/waku/factory/waku_conf.nim @@ -58,6 +58,12 @@ type NetworkConfig* = object # TODO: make enum extMultiAddrs*: seq[MultiAddress] extMultiAddrsOnly*: bool +type EligibilityConf* = object + enabled*: bool + receiverAddress*: string + paymentAmountWei*: uint32 + ethClientUrls*: seq[string] + ## `WakuConf` is a valid configuration for a Waku node ## All information needed by a waku node should be contained ## In this object. A convenient `validate` method enables doing @@ -93,6 +99,7 @@ type WakuConf* {.requiresInit.} = ref object restServerConf*: Option[RestServerConf] metricsServerConf*: Option[MetricsServerConf] webSocketConf*: Option[WebSocketConf] + eligibilityConf*: Option[EligibilityConf] portsShift*: uint16 dnsAddrs*: bool @@ -133,35 +140,39 @@ type WakuConf* {.requiresInit.} = ref object p2pReliability*: bool -proc logConf*(conf: WakuConf) = +proc logConf*(wakuConf: WakuConf) = info "Configuration: Enabled protocols", - relay = conf.relay, - rlnRelay = conf.rlnRelayConf.isSome(), - store = conf.storeServiceConf.isSome(), - filter = conf.filterServiceConf.isSome(), - lightPush = conf.lightPush, - peerExchange = conf.peerExchange + relay = wakuConf.relay, + rlnRelay = wakuConf.rlnRelayConf.isSome(), + store = wakuConf.storeServiceConf.isSome(), + filter = wakuConf.filterServiceConf.isSome(), + lightPush = wakuConf.lightPush, + peerExchange = wakuConf.peerExchange - info "Configuration. Network", cluster = conf.clusterId + info "Configuration. Network", cluster = wakuConf.clusterId - for shard in conf.shards: + for shard in wakuConf.shards: info "Configuration. Shards", shard = shard - if conf.discv5Conf.isSome(): - for i in conf.discv5Conf.get().bootstrapNodes: + if wakuConf.discv5Conf.isSome(): + for i in wakuConf.discv5Conf.get().bootstrapNodes: info "Configuration. Bootstrap nodes", node = i.string - if conf.rlnRelayConf.isSome(): - var rlnRelayConf = conf.rlnRelayConf.get() + if wakuConf.rlnRelayConf.isSome(): + var rlnRelayConf = wakuConf.rlnRelayConf.get() if rlnRelayConf.dynamic: info "Configuration. Validation", mechanism = "onchain rln", contract = rlnRelayConf.ethContractAddress.string, - maxMessageSize = conf.maxMessageSizeBytes, + maxMessageSize = wakuConf.maxMessageSizeBytes, rlnEpochSizeSec = rlnRelayConf.epochSizeSec, rlnRelayUserMessageLimit = rlnRelayConf.userMessageLimit, ethClientUrls = rlnRelayConf.ethClientUrls + if wakuConf.eligibilityConf.isSome(): + let ec = wakuConf.eligibilityConf.get() + debug "eligibility: EligibilityConf created", enabled = ec.enabled, receiverAddress = $ec.receiverAddress, paymentAmountWei = ec.paymentAmountWei, ethClientUrls = ec.ethClientUrls + proc validateNodeKey(wakuConf: WakuConf): Result[void, string] = wakuConf.nodeKey.getPublicKey().isOkOr: return err("nodekey param is invalid") @@ -240,4 +251,17 @@ proc validate*(wakuConf: WakuConf): Result[void, string] = ?wakuConf.validateNodeKey() ?wakuConf.validateShards() ?wakuConf.validateNoEmptyStrings() + + if wakuConf.eligibilityConf.isSome(): + let ec = wakuConf.eligibilityConf.get() + debug "eligibility: EligibilityConf validation start" + if ec.enabled: + if not wakuConf.rlnRelayConf.isSome(): + debug "eligibility: EligibilityConf validation failed - RLN relay not enabled" + return err("eligibility: RLN relay must be enabled if eligibility is enabled") + if not wakuConf.lightPush: + debug "eligibility: EligibilityConf validation failed - Lightpush not enabled" + return err("eligibility: Lightpush must be enabled if eligibility is enabled") + debug "eligibility: EligibilityConf validation successful" + return ok() diff --git a/waku/node/waku_node.nim b/waku/node/waku_node.nim index 04bf54c79..a5b46f144 100644 --- a/waku/node/waku_node.nim +++ b/waku/node/waku_node.nim @@ -1097,7 +1097,7 @@ proc legacyLightpushPublish*( except CatchableError: return err(getCurrentExceptionMsg()) -# TODO: Move to application module (e.g., wakunode2.nim) +# TODO: Move to application module (e.g, wakunode2.nim) proc legacyLightpushPublish*( node: WakuNode, pubsubTopic: Option[PubsubTopic], message: WakuMessage ): Future[legacy_lightpush_protocol.WakuLightPushResult[string]] {. @@ -1223,21 +1223,29 @@ proc lightpushPublish*( debug "eligibilityManager is enabled" var em = node.peerManager.eligibilityManager.get() # FIXME: where should I init eligibilityManager? + # FIXME: extract values from... config parameters (which config?) # FIXME: how to reuse EthClient from CLI arguments? debug "initializing eligibilityManager..." - let ethClient = "https://sepolia.infura.io/v3/470c2e9a16f24057aee6660081729fb9" - em = await EligibilityManager.init(ethClient) - debug "checking eligibilityProof..." - let txNonExistent = TxHash.fromHex("0x0000000000000000000000000000000000000000000000000000000000000000") - let expectedToAddress = Address.fromHex("0xe8284Af9A5F3b0CD1334DBFaf512F09BeDA805a3") - let expectedValueWei = 30000000000000.u256 - - let isEligible = await em.isEligibleTxId( - eligibilityProof.get(), expectedToAddress, expectedValueWei - ) - if isEligible.isErr(): - let msg = "Eligibility check failed!" - #debug msg + try: + let ethClient = "https://sepolia.infura.io/v3/470c2e9a16f24057aee6660081729fb9" + em = await EligibilityManager.init(ethClient) + debug "checking eligibilityProof..." + + # Check if eligibility proof is provided before accessing it + if eligibilityProof.isNone(): + let msg = "Eligibility proof is required" + return lighpushErrorResult(PAYMENT_REQUIRED, msg) + + #let txNonExistent = TxHash.fromHex("0x0000000000000000000000000000000000000000000000000000000000000000") + let expectedToAddress = Address.fromHex("0xe8284Af9A5F3b0CD1334DBFaf512F09BeDA805a3") + let expectedValueWei = 30000000000000.u256 + + let isEligible = await em.isEligibleTxId( + eligibilityProof.get(), expectedToAddress, expectedValueWei + ) + + except CatchableError: + let msg = "Eligibility check threw exception: " & getCurrentExceptionMsg() return lighpushErrorResult(PAYMENT_REQUIRED, msg) debug "Eligibility check passed!"