Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 23 additions & 20 deletions ts-tests/tests/test-contract-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AbiItem } from "web3-utils";

import Test from "../build/contracts/Storage.json";
import { GENESIS_ACCOUNT, GENESIS_ACCOUNT_PRIVATE_KEY, FIRST_CONTRACT_ADDRESS } from "./config";
import { createAndFinalizeBlock, customRequest, describeWithFrontier } from "./util";
import { createAndFinalizeBlock, customRequest, describeWithFrontier, waitForReceipt } from "./util";

describeWithFrontier("Frontier RPC (Contract)", (context) => {
const TEST_CONTRACT_BYTECODE = Test.bytecode;
Expand Down Expand Up @@ -81,34 +81,37 @@ describeWithFrontier("Frontier RPC (Contract)", (context) => {
});

it("SSTORE cost should properly take into account transaction initial value", async function () {
let nonce = await context.web3.eth.getTransactionCount(GENESIS_ACCOUNT);
this.timeout(30000);

await context.web3.eth.accounts.wallet.add(GENESIS_ACCOUNT_PRIVATE_KEY);
let nonce = await context.web3.eth.getTransactionCount(GENESIS_ACCOUNT);
const contract = new context.web3.eth.Contract(TEST_CONTRACT_ABI, FIRST_CONTRACT_ADDRESS, {
from: GENESIS_ACCOUNT,
gasPrice: "0x3B9ACA00",
});

const promisify = (inner) => new Promise((resolve, reject) => inner(resolve, reject));

let tx1 = contract.methods
.setStorage("0x2A", "0x1")
.send({ from: GENESIS_ACCOUNT, gas: "0x100000", nonce: nonce++ });
const sendSetStorageTx = async (value: string, txNonce: number) => {
const tx = await context.web3.eth.accounts.signTransaction(
{
from: GENESIS_ACCOUNT,
to: FIRST_CONTRACT_ADDRESS,
data: contract.methods.setStorage("0x2A", value).encodeABI(),
value: "0x00",
gasPrice: "0x3B9ACA00",
gas: "0x100000",
nonce: txNonce,
},
GENESIS_ACCOUNT_PRIVATE_KEY
);

let tx2 = contract.methods
.setStorage("0x2A", "0x1")
.send({ from: GENESIS_ACCOUNT, gas: "0x100000", nonce: nonce++ });
const txHash = (await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction])).result;
await createAndFinalizeBlock(context.web3);

let tx3 = contract.methods
.setStorage("0x2A", "0x2")
.send(
{ from: GENESIS_ACCOUNT, gas: "0x100000", nonce: nonce++ },
async (hash) => await createAndFinalizeBlock(context.web3)
);
return waitForReceipt(context.web3, txHash);
};

tx1 = await tx1;
tx2 = await tx2;
tx3 = await tx3;
const tx1 = await sendSetStorageTx("0x1", nonce++);
const tx2 = await sendSetStorageTx("0x1", nonce++);
const tx3 = await sendSetStorageTx("0x2", nonce++);

// cost minus SSTORE
const baseCost = 24029;
Expand Down
23 changes: 8 additions & 15 deletions ts-tests/tests/test-receipt-consistency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import { expect } from "chai";
import { step } from "mocha-steps";

import { GENESIS_ACCOUNT, GENESIS_ACCOUNT_PRIVATE_KEY } from "./config";
import { createAndFinalizeBlockNowait, describeWithFrontier, customRequest, waitForBlock } from "./util";
import {
createAndFinalizeBlockNowait,
describeWithFrontier,
customRequest,
waitForBlock,
waitForReceipt,
} from "./util";

/**
* Test for receipt consistency (ADR-003).
Expand All @@ -28,19 +34,6 @@ describeWithFrontier("Frontier RPC (Receipt Consistency)", (context) => {
throw new Error(`Timed out waiting for txpool pending >= ${minPending}`);
}

async function waitForReceipt(txHash: string, timeoutMs = 10000) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const receipt = await context.web3.eth.getTransactionReceipt(txHash);
if (receipt !== null) {
return receipt;
}
await createAndFinalizeBlockNowait(context.web3);
await new Promise<void>((resolve) => setTimeout(resolve, 50));
}
throw new Error(`Timed out waiting for receipt ${txHash}`);
}

step("should return receipt immediately after block is visible", async function () {
const tx = await context.web3.eth.accounts.signTransaction(
{
Expand Down Expand Up @@ -118,7 +111,7 @@ describeWithFrontier("Frontier RPC (Receipt Consistency)", (context) => {

// All receipts should eventually be available and point to visible blocks.
for (let i = 0; i < txCount; i++) {
const receipt = await waitForReceipt(txHashes[i]);
const receipt = await waitForReceipt(context.web3, txHashes[i]);

expect(receipt, `Receipt for tx ${i}`).to.not.be.null;
expect(receipt.transactionHash).to.equal(txHashes[i]);
Expand Down
43 changes: 26 additions & 17 deletions ts-tests/tests/test-selfdestruct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { AbiItem } from "web3-utils";

import SelfDestructAfterCreate2 from "../build/contracts/SelfDestructAfterCreate2.json";
import { GENESIS_ACCOUNT, GENESIS_ACCOUNT_PRIVATE_KEY, FIRST_CONTRACT_ADDRESS } from "./config";
import { createAndFinalizeBlock, customRequest, describeWithFrontier } from "./util";
import { createAndFinalizeBlock, customRequest, describeWithFrontier, waitForReceipt } from "./util";

chaiUse(chaiAsPromised);

Expand Down Expand Up @@ -50,32 +50,41 @@ describeWithFrontier("Test self-destruct contract", (context) => {
result: TEST_CONTRACT_DEPLOYED_BYTECODE,
});

// Prepare signer and fetch latest nonce
await context.web3.eth.accounts.wallet.add(GENESIS_ACCOUNT_PRIVATE_KEY);
let nonce = await context.web3.eth.getTransactionCount(GENESIS_ACCOUNT);

const contract = new context.web3.eth.Contract(TEST_CONTRACT_ABI, FIRST_CONTRACT_ADDRESS, {
from: GENESIS_ACCOUNT,
gasPrice: "0x3B9ACA00",
});

let tx1 = contract.methods.step1().send({ from: GENESIS_ACCOUNT, gas: "0x100000", nonce: nonce++ });
const sendTx = async (data: string, txNonce: number) => {
const signed = await context.web3.eth.accounts.signTransaction(
{
from: GENESIS_ACCOUNT,
to: FIRST_CONTRACT_ADDRESS,
data,
value: "0x00",
gasPrice: "0x3B9ACA00",
gas: "0x100000",
nonce: txNonce,
},
GENESIS_ACCOUNT_PRIVATE_KEY
);
const txHash = (await customRequest(context.web3, "eth_sendRawTransaction", [signed.rawTransaction]))
.result as string;
return txHash;
};

let tx2 = contract.methods.step2().send({ from: GENESIS_ACCOUNT, gas: "0x100000", nonce: nonce++ });
const tx1Hash = await sendTx(contract.methods.step1().encodeABI(), nonce++);
const tx2Hash = await sendTx(contract.methods.step2().encodeABI(), nonce++);
const tx3Hash = await sendTx(contract.methods.cannotRecreateInTheSameCall().encodeABI(), nonce++);

let tx3 = contract.methods
.cannotRecreateInTheSameCall()
.send(
{ from: GENESIS_ACCOUNT, gas: "0x100000", nonce: nonce++ },
async (_hash) => await createAndFinalizeBlock(context.web3)
);
await createAndFinalizeBlock(context.web3);

const { transactionHash: tx1Hash } = await tx1;
const { transactionHash: tx2Hash } = await tx2;
const { transactionHash: tx3Hash } = await tx3;
const tx1Receipt = await waitForReceipt(context.web3, tx1Hash);
const tx2Receipt = await waitForReceipt(context.web3, tx2Hash);
const tx3Receipt = await waitForReceipt(context.web3, tx3Hash);

for (let txHash of [tx1Hash, tx2Hash, tx3Hash]) {
const receipt = await context.web3.eth.getTransactionReceipt(txHash);
for (const receipt of [tx1Receipt, tx2Receipt, tx3Receipt]) {
expect(receipt.status).to.be.true;
}

Expand Down
20 changes: 4 additions & 16 deletions ts-tests/tests/test-transaction-version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { expect } from "chai";
import { step } from "mocha-steps";

import { GENESIS_ACCOUNT, GENESIS_ACCOUNT_PRIVATE_KEY, CHAIN_ID } from "./config";
import { createAndFinalizeBlock, describeWithFrontier } from "./util";
import { createAndFinalizeBlock, describeWithFrontier, waitForReceipt } from "./util";

// We use ethers library in this test as apparently web3js's types are not fully EIP-1559 compliant yet.
describeWithFrontier("Frontier RPC (Transaction Version)", (context) => {
Expand All @@ -30,18 +30,6 @@ describeWithFrontier("Frontier RPC (Transaction Version)", (context) => {
throw new Error(`Timed out waiting for transaction ${txHash} to reach the pool`);
}

async function waitForReceipt(txHash: string, timeoutMs = 5000) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const receipt = await context.web3.eth.getTransactionReceipt(txHash);
if (receipt !== null) {
return receipt;
}
await new Promise<void>((resolve) => setTimeout(resolve, 50));
}
throw new Error(`Timed out waiting for receipt ${txHash}`);
}

step("should handle Legacy transaction type 0", async function () {
let tx = {
from: GENESIS_ACCOUNT,
Expand All @@ -56,7 +44,7 @@ describeWithFrontier("Frontier RPC (Transaction Version)", (context) => {
const txHash = (await sendTransaction(context, tx)).hash;
await waitForTransactionSeen(txHash);
await createAndFinalizeBlock(context.web3);
let receipt = await waitForReceipt(txHash);
let receipt = await waitForReceipt(context.web3, txHash);
const minedBlock = await context.web3.eth.getBlock(receipt.blockNumber);
expect(minedBlock.transactions).to.include(txHash);
expect(receipt.transactionHash).to.be.eq(txHash);
Expand All @@ -82,7 +70,7 @@ describeWithFrontier("Frontier RPC (Transaction Version)", (context) => {
const txHash = (await sendTransaction(context, tx)).hash;
await waitForTransactionSeen(txHash);
await createAndFinalizeBlock(context.web3);
let receipt = await waitForReceipt(txHash);
let receipt = await waitForReceipt(context.web3, txHash);
const minedBlock = await context.web3.eth.getBlock(receipt.blockNumber);
expect(minedBlock.transactions).to.include(txHash);
expect(receipt.transactionHash).to.be.eq(txHash);
Expand All @@ -109,7 +97,7 @@ describeWithFrontier("Frontier RPC (Transaction Version)", (context) => {
const txHash = (await sendTransaction(context, tx)).hash;
await waitForTransactionSeen(txHash);
await createAndFinalizeBlock(context.web3);
let receipt = await waitForReceipt(txHash);
let receipt = await waitForReceipt(context.web3, txHash);
const minedBlock = await context.web3.eth.getBlock(receipt.blockNumber);
expect(minedBlock.transactions).to.include(txHash);
expect(receipt.transactionHash).to.be.eq(txHash);
Expand Down
73 changes: 65 additions & 8 deletions ts-tests/tests/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,66 @@ export async function waitForBlock(
throw new Error(`Timeout waiting for block ${blockTag} to be indexed${errorSuffix}`);
}

// Create a block, finalize it, and wait for it to be indexed by mapping-sync.
// Create a block, optionally finalize it, and wait for it to be indexed by mapping-sync.
// This ensures the block is visible via eth_getBlockByNumber before returning.
// When finalize is false the block is still imported (best block); we wait for it to be
// visible as "latest". The node exposes the best chain to eth RPC, so this works for both.
export async function createAndFinalizeBlock(web3: Web3, finalize: boolean = true) {
const response = await customRequest(web3, "engine_createBlock", [true, finalize, null]);
if (!response.result) {
if (!response?.result?.hash) {
throw new Error(`Unexpected result: ${JSON.stringify(response)}`);
}
const blockHash = response.result.hash as string;

// Use chain head as source of truth for the new block height, then wait until
// mapping-sync exposes that exact block via eth RPC.
const head = (await customRequest(web3, "chain_getHeader", [])).result;
if (!head?.number) {
throw new Error(`Unexpected chain head response: ${JSON.stringify(head)}`);
// Get the block number from the created block's header. Poll until the header is visible
// (import can lag) and retry on transient RPC errors.
const headerTimeout = 10_000;
const headerStart = Date.now();
let header: { number?: string } | null = null;
let headerLastError: Error | null = null;
while (Date.now() - headerStart < headerTimeout) {
try {
const headerResp = await customRequest(web3, "chain_getHeader", [blockHash]);
const h = headerResp.result as { number?: string } | null;
if (h?.number != null) {
header = h;
break;
}
} catch (error) {
headerLastError = error instanceof Error ? error : new Error(String(error));
}
await new Promise<void>((r) => setTimeout(r, 50));
}
if (!header?.number) {
const errSuffix = headerLastError ? ` (last error: ${headerLastError.message})` : "";
throw new Error(`chain_getHeader(${blockHash}) returned no header for created block${errSuffix}`);
}
const expectedNumber = parseInt(header.number, 16);
const expectedBlockTag = "0x" + expectedNumber.toString(16);

await waitForBlock(web3, expectedBlockTag, 10_000);

await waitForBlock(web3, head.number, 5000);
// Also wait for eth_blockNumber / "latest" to be at least the new block, so tests that
// assert on getBlockNumber() or use "latest" see the block we just created.
// Use >= so we don't timeout if the node advances past expectedNumber between polls.
// Retry on transient RPC errors instead of failing fast.
const rpcSyncTimeout = 10_000;
const rpcStart = Date.now();
let rpcLastError: Error | null = null;
while (Date.now() - rpcStart < rpcSyncTimeout) {
try {
const current = await customRequest(web3, "eth_blockNumber", []);
const n = current.result != null ? parseInt(current.result, 16) : -1;
if (n >= expectedNumber) {
return;
}
} catch (error) {
rpcLastError = error instanceof Error ? error : new Error(String(error));
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
await new Promise<void>((r) => setTimeout(r, 50));
}
const rpcErrorSuffix = rpcLastError ? ` (last error: ${rpcLastError.message})` : "";
throw new Error(`eth_blockNumber did not reach ${expectedNumber} after ${rpcSyncTimeout}ms${rpcErrorSuffix}`);
}

// Create a block and finalize it without waiting for indexing.
Expand All @@ -92,6 +136,19 @@ export async function createAndFinalizeBlockNowait(web3: Web3) {
}
}

// Wait for a receipt to be available for a given transaction hash.
export async function waitForReceipt(web3: Web3, txHash: string, timeoutMs = 10000) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const receipt = await web3.eth.getTransactionReceipt(txHash);
if (receipt !== null) {
return receipt;
}
await new Promise<void>((resolve) => setTimeout(resolve, 50));
}
throw new Error(`Timed out waiting for receipt ${txHash}`);
}

export async function startFrontierNode(
provider?: string,
additionalArgs: string[] = []
Expand Down
Loading