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 13 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
15 changes: 5 additions & 10 deletions src/chains/ethereum/ethereum/src/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ import {
calculateIntrinsicGas,
InternalTransactionReceipt,
VmTransaction,
TypedTransaction
TypedTransaction,
serializeForDb
} from "@ganache/ethereum-transaction";
import { Block, RuntimeBlock, Snapshots } from "@ganache/ethereum-block";
import {
Expand Down Expand Up @@ -445,7 +446,7 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
// TODO: the block has already done most of the work serializing the tx
// we should reuse it, if possible
// https://github.com/trufflesuite/ganache/issues/4341
const serialized = tx.serializeForDb(blockHash, blockNumberQ, index);
const serialized = serializeForDb(tx, blockHash, blockNumberQ, index);
this.transactions.set(hash, serialized);

// save receipt to the database
Expand Down Expand Up @@ -1116,10 +1117,7 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
const to = hasToAddress ? new Address(transaction.to.toBuffer()) : null;

const common = this.fallback
? this.fallback.getCommonForBlockNumber(
this.common,
BigInt(transaction.block.header.number.toString())
)
? this.fallback.getCommonForBlock(this.common, transaction.block.header)
: this.common;

const gasLeft =
Expand Down Expand Up @@ -1253,10 +1251,7 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
} as any;

const common = this.fallback
? this.fallback.getCommonForBlockNumber(
this.common,
BigInt(block.header.number.toString())
)
? this.fallback.getCommonForBlock(this.common, block.header)
: this.common;

// TODO: prefixCodeHashes should eventually be conditional
Expand Down
38 changes: 22 additions & 16 deletions src/chains/ethereum/ethereum/src/data-managers/block-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
BlockRawTransaction,
EthereumRawBlock,
EthereumRawBlockHeader,
GanacheRawBlock,
Head,
serialize,
WithdrawalRaw
Expand All @@ -21,7 +22,7 @@ import {
} from "@ganache/ethereum-transaction";
import { GanacheLevelUp } from "../database";
import { Ethereum } from "../api-types";
import { encode } from "@ganache/rlp";
import { decode, encode } from "@ganache/rlp";

const LATEST_INDEX_KEY = BUFFER_ZERO;

