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

fix: log warning regarding transactions with future-nonces when mining in eager mode #4166

Merged
merged 8 commits into from
Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 15 additions & 4 deletions src/chains/ethereum/ethereum/src/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1054,11 +1054,22 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
process.nextTick(this.emit.bind(this), "pendingTransaction", transaction);
}

const hash = transaction.hash;
if (this.#isPaused() || !this.#instamine) {
const { hash } = transaction;
const instamine = this.#instamine;
if (!instamine || this.#isPaused()) {
return hash;
} else {
if (this.#instamine && this.#options.miner.instamine === "eager") {
const options = this.#options;
// if the transaction is not executable, we just have to return the hash
if (instamine && options.miner.instamine === "eager") {
if (!isExecutable) {
// users have been confused about why ganache "hangs" when sending a
// transaction with a "too-high" nonce. This message should help them
// understand what's going on.
options.logging.logger.log(
`Transaction "${hash}" has a too-high nonce; this transaction has been added to the pool, and will be processed when its nonce is reached. See https://github.com/trufflesuite/ganache/issues/4165 for more information.`
);
}
// in eager instamine mode we must wait for the transaction to be saved
// before we can return the hash
const { status, error } = await transaction.once("finalized");
Expand All @@ -1067,7 +1078,7 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
// vmErrorsOnRPCResponse is enabled.
if (
error &&
(status === "rejected" || this.#options.chain.vmErrorsOnRPCResponse)
(status === "rejected" || options.chain.vmErrorsOnRPCResponse)
)
throw error;
}
Expand Down
100 changes: 0 additions & 100 deletions src/chains/ethereum/ethereum/tests/api/eth/greedyInstamining.test.ts

This file was deleted.

146 changes: 146 additions & 0 deletions src/chains/ethereum/ethereum/tests/api/eth/instamine.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import getProvider from "../../helpers/getProvider";
davidmurdoch marked this conversation as resolved.
Show resolved Hide resolved
import assert from "assert";
import { Logger } from "@ganache/ethereum-options/typings/src/logging-options";

describe("api", () => {
describe("eth", () => {
describe("instamine modes (eager/strict)", () => {
describe("strict", () => {
it("when in strict instamine mode, does not mine before returning the tx hash", async () => {
const provider = await getProvider({
miner: { instamine: "strict" }
});
const accounts = await provider.send("eth_accounts");

const hash = await provider.send("eth_sendTransaction", [
{
from: accounts[0],
to: accounts[1],
value: "0x1"
}
]);
const receipt = await provider.send("eth_getTransactionReceipt", [
hash
]);
assert.strictEqual(receipt, null);
});
});

describe("eager", () => {
it("mines before returning the tx hash", async () => {
const provider = await getProvider({
miner: { instamine: "eager" }
});
const accounts = await provider.send("eth_accounts");

const hash = await provider.send("eth_sendTransaction", [
{
from: accounts[0],
to: accounts[1],
value: "0x1"
}
]);
const receipt = await provider.send("eth_getTransactionReceipt", [
hash
]);
assert.notStrictEqual(receipt, null);
});

it("log a message about future-nonce transactions in eager mode", async () => {
let logger: Logger;
const logPromise = new Promise<boolean>(resolve => {
logger = {
log: (msg: string) => {
const regex =
/Transaction "0x[a-zA-z0-9]{64}" has a too-high nonce; this transaction has been added to the pool, and will be processed when its nonce is reached\. See https:\/\/github.com\/trufflesuite\/ganache\/issues\/4165 for more information\./;
if (regex.test(msg)) resolve(true);
}
};
});

const provider = await getProvider({
logging: { logger },
miner: { instamine: "eager" },
chain: { vmErrorsOnRPCResponse: true }
});
const [from, to] = await provider.send("eth_accounts");
const futureNonceTx = { from, to, nonce: "0x1" };
const futureNonceProm = provider.send("eth_sendTransaction", [
futureNonceTx
]);

// send a transaction to fill the nonce gap
provider.send("eth_sendTransaction", [{ from, to }]); // we don't await this on purpose.

const result = await Promise.race([futureNonceProm, logPromise]);
// `logPromise` should resolve before the the hash gets returned
// (logPromise returns true)
assert.strictEqual(result, true);

// now our nonce gap is filled so the original tx is mined
const receipt = await provider.send("eth_getTransactionReceipt", [
await futureNonceProm
]);
assert.notStrictEqual(receipt, null);
});

it("handles transaction balance errors, callback style", done => {
getProvider({
miner: { instamine: "eager" },
chain: { vmErrorsOnRPCResponse: true }
}).then(async provider => {
const [from, to] = await provider.send("eth_accounts");
const balance = parseInt(
await provider.send("eth_getBalance", [from]),
16
);
const gasCost = 99967968750001;
// send a transaction that will spend some of the balance
provider.request({
method: "eth_sendTransaction",
params: [
{
from,
to
}
]
});

// send another transaction while the previous transaction is still
// pending. this transaction appears to have enough balance to run,
// so the transaction pool will accept it, but when it runs in the VM
// it won't have enough balance to run.
provider.send(
{
jsonrpc: "2.0",
id: "1",
method: "eth_sendTransaction",
params: [
{
from,
to,
value: `0x${(balance - gasCost).toString(16)}`
}
]
},
(e, r) => {
assert(
e.message.includes(
"sender doesn't have enough funds to send tx"
)
);
assert.strictEqual(e.message, (r as any).error.message);
assert.strictEqual((r as any).error.code, -32000);
assert.strictEqual(
typeof (r as any).error.data.result,
"string"
);
done();
}
);
});
});
});
});
});
});