Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement EIP-2930 #1379

Merged
merged 71 commits into from
Apr 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
5577e71
Delete unused type
alcuadrado Apr 6, 2021
5ff3e63
Add common access list io-ts types
alcuadrado Apr 6, 2021
7b4af7c
Update call and tx request io-ts types
alcuadrado Apr 6, 2021
fce2d7c
Partial implementation
alcuadrado Apr 6, 2021
9657e60
Fix receipt validation when forking from before byzantium
alcuadrado Apr 6, 2021
131566b
Add TODO
alcuadrado Apr 6, 2021
7722cac
Update RPC output types
alcuadrado Apr 6, 2021
784283e
Improve doc of fake sender tx
alcuadrado Apr 7, 2021
14312a8
Add missing override to FakeSenderTransaction
alcuadrado Apr 7, 2021
66a56b5
Create FakeSenderAccessList2930Transaction
alcuadrado Apr 7, 2021
e35e3d2
Create a fake access list tx when appropiate
alcuadrado Apr 7, 2021
11c88f3
Handle fake access list txs in the tx pool
alcuadrado Apr 7, 2021
01aa3d3
Fix tx pool serialization
alcuadrado Apr 7, 2021
dfa1d17
Simplify some type definitions
alcuadrado Apr 7, 2021
aa55a93
Add scafolds of RPC validation and output tests
alcuadrado Apr 7, 2021
402226e
Update packages/hardhat-core/src/internal/hardhat-network/provider/mo…
alcuadrado Apr 7, 2021
168f389
Update packages/hardhat-core/src/internal/hardhat-network/provider/mo…
alcuadrado Apr 7, 2021
eb6556f
Update packages/hardhat-core/src/internal/hardhat-network/provider/Tx…
alcuadrado Apr 7, 2021
912780c
Add more tests and address PR comments
alcuadrado Apr 7, 2021
38a576e
Don't pass numbers to tx's constructors
alcuadrado Apr 7, 2021
e63f330
Add test for access list tx from impersonated account
fvictorio Apr 8, 2021
eb46abf
Add support for EIP-2930 txs to LocalAccountsProvider
fvictorio Apr 7, 2021
4753312
Check that the raw tx matches the sent tx values
fvictorio Apr 8, 2021
8795510
Merge pull request #1383 from nomiclabs/local-accounts-provider-eip2930
alcuadrado Apr 8, 2021
e88a83c
Test given block number instead of next one
fvictorio Apr 5, 2021
9dde395
Run node tests in a Berlin block
fvictorio Apr 6, 2021
8cf76ae
Implement EIP2929StateManager interface in ForkStateManager
fvictorio Apr 6, 2021
43f65ac
Add EIP-2930 properties to RpcTransaction
fvictorio Apr 6, 2021
edfe509
Add TODO with link to relevant issue
fvictorio Apr 6, 2021
43d91b1
Call methods from DefaultStateManager instead of copying them
fvictorio Apr 6, 2021
9be4770
Rename ForkPoint and blockNumber
fvictorio Apr 6, 2021
2274700
Add ReadOnlyValidEIP2930Transaction
fvictorio Apr 7, 2021
8867b5d
Update error messages in ReadOnlyValidEIP2930Transaction
fvictorio Apr 8, 2021
fb21cae
Remove unnecessary overrides
fvictorio Apr 8, 2021
c78adbc
Update packages/hardhat-core/src/internal/hardhat-network/provider/fo…
fvictorio Apr 8, 2021
34ce0c7
Use transaction type instead of accessList
fvictorio Apr 8, 2021
1cfe9d7
Remove workaround in HardhatNode test
fvictorio Apr 8, 2021
fa0f6ac
Delete unused type
alcuadrado Apr 6, 2021
4a3ecc6
Add common access list io-ts types
alcuadrado Apr 6, 2021
0f16a45
Update call and tx request io-ts types
alcuadrado Apr 6, 2021
52c0aca
Partial implementation
alcuadrado Apr 6, 2021
c4152cc
Fix receipt validation when forking from before byzantium
alcuadrado Apr 6, 2021
ee5d32c
Add TODO
alcuadrado Apr 6, 2021
b8bd76a
Update RPC output types
alcuadrado Apr 6, 2021
bd9e1eb
Improve doc of fake sender tx
alcuadrado Apr 7, 2021
6b98ce7
Add missing override to FakeSenderTransaction
alcuadrado Apr 7, 2021
6346e0f
Create FakeSenderAccessList2930Transaction
alcuadrado Apr 7, 2021
c72e447
Create a fake access list tx when appropiate
alcuadrado Apr 7, 2021
94a68f6
Handle fake access list txs in the tx pool
alcuadrado Apr 7, 2021
ab891ec
Fix tx pool serialization
alcuadrado Apr 7, 2021
c69e127
Simplify some type definitions
alcuadrado Apr 7, 2021
3a9f187
Add scafolds of RPC validation and output tests
alcuadrado Apr 7, 2021
9e5e191
Update packages/hardhat-core/src/internal/hardhat-network/provider/mo…
alcuadrado Apr 7, 2021
90a77e7
Update packages/hardhat-core/src/internal/hardhat-network/provider/mo…
alcuadrado Apr 7, 2021
46fc065
Update packages/hardhat-core/src/internal/hardhat-network/provider/Tx…
alcuadrado Apr 7, 2021
eba87f6
Add more tests and address PR comments
alcuadrado Apr 7, 2021
7543466
Don't pass numbers to tx's constructors
alcuadrado Apr 7, 2021
879c73b
Add test for access list tx from impersonated account
fvictorio Apr 8, 2021
89f4514
Add support for EIP-2930 txs to LocalAccountsProvider
fvictorio Apr 7, 2021
53ad786
Check that the raw tx matches the sent tx values
fvictorio Apr 8, 2021
92e80d7
Fix provider error assertions
alcuadrado Apr 8, 2021
1bb71e6
Change an incorrect EIP number
alcuadrado Apr 8, 2021
56640b2
Move the workarounds to a different file
alcuadrado Apr 8, 2021
be76f15
Add EIP-2930 txs tests
alcuadrado Apr 8, 2021
62360a9
Accept null chainIds
fvictorio Apr 8, 2021
347af93
Improve full block test and run it from byzantium
alcuadrado Apr 8, 2021
69f9ad3
Throw if trying to initialize a hadfork with a blck that doesn't exist
alcuadrado Apr 8, 2021
1b33410
Disable linter rule in a line
alcuadrado Apr 8, 2021
9f68baf
Merge branch 'update-tx-and-call-request-types' into forked-2930-tx
fvictorio Apr 8, 2021
c40e7eb
Merge pull request #1381 from nomiclabs/forked-2930-tx
alcuadrado Apr 8, 2021
6587851
Fix incorrect merge
alcuadrado Apr 8, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as t from "io-ts";

