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

Commit 1c378ac

Browse files
committed
add merge support
1 parent 27e0ad6 commit 1c378ac

File tree

15 files changed

+201
-33
lines changed

15 files changed

+201
-33
lines changed

src/chains/ethereum/block/src/runtime-block.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
import {
2-
Data,
3-
Quantity,
4-
BUFFER_EMPTY,
5-
BUFFER_32_ZERO,
6-
BUFFER_8_ZERO
7-
} from "@ganache/utils";
1+
import { Data, Quantity, BUFFER_EMPTY, BUFFER_8_ZERO } from "@ganache/utils";
82
import { KECCAK256_RLP_ARRAY } from "@ethereumjs/util";
93
import { EthereumRawBlockHeader, serialize } from "./serialize";
104
import { Address } from "@ganache/ethereum-address";
@@ -88,7 +82,11 @@ export class RuntimeBlock {
8882
gasLimit: bigint;
8983
gasUsed: bigint;
9084
timestamp: bigint;
91-
baseFeePerGas?: bigint;
85+
mixHash: Buffer;
86+
// prevRandao is mixHash, but for the merge and it must be
87+
// given to the VM this way
88+
prevRandao: Buffer;
89+
baseFeePerGas: bigint | undefined;
9290
};
9391

9492
constructor(
@@ -100,7 +98,8 @@ export class RuntimeBlock {
10098
timestamp: Quantity,
10199
difficulty: Quantity,
102100
previousBlockTotalDifficulty: Quantity,
103-
baseFeePerGas?: bigint
101+
mixHash: Buffer,
102+
baseFeePerGas: bigint | undefined
104103
) {
105104
const ts = timestamp.toBuffer();
106105
const coinbaseBuffer = coinbase.toBuffer();
@@ -115,7 +114,9 @@ export class RuntimeBlock {
115114
gasLimit: Quantity.toBigInt(gasLimit),
116115
gasUsed: Quantity.toBigInt(gasUsed),
117116
timestamp: Quantity.toBigInt(ts),
118-
baseFeePerGas: baseFeePerGas === undefined ? 0n : baseFeePerGas
117+
baseFeePerGas: baseFeePerGas === undefined ? 0n : baseFeePerGas,
118+
mixHash,
119+
prevRandao: mixHash
119120
};
120121
// When forking we might get a block that doesn't have a baseFeePerGas value,
121122
// but EIP-1559 might be active on our chain. We need to keep track on if
@@ -153,7 +154,7 @@ export class RuntimeBlock {
153154
gasUsed === 0n ? BUFFER_EMPTY : Quantity.toBuffer(gasUsed),
154155
Quantity.toBuffer(header.timestamp),
155156
extraData.toBuffer(),
156-
BUFFER_32_ZERO, // mixHash
157+
header.mixHash,
157158
BUFFER_8_ZERO // nonce
158159
];
159160
if (this.serializeBaseFeePerGas && header.baseFeePerGas !== undefined) {

src/chains/ethereum/ethereum/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@
7474
"env-paths": "2.2.1",
7575
"eth-sig-util": "2.5.3",
7676
"ethereumjs-abi": "0.6.8",
77-
"keccak": "3.0.2",
7877
"level": "8.0.0",
7978
"lodash.clonedeep": "4.5.0",
8079
"lru-cache": "6.0.0",

src/chains/ethereum/ethereum/src/api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,7 @@ export default class EthereumApi implements Api {
954954
parentHeader.timestamp,
955955
options.miner.difficulty,
956956
parentHeader.totalDifficulty,
957+
blockchain.getMixHash(parentHeader.parentHash.toBuffer()),
957958
0n // no baseFeePerGas for estimates
958959
);
959960
const runArgs: EstimateGasRunArgs = {
@@ -2879,6 +2880,7 @@ export default class EthereumApi implements Api {
28792880
parentHeader.timestamp,
28802881
options.miner.difficulty,
28812882
parentHeader.totalDifficulty,
2883+
blockchain.getMixHash(parentHeader.parentHash.toBuffer()),
28822884
baseFeePerGasBigInt
28832885
);
28842886

src/chains/ethereum/ethereum/src/blockchain.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ import {
3838
BUFFER_32_ZERO,
3939
BUFFER_256_ZERO,
4040
findInsertPosition,
41-
KNOWN_CHAINIDS
41+
KNOWN_CHAINIDS,
42+
keccak
4243
} from "@ganache/utils";
4344
import AccountManager from "./data-managers/account-manager";
4445
import BlockManager from "./data-managers/block-manager";
@@ -265,6 +266,8 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
265266
);
266267
}
267268

269+
this.isPostMerge = this.common.gteHardfork("merge");
270+
268271
const blocks = (this.blocks = await BlockManager.initialize(
269272
this,
270273
common,
@@ -596,12 +599,18 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
596599
minerOptions.blockGasLimit.toBuffer(),
597600
BUFFER_ZERO,
598601
Quantity.from(timestamp),
599-
minerOptions.difficulty,
602+
this.isPostMerge ? Quantity.Zero : minerOptions.difficulty,
600603
previousHeader.totalDifficulty,
604+
this.getMixHash(previousBlock.hash().toBuffer()),
601605
Block.calcNextBaseFee(previousBlock)
602606
);
603607
};
604608

609+
getMixHash(data: Buffer) {
610+
// mixHash is used as an RNG post merge hardfork
611+
return this.isPostMerge ? keccak(data) : BUFFER_32_ZERO;
612+
}
613+
605614
isStarted = () => {
606615
return this.#state === Status.started;
607616
};
@@ -767,8 +776,9 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
767776
blockGasLimit.toBuffer(),
768777
BUFFER_ZERO,
769778
Quantity.from(timestamp),
770-
minerOptions.difficulty,
779+
this.isPostMerge ? Quantity.Zero : minerOptions.difficulty,
771780
fallbackBlock.header.totalDifficulty,
781+
this.getMixHash(fallbackBlock.hash().toBuffer()),
772782
baseFeePerGas
773783
);
774784

@@ -806,15 +816,19 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
806816
const baseFeePerGas = this.common.isActivatedEIP(1559)
807817
? Block.INITIAL_BASE_FEE_PER_GAS
808818
: undefined;
819+
809820
const genesis = new RuntimeBlock(
810821
rawBlockNumber,
811822
Data.from(BUFFER_32_ZERO),
812823
this.coinbase,
813824
blockGasLimit.toBuffer(),
814825
BUFFER_ZERO,
815826
Quantity.from(timestamp),
816-
this.#options.miner.difficulty,
827+
this.isPostMerge ? Quantity.Zero : this.#options.miner.difficulty,
817828
Quantity.Zero, // we start the totalDifficulty at 0
829+
// we use the initial trie root as the genesis block's mixHash as it
830+
// is deterministic based on initial wallet conditions
831+
this.isPostMerge ? keccak(this.trie.root()) : BUFFER_32_ZERO,
818832
baseFeePerGas
819833
);
820834

@@ -1447,6 +1461,8 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
14471461
};
14481462
};
14491463