Expand Down Expand Up @@ -171,10 +172,10 @@ export default class BlockManager extends Manager<Block> {
if (json == null) {
return null;
} else {
const common = fallback.getCommonForBlockNumber(
this.#common,
BigInt(json.number)
);
const common = fallback.getCommonForBlock(this.#common, {
number: BigInt(json.number),
timestamp: BigInt(json.timestamp)
});

return BlockManager.rawFromJSON(json, common);
}
Expand Down Expand Up @@ -226,12 +227,12 @@ export default class BlockManager extends Manager<Block> {
true
]);
if (json) {
const blockNumber = BigInt(json.number);
if (blockNumber <= fallback.blockNumber.toBigInt()) {
const common = fallback.getCommonForBlockNumber(
this.#common,
blockNumber
);
const number = BigInt(json.number);
if (number <= fallback.blockNumber.toBigInt()) {
const common = fallback.getCommonForBlock(this.#common, {
number,
timestamp: BigInt(json.timestamp)
});
return new Block(BlockManager.rawFromJSON(json, common), common);
}
}
Expand Down Expand Up @@ -272,9 +273,14 @@ export default class BlockManager extends Manager<Block> {
if (fallback) {
const block = await this.fromFallback(blockNumber);
if (block) {
const header: EthereumRawBlockHeader =
decode<GanacheRawBlock>(block)[0];
return new Block(
block,
fallback.getCommonForBlockNumber(common, blockNumber.toBigInt())
fallback.getCommonForBlock(common, {
number: blockNumber.toBigInt(),
timestamp: Quantity.toBigInt(header[11])
})
);
}
}
Expand Down Expand Up @@ -319,10 +325,10 @@ export default class BlockManager extends Manager<Block> {
{ disableCache: true }
);
if (json) {
const common = fallback.getCommonForBlockNumber(
this.#common,
BigInt(json.number)
);
const common = fallback.getCommonForBlock(this.#common, {
number: BigInt(json.number),
timestamp: BigInt(json.timestamp)
});
return new Block(BlockManager.rawFromJSON(json, common), common);
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ import Blockchain from "../blockchain";
import PromiseQueue from "@ganache/promise-queue";
import type { Common } from "@ethereumjs/common";
import { Data, Quantity } from "@ganache/utils";
import { Address } from "@ganache/ethereum-address";
import {
GanacheRawExtraTx,
TransactionFactory,
Transaction,
TypedTransaction
TypedTransaction,
serializeRpcForDb
} from "@ganache/ethereum-transaction";
import { GanacheLevelUp } from "../database";

Expand Down Expand Up @@ -57,20 +56,7 @@ export default class TransactionManager extends Manager<NoOp> {
// fallback's blocknumber because it doesn't exist in our local chain.
if (!fallback.isValidForkBlockNumber(blockNumber)) return null;

const extra: GanacheRawExtraTx = [
Address.toBuffer(tx.from),
Data.toBuffer((tx as any).hash, 32),
blockHash.toBuffer(),
blockNumber.toBuffer(),
index.toBuffer(),
Quantity.toBuffer(tx.gasPrice)
];
const common = fallback.getCommonForBlockNumber(
fallback.common,
blockNumber.toBigInt()
);
const runTx = TransactionFactory.fromRpc(tx, common, extra);
return runTx.serializeForDb(blockHash, blockNumber, index);
return serializeRpcForDb(tx, blockHash, blockNumber, index);
};

public async getRaw(transactionHash: Buffer): Promise<Buffer> {
Expand Down
38 changes: 18 additions & 20 deletions src/chains/ethereum/ethereum/src/forking/fork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,11 @@ export class Fork {
cacheProm,
this.#setCommonFromChain(chainIdPromise)
]);
const common = this.getCommonForBlockNumber(
this.common,
this.blockNumber.toBigInt()
);

const common = this.getCommonForBlock(this.common, {
timestamp: BigInt(block.timestamp),
number: BigInt(block.number)
});
this.block = new Block(BlockManager.rawFromJSON(block, common), common);
if (!chainOptions.time && minerOptions.timestampIncrement !== "clock") {
chainOptions.time = new Date(
Expand All @@ -238,6 +239,7 @@ export class Fork {
1000
);
}

if (cache) await this.initCache(cache);
}
private async initCache(cache: PersistentCache) {
Expand Down Expand Up @@ -275,18 +277,11 @@ export class Fork {
: this.blockNumber;
}

/**
* If the `blockNumber` is before our `fork.blockNumber`, return a `Common`
* instance, applying the rules from the remote chain's `common` via its
* original `chainId`. If the remote chain's `chainId` is now "known", return
* a `Common` with our local `common`'s rules applied, but with the remote
* chain's `chainId`. If the block is greater than or equal to our
* `fork.blockNumber` return `common`.
* @param common -
* @param blockNumber -
*/
public getCommonForBlockNumber(common: Common, blockNumber: BigInt) {
if (blockNumber <= this.blockNumber.toBigInt()) {
public getCommonForBlock(
common: Common,
block: { number: bigint; timestamp: bigint }
): Common {
if (block.number <= this.blockNumber.toBigInt()) {
// we are at or before our fork block

let forkCommon: Common;
Expand All @@ -295,13 +290,16 @@ export class Fork {
let hardfork;
// hardforks are iterated from earliest to latest
for (const hf of common.hardforks()) {
if (hf.block === null) continue;
if (blockNumber >= BigInt(hf.block)) {
if (hf.timestamp) {
const hfTimestamp = BigInt(hf.timestamp);
if (block.timestamp >= hfTimestamp) {
hardfork = hf.name;
}
} else if (hf.block && block.number >= BigInt(hf.block)) {
hardfork = hf.name;
} else {
break;
}
}

forkCommon = new Common({ chain: this.chainId, hardfork });
} else {
// we don't know about this chain or hardfork, so just carry on per usual,
Expand Down
130 changes: 130 additions & 0 deletions src/chains/ethereum/ethereum/tests/forking/fork.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { EthereumOptionsConfig } from "@ganache/ethereum-options";
import { Fork } from "../../src/forking/fork";
import { KNOWN_CHAINIDS, Quantity } from "@ganache/utils";
import { Common } from "@ethereumjs/common/dist/common";
import ganache from "../../../../../packages/core";
import Server from "../../../../../packages/core/lib/src/server";
import assert from "assert";
import { logging } from "./helpers";

describe("Fork", () => {
const port = 9999;
const networkId = 1;
const accounts = [];
const forkOptions = {
fork: {
url: `http://localhost:${port}`
},
logging
};

let remoteServer: Server;
let fork: Fork;

before(async () => {
remoteServer = ganache.server({
wallet: { deterministic: true },
chain: { networkId: networkId },
logging
});
await remoteServer.listen(port);
});

beforeEach(async () => {
const providerOptions = EthereumOptionsConfig.normalize(forkOptions);
fork = new Fork(providerOptions, accounts);
await fork.initialize();
});

afterEach(async () => {
await fork.close();
});

after(async () => {
await remoteServer.close();
});

describe("getCommonForBlock()", () => {
it("should return a Common for known chainIds", () => {
KNOWN_CHAINIDS.forEach(chainId => {
if (chainId === 42) {
// Skip kovan, because it is no longer supported by ethereumjs. To be
// removed in https://github.com/trufflesuite/ganache/issues/4461
} else {
assert.doesNotThrow(() => {
const parentCommon = new Common({ chain: chainId });

fork.getCommonForBlock(parentCommon, {
number: 0n,
timestamp: 0n
});
});
}
});
});

it("should resolve the correct hardfork based on block number for known chainId", () => {
const mainnet = 1;
const mergeBlocknumber = 15537394n;

// ensure that the fork blockNumber is after the merge blockNumber
fork.blockNumber = Quantity.from(mergeBlocknumber + 100n);
fork.chainId = mainnet;

const parentCommon = new Common({ chain: mainnet });
const blocknumberToHardfork: [bigint, string][] = [
[mergeBlocknumber - 1n, "grayGlacier"],
[mergeBlocknumber, "merge"],
[mergeBlocknumber + 1n, "merge"]
];

blocknumberToHardfork.forEach(([number, expectedHardfork]) => {
const common = fork.getCommonForBlock(parentCommon, {
number, // the block at which paris is scheduled
Copy link
Contributor

Choose a reason for hiding this comment

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

Whats this about paris?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

apologies, the base branch was wrong here - as it depends on #4455 where I've fixed this, along with a minor change to the above comment https://github.com/trufflesuite/ganache/pull/4455/files/cb95798713916bda17494b7129aa8eb99220a786..163c987b7d9dbdf24eee0cc5569af085cf318989

timestamp: 0n
});

const hf = common.hardfork();

assert.strictEqual(
hf,
expectedHardfork,
`Unexpected hardfork with blocknumber: ${number}`
);
});
});

it("should resolve the correct hardfork based on timestamp for known chainId", () => {
// we use sepolia because it has shanghai hf scheduled
const sepolia = 11155111;
const shanghaiTimestamp = 1677557088n;
const mergeForkIdTransitionBlockNumber = 1735371n;

// ensure that the fork blockNumber is after the mergeForkIdTransition blockNumber
fork.blockNumber = Quantity.from(mergeForkIdTransitionBlockNumber + 100n);
fork.chainId = sepolia;

const timstampToHardfork: [bigint, string][] = [
[shanghaiTimestamp - 1n, "mergeForkIdTransition"],
[shanghaiTimestamp, "shanghai"],
[shanghaiTimestamp + 1n, "shanghai"]
];

const parentCommon = new Common({ chain: sepolia });
timstampToHardfork.forEach(([timestamp, expectedHardfork]) => {
const common = fork.getCommonForBlock(parentCommon, {
number: mergeForkIdTransitionBlockNumber,
timestamp
});

const hf = common.hardfork();

assert.strictEqual(
hf,
expectedHardfork,
`Unexpected hardfork with timestamp: ${timestamp}`
);
});
});
});
});
1 change: 1 addition & 0 deletions src/chains/ethereum/transaction/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ export * from "./src/transaction-receipt";
export * from "./src/transaction-factory";
export * from "./src/transaction-types";
export * from "./src/vm-transaction";
export * from "./src/transaction-serialization";
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ export class EIP1559FeeMarketTransaction extends RuntimeTransaction {
const maxPriorityFeePerGas = this.maxPriorityFeePerGas.toBigInt();
const a = maxFeePerGas - baseFeePerGas;
const tip = a < maxPriorityFeePerGas ? a : maxPriorityFeePerGas;

this.effectiveGasPrice = Quantity.from(baseFeePerGas + tip);
}
}
1 change: 1 addition & 0 deletions src/chains/ethereum/transaction/src/hardfork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export type Hardfork =
| "arrowGlacier"
| "grayGlacier"
| "merge"
| "mergeForkIdTransition"
| "shanghai";
2 changes: 2 additions & 0 deletions src/chains/ethereum/transaction/src/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const Params = {
| "arrowGlacier"
| "grayGlacier"
| "merge"
| "mergeForkIdTransition"
| "shanghai",
bigint
>([
Expand All @@ -47,6 +48,7 @@ export const Params = {
["arrowGlacier", 16n],
["grayGlacier", 16n],
["merge", 16n],
["mergeForkIdTransition", 16n],
["shanghai", 16n]
]),

Expand Down
Loading