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

Commit

Permalink
feat: add txpool_content RPC method. (#1539)
Browse files Browse the repository at this point in the history
This adds support for the txpool_content RPC method, which is a Geth extension to the JSON-RPC API.  For more details, see the specification at https://geth.ethereum.org/docs/rpc/ns-txpool.

Co-authored-by: David Murdoch <[email protected]>
  • Loading branch information
domob1812 and davidmurdoch authored Jan 4, 2022
1 parent 4e9a443 commit bdf6a64
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 6 deletions.
54 changes: 53 additions & 1 deletion src/chains/ethereum/ethereum/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ import { BaseFeeHeader, Block, RuntimeBlock } from "@ganache/ethereum-block";
import {
TypedRpcTransaction,
TransactionFactory,
TypedTransaction
TypedTransaction,
TypedTransactionJSON
} from "@ganache/ethereum-transaction";
import { toRpcSig, ecsign, hashPersonalMessage } from "ethereumjs-util";
import { TypedData as NotTypedData, signTypedData_v4 } from "eth-sig-util";
import {
Data,
Heap,
Quantity,
PromiEvent,
Api,
Expand Down Expand Up @@ -3128,4 +3130,54 @@ export default class EthereumApi implements Api {
return "2";
}
//#endregion

//#region txpool

/**
* Returns the current content of the transaction pool.
*
* @returns The transactions currently pending or queued in the transaction pool.
* @example
* ```javascript
* const [from] = await provider.request({ method: "eth_accounts", params: [] });
* const pendingTx = await provider.request({ method: "eth_sendTransaction", params: [{ from, gas: "0x5b8d80", nonce:"0x0" }] });
* const queuedTx = await provider.request({ method: "eth_sendTransaction", params: [{ from, gas: "0x5b8d80", nonce:"0x2" }] });
* const pool = await provider.send("txpool_content");
* console.log(pool);
* ```
*/
@assertArgLength(0)
async txpool_content(): Promise<{
pending: Map<string, Map<string, TypedTransactionJSON>>;
queued: Map<string, Map<string, TypedTransactionJSON>>;
}> {
const { transactions, common } = this.#blockchain;
const { transactionPool } = transactions;

const processMap = (map: Map<string, Heap<TypedTransaction>>) => {
let res = new Map<string, Map<string, TypedTransactionJSON>>();
for (let [_, transactions] of map) {
const arr = transactions.array;
for (let i = 0; i < transactions.length; ++i) {
const tx = arr[i];
const from = tx.from.toString();
if (res[from] === undefined) {
res[from] = {};
}
// The nonce keys are actual decimal numbers (as strings) and not
// hex literals (based on what geth returns).
const nonce = tx.nonce.toBigInt().toString();
res[from][nonce] = tx.toJSON(common);
}
}
return res;
};

return {
pending: processMap(transactionPool.executables.pending),
queued: processMap(transactionPool.origins)
};
}

//#endregion
}
10 changes: 5 additions & 5 deletions src/chains/ethereum/ethereum/src/transaction-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,14 @@ export default class TransactionPool extends Emittery<{ drain: undefined }> {
super();
this.#blockchain = blockchain;
this.#options = options;
this.#origins = origins;
this.origins = origins;
this.#priceBump = options.priceBump;
}
public readonly executables: Executables = {
inProgress: new Set(),
pending: new Map()
};
readonly #origins: Map<string, Heap<TypedTransaction>>;
public readonly origins: Map<string, Heap<TypedTransaction>>;
readonly #accountPromises = new Map<
string,
Promise<{ balance: Quantity; nonce: Quantity }>
Expand Down Expand Up @@ -227,7 +227,7 @@ export default class TransactionPool extends Emittery<{ drain: undefined }> {
);
}

const origins = this.#origins;
const origins = this.origins;
const queuedOriginTransactions = origins.get(origin);

let transactionPlacement = TriageOption.FutureQueue;
Expand Down Expand Up @@ -404,7 +404,7 @@ export default class TransactionPool extends Emittery<{ drain: undefined }> {
}

