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

Commit a3ae75f

Browse files
authored
fix: store transactions with the correct effectiveGasPrice (#4112)
1 parent f62999d commit a3ae75f

File tree

9 files changed

+88
-77
lines changed

9 files changed

+88
-77
lines changed

src/chains/ethereum/block/src/block.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export class Block {
9393
// get the maxFeePerGas and maxPriorityFeePerGas, use those to calculate
9494
// the effectiveGasPrice and add it to `extra` above, or we can just
9595
// leave it out of extra and update the effectiveGasPrice after like this
96-
tx.updateEffectiveGasPrice(header.baseFeePerGas);
96+
tx.updateEffectiveGasPrice(header.baseFeePerGas?.toBigInt());
9797
return txFn(tx);
9898
}) as IncludeTransactions extends true ? TypedTransactionJSON[] : Data[];
9999

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1885,16 +1885,12 @@ export default class EthereumApi implements Api {
18851885

18861886
const transactionPromise = transactions.get(txHash);
18871887
const receiptPromise = transactionReceipts.get(txHash);
1888-
const blockPromise = transactionPromise.then(t =>
1889-
t ? blocks.get(t.blockNumber.toBuffer()) : null
1890-
);
1891-
const [transaction, receipt, block] = await Promise.all([
1888+
const [transaction, receipt] = await Promise.all([
18921889
transactionPromise,
1893-
receiptPromise,
1894-
blockPromise
1890+
receiptPromise
18951891
]);
18961892
if (transaction) {
1897-
return receipt.toJSON(block, transaction, common);
1893+
return receipt.toJSON(transaction, common);
18981894
}
18991895

19001896
// if we are performing "strict" instamining, then check to see if the

src/chains/ethereum/ethereum/src/miner/miner.ts

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ const updateBloom = (blockBloom: Buffer, bloom: Buffer) => {
7171
const sortByPrice = (values: TypedTransaction[], a: number, b: number) =>
7272
values[a].effectiveGasPrice > values[b].effectiveGasPrice;
7373

74-
const refresher = (item: TypedTransaction, context: Quantity) =>
74+
const refresher = (item: TypedTransaction, context: bigint) =>
7575
item.updateEffectiveGasPrice(context);
7676

7777
export default class Miner extends Emittery<{
@@ -93,7 +93,7 @@ export default class Miner extends Emittery<{
9393
#isBusy: boolean = false;
9494
#paused: boolean = false;
9595
#resumer: Promise<void>;
96-
#currentBlockBaseFeePerGas: Quantity;
96+
#currentBlockBaseFeePerGas: bigint;
9797
#resolver: (value: void) => void;
9898

9999
/**
@@ -127,10 +127,7 @@ export default class Miner extends Emittery<{
127127
}
128128

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

@@ -182,7 +179,7 @@ export default class Miner extends Emittery<{
182179
this.#updatePricedHeap();
183180
return;
184181
} else {
185-
this.#setCurrentBlockBaseFeePerGas(block);
182+
this.#currentBlockBaseFeePerGas = block.header.baseFeePerGas;
186183
this.#setPricedHeap();
187184
const result = await this.#mine(block, maxTransactions, onlyOneBlock);
188185
this.emit("idle");
@@ -584,15 +581,4 @@ export default class Miner extends Emittery<{
584581
public toggleStepEvent(enable: boolean) {
585582
this.#emitStepEvent = enable;
586583
}
587-
588-
/**
589-
* Sets the #currentBlockBaseFeePerGas property if the current block
590-
* has a baseFeePerGas property
591-
*/
592-
#setCurrentBlockBaseFeePerGas = (block: RuntimeBlock) => {
593-
const baseFeePerGas = block.header.baseFeePerGas;
594-
// before london hard fork, there will be no baseFeePerGas on the block
595-
this.#currentBlockBaseFeePerGas =
596-
baseFeePerGas === undefined ? undefined : Quantity.from(baseFeePerGas);
597-
};
598584
}

src/chains/ethereum/ethereum/src/transaction-pool.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import { EthereumInternalOptions } from "@ganache/ethereum-options";
1515
import { Executables } from "./miner/executables";
1616
import { TypedTransaction } from "@ganache/ethereum-transaction";
17+
import { Block } from "@ganache/ethereum-block";
1718

1819
/**
1920
* Checks if the `replacer` is eligible to replace the `replacee` transaction
@@ -188,7 +189,10 @@ export default class TransactionPool extends Emittery<{ drain: undefined }> {
188189
!transaction.effectiveGasPrice &&
189190
this.#blockchain.common.isActivatedEIP(1559)
190191
) {
191-
const baseFeePerGas = this.#blockchain.blocks.latest.header.baseFeePerGas;
192+
const baseFeePerGas = Block.calcNextBaseFee(
193+
this.#blockchain.blocks.latest
194+
);
195+
192196
transaction.updateEffectiveGasPrice(baseFeePerGas);
193197
}
194198

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

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -543,34 +543,67 @@ describe("api", () => {
543543
assert(receipt.transactionIndex, "0x0");
544544
});
545545

546-
it("eth_getTransactionByHash", async () => {
547-
await provider.send("eth_subscribe", ["newHeads"]);
548-
const txJson = {
549-
type: "0x2",
550-
chainId: "0x539",
551-
nonce: "0x0",
552-
from: accounts[0],
553-
to: accounts[1],
554-
value: "0x1",
555-
maxPriorityFeePerGas: "0xf",
556-
maxFeePerGas: "0xfffffffff",
557-
gas: "0x15f90",
558-
input: "0x01",
559-
accessList: []
560-
} as any;
561-
const hash = await provider.send("eth_sendTransaction", [txJson]);
562-
const _message = await provider.once("message");
563-
// we want these values set for when we check against the return data,
564-
// but they shouldn't be used in eth_sendTransaction, so we'll set them now
565-
txJson.transactionIndex = "0x0";
566-
txJson.gasPrice = "0x342770cf";
567-
568-
const tx = await provider.send("eth_getTransactionByHash", [hash]);
546+
describe("eth_getTransactionByHash", () => {
547+
it("returns a transaction matching the sent transaction", async () => {
548+
await provider.send("eth_subscribe", ["newHeads"]);
549+
const txJson = {
550+
type: "0x2",
551+
chainId: "0x539",
552+
nonce: "0x0",
553+
from: accounts[0],
554+
to: accounts[1],
555+
value: "0x1",
556+
maxPriorityFeePerGas: "0xf",
557+
maxFeePerGas: "0xfffffffff",
558+
gas: "0x15f90",
559+
input: "0x01",
560+
accessList: []
561+
} as any;
562+
const hash = await provider.send("eth_sendTransaction", [txJson]);
563+
const _message = await provider.once("message");
564+
// we want these values set for when we check against the return data,
565+
// but they shouldn't be used in eth_sendTransaction, so we'll set them now
566+
txJson.transactionIndex = "0x0";
567+
txJson.gasPrice = "0x342770cf";
568+
569+
const tx = await provider.send("eth_getTransactionByHash", [hash]);
570+
571+
// loop over all of the data we set to verify it matches
572+
for (const [key, value] of Object.entries(txJson)) {
573+
assert.deepStrictEqual(value, tx[key]);
574+
}
575+
});
569576

570-
// loop over all of the data we set to verify it matches
571-
for (const [key, value] of Object.entries(txJson)) {
572-
assert.deepStrictEqual(value, tx[key]);
573-
}
577+
it("has a `gasPrice` that matches the corresponding receipt's `effectiveGasPrice` after the transaction is mined", async () => {
578+
await provider.send("eth_subscribe", ["newHeads"]);
579+
await provider.send("miner_stop", []);
580+
const txJson = {
581+
type: "0x2",
582+
from: accounts[0],
583+
to: accounts[1],
584+
maxPriorityFeePerGas: "0x77359400",
585+
maxFeePerGas: "0x6FC23AC00"
586+
} as any;
587+
// we previously had a bug where the `gasPrice` field returned from
588+
// `eth_getTransactionByHash` was incorrect when multiple transactions
589+
// were sent, because we weren't assigning the correct `effectiveGasPrice`
590+
// when adding the transaction to the pool. see:
591+
// https://github.com/trufflesuite/ganache/issues/4094
592+
const hashes = await Promise.all([
593+
provider.send("eth_sendTransaction", [txJson]),
594+
provider.send("eth_sendTransaction", [txJson])
595+
]);
596+
await provider.send("miner_start", []);
597+
const _message = await provider.once("message");
598+
for (const hash of hashes) {
599+
const tx = await provider.send("eth_getTransactionByHash", [hash]);
600+
const receipt = await provider.send("eth_getTransactionReceipt", [
601+
hash
602+
]);
603+
assert.deepStrictEqual(tx.gasPrice, receipt.effectiveGasPrice);
604+
assert.deepStrictEqual(tx.gasPrice.toString(), "0xab5d04c0");
605+
}
606+
});
574607
});
575608
});
576609
});

src/chains/ethereum/ethereum/tests/transaction-pool.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,13 @@ describe("transaction pool", async () => {
9292
},
9393
common,
9494
blocks: {
95-
latest: { header: { baseFeePerGas: Quantity.from(875000000) } }
95+
latest: {
96+
header: {
97+
baseFeePerGas: Quantity.from(875000000),
98+
gasLimit: Quantity.from(30000000),
99+
gasUsed: Quantity.from(0)
100+
}
101+
}
96102
}
97103
};
98104
});
@@ -147,9 +153,7 @@ describe("transaction pool", async () => {
147153
}
148154
},
149155
common,
150-
blocks: {
151-
latest: { header: { baseFeePerGas: Quantity.from(875000000) } }
152-
}
156+
blocks: blockchain.blocks
153157
} as any;
154158
const txPool = new TransactionPool(options.miner, fakeNonceChain, origins);
155159
const executableTx = TransactionFactory.fromRpc(executableRpc, common);

src/chains/ethereum/transaction/src/eip1559-fee-market-transaction.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -253,12 +253,11 @@ export class EIP1559FeeMarketTransaction extends RuntimeTransaction {
253253
return computeIntrinsicsFeeMarketTx(v, <EIP1559FeeMarketDatabaseTx>raw);
254254
}
255255

256-
public updateEffectiveGasPrice(baseFeePerGas: Quantity) {
257-
const baseFeePerGasBigInt = baseFeePerGas.toBigInt();
256+
public updateEffectiveGasPrice(baseFeePerGas: bigint) {
258257
const maxFeePerGas = this.maxFeePerGas.toBigInt();
259258
const maxPriorityFeePerGas = this.maxPriorityFeePerGas.toBigInt();
260-
const a = maxFeePerGas - baseFeePerGasBigInt;
259+
const a = maxFeePerGas - baseFeePerGas;
261260
const tip = a < maxPriorityFeePerGas ? a : maxPriorityFeePerGas;
262-
this.effectiveGasPrice = Quantity.from(baseFeePerGasBigInt + tip);
261+
this.effectiveGasPrice = Quantity.from(baseFeePerGas + tip);
263262
}
264263
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,5 +256,5 @@ export abstract class RuntimeTransaction extends BaseTransaction {
256256
);
257257

258258
protected abstract toVmTransaction();
259-
protected abstract updateEffectiveGasPrice(baseFeePerGas?: Quantity);
259+
protected abstract updateEffectiveGasPrice(baseFeePerGas: bigint);
260260
}

src/chains/ethereum/transaction/src/transaction-receipt.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -133,30 +133,19 @@ export class InternalTransactionReceipt {
133133
}
134134
}
135135

136-
public toJSON(
137-
block: {
138-
hash(): Data;
139-
header: { number: Quantity; baseFeePerGas?: Quantity };
140-
},
141-
transaction: TypedTransaction,
142-
common: Common
143-
) {
136+
public toJSON(transaction: TypedTransaction, common: Common) {
144137
const raw = this.raw;
145138
const contractAddress =
146139
this.contractAddress.length === 0
147140
? null
148141
: Data.from(this.contractAddress);
149-
const blockHash = block.hash();
150-
const blockNumber = block.header.number;
142+
const { blockHash, blockNumber } = transaction;
151143
const blockLog = BlockLogs.create(blockHash);
152144
const transactionHash = transaction.hash;
153145
const transactionIndex = transaction.index;
154146
blockLog.blockNumber = blockNumber;
155147
raw[3].forEach(l => blockLog.append(transactionIndex, transactionHash, l));
156148
const logs = [...blockLog.toJSON()];
157-
if (block.header.baseFeePerGas) {
158-
transaction.updateEffectiveGasPrice(block.header.baseFeePerGas);
159-
}
160149
const json: TransactionReceipt = {
161150
transactionHash,
162151
transactionIndex,

0 commit comments

Comments
 (0)