-
Notifications
You must be signed in to change notification settings - Fork 680
feat: Add remaining txpool methods #3719
base: develop
Are you sure you want to change the base?
Changes from all commits
1f17177
f053f6d
46609dc
1b76dc4
88e62fc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -3426,39 +3426,83 @@ export default class EthereumApi implements Api { | |||||
*/ | ||||||
@assertArgLength(0) | ||||||
async txpool_content(): Promise<Ethereum.Pool.Content<"private">> { | ||||||
const { transactions, common } = this.#blockchain; | ||||||
const { transactions } = this.#blockchain; | ||||||
const { | ||||||
transactionPool: { executables, origins } | ||||||
transactionPool: { executables, origins, processMap } | ||||||
} = transactions; | ||||||
|
||||||
const processMap = (map: Map<string, Heap<TypedTransaction>>) => { | ||||||
let res: Record< | ||||||
string, | ||||||
Record<string, Ethereum.Pool.Transaction<"private">> | ||||||
> = {}; | ||||||
for (let [_, { array, length }] of map) { | ||||||
for (let i = 0; i < length; ++i) { | ||||||
const transaction = array[i]; | ||||||
const from = transaction.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 = transaction.nonce.toBigInt().toString(); | ||||||
res[from][nonce] = transaction.toJSON( | ||||||
common | ||||||
) as Ethereum.Pool.Transaction<"private">; | ||||||
} | ||||||
} | ||||||
return res; | ||||||
}; | ||||||
|
||||||
return { | ||||||
pending: processMap(executables.pending), | ||||||
queued: processMap(origins) | ||||||
}; | ||||||
} | ||||||
|
||||||
|
||||||
/** | ||||||
* Returns transactions created by the specified address currently pending inclusion in the next blocks, as well as the ones that are scheduled for future execution. | ||||||
* | ||||||
* @param address - The account address | ||||||
* @returns The transactions currently pending or queued in the transaction pool by address. | ||||||
* @example | ||||||
* ```javascript | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For some reason these examples aren't working in the generated docs - but the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found it! await provider.send("miner_stop") Also, for the |
||||||
* 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_contentFrom", [from]); | ||||||
* console.log(pool); | ||||||
* ``` | ||||||
*/ | ||||||
@assertArgLength(1) | ||||||
async txpool_contentFrom(address: DATA): Promise<Ethereum.Pool.Content<"private">> { | ||||||
const { transactions } = this.#blockchain; | ||||||
const { | ||||||
transactionPool: { executables, origins, processMap } | ||||||
} = transactions; | ||||||
|
||||||
const fromAddress = Address.from(address); | ||||||
|
||||||
return { | ||||||
pending: processMap(executables.pending, fromAddress), | ||||||
queued: processMap(origins, fromAddress) | ||||||
}; | ||||||
} | ||||||
|
||||||
/** | ||||||
* Returns the number of transactions currently pending for inclusion in the next block(s), as well as the ones that are being scheduled for future execution only. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
* | ||||||
* @returns The count of 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_status"); | ||||||
* console.log(pool); | ||||||
* ``` | ||||||
*/ | ||||||
@assertArgLength(0) | ||||||
async txpool_status(): Promise<{pending: number, queued: number}> { | ||||||
const { transactions } = this.#blockchain; | ||||||
const { | ||||||
transactionPool: { executables, origins } | ||||||
} = transactions; | ||||||
|
||||||
let pending = 0; | ||||||
let queued = 0; | ||||||
|
||||||
for (const [_, transactions] of executables.pending) { | ||||||
pending += transactions?.array.length || 0; | ||||||
} | ||||||
|
||||||
for (const [_, transactions] of origins) { | ||||||
queued += transactions?.array.length || 0; | ||||||
} | ||||||
|
||||||
return { | ||||||
pending, | ||||||
queued | ||||||
}; | ||||||
} | ||||||
|
||||||
//#endregion | ||||||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -14,6 +14,8 @@ import { | |||||||||||||||||||||||||
import { EthereumInternalOptions } from "@ganache/ethereum-options"; | ||||||||||||||||||||||||||
import { Executables } from "./miner/executables"; | ||||||||||||||||||||||||||
import { TypedTransaction } from "@ganache/ethereum-transaction"; | ||||||||||||||||||||||||||
import { Ethereum } from "./api-types"; | ||||||||||||||||||||||||||
import { Address } from "@ganache/ethereum-address"; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
* Checks if the `replacer` is eligible to replace the `replacee` transaction | ||||||||||||||||||||||||||
|
@@ -453,6 +455,33 @@ export default class TransactionPool extends Emittery<{ drain: undefined }> { | |||||||||||||||||||||||||
return null; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we'll need a js-doc comment here. Could you add something like this? (feel free to refine)
Suggested change
|
||||||||||||||||||||||||||
public processMap(map: Map<string, Heap<TypedTransaction>>, address?: Address) { | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We probably should have come up with a better name for this function when we first reviewed it. But especially now that it's being used elsewhere, I think we should rename this. The trouble is deciding on the name! Perhaps |
||||||||||||||||||||||||||
let res: Record< | ||||||||||||||||||||||||||
string, | ||||||||||||||||||||||||||
Record<string, Ethereum.Pool.Transaction<"private">> | ||||||||||||||||||||||||||
> = {}; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
for (let [_, { array, length }] of map) { | ||||||||||||||||||||||||||
for (let i = 0; i < length; ++i) { | ||||||||||||||||||||||||||
const transaction = array[i]; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
if(address && transaction.from.toString() != address.toString()) { | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're getting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we also store |
||||||||||||||||||||||||||
continue; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
const from = transaction.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 = transaction.nonce.toBigInt().toString(); | ||||||||||||||||||||||||||
res[from][nonce] = transaction.toJSON() as Ethereum.Pool.Transaction<"private">; | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe we will still need to pass in a |
||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
return res; | ||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
readonly drain = () => { | ||||||||||||||||||||||||||
// notify listeners (the blockchain, then the miner, eventually) that we | ||||||||||||||||||||||||||
// have executable transactions ready | ||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -120,4 +120,110 @@ describe("txpool", () => { | |
assert.deepStrictEqual(queued, {}); | ||
}); | ||
}); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For some reason github isn't formatting this file as typescript. I wonder what's going on with that 🤔 |
||
describe("contentFrom", () => { | ||
let provider: EthereumProvider; | ||
let accounts: string[]; | ||
|
||
beforeEach(async () => { | ||
provider = await getProvider({ | ||
miner: { blockTime: 1000 } | ||
}); | ||
accounts = await provider.send("eth_accounts"); | ||
}); | ||
|
||
it("handles pending and queued transactions", async () => { | ||
const pendingTransactions = await Promise.all([ | ||
provider.send("eth_sendTransaction", [ | ||
{ | ||
from: accounts[1], | ||
to: accounts[2] | ||
} | ||
]), | ||
provider.send("eth_sendTransaction", [ | ||
{ | ||
from: accounts[2], | ||
to: accounts[3] | ||
} | ||
]), | ||
provider.send("eth_sendTransaction", [ | ||
{ | ||
from: accounts[1], | ||
to: accounts[2] | ||
} | ||
]) | ||
]); | ||
|
||
const queuedTransactions = await Promise.all([ | ||
provider.send("eth_sendTransaction", [ | ||
{ | ||
from: accounts[1], | ||
to: accounts[2], | ||
nonce: "0x123", | ||
} | ||
]) | ||
]); | ||
|
||
const { pending, queued } = await provider.send("txpool_contentFrom", [accounts[1]]); | ||
|
||
const pendingAddresses = Object.keys(pending); | ||
const queuedAddresses = Object.keys(queued); | ||
|
||
assert(pendingAddresses.findIndex((value) => value === accounts[1]) == 0) | ||
assert(pendingAddresses.findIndex((value) => value === accounts[2]) == -1) | ||
assert(pendingAddresses.length == 1) | ||
|
||
assert(queuedAddresses.findIndex((value) => value === accounts[1]) == 0) | ||
assert(queuedAddresses.findIndex((value) => value === accounts[2]) == -1) | ||
assert(queuedAddresses.length == 1) | ||
}) | ||
}) | ||
|
||
describe("status", () => { | ||
let provider: EthereumProvider; | ||
let accounts: string[]; | ||
beforeEach(async () => { | ||
provider = await getProvider({ | ||
miner: { blockTime: 1000 } | ||
}); | ||
accounts = await provider.send("eth_accounts"); | ||
}); | ||
|
||
it("handles pending and queued transactions", async () => { | ||
const pendingTransactions = await Promise.all([ | ||
provider.send("eth_sendTransaction", [ | ||
{ | ||
from: accounts[1], | ||
to: accounts[2] | ||
} | ||
]), | ||
provider.send("eth_sendTransaction", [ | ||
{ | ||
from: accounts[2], | ||
to: accounts[3] | ||
} | ||
]), | ||
provider.send("eth_sendTransaction", [ | ||
{ | ||
from: accounts[1], | ||
to: accounts[2] | ||
} | ||
]) | ||
]); | ||
|
||
const queuedTransactions = await Promise.all([ | ||
provider.send("eth_sendTransaction", [ | ||
{ | ||
from: accounts[1], | ||
to: accounts[2], | ||
nonce: "0x123", | ||
} | ||
]) | ||
]); | ||
|
||
const { pending, queued } = await provider.send("txpool_status"); | ||
assert.strictEqual(pending, pendingTransactions.length); | ||
assert.strictEqual(queued, queuedTransactions.length); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.