logos-delivery/waku/incentivization/eligibility_manager.nim
Fabiana Cecin 549834203d
Bump to nim-libp2p 2.0.0
* bump libp2p pin to release/v2.0.0 (c43199378)
* pin nimble.lock: lsquic/websock/boringssl/protobuf_serialization/npeg/jwt
* add libp2p_mix dep and point libp2p/protocols/mix -> libp2p_mix
* migrate rng to libp2p Rng type (prod, channels, noise, tests)
* noise: take Rng, extract bearSslDrbg internally
* waku_switch: TransportConfig factory; withMaxInOut; local MaxConnections
* waku_relay/rendezvous/discv5/kademlia: v2.0.0 API (rng, config, ServiceDiscovery)
* tests: newStandardSwitch shim; PeerId.random(rng); common.rng()/crypto.newRng()
* drop libp2p/utils/semaphore (use chronos AsyncSemaphore)
* add waku/compat/option_valueor shim where needed
* add std/options where transitive re-export dropped
2026-06-02 15:42:58 -03:00

96 lines
4.0 KiB
Nim

import waku/compat/option_valueor
import std/[options, sets], chronos, web3, stew/byteutils, stint, results, chronicles
import waku/incentivization/rpc, tests/waku_rln_relay/utils_onchain
const SimpleTransferGasUsed = Quantity(21000)
const TxReceiptQueryTimeout = 3.seconds
type EligibilityManager* = ref object # FIXME: make web3 private?
web3*: Web3
seenTxIds*: HashSet[TxHash]
# Initialize the eligibilityManager with a web3 instance
proc init*(
T: type EligibilityManager, ethClient: string
): Future[EligibilityManager] {.async.} =
return
EligibilityManager(web3: await newWeb3(ethClient), seenTxIds: initHashSet[TxHash]())
# TODO: handle error if web3 instance is not established
# Clean up the web3 instance
proc close*(eligibilityManager: EligibilityManager) {.async.} =
await eligibilityManager.web3.close()
proc getTransactionByHash(
eligibilityManager: EligibilityManager, txHash: TxHash
): Future[TransactionObject] {.async.} =
await eligibilityManager.web3.provider.eth_getTransactionByHash(txHash)
proc getMinedTransactionReceipt(
eligibilityManager: EligibilityManager, txHash: TxHash
): Future[Result[ReceiptObject, string]] {.async.} =
let txReceipt = eligibilityManager.web3.getMinedTransactionReceipt(txHash)
if (await txReceipt.withTimeout(TxReceiptQueryTimeout)):
return ok(txReceipt.value())
else:
return err("Timeout on tx receipt query, tx hash: " & $txHash)
proc getTxAndTxReceipt(
eligibilityManager: EligibilityManager, txHash: TxHash
): Future[Result[(TransactionObject, ReceiptObject), string]] {.async.} =
let tx = await eligibilityManager.getTransactionByHash(txHash)
let txReceipt = (await eligibilityManager.getMinedTransactionReceipt(txHash)).valueOr:
return err("Cannot get tx receipt: " & error)
return ok((tx, txReceipt))
proc isEligibleTxId*(
eligibilityManager: EligibilityManager,
eligibilityProof: EligibilityProof,
expectedToAddress: Address,
expectedValueWei: UInt256,
): Future[Result[void, string]] {.async.} =
## We consider a tx eligible,
## in the context of service incentivization PoC,
## if it is confirmed and pays the expected amount to the server's address.
## See spec: https://github.com/waku-org/specs/blob/master/standards/core/incentivization.md
if eligibilityProof.proofOfPayment.isNone():
return err("Eligibility proof is empty")
var tx: TransactionObject
var txReceipt: ReceiptObject
let txHash = TxHash.fromHex(byteutils.toHex(eligibilityProof.proofOfPayment.get()))
# check that it is not a double-spend
let txHashWasSeen = (txHash in eligibilityManager.seenTxIds)
eligibilityManager.seenTxIds.incl(txHash)
if txHashWasSeen:
return err("TxHash " & $txHash & " was already checked (double-spend attempt)")
try:
let txAndTxReceipt = await eligibilityManager.getTxAndTxReceipt(txHash)
txAndTxReceipt.isOkOr:
return err("Failed to fetch tx or tx receipt")
(tx, txReceipt) = txAndTxReceipt.value()
except ValueError:
let errorMsg = "Failed to fetch tx or tx receipt: " & getCurrentExceptionMsg()
error "exception in isEligibleTxId", error = $errorMsg
return err($errorMsg)
# check that it is not a contract creation tx
let toAddressOption = txReceipt.to
if toAddressOption.isNone():
# this is a contract creation tx
return err("A contract creation tx is not eligible")
# check that it is a simple transfer (not a contract call)
# a simple transfer uses 21000 gas
let gasUsed = txReceipt.gasUsed
let isSimpleTransferTx = (gasUsed == SimpleTransferGasUsed)
if not isSimpleTransferTx:
return err("A contract call tx is not eligible")
# check that the to address is "as expected"
let toAddress = toAddressOption.get()
if toAddress != expectedToAddress:
return err("Wrong destination address: " & $toAddress)
# check that the amount is "as expected"
let txValueWei = tx.value
if txValueWei != expectedValueWei:
return err("Wrong tx value: got " & $txValueWei & ", expected " & $expectedValueWei)
return ok()