Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

feat: london #1135

Merged
merged 83 commits into from
Sep 17, 2021
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
1fccc78
add eip1559 raw and rpc types
MicaiahReid Aug 27, 2021
6239952
add eip 1559 tx type
MicaiahReid Aug 27, 2021
48762ec
add standardized gas price across tx types
MicaiahReid Aug 27, 2021
496a187
add london hardfork
MicaiahReid Aug 27, 2021
cd5fedd
add eip1559 to vm tx type
MicaiahReid Aug 27, 2021
21ec3af
add base fee per gas to block
MicaiahReid Aug 27, 2021
3b9c179
use standardized gas price func
MicaiahReid Aug 27, 2021
6fc7af0
normalize type2 txs to legacy pre-berlin
MicaiahReid Aug 27, 2021
20cd47f
dont allow nullable nonce
MicaiahReid Aug 30, 2021
d5d7df1
refine baseFeePerGas formula
MicaiahReid Aug 30, 2021
f100fc5
normalize txs to type 1559 when appropriate
MicaiahReid Aug 30, 2021
079275e
eip1559 tests
MicaiahReid Aug 30, 2021
44f7163
export eip1559 txs with @ganache/ethereum-tx
MicaiahReid Sep 1, 2021
333a433
remove unused receipt params
MicaiahReid Sep 1, 2021
4215ae9
add tests for calculating baseFeePerGas
MicaiahReid Sep 1, 2021
dd917ed
fix bug with block.toJSON
MicaiahReid Sep 3, 2021
abd99ba
add effectiveGasPrice and methods to update
MicaiahReid Sep 3, 2021
8e1d289
order/reorder tx pool based off of effectiveGasPrice
MicaiahReid Sep 3, 2021
e4010e1
add capacity enum to miner
MicaiahReid Sep 3, 2021
13a81fa
remove unnecessary data
MicaiahReid Sep 7, 2021
ee49a30
remove instamine from miner. have calcNextBaseFee to return buff
MicaiahReid Sep 7, 2021
8ca42e7
add back gasPrice to receipt
MicaiahReid Sep 7, 2021
c80a003
test for miner and txPool ordering post london
MicaiahReid Sep 7, 2021
84184b4
add miner design decisions
MicaiahReid Sep 7, 2021
9ad02e6
initialize baseFeePerGas as an estimate on eip1559 tx
MicaiahReid Sep 7, 2021
4143a46
fix test that broke due to typed tx hash fix.
MicaiahReid Sep 7, 2021
2a6b1df
remove todo
MicaiahReid Sep 7, 2021
9196e8b
add `london` to `TRANSACTION_DATA_NON_ZERO_GAS`
davidmurdoch Sep 7, 2021
10aceb4
remove to-do and unnecessary conversion of datatype
MicaiahReid Sep 8, 2021
39791ec
improving "it" statements in tests
MicaiahReid Sep 8, 2021
53d841a
clarify comments in test
MicaiahReid Sep 8, 2021
4ba7e37
remove .only from test 😬
MicaiahReid Sep 8, 2021
4534884
add JSDOC for Heap.prototype.refresh and refresher
davidmurdoch Sep 9, 2021
43e3c77
set higher gas price for in revert test so CI will run
davidmurdoch Sep 9, 2021
3054f48
make tests pass
davidmurdoch Sep 16, 2021
e5ba47c
Fix genesis block parent hash
davidmurdoch Sep 16, 2021
eb2e95f
tx 1 and 2 tests shouldn't include 27 in their v values
davidmurdoch Sep 16, 2021
6fe0fd0
make transaction logging easier to read
davidmurdoch Sep 16, 2021
d7bc2c9
fix racey test
davidmurdoch Sep 16, 2021
cbecdfc
fix test comment typo
davidmurdoch Sep 17, 2021
874e9db
ignore logging output on chatty block test
davidmurdoch Sep 17, 2021
6489b60
update docs
davidmurdoch Sep 17, 2021
8f22af5
remove unused import
davidmurdoch Sep 17, 2021
93f50b1
undo some unnecessary changes
davidmurdoch Sep 17, 2021
0c7a271
add JSDOC to miner's new Capacity enum
davidmurdoch Sep 17, 2021
ca50436
use blockchain var
davidmurdoch Sep 17, 2021
d6206ad
use `Capacity` enum for call to `mine`
davidmurdoch Sep 17, 2021
1ec93f5
remove unused imports
davidmurdoch Sep 17, 2021
3e6b59a
fix comment typo
davidmurdoch Sep 17, 2021
c5e83d0
remove infinite test timeout
davidmurdoch Sep 17, 2021
e6e3571
remove unused dep
davidmurdoch Sep 17, 2021
ded9da3
fix type
davidmurdoch Sep 17, 2021
89a2657
don't skip all the tests :facepalm:
davidmurdoch Sep 17, 2021
90a4784
remove testing promise thing
davidmurdoch Sep 17, 2021
4931230
revert back to ignoring
davidmurdoch Sep 17, 2021
27e39ec
update docs
davidmurdoch Sep 17, 2021
bd7e6db
Remove unused import
MicaiahReid Sep 17, 2021
ab01639
remove test
davidmurdoch Sep 17, 2021
108240f
update docs
davidmurdoch Sep 17, 2021
1a556f0
Merge branch 'feat/eip-1559-tx-david-temp' of github.com:trufflesuite…
davidmurdoch Sep 17, 2021
f272389
fix comment typo
davidmurdoch Sep 17, 2021
641c60f
move miner's `refresher` off fthe miner class
davidmurdoch Sep 17, 2021
f93d1b6
try to fix github actions auth issue
davidmurdoch Sep 17, 2021
dd52708
short circuit in Wuantity.toBuffer
davidmurdoch Sep 17, 2021
b80b6fe
use bigint when doing gas math stuff
davidmurdoch Sep 17, 2021
dab86c0
fix tx type 1 signing error
davidmurdoch Sep 17, 2021
d75de71
always validate signature recovery ID
davidmurdoch Sep 17, 2021
7c73c80
update docs
davidmurdoch Sep 17, 2021
2f3ebca
rename repo-token to token in github actions
davidmurdoch Sep 17, 2021
7ec7b01
fix typo in github action config
davidmurdoch Sep 17, 2021
0c61590
fix typos
davidmurdoch Sep 17, 2021
67a48a5
fix typos
davidmurdoch Sep 17, 2021
d2e3a09
don't allow unlimited time in test
davidmurdoch Sep 17, 2021
68f6b53
use static eth_gasPrice for gas price in test
davidmurdoch Sep 17, 2021
8d583ee
undo whitespace change
davidmurdoch Sep 17, 2021
81bb0fd
Add error for transaction decode failure.
davidmurdoch Sep 17, 2021
34e702f
Update src/packages/utils/src/utils/heap.ts
davidmurdoch Sep 17, 2021
98324f0
rename lastMaxBlockBaseFee to maxPossibleBaseFee
davidmurdoch Sep 17, 2021
b3a22ae
rename calcMaxNBlocksBaseFee to calcNBlocksMaxBaseFee
davidmurdoch Sep 17, 2021
cb5feba
Merge branch 'feat/eip-1559-tx-david-temp' of github.com:trufflesuite…
davidmurdoch Sep 17, 2021
958cc40
move type def from runtime-block to block
davidmurdoch Sep 17, 2021
34d2240
update error message in test failure
davidmurdoch Sep 17, 2021
77b9561
update docs
davidmurdoch Sep 17, 2021
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
20 changes: 20 additions & 0 deletions src/chains/ethereum/block/src/block-params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// NOTE these params may need to be changed at each hardfork
// they can be tracked here: https://github.com/ethereumjs/ethereumjs-vm/blob/master/packages/common/src/hardforks/

