Skip to content

Commit

Permalink
Merge pull request #1376 from nomiclabs/fork-berlin
Browse files Browse the repository at this point in the history
Add test block with EIP-2930 tx
  • Loading branch information
alcuadrado authored Apr 7, 2021
2 parents 888378a + 8acefe2 commit 4566718
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import * as t from "io-ts";
import { nullable, optional } from "../../../../util/io-ts";
import { rpcAddress, rpcData, rpcHash, rpcQuantity } from "../base-types";

const rpcAccessListItem = t.type({
address: rpcData,
storageKeys: t.array(rpcData),
});

export const rpcAccessList = t.array(rpcAccessListItem);

export type RpcTransaction = t.TypeOf<typeof rpcTransaction>;
export const rpcTransaction = t.type(
{
Expand All @@ -21,6 +28,11 @@ export const rpcTransaction = t.type(
v: rpcQuantity,
r: rpcQuantity,
s: rpcQuantity,

// EIP-2929/2930 properties
type: optional(rpcQuantity),
chainId: optional(rpcQuantity),
accessList: optional(rpcAccessList),
},
"RpcTransaction"
);
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { StateManager } from "@ethereumjs/vm/dist/state";
import { DefaultStateManager } from "@ethereumjs/vm/dist/state";
import { EIP2929StateManager } from "@ethereumjs/vm/dist/state/interface";
import {
Account,
Address,
Expand Down Expand Up @@ -37,7 +38,7 @@ const notCheckpointedError = (method: string) =>
const notSupportedError = (method: string) =>
new Error(`${method} is not supported when forking from remote network`);

export class ForkStateManager implements StateManager {
export class ForkStateManager implements EIP2929StateManager {
private _state: State = ImmutableMap();
private _initialStateRoot: string = randomHash();
private _stateRoot: string = this._initialStateRoot;
Expand All @@ -47,6 +48,12 @@ export class ForkStateManager implements StateManager {
private _contextBlockNumber = this._forkBlockNumber.clone();
private _contextChanged = false;

// used by the DefaultStateManager calls
private _accessedStorage: Array<Map<string, Set<string>>> = [new Map()];
private _accessedStorageReverted: Array<Map<string, Set<string>>> = [
new Map(),
];

constructor(
private readonly _jsonRpcClient: JsonRpcClient,
private readonly _forkBlockNumber: BN
Expand Down Expand Up @@ -371,6 +378,37 @@ export class ForkStateManager implements StateManager {
return value;
}

// the following methods are copied verbatim from
// DefaultStateManager

public isWarmedAddress(address: Buffer): boolean {
return DefaultStateManager.prototype.isWarmedAddress.call(this, address);
}

public addWarmedAddress(address: Buffer): void {
return DefaultStateManager.prototype.addWarmedAddress.call(this, address);
}

public isWarmedStorage(address: Buffer, slot: Buffer): boolean {
return DefaultStateManager.prototype.isWarmedStorage.call(
this,
address,
slot
);
}

public addWarmedStorage(address: Buffer, slot: Buffer): void {
return DefaultStateManager.prototype.addWarmedStorage.call(
this,
address,
slot
);
}

public clearWarmedAccounts(): void {
return DefaultStateManager.prototype.clearWarmedAccounts.call(this);
}

private _putAccount(address: Address, account: Account): void {
// Because the vm only ever modifies the nonce, balance and codeHash using this
// method we ignore the stateRoot property
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { TxData } from "@ethereumjs/tx";
import { AccessListEIP2930TxData, TxData } from "@ethereumjs/tx";

import { RpcTransaction } from "../../../core/jsonrpc/types/output/transaction";

export function rpcToTxData(rpcTransaction: RpcTransaction): TxData {
return {
export function rpcToTxData(
rpcTransaction: RpcTransaction
): TxData | AccessListEIP2930TxData {
const txData: AccessListEIP2930TxData = {
gasLimit: rpcTransaction.gas,
gasPrice: rpcTransaction.gasPrice,
to: rpcTransaction.to ?? undefined,
Expand All @@ -14,4 +16,19 @@ export function rpcToTxData(rpcTransaction: RpcTransaction): TxData {
s: rpcTransaction.s,
value: rpcTransaction.value,
};

if (rpcTransaction.type !== undefined) {
txData.type = rpcTransaction.type;
}
if (rpcTransaction.chainId !== undefined) {
txData.chainId = rpcTransaction.chainId;
}
if (rpcTransaction.accessList !== undefined) {
txData.accessList = rpcTransaction.accessList.map((item) => [
item.address,
item.storageKeys,
]);
}

return txData;
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,12 @@ import {

// tslint:disable no-string-literal

interface ForkPoint {
interface ForkedBlock {
networkName: string;
url?: string;
/**
* Fork block number.
* This is the last observable block from the remote blockchain.
* Later blocks are all constructed by Hardhat Network.
*/
blockNumber: number;
blockToRun: number;
chainId: number;
hardfork: "istanbul" | "muirGlacier";
hardfork: "istanbul" | "muirGlacier" | "berlin";
}

describe("HardhatNode", () => {
Expand Down Expand Up @@ -552,58 +547,58 @@ describe("HardhatNode", () => {

describe("full block", function () {
this.timeout(120000);
// Note that here `blockNumber` is the number of the forked block, not the number of the "simulated" block.
// Tests are written to fork this block and execute all transactions of the block following the forked block.
// This means that if the forked block number is 9300076, what the test will do is:
// - setup a forked blockchain based on block 9300076
// - fetch all transactions from 9300077
// - create a new block with them
// - execute the whole block and save it with the rest of the blockchain
const forkPoints: ForkPoint[] = [
const forkedBlocks: ForkedBlock[] = [
{
networkName: "mainnet",
url: ALCHEMY_URL,
blockNumber: 9300076,
blockToRun: 9300077,
chainId: 1,
hardfork: "muirGlacier",
},
{
networkName: "kovan",
url: (ALCHEMY_URL ?? "").replace("mainnet", "kovan"),
blockNumber: 23115226,
blockToRun: 23115227,
chainId: 42,
hardfork: "istanbul",
},
{
networkName: "rinkeby",
url: (ALCHEMY_URL ?? "").replace("mainnet", "rinkeby"),
blockNumber: 8004364,
blockToRun: 8004365,
chainId: 4,
hardfork: "istanbul",
},
{
networkName: "ropsten",
url: (ALCHEMY_URL ?? "").replace("mainnet", "ropsten"),
blockToRun: 9812365, // this block has a EIP-2930 tx
chainId: 3,
hardfork: "berlin",
},
];

for (const {
url,
blockNumber,
blockToRun,
networkName,
chainId,
hardfork,
} of forkPoints) {
} of forkedBlocks) {
it(`should run a ${networkName} block and produce the same results`, async function () {
if (url === undefined || url === "") {
this.skip();
}

const forkConfig = {
jsonRpcUrl: url,
blockNumber,
blockNumber: blockToRun - 1,
};

const { forkClient } = await makeForkClient(forkConfig);

const rpcBlock = await forkClient.getBlockByNumber(
new BN(blockNumber + 1),
new BN(blockToRun),
true
);

Expand All @@ -626,16 +621,13 @@ describe("HardhatNode", () => {

const [common, forkedNode] = await HardhatNode.create(forkedNodeConfig);

let block = Block.fromBlockData(rpcToBlockData(rpcBlock), { common });
const block = Block.fromBlockData(rpcToBlockData(rpcBlock), {
common,
freeze: false,
});

block = Block.fromBlockData(
{
...block,
// We wipe the receiptTrie just to be sure that it's not copied over
header: { ...block.header, receiptTrie: Buffer.alloc(32, 0) },
},
{ common }
);
// We wipe the receiptTrie just to be sure that it's not copied over
(block as any).header.receiptTrie = Buffer.alloc(32, 0);

forkedNode["_vmTracer"].disableTracing();

Expand All @@ -650,15 +642,16 @@ describe("HardhatNode", () => {

const modifiedBlock = afterBlockEvent.block;

await forkedNode["_vm"].blockchain.putBlock(modifiedBlock);
await forkedNode["_saveBlockAsSuccessfullyRun"](
modifiedBlock,
afterBlockEvent
);
// Restore the receipt trie
(block as any).header.receiptTrie = modifiedBlock.header.receiptTrie;

const newBlock = await forkedNode.getBlockByNumber(
new BN(blockNumber + 1)
);
// TODO we should use modifiedBlock instead of block here,
// but we can't because of a bug in the vm
// TODO: Change this once https://github.com/ethereumjs/ethereumjs-monorepo/pull/1185 is released.
await forkedNode["_vm"].blockchain.putBlock(block);
await forkedNode["_saveBlockAsSuccessfullyRun"](block, afterBlockEvent);

const newBlock = await forkedNode.getBlockByNumber(new BN(blockToRun));

if (newBlock === undefined) {
assert.fail();
Expand Down

0 comments on commit 4566718

Please sign in to comment.