import { rpcData } from "./base-types";

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

export const rpcAccessList = t.array(rpcAccessListTuple);

export type RpcAccessListTuple = t.TypeOf<typeof rpcAccessListTuple>;

export type RpcAccessList = t.TypeOf<typeof rpcAccessList>;
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as t from "io-ts";

import { optional } from "../../../../util/io-ts";
import { rpcAccessList } from "../access-list";
import { rpcAddress, rpcData, rpcQuantity } from "../base-types";

// Type used by eth_call and eth_estimateGas
// TODO: Update to Berlin
export const rpcCallRequest = t.type(
{
from: optional(rpcAddress),
Expand All @@ -13,19 +13,9 @@ export const rpcCallRequest = t.type(
gasPrice: optional(rpcQuantity),
value: optional(rpcQuantity),
data: optional(rpcData),
accessList: optional(rpcAccessList),
},
"RpcCallRequest"
);

// This type represents possibly valid inputs to rpcCallRequest.
// TODO: It can probably be inferred by io-ts.
export interface RpcCallRequestInput {
from?: string;
to: string;
gas?: string;
gasPrice?: string;
value?: string;
data?: string;
}

export type RpcCallRequest = t.TypeOf<typeof rpcCallRequest>;
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as t from "io-ts";

import { optional } from "../../../../util/io-ts";
import { rpcAccessList } from "../access-list";
import { rpcAddress, rpcData, rpcQuantity } from "../base-types";

// Type used by eth_sendTransaction
// TODO: Update to Berlin
export const rpcTransactionRequest = t.type(
{
from: rpcAddress,
Expand All @@ -14,6 +14,8 @@ export const rpcTransactionRequest = t.type(
value: optional(rpcQuantity),
nonce: optional(rpcQuantity),
data: optional(rpcData),
accessList: optional(rpcAccessList),
chainId: optional(rpcQuantity),
},
"RpcTransactionRequest"
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as t from "io-ts";

import { nullable } from "../../../../util/io-ts";
import { nullable, optional } from "../../../../util/io-ts";
import { rpcAddress, rpcData, rpcHash, rpcQuantity } from "../base-types";

import { rpcLog } from "./log";
Expand All @@ -19,7 +19,10 @@ export const rpcTransactionReceipt = t.type(
contractAddress: nullable(rpcAddress),
logs: t.array(rpcLog, "RpcLog Array"),
logsBloom: rpcData,
status: rpcQuantity,
// This should be just optional, but Alchemy returns null
status: optional(nullable(rpcQuantity)),
alcuadrado marked this conversation as resolved.
Show resolved Hide resolved
root: optional(rpcData),
type: optional(rpcQuantity),
},
"RpcTransactionReceipt"
);
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const rpcTransaction = t.type(

// EIP-2929/2930 properties
type: optional(rpcQuantity),
chainId: optional(rpcQuantity),
chainId: optional(nullable(rpcQuantity)),
accessList: optional(rpcAccessList),
},
"RpcTransaction"
Expand Down
33 changes: 26 additions & 7 deletions packages/hardhat-core/src/internal/core/providers/accounts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Transaction as TransactionT } from "@ethereumjs/tx";
import { BN } from "ethereumjs-util";
import * as t from "io-ts";

Expand Down Expand Up @@ -188,9 +187,11 @@ export class LocalAccountsProvider extends ProviderWrapperWithChainId {
chainId: number,
privateKey: Buffer
): Promise<Buffer> {
const chains = await import("@ethereumjs/common/dist/chains");
const { chains } = await import("@ethereumjs/common/dist/chains");

const { Transaction } = await import("@ethereumjs/tx");
const { AccessListEIP2930Transaction, Transaction } = await import(
"@ethereumjs/tx"
);

const { default: Common } = await import("@ethereumjs/common");

Expand All @@ -200,18 +201,36 @@ export class LocalAccountsProvider extends ProviderWrapperWithChainId {
};

const common =
chains.chains.names[chainId] !== undefined
? new Common({ chain: chainId })
chains.names[chainId] !== undefined
? new Common({ chain: chainId, hardfork: "berlin" })
: Common.forCustomChain(
"mainnet",
{
chainId,
networkId: chainId,
},
"istanbul"
"berlin"
);

const transaction = Transaction.fromTxData(txData, { common });
let transaction;
if (txData.accessList !== undefined) {
// we convert the access list to the type
// that AccessListEIP2930Transaction expects
const accessList = txData.accessList.map(
({ address, storageKeys }) =>
[address, storageKeys] as [Buffer, Buffer[]]
);

transaction = AccessListEIP2930Transaction.fromTxData(
{
...txData,
accessList,
},
{ common }
);
} else {
transaction = Transaction.fromTxData(txData, { common });
}

const signedTransaction = transaction.sign(privateKey);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
import { Transaction } from "@ethereumjs/tx";
import { BN, toBuffer } from "ethereumjs-util";
import { TypedTransaction } from "@ethereumjs/tx";
import { BN } from "ethereumjs-util";
import {
List as ImmutableList,
Map as ImmutableMap,
Record as ImmutableRecord,
} from "immutable";

import { FakeSenderTransaction } from "./transactions/FakeSenderTransaction";
import { bnToHex } from "./utils/bnToHex";

export interface OrderedTransaction {
orderId: number;
data: Transaction | FakeSenderTransaction;
data: TypedTransaction;
}

interface ImmutableOrderedTransaction {
orderId: number;
fakeFrom: string | undefined;
data: ImmutableList<string>;
data: string;
txType: number;
}

export const makeSerializedTransaction = ImmutableRecord<
ImmutableOrderedTransaction
>({
orderId: 0,
fakeFrom: undefined,
data: ImmutableList(),
data: "",
txType: 0,
});

export type SerializedTransaction = ImmutableRecord<
Expand All @@ -48,7 +49,3 @@ export const makePoolState = ImmutableRecord<PoolState>({
hashToTransaction: ImmutableMap(),
blockGasLimit: bnToHex(new BN(9500000)),
});

export function retrieveNonce(tx: SerializedTransaction) {
return new BN(toBuffer(tx.get("data").get(0)));
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Common from "@ethereumjs/common";
import { Transaction } from "@ethereumjs/tx";
import { TransactionFactory, TypedTransaction } from "@ethereumjs/tx";
import { StateManager } from "@ethereumjs/vm/dist/state";
import { Address, BN, bufferToHex, toBuffer } from "ethereumjs-util";
import { List as ImmutableList, Record as ImmutableRecord } from "immutable";
Expand All @@ -12,10 +12,10 @@ import {
makeSerializedTransaction,
OrderedTransaction,
PoolState,
retrieveNonce,
SenderTransactions,
SerializedTransaction,
} from "./PoolState";
import { FakeSenderAccessListEIP2930Transaction } from "./transactions/FakeSenderAccessListEIP2930Transaction";
import { FakeSenderTransaction } from "./transactions/FakeSenderTransaction";
import { bnToHex } from "./utils/bnToHex";
import { reorganizeTransactionsLists } from "./utils/reorganizeTransactionsLists";
Expand All @@ -25,37 +25,47 @@ import { reorganizeTransactionsLists } from "./utils/reorganizeTransactionsLists
export function serializeTransaction(
tx: OrderedTransaction
): SerializedTransaction {
const fields = tx.data.raw().map((field) => bufferToHex(field));
const immutableFields = ImmutableList(fields);
const isFake = tx.data instanceof FakeSenderTransaction;
const rlpSerialization = bufferToHex(tx.data.serialize());
const isFake =
tx.data instanceof FakeSenderTransaction ||
tx.data instanceof FakeSenderAccessListEIP2930Transaction;
return makeSerializedTransaction({
orderId: tx.orderId,
fakeFrom: isFake ? tx.data.getSenderAddress().toString() : undefined,
data: immutableFields,
data: rlpSerialization,
txType: tx.data.transactionType,
});
}

type ArrayWithFrom<T> = T[] & { from?: string };

export function deserializeTransaction(
tx: SerializedTransaction,
common: Common
): OrderedTransaction {
const fields: ArrayWithFrom<Buffer> = tx
.get("data")
.map((field) => toBuffer(field))
.toArray();

const rlpSerialization = tx.get("data");
const fakeFrom = tx.get("fakeFrom");

let data;
if (fakeFrom !== undefined) {
data = FakeSenderTransaction.fromSenderAndValuesArray(
Address.fromString(fakeFrom),
fields,
{ common }
);
const sender = Address.fromString(fakeFrom);
const serialization = toBuffer(rlpSerialization);

if (tx.get("txType") === 1) {
data = FakeSenderAccessListEIP2930Transaction.fromSenderAndRlpSerializedTx(
sender,
serialization,
{ common }
);
} else {
data = FakeSenderTransaction.fromSenderAndRlpSerializedTx(
sender,
serialization,
{ common }
);
}
} else {
data = Transaction.fromValuesArray(fields, { common });
data = TransactionFactory.fromSerializedData(toBuffer(rlpSerialization), {
common,
});
}
return {
orderId: tx.get("orderId"),
Expand Down Expand Up @@ -84,7 +94,7 @@ export class TxPool {
this._deserializeTransaction = (tx) => deserializeTransaction(tx, common);
}

public async addTransaction(tx: Transaction) {
public async addTransaction(tx: TypedTransaction) {
const senderAddress = this._getSenderAddress(tx);
const senderNonce = await this.getExecutableNonce(senderAddress);

Expand Down Expand Up @@ -157,7 +167,8 @@ export class TxPool {
return account.nonce;
}

const lastPendingTxNonce = retrieveNonce(lastPendingTx);
const lastPendingTxNonce = this._deserializeTransaction(lastPendingTx).data
.nonce;
return lastPendingTxNonce.addn(1);
}

Expand Down Expand Up @@ -246,7 +257,7 @@ export class TxPool {
this._setQueued(newQueued);
}

private _getSenderAddress(tx: Transaction): Address {
private _getSenderAddress(tx: TypedTransaction): Address {
try {
return tx.getSenderAddress(); // verifies signature
} catch (e) {
Expand Down Expand Up @@ -286,7 +297,7 @@ export class TxPool {
return map.set(address, accountTxs.remove(indexOfTx));
}

private _addPendingTransaction(tx: Transaction) {
private _addPendingTransaction(tx: TypedTransaction) {
const orderedTx = serializeTransaction({
orderId: this._nextOrderId++,
data: tx,
Expand All @@ -298,15 +309,16 @@ export class TxPool {

const { newPending, newQueued } = reorganizeTransactionsLists(
accountTransactions.push(orderedTx),
this._getQueuedForAddress(hexSenderAddress) ?? ImmutableList()
this._getQueuedForAddress(hexSenderAddress) ?? ImmutableList(),
(stx) => this._deserializeTransaction(stx).data.nonce
);

this._setPendingForAddress(hexSenderAddress, newPending);
this._setQueuedForAddress(hexSenderAddress, newQueued);
this._setTransactionByHash(bufferToHex(tx.hash()), orderedTx);
}

private _addQueuedTransaction(tx: Transaction) {
private _addQueuedTransaction(tx: TypedTransaction) {
const orderedTx = serializeTransaction({
orderId: this._nextOrderId++,
data: tx,
Expand All @@ -323,7 +335,7 @@ export class TxPool {
}

private async _validateTransaction(
tx: Transaction,
tx: TypedTransaction,
senderAddress: Address,
senderNonce: BN
) {
Expand Down Expand Up @@ -385,7 +397,7 @@ export class TxPool {
}
}

private _knownTransaction(tx: Transaction): boolean {
private _knownTransaction(tx: TypedTransaction): boolean {
const senderAddress = tx.getSenderAddress().toString();
return (
this._transactionExists(tx, this._getPendingForAddress(senderAddress)) ||
Expand All @@ -394,7 +406,7 @@ export class TxPool {
}

private _transactionExists(
tx: Transaction,
tx: TypedTransaction,
txList: SenderTransactions | undefined
) {
const existingTx = txList?.find((etx) =>
Expand All @@ -403,13 +415,13 @@ export class TxPool {
return existingTx !== undefined;
}

private _txWithNonceExists(tx: Transaction): boolean {
private _txWithNonceExists(tx: TypedTransaction): boolean {
const senderAddress = tx.getSenderAddress().toString();
const queuedTxs: SenderTransactions =
this._getQueuedForAddress(senderAddress) ?? ImmutableList();

const queuedTx = queuedTxs.find((ftx) =>
retrieveNonce(ftx).eq(new BN(tx.nonce))
this._deserializeTransaction(ftx).data.nonce.eq(tx.nonce)
);
return queuedTx !== undefined;
}
Expand Down