1464+
isPostMerge: boolean;
1465+
14501466
#prepareNextBlock = (
14511467
targetBlock: Block,
14521468
parentBlock: Block,
@@ -1464,8 +1480,9 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
14641480
BUFFER_ZERO,
14651481
// make sure we use the same timestamp as the target block
14661482
targetBlock.header.timestamp,
1467-
this.#options.miner.difficulty,
1483+
this.isPostMerge ? Quantity.Zero : this.#options.miner.difficulty,
14681484
parentBlock.header.totalDifficulty,
1485+
this.getMixHash(parentBlock.hash().toBuffer()),
14691486
Block.calcNextBaseFee(parentBlock)
14701487
) as RuntimeBlock & {
14711488
uncleHeaders: [];

src/chains/ethereum/ethereum/src/data-managers/block-manager.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ export default class BlockManager extends Manager<Block> {
149149
getBlockByTag(tag: Tag) {
150150
switch (tag) {
151151
case "latest":
152+
case "finalized":
153+
case "safe":
152154
return this.latest;
153155
case "pending":
154156
// TODO: build a real pending block!

src/chains/ethereum/ethereum/src/wallet.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import secp256k1, { SECP256K1_N } from "@ganache/secp256k1";
1717
import { mnemonicToSeedSync } from "bip39";
1818
import { alea } from "seedrandom";
1919
import crypto from "crypto";
20-
import createKeccakHash from "keccak";
2120
import { writeFileSync } from "fs";
2221
import { EthereumInternalOptions } from "@ganache/ethereum-options";
2322
import { Address } from "@ganache/ethereum-address";
@@ -373,9 +372,7 @@ export default class Wallet {
373372
cipher.update(privateKey.toBuffer()),
374373
cipher.final()
375374
]);
376-
const mac = createKeccakHash("keccak256")
377-
.update(Buffer.concat([derivedKey.slice(16, 32), ciphertext]))
378-
.digest();
375+
const mac = keccak(Buffer.concat([derivedKey.slice(16, 32), ciphertext]));
379376
return {
380377
crypto: {
381378
cipher: CIPHER,
@@ -420,9 +417,9 @@ export default class Wallet {
420417
kdfParams.dklen,
421418
{ ...kdfParams, N: kdfParams.n }
422419
);
423-
localMac = createKeccakHash("keccak256")
424-
.update(Buffer.concat([derivedKey.slice(16, 32), ciphertext]))
425-
.digest();
420+
localMac = keccak(
421+
Buffer.concat([derivedKey.slice(16, 32), ciphertext])
422+
);
426423
} catch {
427424
localMac = null;
428425
}

src/chains/ethereum/ethereum/tests/api/eth/call.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { EthereumProvider } from "../../../src/provider";
44
import getProvider from "../../helpers/getProvider";
55
import compile, { CompileOutput } from "../../helpers/compile";
66
import { join } from "path";
7-
import { BUFFER_EMPTY, Data, Quantity } from "@ganache/utils";
7+
import { BUFFER_32_ZERO, BUFFER_EMPTY, Data, Quantity } from "@ganache/utils";
88
import { CallError } from "@ganache/ethereum-utils";
99
import Blockchain from "../../../src/blockchain";
1010
import Wallet from "../../../src/wallet";
@@ -884,6 +884,7 @@ describe("api", () => {
884884
parentHeader.timestamp,
885885
Quantity.One, // difficulty
886886
parentHeader.totalDifficulty,
887+
BUFFER_32_ZERO,
887888
parentHeader.baseFeePerGas.toBigInt()
888889
);
889890
simTx = {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.11;
3+
4+
contract Merge {
5+
uint256 public difficulty;
6+
7+
constructor() {
8+
require(block.difficulty > 1);
9+
difficulty = block.difficulty;
10+
}
11+
12+
function getCurrentDifficulty() public view returns (uint256) {
13+
require(block.difficulty > 1);
14+
return uint256(block.difficulty);
15+
}
16+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import assert from "assert";
2+
import { join } from "path";
3+
import { EthereumProvider } from "../src/provider";
4+
import compile from "./helpers/compile";
5+
import getProvider from "./helpers/getProvider";
6+
7+
describe("merge", () => {
8+
let provider: EthereumProvider;
9+
let accounts: string[];
10+
beforeEach("get provider", async () => {
11+
provider = await getProvider({
12+
wallet: { seed: "temet nosce" },
13+
chain: { hardfork: "merge" }
14+
});
15+
accounts = await provider.send("eth_accounts");
16+
});
17+
describe("safe", () => {
18+
it("returns the safe block", async () => {
19+
const safe = await provider.send("eth_getBlockByNumber", ["safe", true]);
20+
const latest = await provider.send("eth_getBlockByNumber", [
21+
"latest",
22+
true
23+
]);
24+
assert.deepStrictEqual(safe, latest);
25+
});
26+
});
27+
describe("finalized", () => {
28+
it("returns the finalized block", async () => {
29+
const finalized = await provider.send("eth_getBlockByNumber", [
30+
"finalized",
31+
true
32+
]);
33+
const latest = await provider.send("eth_getBlockByNumber", [
34+
"latest",
35+
true
36+
]);
37+
assert.deepStrictEqual(finalized, latest);
38+
});
39+
});
40+
41+
describe("difficulty", () => {
42+
let contract: ReturnType<typeof compile>;
43+
let contractAddress: string;
44+
beforeEach("deploy contract", async () => {
45+
contract = compile(join(__dirname, "./contracts/Merge.sol"));
46+
const transaction = {
47+
from: accounts[0],
48+
data: contract.code,
49+
gasLimit: "0x2fefd8"
50+
};
51+
const txHash = await provider.send("eth_sendTransaction", [transaction]);
52+
const receipt = await provider.send("eth_getTransactionReceipt", [
53+
txHash
54+
]);
55+
contractAddress = receipt.contractAddress;
56+
});
57+
it("uses the block's mixhash value as the PREVRANDAO opcode", async () => {
58+
const block = await provider.send("eth_getBlockByNumber", [
59+
"latest",
60+
false
61+
]);
62+
assert.strictEqual(block.difficulty, "0x0");
63+
64+
const constructorResult = await provider.send("eth_call", [
65+
{
66+
from: accounts[0],
67+
to: contractAddress,
68+
data: `0x${contract.contract.evm.methodIdentifiers["difficulty()"]}`
69+
},
70+
block.number
71+
]);
72+
73+
const txHash = await provider.send("eth_sendTransaction", [
74+
{
75+
from: accounts[0],
76+
to: contractAddress,
77+
gasLimit: "0x2fefd8",
78+
data: `0x${contract.contract.evm.methodIdentifiers["getCurrentDifficulty()"]}`
79+
}
80+
]);
81+
const receipt = await provider.send("eth_getTransactionReceipt", [
82+
txHash
83+
]);
84+
// make sure it didn't revert, which is will do if `difficulty` is not > `1`
85+
assert.strictEqual(receipt.status, "0x1");
86+
87+
const viewResult1 = await provider.send("eth_call", [
88+
{
89+
from: accounts[0],
90+
to: contractAddress,
91+
data: `0x${contract.contract.evm.methodIdentifiers["getCurrentDifficulty()"]}`
92+
},
93+
block.number
94+
]);
95+
96+
// it shouldn't be all 0s for the merge:
97+
assert.notStrictEqual(
98+
block.mixHash,
99+
"0x0000000000000000000000000000000000000000000000000000000000000000"
100+
);
101+
102+
// the results should match
103+
assert.strictEqual(constructorResult, block.mixHash);
104+
assert.strictEqual(viewResult1, block.mixHash);
105+
106+
const lastblock = await provider.send("eth_getBlockByNumber", [
107+
receipt.blockNumber,
108+
false
109+
]);
110+
assert.strictEqual(lastblock.difficulty, "0x0");
111+
const viewResult2 = await provider.send("eth_call", [
112+
{
113+
from: accounts[0],
114+
to: contractAddress,
115+
data: `0x${contract.contract.evm.methodIdentifiers["getCurrentDifficulty()"]}`
116+
},
117+
"latest"
118+
]);
119+
// it changed
120+
assert.notStrictEqual(viewResult1, viewResult2);
121+
assert.strictEqual(viewResult2, lastblock.mixHash);
122+
}).timeout(0);
123+
});
124+
});

src/chains/ethereum/options/src/chain-options.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ const HARDFORKS = [
1111
"berlin",
1212
"london",
1313
"arrowGlacier",
14-
"grayGlacier"
14+
"grayGlacier",
15+
"merge"
1516
] as const;
1617

1718
export type Hardfork = Writeable<ArrayToTuple<typeof HARDFORKS>>;

0 commit comments

Comments
 (0)