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 4 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
7 changes: 6 additions & 1 deletion src/chains/ethereum/ethereum/src/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1058,7 +1058,12 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
if (this.#isPaused() || !this.#instamine) {
return hash;
} else {
if (this.#instamine && this.#options.miner.instamine === "eager") {
// if the transaction is not executable, we just have to return the hash
if (
this.#instamine &&
this.#options.miner.instamine === "eager" &&
isExecutable
davidmurdoch marked this conversation as resolved.
Show resolved Hide resolved
) {
// 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 Down
100 changes: 0 additions & 100 deletions src/chains/ethereum/ethereum/tests/api/eth/greedyInstamining.test.ts

This file was deleted.

138 changes: 138 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,138 @@
import getProvider from "../../helpers/getProvider";
davidmurdoch marked this conversation as resolved.
Show resolved Hide resolved
import assert from "assert";

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("returns the tx hash before mining for future-nonce transactions", async () => {
davidmurdoch marked this conversation as resolved.
Show resolved Hide resolved
const provider = await getProvider({
miner: { instamine: "eager" },
chain: { vmErrorsOnRPCResponse: true }
});
const [from, to] = await provider.send("eth_accounts");
const futureNonceTx = { from, to, nonce: "0x1" };
const futureNonceTxHash = await provider.send("eth_sendTransaction", [
futureNonceTx
]);
assert.notEqual(futureNonceTxHash, null);
const nullReceipt = await provider.send("eth_getTransactionReceipt", [
futureNonceTxHash
]);
assert.strictEqual(nullReceipt, null);
await provider.send("eth_subscribe", ["newHeads"]);
// send a transaction to fill the nonce gap
await provider.send("eth_sendTransaction", [{ from, to }]);
// usually we don't have to wait for these messages in eager mode,
// but because instamine only mines one tx per block, we need to
// wait for our future-nonce tx to be mined before fetching the
// receipt
await provider.once("message");
await provider.once("message");
// now our nonce gap is filled so the original tx is mined
const receipt = await provider.send("eth_getTransactionReceipt", [
futureNonceTxHash
]);
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();
}
);
});
});
});
});
});
});