From 79846e8433e427af84fc450465836903326ea1f6 Mon Sep 17 00:00:00 2001 From: Sergei Tikhomirov Date: Fri, 31 Jan 2025 18:53:46 +0100 Subject: [PATCH] feat: incentivization POC: add double-spend check for txid-based eligibility (#3264) * add double-spend check for txid-based eligibility * Apply suggestions from code review Co-authored-by: Ivan FB <128452529+Ivansete-status@users.noreply.github.com> * split assert into two in double-spending test * remove unnecessary import Co-authored-by: Ivan FB <128452529+Ivansete-status@users.noreply.github.com> --------- Co-authored-by: Ivan FB <128452529+Ivansete-status@users.noreply.github.com> --- tests/incentivization/test_poc.nim | 19 ++++++++++++++++++- waku/incentivization/eligibility_manager.nim | 10 ++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/tests/incentivization/test_poc.nim b/tests/incentivization/test_poc.nim index 91c030cea..7490c2304 100644 --- a/tests/incentivization/test_poc.nim +++ b/tests/incentivization/test_poc.nim @@ -1,7 +1,7 @@ {.used.} import - std/[options], + std/options, testutils/unittests, chronos, web3, @@ -194,5 +194,22 @@ suite "Waku Incentivization PoC Eligibility Proofs": assert isEligible.isOk(), isEligible.error + asyncTest "incentivization PoC: double-spend tx is not eligible": + ## Test that the same tx submitted twice is not eligible the second time + + let eligibilityProof = + EligibilityProof(proofOfPayment: some(@(txHashRightReceiverRightAmount.bytes()))) + + let isEligibleOnce = await manager.isEligibleTxId( + eligibilityProof, receiverExpected, TxValueExpectedWei + ) + + let isEligibleTwice = await manager.isEligibleTxId( + eligibilityProof, receiverExpected, TxValueExpectedWei + ) + + assert isEligibleOnce.isOk() + assert isEligibleTwice.isErr(), isEligibleTwice.error + # Stop Anvil daemon stopAnvil(runAnvil) diff --git a/waku/incentivization/eligibility_manager.nim b/waku/incentivization/eligibility_manager.nim index 74d676fba..3343f7186 100644 --- a/waku/incentivization/eligibility_manager.nim +++ b/waku/incentivization/eligibility_manager.nim @@ -1,4 +1,4 @@ -import std/options, chronos, web3, stew/byteutils, stint, results, chronicles +import std/[options, sets], chronos, web3, stew/byteutils, stint, results, chronicles import waku/incentivization/rpc, tests/waku_rln_relay/[utils_onchain, utils] @@ -7,12 +7,13 @@ 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.} = - result = EligibilityManager(web3: await newWeb3(ethClient)) + return EligibilityManager(web3: await newWeb3(ethClient), seenTxIds: initHashSet[TxHash]()) # TODO: handle error if web3 instance is not established # Clean up the web3 instance @@ -60,6 +61,11 @@ proc isEligibleTxId*( 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: