Skip to content

Commit

Permalink
Merge af16879 into 6627e34
Browse files Browse the repository at this point in the history
  • Loading branch information
s-tikhomirov authored Dec 20, 2024
2 parents 6627e34 + af16879 commit c5f7990
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 19 deletions.
2 changes: 1 addition & 1 deletion tests/incentivization/test_all.nim
Original file line number Diff line number Diff line change
@@ -1 +1 @@
import ./test_rpc_codec
import ./test_rpc_codec, ./test_poc
79 changes: 79 additions & 0 deletions tests/incentivization/test_poc.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
{.used.}

import
std/[options], testutils/unittests, chronos, web3, stew/byteutils, stint, strutils, os

import
waku/[node/peer_manager, waku_core],
../testlib/[assertions],
waku/incentivization/[rpc, rpc_codec, common, txid_proof]

# All txids from Ethereum Sepolia testnet
const TxHashNonExisting* =
TxHash.fromHex("0x0000000000000000000000000000000000000000000000000000000000000000")
const TxHashContractCreation* =
TxHash.fromHex("0xa2e39bee557144591fb7b2891ef44e1392f86c5ba1fc0afb6c0e862676ffd50f")
const TxHashContractCall* =
TxHash.fromHex("0x2761f066eeae9a259a0247f529133dd01b7f57bf74254a64d897433397d321cb")
const TxHashSimpleTransfer* =
TxHash.fromHex("0xa3985984b2ec3f1c3d473eb57a4820a56748f25dabbf9414f2b8380312b439cc")
const ExpectedToAddress = Address.fromHex("0x5e809a85aa182a9921edd10a4163745bb3e36284")
const ExpectedValue = 200500000000005063.u256

# To set up the environment variable (replace Infura with your provider if needed):
# $ export WEB3_RPC_URL="https://sepolia.infura.io/v3/YOUR_API_KEY"
const EthClient = os.getEnv("WEB3_RPC_URL")

suite "Waku Incentivization PoC Eligibility Proofs":
## Tests for service incentivization PoC.
## In a client-server interaction, a client submits an eligibility proof to the server.
## The server provides the service if and only if the proof is valid.
## In PoC, a txid serves as eligibility proof.
## The txid reflects the confirmed payment from the client to the server.
## The request is eligible if the tx is confirmed and pays the correct amount to the correct address.
## The tx must also be of a "simple transfer" type (not a contract creation, not a contract call).
## See spec: https://github.com/waku-org/specs/blob/master/standards/core/incentivization.md

asyncTest "incentivization PoC: non-existent tx is not eligible":
## Test that an unconfirmed tx is not eligible.
let eligibilityProof =
EligibilityProof(proofOfPayment: some(@(TxHashNonExisting.bytes())))
let isEligible = await isEligibleTxId(
eligibilityProof, ExpectedToAddress, ExpectedValue, EthClient
)
check:
isEligible.isErr()

asyncTest "incentivization PoC: contract creation tx is not eligible":
## Test that a contract creation tx is not eligible.
let eligibilityProof =
EligibilityProof(proofOfPayment: some(@(TxHashContractCreation.bytes())))
let isEligible = await isEligibleTxId(
eligibilityProof, ExpectedToAddress, ExpectedValue, EthClient
)
check:
isEligible.isErr()

asyncTest "incentivization PoC: contract call tx is not eligible":
## Test that a contract call tx is not eligible.
## This assumes a payment in native currency (ETH), not a token.
let eligibilityProof =
EligibilityProof(proofOfPayment: some(@(TxHashContractCall.bytes())))
let isEligible = await isEligibleTxId(
eligibilityProof, ExpectedToAddress, ExpectedValue, EthClient
)
check:
isEligible.isErr()

asyncTest "incentivization PoC: simple transfer tx is eligible":
## Test that a simple transfer tx is eligible (if necessary conditions hold).
let eligibilityProof =
EligibilityProof(proofOfPayment: some(@(TxHashSimpleTransfer.bytes())))
let isEligible = await isEligibleTxId(
eligibilityProof, ExpectedToAddress, ExpectedValue, EthClient
)
check:
isEligible.isOk()

# TODO: add tests for simple transfer txs with wrong amount and wrong receiver
# TODO: add test for failing Web3 provider
29 changes: 13 additions & 16 deletions tests/incentivization/test_rpc_codec.nim
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import
std/options,
std/strscans,
testutils/unittests,
chronicles,
chronos,
libp2p/crypto/crypto
import std/options, testutils/unittests, chronos, libp2p/crypto/crypto, web3

