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

Commit

Permalink
fix: store transactions with the correct effectiveGasPrice (#4112)
Browse files Browse the repository at this point in the history
  • Loading branch information
MicaiahReid authored Jan 9, 2023
1 parent f62999d commit a3ae75f
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 77 deletions.
2 changes: 1 addition & 1 deletion src/chains/ethereum/block/src/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export class Block {
// get the maxFeePerGas and maxPriorityFeePerGas, use those to calculate
// the effectiveGasPrice and add it to `extra` above, or we can just
// leave it out of extra and update the effectiveGasPrice after like this
tx.updateEffectiveGasPrice(header.baseFeePerGas);
tx.updateEffectiveGasPrice(header.baseFeePerGas?.toBigInt());
return txFn(tx);
}) as IncludeTransactions extends true ? TypedTransactionJSON[] : Data[];

Expand Down
10 changes: 3 additions & 7 deletions src/chains/ethereum/ethereum/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1885,16 +1885,12 @@ export default class EthereumApi implements Api {

const transactionPromise = transactions.get(txHash);
const receiptPromise = transactionReceipts.get(txHash);
const blockPromise = transactionPromise.then(t =>
t ? blocks.get(t.blockNumber.toBuffer()) : null
);
const [transaction, receipt, block] = await Promise.all([
const [transaction, receipt] = await Promise.all([
transactionPromise,
receiptPromise,
blockPromise
receiptPromise
]);
if (transaction) {
return receipt.toJSON(block, transaction, common);
return receipt.toJSON(transaction, common);
}

// if we are performing "strict" instamining, then check to see if the
Expand Down
24 changes: 5 additions & 19 deletions src/chains/ethereum/ethereum/src/miner/miner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const updateBloom = (blockBloom: Buffer, bloom: Buffer) => {
const sortByPrice = (values: TypedTransaction[], a: number, b: number) =>
values[a].effectiveGasPrice > values[b].effectiveGasPrice;

const refresher = (item: TypedTransaction, context: Quantity) =>
const refresher = (item: TypedTransaction, context: bigint) =>
item.updateEffectiveGasPrice(context);

export default class Miner extends Emittery<{
Expand All @@ -93,7 +93,7 @@ export default class Miner extends Emittery<{
#isBusy: boolean = false;
#paused: boolean = false;
#resumer: Promise<void>;
#currentBlockBaseFeePerGas: Quantity;
#currentBlockBaseFeePerGas: bigint;
#resolver: (value: void) => void;

/**
Expand Down Expand Up @@ -127,10 +127,7 @@ export default class Miner extends Emittery<{
}

// create a Heap that sorts by gasPrice
readonly #priced = new Heap<TypedTransaction, Quantity>(
sortByPrice,
refresher
);
readonly #priced = new Heap<TypedTransaction, bigint>(sortByPrice, refresher);
/*
* @param executables - A live Map of pending transactions from the transaction
* pool. The miner will update this Map by removing the best transactions
Expand All @@ -149,7 +146,7 @@ export default class Miner extends Emittery<{
this.#executables = executables;
this.#createBlock = (previousBlock: Block) => {
const newBlock = createBlock(previousBlock);
this.#setCurrentBlockBaseFeePerGas(newBlock);
this.#currentBlockBaseFeePerGas = newBlock.header.baseFeePerGas;
return newBlock;
};

Expand Down Expand Up @@ -182,7 +179,7 @@ export default class Miner extends Emittery<{
this.#updatePricedHeap();
return;
} else {
this.#setCurrentBlockBaseFeePerGas(block);
this.#currentBlockBaseFeePerGas = block.header.baseFeePerGas;
this.#setPricedHeap();
const result = await this.#mine(block, maxTransactions, onlyOneBlock);
this.emit("idle");
Expand Down Expand Up @@ -584,15 +581,4 @@ export default class Miner extends Emittery<{
public toggleStepEvent(enable: boolean) {
this.#emitStepEvent = enable;
}

/**
* Sets the #currentBlockBaseFeePerGas property if the current block
* has a baseFeePerGas property
*/
#setCurrentBlockBaseFeePerGas = (block: RuntimeBlock) => {
const baseFeePerGas = block.header.baseFeePerGas;
// before london hard fork, there will be no baseFeePerGas on the block
this.#currentBlockBaseFeePerGas =
baseFeePerGas === undefined ? undefined : Quantity.from(baseFeePerGas);
};
}
6 changes: 5 additions & 1 deletion src/chains/ethereum/ethereum/src/transaction-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { EthereumInternalOptions } from "@ganache/ethereum-options";
import { Executables } from "./miner/executables";
import { TypedTransaction } from "@ganache/ethereum-transaction";
import { Block } from "@ganache/ethereum-block";

/**
* Checks if the `replacer` is eligible to replace the `replacee` transaction
Expand Down Expand Up @@ -188,7 +189,10 @@ export default class TransactionPool extends Emittery<{ drain: undefined }> {
!transaction.effectiveGasPrice &&
this.#blockchain.common.isActivatedEIP(1559)
) {
const baseFeePerGas = this.#blockchain.blocks.latest.header.baseFeePerGas;
const baseFeePerGas = Block.calcNextBaseFee(
this.#blockchain.blocks.latest
);

transaction.updateEffectiveGasPrice(baseFeePerGas);
}

Expand Down
87 changes: 60 additions & 27 deletions src/chains/ethereum/ethereum/tests/api/eth/eth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -543,34 +543,67 @@ describe("api", () => {
assert(receipt.transactionIndex, "0x0");
});

it("eth_getTransactionByHash", async () => {
await provider.send("eth_subscribe", ["newHeads"]);
const txJson = {
type: "0x2",
chainId: "0x539",
nonce: "0x0",
from: accounts[0],
to: accounts[1],
value: "0x1",
maxPriorityFeePerGas: "0xf",
maxFeePerGas: "0xfffffffff",
gas: "0x15f90",
input: "0x01",
accessList: []
} as any;
const hash = await provider.send("eth_sendTransaction", [txJson]);
const _message = await provider.once("message");
// we want these values set for when we check against the return data,
// but they shouldn't be used in eth_sendTransaction, so we'll set them now
txJson.transactionIndex = "0x0";
txJson.gasPrice = "0x342770cf";

const tx = await provider.send("eth_getTransactionByHash", [hash]);
describe("eth_getTransactionByHash", () => {
it("returns a transaction matching the sent transaction", async () => {
await provider.send("eth_subscribe", ["newHeads"]);
const txJson = {
type: "0x2",
chainId: "0x539",
nonce: "0x0",
from: accounts[0],
to: accounts[1],
value: "0x1",
maxPriorityFeePerGas: "0xf",
maxFeePerGas: "0xfffffffff",
gas: "0x15f90",
input: "0x01",
accessList: []
} as any;
const hash = await provider.send("eth_sendTransaction", [txJson]);
const _message = await provider.once("message");
// we want these values set for when we check against the return data,
// but they shouldn't be used in eth_sendTransaction, so we'll set them now
txJson.transactionIndex = "0x0";
txJson.gasPrice = "0x342770cf";

const tx = await provider.send("eth_getTransactionByHash", [hash]);

// loop over all of the data we set to verify it matches
for (const [key, value] of Object.entries(txJson)) {
assert.deepStrictEqual(value, tx[key]);
}
});

// loop over all of the data we set to verify it matches
for (const [key, value] of Object.entries(txJson)) {
assert.deepStrictEqual(value, tx[key]);
}
it("has a `gasPrice` that matches the corresponding receipt's `effectiveGasPrice` after the transaction is mined", async () => {
await provider.send("eth_subscribe", ["newHeads"]);
await provider.send("miner_stop", []);
const txJson = {
type: "0x2",
from: accounts[0],
to: accounts[1],
maxPriorityFeePerGas: "0x77359400",
maxFeePerGas: "0x6FC23AC00"
} as any;
// we previously had a bug where the `gasPrice` field returned from
// `eth_getTransactionByHash` was incorrect when multiple transactions
// were sent, because we weren't assigning the correct `effectiveGasPrice`
// when adding the transaction to the pool. see:
// https://github.com/trufflesuite/ganache/issues/4094
const hashes = await Promise.all([
provider.send("eth_sendTransaction", [txJson]),
provider.send("eth_sendTransaction", [txJson])
]);
await provider.send("miner_start", []);
const _message = await provider.once("message");
for (const hash of hashes) {
const tx = await provider.send("eth_getTransactionByHash", [hash]);
const receipt = await provider.send("eth_getTransactionReceipt", [
hash
]);
assert.deepStrictEqual(tx.gasPrice, receipt.effectiveGasPrice);
assert.deepStrictEqual(tx.gasPrice.toString(), "0xab5d04c0");
}
});
});
});
});
12 changes: 8 additions & 4 deletions src/chains/ethereum/ethereum/tests/transaction-pool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,13 @@ describe("transaction pool", async () => {
},
common,
blocks: {
latest: { header: { baseFeePerGas: Quantity.from(875000000) } }
latest: {
header: {
baseFeePerGas: Quantity.from(875000000),
gasLimit: Quantity.from(30000000),
gasUsed: Quantity.from(0)
}
}
}
};
});
Expand Down Expand Up @@ -147,9 +153,7 @@ describe("transaction pool", async () => {
}
},
common,
blocks: {
latest: { header: { baseFeePerGas: Quantity.from(875000000) } }
}
blocks: blockchain.blocks
} as any;
const txPool = new TransactionPool(options.miner, fakeNonceChain, origins);
const executableTx = TransactionFactory.fromRpc(executableRpc, common);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,11 @@ export class EIP1559FeeMarketTransaction extends RuntimeTransaction {
return computeIntrinsicsFeeMarketTx(v, <EIP1559FeeMarketDatabaseTx>raw);
}

public updateEffectiveGasPrice(baseFeePerGas: Quantity) {
const baseFeePerGasBigInt = baseFeePerGas.toBigInt();
public updateEffectiveGasPrice(baseFeePerGas: bigint) {
const maxFeePerGas = this.maxFeePerGas.toBigInt();
const maxPriorityFeePerGas = this.maxPriorityFeePerGas.toBigInt();
const a = maxFeePerGas - baseFeePerGasBigInt;
const a = maxFeePerGas - baseFeePerGas;
const tip = a < maxPriorityFeePerGas ? a : maxPriorityFeePerGas;
this.effectiveGasPrice = Quantity.from(baseFeePerGasBigInt + tip);
this.effectiveGasPrice = Quantity.from(baseFeePerGas + tip);
}
}
2 changes: 1 addition & 1 deletion src/chains/ethereum/transaction/src/runtime-transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,5 +256,5 @@ export abstract class RuntimeTransaction extends BaseTransaction {
);

protected abstract toVmTransaction();
protected abstract updateEffectiveGasPrice(baseFeePerGas?: Quantity);
protected abstract updateEffectiveGasPrice(baseFeePerGas: bigint);
}
15 changes: 2 additions & 13 deletions src/chains/ethereum/transaction/src/transaction-receipt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,30 +133,19 @@ export class InternalTransactionReceipt {
}
}

public toJSON(
block: {
hash(): Data;
header: { number: Quantity; baseFeePerGas?: Quantity };
},
transaction: TypedTransaction,
common: Common
) {
public toJSON(transaction: TypedTransaction, common: Common) {
const raw = this.raw;
const contractAddress =
this.contractAddress.length === 0
? null
: Data.from(this.contractAddress);
const blockHash = block.hash();
const blockNumber = block.header.number;
const { blockHash, blockNumber } = transaction;
const blockLog = BlockLogs.create(blockHash);
const transactionHash = transaction.hash;
const transactionIndex = transaction.index;
blockLog.blockNumber = blockNumber;
raw[3].forEach(l => blockLog.append(transactionIndex, transactionHash, l));
const logs = [...blockLog.toJSON()];
if (block.header.baseFeePerGas) {
transaction.updateEffectiveGasPrice(block.header.baseFeePerGas);
}
const json: TransactionReceipt = {
transactionHash,
transactionIndex,
Expand Down

0 comments on commit a3ae75f

Please sign in to comment.