import { Quantity } from "@ganache/utils";

export const BlockParams = {
/**
* Base fee per gas for blocks without a parent containing a base fee per gas.
*/
INITIAL_BASE_FEE_PER_GAS: 1000000000n,
/**
* Divisor used to set a block's target gas usage.
*/
ELASTICITY: 2n,

/**
* Divisor used to limit the amount the base fee per gas can change from one block to another.
*/
BASE_FEE_MAX_CHANGE_DENOMINATOR: 8n
};
46 changes: 43 additions & 3 deletions src/chains/ethereum/block/src/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
serialize
} from "./serialize";
import { Address } from "@ganache/ethereum-address";

import { BlockParams } from "./block-params";
export class Block {
protected _size: number;
protected _raw: EthereumRawBlockHeader;
Expand Down Expand Up @@ -64,12 +64,11 @@ export class Block {
});
}

toJSON(includeFullTransactions = false) {
toJSON(includeFullTransactions = false, common: Common) {
Copy link
Contributor

@MicaiahReid MicaiahReid Sep 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this already has access to common, but sometimes when we initialize a block, we don't set common to save some extra work that would be done when it's set. (see comment on lines 184-187 of runtime-block.ts for more info on that.)

So instead, we pass in common to this function. Any thoughts on always passing common into the block constructor, allowing us to use this._common here? @davidmurdoch

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably fine to leave it as is and fix when we re-organize the transactions classes

const hash = this.hash();
const txFn = this.getTxFn(includeFullTransactions);
const hashBuffer = hash.toBuffer();
const number = this.header.number.toBuffer();
const common = this._common;
const jsonTxs = this._rawTransactions.map((raw, index) => {
const [from, hash] = this._rawTransactionMetaData[index];
const extra: GanacheRawExtraTx = [
Expand Down Expand Up @@ -156,4 +155,45 @@ export class Block {
block._size = size;
return block;
}

static calcNextBaseFee(common: Common, parentBlock?: Block) {
if (!common.isActivatedEIP(1559)) {
return undefined;
}
let nextBaseFee: bigint;
// genesis block
if (parentBlock === undefined) {
nextBaseFee = BlockParams.INITIAL_BASE_FEE_PER_GAS;
} else {
const header = parentBlock.header;
const parentGasTarget =
header.gasLimit.toBigInt() / BlockParams.ELASTICITY;
const parentGasUsed = header.gasUsed.toBigInt();
const baseFeePerGas = header.baseFeePerGas
? header.baseFeePerGas.toBigInt()
: BlockParams.INITIAL_BASE_FEE_PER_GAS;
if (parentGasTarget === parentGasUsed) {
nextBaseFee = baseFeePerGas;
} else if (parentGasUsed > parentGasTarget) {
const gasUsedDelta = parentGasUsed - parentGasTarget;
const adjustedFeeDelta =
(baseFeePerGas * gasUsedDelta) /
parentGasTarget /
BlockParams.BASE_FEE_MAX_CHANGE_DENOMINATOR;
if (adjustedFeeDelta > 1n) {
nextBaseFee = baseFeePerGas + adjustedFeeDelta;
} else {
nextBaseFee = baseFeePerGas + 1n;
}
} else {
const gasUsedDelta = parentGasTarget - parentGasUsed;
const adjustedFeeDelta =
(baseFeePerGas * gasUsedDelta) /
parentGasTarget /
BlockParams.BASE_FEE_MAX_CHANGE_DENOMINATOR;
nextBaseFee = baseFeePerGas - adjustedFeeDelta;
}
}
return Quantity.from(nextBaseFee).toBuffer();
}
}
18 changes: 14 additions & 4 deletions src/chains/ethereum/block/src/runtime-block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type BlockHeader = {
extraData: Data;
mixHash: Data;
nonce: Data;
baseFeePerGas?: Quantity;
};

/**
Expand Down Expand Up @@ -79,7 +80,9 @@ export function makeHeader(
extraData: Data.from(raw[12]),
mixHash: Data.from(raw[13], 32),
nonce: Data.from(raw[14], 8),
totalDifficulty: Quantity.from(totalDifficulty, false)
totalDifficulty: Quantity.from(totalDifficulty, false),
baseFeePerGas:
raw[15] === BUFFER_EMPTY ? undefined : Quantity.from(raw[15], false)
};
}

Expand All @@ -96,6 +99,7 @@ export class RuntimeBlock {
gasLimit: BnExtra;
gasUsed: BnExtra;
timestamp: BnExtra;
baseFeePerGas?: BnExtra;
};

constructor(
Expand All @@ -106,7 +110,8 @@ export class RuntimeBlock {
gasUsed: Buffer,
timestamp: Quantity,
difficulty: Quantity,
previousBlockTotalDifficulty: Quantity
previousBlockTotalDifficulty: Quantity,
baseFeePerGas?: Buffer
) {
const ts = timestamp.toBuffer();
const coinbaseBuffer = coinbase.toBuffer();
Expand All @@ -120,7 +125,9 @@ export class RuntimeBlock {
).toBuffer(),
gasLimit: new BnExtra(gasLimit),
gasUsed: new BnExtra(gasUsed),
timestamp: new BnExtra(ts)
timestamp: new BnExtra(ts),
baseFeePerGas:
baseFeePerGas === undefined ? undefined : new BnExtra(baseFeePerGas)
};
}

Expand Down Expand Up @@ -154,7 +161,10 @@ export class RuntimeBlock {
header.timestamp.buf,
extraData.toBuffer(),
BUFFER_32_ZERO, // mixHash
BUFFER_8_ZERO // nonce
BUFFER_8_ZERO, // nonce
header.baseFeePerGas === undefined
? BUFFER_EMPTY
: header.baseFeePerGas.buf
];
const { totalDifficulty } = header;
const txs: TypedDatabaseTransaction[] = [];
Expand Down
3 changes: 2 additions & 1 deletion src/chains/ethereum/block/src/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export type EthereumRawBlockHeader = [
timestamp: Buffer,
extraData: Buffer,
mixHash: Buffer,
nonce: Buffer
nonce: Buffer,
baseFeePerGas?: Buffer
];
export type EthereumRawBlock = [
rawHeader: EthereumRawBlockHeader,
Expand Down
103 changes: 100 additions & 3 deletions src/chains/ethereum/block/tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,103 @@
import assert from "assert";
import ethereumBlock from "../";
import { Address } from "@ganache/ethereum-address";
import { BUFFER_ZERO, Data, Quantity } from "@ganache/utils";
import Common from "@ethereumjs/common";
import Wallet from "../../ethereum/src/wallet";
import { TransactionFactory } from "@ganache/ethereum-transaction";
import { TypedRpcTransaction } from "@ganache/ethereum-transaction";
import Blockchain from "../../ethereum/src/blockchain";
import { EthereumOptionsConfig } from "../../options/src/index";

describe("@ganache/ethereum-block", () => {
it("needs tests");
describe("@ganache/ethereum-block", async () => {
describe("baseFeePerGas calculations", () => {
let blockchain: Blockchain;
before(async function () {
this.timeout(0);
const privKey = `0x${"46".repeat(32)}`;
const privKeyData = Data.from(privKey);
const options = EthereumOptionsConfig.normalize({
wallet: {
accounts: [
{ secretKey: privKey, balance: 1000000000000000000000n },
{
secretKey: `0x${"46".repeat(31)}47`,
balance: 1000000000000000000000n
}
]
},
miner: {
blockGasLimit: "0xB749E0"
},
chain: { chainId: 1337 }
});
const wallet = new Wallet(options.wallet);
const [from, to] = wallet.addresses;
const fromAddress = new Address(from);
const tx: TypedRpcTransaction = {
type: "0x2",
from: from,
to: to,
maxFeePerGas: "0x344221FFF",
chainId: "0x539",
gas: "0x5208"
};

const common = Common.forCustomChain(
"mainnet",
{
name: "ganache",
chainId: 1337,
comment: "Local test network",
bootstrapNodes: []
},
"london"
);
blockchain = new Blockchain(options, fromAddress);
await blockchain.initialize(wallet.initialAccounts);
// to verify our calculations for the block's baseFeePerGas,
// we're comparing our data to geth. Geth's gasLimit changes
// every block, so we need to set those values here according
// to what geth had. We'll need to reset each time a block is
// mined and we'll need to mine blocks such that we have one
// with each of the cases: 1. gasUsed < gasTarget, 2. gasUsed >
// gasTarget, gasUsed = gasTarget.
const gethBlockData = [
{ txCount: 286, newGasLimit: 12000271 },
{ txCount: 290, newGasLimit: 11988553 },
{ txCount: 10, newGasLimit: 11976847 },
// because we use the previous block to calculate the base fee,
// send/mine one more tx so we can see what the reulting base fee
// is from the previous block
{ txCount: 1, newGasLimit: 11965152 }
];

for (let i = 0; i < gethBlockData.length; i++) {
const data = gethBlockData[i];
options.miner.blockGasLimit = Quantity.from(data.newGasLimit);
blockchain.pause();
for (let j = 0; j < data.txCount; j++) {
const feeMarketTx = TransactionFactory.fromRpc(tx, common);
await blockchain.queueTransaction(feeMarketTx, privKeyData);
}
// mine all txs in that group before moving onto the next block
await blockchain.resume();
}
});
it("has initial baseFeePerGas of 1000000000 for genesis block", async () => {
const block = await blockchain.blocks.get(BUFFER_ZERO);
assert.strictEqual(block.header.baseFeePerGas.toNumber(), 1000000000);
});
it("calculates baseFeePerGas correctly when gasUsed is equal to gasTarget", async () => {
const block = await blockchain.blocks.get(Buffer.from([2]));
assert.strictEqual(block.header.baseFeePerGas.toNumber(), 875106911);
});
it("calculates baseFeePerGas correctly when gasUsed is above gasTarget", async () => {
const block = await blockchain.blocks.get(Buffer.from([3]));
assert.strictEqual(block.header.baseFeePerGas.toNumber(), 876853759);
});
it("calculates baseFeePerGas correctly when gasUsed is below gasTarget", async () => {
const block = await blockchain.blocks.get(Buffer.from([4]));
assert.strictEqual(block.header.baseFeePerGas.toNumber(), 771090691);
});
});
});
28 changes: 22 additions & 6 deletions src/chains/ethereum/ethereum/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { parseFilterDetails, parseFilterRange } from "./helpers/filter-parsing";
import { decode } from "@ganache/rlp";
import { Address } from "@ganache/ethereum-address";
import { GanacheRawBlock } from "@ganache/ethereum-block";
import { Capacity } from "./miner/miner";

// Read in the current ganache version from core's package.json
const { version } = $INLINE_JSON("../../../../packages/ganache/package.json");
Expand Down Expand Up @@ -276,13 +277,21 @@ export default class EthereumApi implements Api {
// Developers like to move the blockchain forward by thousands of blocks
// at a time and doing this would make it way faster
for (let i = 0; i < blocks; i++) {
const transactions = await blockchain.mine(-1, timestamp, true);
const transactions = await blockchain.mine(
Capacity.FillBlock,
timestamp,
true
);
if (vmErrorsOnRPCResponse) {
assertExceptionalTransactions(transactions);
}
}
} else {
const transactions = await blockchain.mine(-1, arg as number, true);
const transactions = await blockchain.mine(
Capacity.FillBlock,
arg as number,
true
);
if (vmErrorsOnRPCResponse) {
assertExceptionalTransactions(transactions);
}
Expand Down Expand Up @@ -325,7 +334,7 @@ export default class EthereumApi implements Api {

// TODO: do we need to mine a block here? The changes we're making really don't make any sense at all
// and produce an invalid trie going forward.
await blockchain.mine(0);
await blockchain.mine(Capacity.Empty);
return true;
}

Expand Down Expand Up @@ -860,7 +869,7 @@ export default class EthereumApi implements Api {
@assertArgLength(1, 2)
async eth_getBlockByNumber(number: QUANTITY | Tag, transactions = false) {
const block = await this.#blockchain.blocks.get(number).catch(_ => null);
return block ? block.toJSON(transactions) : null;
return block ? block.toJSON(transactions, this.#blockchain.common) : null;
}

/**
Expand Down Expand Up @@ -1638,18 +1647,25 @@ export default class EthereumApi implements Api {
tx.gas = defaultLimit;
}
}

if (tx.gasPrice.isNull()) {
if ("gasPrice" in tx && tx.gasPrice.isNull()) {
tx.gasPrice = this.#options.miner.defaultGasPrice;
}

if ("maxFeePerGas" in tx && tx.maxFeePerGas.isNull()) {
const block = await this.#blockchain.blocks.get("latest").catch(_ => null); // prettier-ignore
tx.maxFeePerGas = Quantity.from(
Block.calcNextBaseFee(this.#blockchain.common, block)
);
}

if (isUnlockedAccount) {
const secretKey = wallet.unlockedAccounts.get(fromString);
return blockchain.queueTransaction(tx, secretKey);
} else {
return blockchain.queueTransaction(tx);
}
}

/**
* Signs a transaction that can be submitted to the network at a later time using `eth_sendRawTransaction`.
*
Expand Down
Loading