import waku/incentivization/rpc, waku/incentivization/rpc_codec
import waku/incentivization/[rpc, rpc_codec, common]

suite "Waku Incentivization Eligibility Codec":
asyncTest "encode eligibility proof":
var byteSequence: seq[byte] = @[1, 2, 3, 4, 5, 6, 7, 8]
let epRpc = EligibilityProof(proofOfPayment: some(byteSequence))
let encoded = encode(epRpc)
asyncTest "encode eligibility proof from txid":
let txHash = TxHash.fromHex(
"0x0000000000000000000000000000000000000000000000000000000000000000"
)
let txHashAsBytes = @(txHash.bytes())
let eligibilityProof = EligibilityProof(proofOfPayment: some(txHashAsBytes))
let encoded = encode(eligibilityProof)
let decoded = EligibilityProof.decode(encoded.buffer).get()
check:
epRpc == decoded
eligibilityProof == decoded

asyncTest "encode eligibility status":
let esRpc = EligibilityStatus(statusCode: uint32(200), statusDesc: some("OK"))
let encoded = encode(esRpc)
let eligibilityStatus = new(EligibilityStatus, true)
let encoded = encode(eligibilityStatus)
let decoded = EligibilityStatus.decode(encoded.buffer).get()
check:
esRpc == decoded
eligibilityStatus == decoded
9 changes: 9 additions & 0 deletions waku/incentivization/common.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import std/options, chronos

import waku/incentivization/[rpc, txid_proof]

proc new*(T: type EligibilityStatus, isEligible: bool): T =
if isEligible:
EligibilityStatus(statusCode: uint32(200), statusDesc: some("OK"))
else:
EligibilityStatus(statusCode: uint32(402), statusDesc: some("Payment Required"))
3 changes: 1 addition & 2 deletions waku/incentivization/rpc.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import json_serialization, std/options
import ../waku_core
import std/options

# Implementing the RFC:
# https://github.com/vacp2p/rfc/tree/master/content/docs/rfcs/73
Expand Down
87 changes: 87 additions & 0 deletions waku/incentivization/txid_proof.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import std/options, chronos, web3, stew/byteutils, stint, results, chronicles

import waku/incentivization/rpc

const SimpleTransferGasUsed = Quantity(21000)
const TxReceiptQueryTimeout = 3.seconds

proc getTransactionByHash(
txHash: TxHash, web3: Web3
): Future[TransactionObject] {.async.} =
await web3.provider.eth_getTransactionByHash(txHash)

proc getMinedTransactionReceipt(
txHash: TxHash, web3: Web3
): Future[Result[ReceiptObject, string]] {.async.} =
let txReceipt = web3.getMinedTransactionReceipt(txHash)
if (await txReceipt.withTimeout(TxReceiptQueryTimeout)):
return ok(txReceipt.value())
else:
return err("Timeout on tx receipt query")

proc getTxAndTxReceipt(
txHash: TxHash, web3: Web3
): Future[Result[(TransactionObject, ReceiptObject), string]] {.async.} =
let txFuture = getTransactionByHash(txHash, web3)
let receiptFuture = getMinedTransactionReceipt(txHash, web3)
await allFutures(txFuture, receiptFuture)
let tx = txFuture.read()
let txReceipt = receiptFuture.read()
if txReceipt.isErr:
return err("Cannot get tx receipt")
return ok((tx, txReceipt.get()))

proc isEligibleTxId*(
eligibilityProof: EligibilityProof,
expectedToAddress: Address,
expectedValue: UInt256,
ethClient: string,
): 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 web3: Web3
try:
web3 = await newWeb3(ethClient)
except ValueError:
let errorMsg =
"Failed to set up a web3 provider connection: " & getCurrentExceptionMsg()
error "exception in isEligibleTxId", error = $errorMsg
return err($errorMsg)
var tx: TransactionObject
var txReceipt: ReceiptObject
let txHash = TxHash.fromHex(byteutils.toHex(eligibilityProof.proofOfPayment.get()))
try:
let txAndTxReceipt = await getTxAndTxReceipt(txHash, web3)
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 txValue = tx.value
if txValue != expectedValue:
return err("Wrong tx value: got " & $txValue & ", expected " & $expectedValue)
defer:
await web3.close()
return ok()

0 comments on commit c5f7990

Please sign in to comment.