public clear() {
this.#origins.clear();
this.origins.clear();
this.#accountPromises.clear();
this.executables.pending.clear();
}
Expand All @@ -422,7 +422,7 @@ export default class TransactionPool extends Emittery<{ drain: undefined }> {
const { pending, inProgress } = this.executables;

// first search pending transactions
for (let [_, transactions] of this.#origins) {
for (let [_, transactions] of this.origins) {
if (transactions === undefined) continue;
const arr = transactions.array;
for (let i = 0; i < transactions.length; i++) {
Expand Down
124 changes: 124 additions & 0 deletions src/chains/ethereum/ethereum/tests/api/txpool/content.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import getProvider from "../../helpers/getProvider";
import assert from "assert";
import EthereumProvider from "../../../src/provider";

describe("txpool", () => {
describe("content", () => {
let provider: EthereumProvider;
let accounts: string[];
beforeEach(async () => {
provider = await getProvider({
miner: { blockTime: 1000 }
});
accounts = await provider.send("eth_accounts");
});

it("returns the expected transaction data fields", async () => {
let txJson = {
from: accounts[1],
to: accounts[2],
value: "0x123",
input: "0xaabbcc",
gas: "0x10000",
type: "0x2",
maxPriorityFeePerGas: "0xf",
maxFeePerGas: "0xffffffff"
} as any;
const hash = await provider.send("eth_sendTransaction", [txJson]);

const { pending } = await provider.send("txpool_content");
const txData = pending[accounts[1]]["0"];

txJson["hash"] = hash;
txJson["blockHash"] = null;
txJson["blockNumber"] = null;
txJson["transactionIndex"] = null;

for (const [key, value] of Object.entries(txJson)) {
assert.deepStrictEqual(value, txData[key]);
}
});

it("handles pending transactions", async () => {
const tx1 = await provider.send("eth_sendTransaction", [
{
from: accounts[1],
to: accounts[2]
}
]);
const tx2 = await provider.send("eth_sendTransaction", [
{
from: accounts[2],
to: accounts[3]
}
]);
const tx3 = await provider.send("eth_sendTransaction", [
{
from: accounts[1],
to: accounts[2]
}
]);

const { pending } = await provider.send("txpool_content");
assert.strictEqual(pending[accounts[1]]["0"].hash, tx1);
assert.strictEqual(pending[accounts[1]]["1"].hash, tx3);
assert.strictEqual(pending[accounts[2]]["0"].hash, tx2);
});

it("handles replaced transactions", async () => {
const tx1 = await provider.send("eth_sendTransaction", [
{
from: accounts[1],
to: accounts[2],
value: "0x42",
nonce: "0x0",
type: "0x2",
maxPriorityFeePerGas: "0xa0000000",
maxFeePerGas: "0xa0000000"
}
]);
const tx2 = await provider.send("eth_sendTransaction", [
{
from: accounts[1],
to: accounts[2],
value: "0x4200",
nonce: "0x0",
type: "0x2",
maxPriorityFeePerGas: "0xf0000000",
maxFeePerGas: "0xf0000000"
}
]);

const { pending } = await provider.send("txpool_content");
assert.strictEqual(pending[accounts[1]]["0"].hash, tx2);
assert.strictEqual(pending[accounts[1]]["1"], undefined);
});

it("handles queued transactions", async () => {
const tx = await provider.send("eth_sendTransaction", [
{
from: accounts[1],
to: accounts[2],
nonce: "0x123"
}
]);

const { queued } = await provider.send("txpool_content");
assert.strictEqual(queued[accounts[1]]["291"].hash, tx);
});

it("does not return confirmed transactions", async () => {
await provider.send("eth_subscribe", ["newHeads"]);
await provider.send("eth_sendTransaction", [
{ from: accounts[1], to: accounts[2] }
]);
await provider.send("evm_mine");
await provider.once("message");

const { pending, queued } = await provider.send("txpool_content");
assert.deepStrictEqual(pending, {});
assert.deepStrictEqual(queued, {});
});

});
});

0 comments on commit bdf6a64

Please sign in to comment.