-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
190 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
import ./test_rpc_codec | ||
import ./test_rpc_codec, ./test_poc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |