Skip to content
This repository was archived by the owner on Feb 26, 2024. It is now read-only.
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
23 changes: 12 additions & 11 deletions src/chains/ethereum/block/src/runtime-block.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import {
Data,
Quantity,
BUFFER_EMPTY,
BUFFER_32_ZERO,
BUFFER_8_ZERO
} from "@ganache/utils";
import { Data, Quantity, BUFFER_EMPTY, BUFFER_8_ZERO } from "@ganache/utils";
import { KECCAK256_RLP_ARRAY } from "@ethereumjs/util";
import { EthereumRawBlockHeader, serialize } from "./serialize";
import { Address } from "@ganache/ethereum-address";
Expand Down Expand Up @@ -88,7 +82,11 @@ export class RuntimeBlock {
gasLimit: bigint;
gasUsed: bigint;
timestamp: bigint;
baseFeePerGas?: bigint;
mixHash: Buffer;
// prevRandao is mixHash, but for the merge and it must be
// given to the VM this way
prevRandao: Buffer;
baseFeePerGas: bigint | undefined;
};

constructor(
Expand All @@ -100,7 +98,8 @@ export class RuntimeBlock {
timestamp: Quantity,
difficulty: Quantity,
previousBlockTotalDifficulty: Quantity,
baseFeePerGas?: bigint
mixHash: Buffer,
baseFeePerGas: bigint | undefined
) {
const ts = timestamp.toBuffer();
const coinbaseBuffer = coinbase.toBuffer();
Expand All @@ -115,7 +114,9 @@ export class RuntimeBlock {
gasLimit: Quantity.toBigInt(gasLimit),
gasUsed: Quantity.toBigInt(gasUsed),
timestamp: Quantity.toBigInt(ts),
baseFeePerGas: baseFeePerGas === undefined ? 0n : baseFeePerGas
baseFeePerGas: baseFeePerGas === undefined ? 0n : baseFeePerGas,
mixHash,
prevRandao: mixHash
};
// When forking we might get a block that doesn't have a baseFeePerGas value,
// but EIP-1559 might be active on our chain. We need to keep track on if
Expand Down Expand Up @@ -153,7 +154,7 @@ export class RuntimeBlock {
gasUsed === 0n ? BUFFER_EMPTY : Quantity.toBuffer(gasUsed),
Quantity.toBuffer(header.timestamp),
extraData.toBuffer(),
BUFFER_32_ZERO, // mixHash
header.mixHash,
BUFFER_8_ZERO // nonce
];
if (this.serializeBaseFeePerGas && header.baseFeePerGas !== undefined) {
Expand Down
1 change: 0 additions & 1 deletion src/chains/ethereum/ethereum/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@
"env-paths": "2.2.1",
"eth-sig-util": "2.5.3",
"ethereumjs-abi": "0.6.8",
"keccak": "3.0.2",
"level": "8.0.0",
"lodash.clonedeep": "4.5.0",
"lru-cache": "6.0.0",
Expand Down
2 changes: 2 additions & 0 deletions src/chains/ethereum/ethereum/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,7 @@ export default class EthereumApi implements Api {
parentHeader.timestamp,
options.miner.difficulty,
parentHeader.totalDifficulty,
blockchain.getMixHash(parentHeader.parentHash.toBuffer()),
0n // no baseFeePerGas for estimates
);
const runArgs: EstimateGasRunArgs = {
Expand Down Expand Up @@ -2879,6 +2880,7 @@ export default class EthereumApi implements Api {
parentHeader.timestamp,
options.miner.difficulty,
parentHeader.totalDifficulty,
blockchain.getMixHash(parentHeader.parentHash.toBuffer()),
baseFeePerGasBigInt
);

Expand Down
27 changes: 22 additions & 5 deletions src/chains/ethereum/ethereum/src/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ import {
BUFFER_32_ZERO,
BUFFER_256_ZERO,
findInsertPosition,
KNOWN_CHAINIDS
KNOWN_CHAINIDS,
keccak
} from "@ganache/utils";
import AccountManager from "./data-managers/account-manager";
import BlockManager from "./data-managers/block-manager";
Expand Down Expand Up @@ -265,6 +266,8 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
);
}

this.isPostMerge = this.common.gteHardfork("merge");

const blocks = (this.blocks = await BlockManager.initialize(
this,
common,
Expand Down Expand Up @@ -596,12 +599,18 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
minerOptions.blockGasLimit.toBuffer(),
BUFFER_ZERO,
Quantity.from(timestamp),
minerOptions.difficulty,
this.isPostMerge ? Quantity.Zero : minerOptions.difficulty,
previousHeader.totalDifficulty,
this.getMixHash(previousBlock.hash().toBuffer()),
Block.calcNextBaseFee(previousBlock)
);
};

getMixHash(data: Buffer) {
// mixHash is used as an RNG post merge hardfork
return this.isPostMerge ? keccak(data) : BUFFER_32_ZERO;
}

isStarted = () => {
return this.#state === Status.started;
};
Expand Down Expand Up @@ -767,8 +776,9 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
blockGasLimit.toBuffer(),
BUFFER_ZERO,
Quantity.from(timestamp),
minerOptions.difficulty,
this.isPostMerge ? Quantity.Zero : minerOptions.difficulty,
fallbackBlock.header.totalDifficulty,
this.getMixHash(fallbackBlock.hash().toBuffer()),
baseFeePerGas
);

Expand Down Expand Up @@ -806,15 +816,19 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
const baseFeePerGas = this.common.isActivatedEIP(1559)
? Block.INITIAL_BASE_FEE_PER_GAS
: undefined;

const genesis = new RuntimeBlock(
rawBlockNumber,
Data.from(BUFFER_32_ZERO),
this.coinbase,
blockGasLimit.toBuffer(),
BUFFER_ZERO,
Quantity.from(timestamp),
this.#options.miner.difficulty,
this.isPostMerge ? Quantity.Zero : this.#options.miner.difficulty,
Quantity.Zero, // we start the totalDifficulty at 0
// we use the initial trie root as the genesis block's mixHash as it
// is deterministic based on initial wallet conditions
this.isPostMerge ? keccak(this.trie.root()) : BUFFER_32_ZERO,
baseFeePerGas
);

Expand Down Expand Up @@ -1447,6 +1461,8 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
};
};

isPostMerge: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

For later when we clean up tech debt: does this make sense to define here? This flag is used all throughout the file, so it seems like it would make sense at the top.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

probably a good idea to move it up in the file, yeah.


#prepareNextBlock = (
targetBlock: Block,
parentBlock: Block,
Expand All @@ -1464,8 +1480,9 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
BUFFER_ZERO,
// make sure we use the same timestamp as the target block
targetBlock.header.timestamp,
this.#options.miner.difficulty,
this.isPostMerge ? Quantity.Zero : this.#options.miner.difficulty,
parentBlock.header.totalDifficulty,
this.getMixHash(parentBlock.hash().toBuffer()),
Block.calcNextBaseFee(parentBlock)
) as RuntimeBlock & {
uncleHeaders: [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ export default class BlockManager extends Manager<Block> {
getBlockByTag(tag: Tag) {
switch (tag) {
case "latest":
case "finalized":
case "safe":
return this.latest;
case "pending":
// TODO: build a real pending block!
Expand Down
11 changes: 4 additions & 7 deletions src/chains/ethereum/ethereum/src/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import secp256k1, { SECP256K1_N } from "@ganache/secp256k1";
import { mnemonicToSeedSync } from "bip39";
import { alea } from "seedrandom";
import crypto from "crypto";
import createKeccakHash from "keccak";
import { writeFileSync } from "fs";
import { EthereumInternalOptions } from "@ganache/ethereum-options";
import { Address } from "@ganache/ethereum-address";
Expand Down Expand Up @@ -373,9 +372,7 @@ export default class Wallet {
cipher.update(privateKey.toBuffer()),
cipher.final()
]);
const mac = createKeccakHash("keccak256")
.update(Buffer.concat([derivedKey.slice(16, 32), ciphertext]))
.digest();
const mac = keccak(Buffer.concat([derivedKey.slice(16, 32), ciphertext]));
return {
crypto: {
cipher: CIPHER,
Expand Down Expand Up @@ -420,9 +417,9 @@ export default class Wallet {
kdfParams.dklen,
{ ...kdfParams, N: kdfParams.n }
);
localMac = createKeccakHash("keccak256")
.update(Buffer.concat([derivedKey.slice(16, 32), ciphertext]))
.digest();
localMac = keccak(
Buffer.concat([derivedKey.slice(16, 32), ciphertext])
);
} catch {
localMac = null;
}
Expand Down
3 changes: 2 additions & 1 deletion src/chains/ethereum/ethereum/tests/api/eth/call.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { EthereumProvider } from "../../../src/provider";
import getProvider from "../../helpers/getProvider";
import compile, { CompileOutput } from "../../helpers/compile";
import { join } from "path";
import { BUFFER_EMPTY, Data, Quantity } from "@ganache/utils";
import { BUFFER_32_ZERO, BUFFER_EMPTY, Data, Quantity } from "@ganache/utils";
import { CallError } from "@ganache/ethereum-utils";
import Blockchain from "../../../src/blockchain";
import Wallet from "../../../src/wallet";
Expand Down Expand Up @@ -884,6 +884,7 @@ describe("api", () => {
parentHeader.timestamp,
Quantity.One, // difficulty
parentHeader.totalDifficulty,
BUFFER_32_ZERO,
parentHeader.baseFeePerGas.toBigInt()
);
simTx = {
Expand Down
16 changes: 16 additions & 0 deletions src/chains/ethereum/ethereum/tests/contracts/Merge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;

contract Merge {
uint256 public difficulty;

constructor() {
require(block.difficulty > 1);
difficulty = block.difficulty;
}

function getCurrentDifficulty() public view returns (uint256) {
require(block.difficulty > 1);
return uint256(block.difficulty);
}
}
124 changes: 124 additions & 0 deletions src/chains/ethereum/ethereum/tests/merge.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import assert from "assert";
import { join } from "path";
import { EthereumProvider } from "../src/provider";
import compile from "./helpers/compile";
import getProvider from "./helpers/getProvider";

describe("merge", () => {
let provider: EthereumProvider;
let accounts: string[];
beforeEach("get provider", async () => {
provider = await getProvider({
wallet: { seed: "temet nosce" },
chain: { hardfork: "merge" }
});
accounts = await provider.send("eth_accounts");
});
describe("safe", () => {
it("returns the safe block", async () => {
const safe = await provider.send("eth_getBlockByNumber", ["safe", true]);
const latest = await provider.send("eth_getBlockByNumber", [
"latest",
true
]);
assert.deepStrictEqual(safe, latest);
});
});
describe("finalized", () => {
it("returns the finalized block", async () => {
const finalized = await provider.send("eth_getBlockByNumber", [
"finalized",
true
]);
const latest = await provider.send("eth_getBlockByNumber", [
"latest",
true
]);
assert.deepStrictEqual(finalized, latest);
});
});

describe("difficulty", () => {
let contract: ReturnType<typeof compile>;
let contractAddress: string;
beforeEach("deploy contract", async () => {
contract = compile(join(__dirname, "./contracts/Merge.sol"));
const transaction = {
from: accounts[0],
data: contract.code,
gasLimit: "0x2fefd8"
};
const txHash = await provider.send("eth_sendTransaction", [transaction]);
const receipt = await provider.send("eth_getTransactionReceipt", [
txHash
]);
contractAddress = receipt.contractAddress;
});
it("uses the block's mixhash value as the PREVRANDAO opcode", async () => {
const block = await provider.send("eth_getBlockByNumber", [
"latest",
false
]);
assert.strictEqual(block.difficulty, "0x0");

const constructorResult = await provider.send("eth_call", [
{
from: accounts[0],
to: contractAddress,
data: `0x${contract.contract.evm.methodIdentifiers["difficulty()"]}`
},
block.number
]);

const txHash = await provider.send("eth_sendTransaction", [
{
from: accounts[0],
to: contractAddress,
gasLimit: "0x2fefd8",
data: `0x${contract.contract.evm.methodIdentifiers["getCurrentDifficulty()"]}`
}
]);
const receipt = await provider.send("eth_getTransactionReceipt", [
txHash
]);
// make sure it didn't revert, which is will do if `difficulty` is not > `1`
assert.strictEqual(receipt.status, "0x1");

const viewResult1 = await provider.send("eth_call", [
{
from: accounts[0],
to: contractAddress,
data: `0x${contract.contract.evm.methodIdentifiers["getCurrentDifficulty()"]}`
},
block.number
]);

// it shouldn't be all 0s for the merge:
assert.notStrictEqual(
block.mixHash,
"0x0000000000000000000000000000000000000000000000000000000000000000"
);

// the results should match
assert.strictEqual(constructorResult, block.mixHash);
assert.strictEqual(viewResult1, block.mixHash);

const lastblock = await provider.send("eth_getBlockByNumber", [
receipt.blockNumber,
false
]);
assert.strictEqual(lastblock.difficulty, "0x0");
const viewResult2 = await provider.send("eth_call", [
{
from: accounts[0],
to: contractAddress,
data: `0x${contract.contract.evm.methodIdentifiers["getCurrentDifficulty()"]}`
},
"latest"
]);
// it changed
assert.notStrictEqual(viewResult1, viewResult2);
assert.strictEqual(viewResult2, lastblock.mixHash);
}).timeout(0);
});
});
3 changes: 2 additions & 1 deletion src/chains/ethereum/options/src/chain-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ const HARDFORKS = [
"berlin",
"london",
"arrowGlacier",
"grayGlacier"
"grayGlacier",
"merge"
] as const;

export type Hardfork = Writeable<ArrayToTuple<typeof HARDFORKS>>;
Expand Down
5 changes: 3 additions & 2 deletions src/chains/ethereum/options/src/miner-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export type MinerConfig = {
};

/**
* Sets the block difficulty
* Sets the block difficulty; value is always 0 after the merge hardfork
*
* @defaultValue 1
*/
Expand Down Expand Up @@ -275,7 +275,8 @@ export const MinerOptions: Definitions<MinerConfig> = {
},
difficulty: {
normalize: Quantity.from,
cliDescription: "Sets the block difficulty.",
cliDescription:
"Sets the block difficulty. Value is always 0 after the merge hardfork.",
default: () => Quantity.One,
cliType: "string",
cliCoerce: toBigIntOrString
Expand Down
Loading