diff --git a/Makefile b/Makefile index d0853109c..0643e390e 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ SPECFLAGS := -schemas 'src/schemas' \ -schemas 'src/engine/openrpc/schemas' \ -methods 'src/eth' \ -methods 'src/debug' \ + -methods 'src/txpool' \ -methods 'src/engine/openrpc/methods' \ -methods 'src/testing' \ -error-groups 'src/error-groups' diff --git a/src/schemas/txpool.yaml b/src/schemas/txpool.yaml new file mode 100644 index 000000000..a20b43639 --- /dev/null +++ b/src/schemas/txpool.yaml @@ -0,0 +1,99 @@ +TxpoolStatus: + type: object + title: Transaction pool status + description: The number of pending and queued transactions in the pool. + required: + - pending + - queued + properties: + pending: + title: pending count + description: Number of transactions ready for inclusion in the next block(s) + $ref: '#/components/schemas/uint' + queued: + title: queued count + description: Number of transactions with nonce gaps awaiting preceding transactions before they can be executed + $ref: '#/components/schemas/uint' + +PendingTransactionInfo: + type: object + title: Pending transaction information + description: Transaction in the pool, not yet included in a block. + allOf: + - title: Contextual information + required: + - from + - hash + properties: + blockHash: + title: block hash + description: Always null for pending transactions + type: "null" + blockNumber: + title: block number + description: Always null for pending transactions + type: "null" + blockTimestamp: + title: block timestamp + description: Always null for pending transactions + type: "null" + from: + title: from address + $ref: '#/components/schemas/address' + hash: + title: transaction hash + $ref: '#/components/schemas/hash32' + transactionIndex: + title: transaction index + description: Always null for pending transactions + type: "null" + - $ref: '#/components/schemas/TransactionSigned' + +TxpoolContentByAddress: + type: object + title: Transactions by nonce + description: Map of nonce to transaction object + additionalProperties: + $ref: '#/components/schemas/PendingTransactionInfo' + +TxpoolContentAddressMap: + type: object + title: Transactions by address + description: Map of address to transactions grouped by nonce + additionalProperties: + $ref: '#/components/schemas/TxpoolContentByAddress' + +TxpoolContent: + type: object + title: Transaction pool content + description: All pending and queued transactions in the pool, grouped by address and nonce. + required: + - pending + - queued + properties: + pending: + title: pending transactions + description: Transactions ready for inclusion in the next block(s) + $ref: '#/components/schemas/TxpoolContentAddressMap' + queued: + title: queued transactions + description: Transactions with nonce gaps awaiting preceding transactions before they can be executed + $ref: '#/components/schemas/TxpoolContentAddressMap' + +TxpoolContentFromResult: + type: object + title: Transaction pool content from address + description: Pending and queued transactions from a specific address, grouped by nonce. + required: + - pending + - queued + properties: + pending: + title: pending transactions + description: Transactions ready for inclusion in the next block(s) + $ref: '#/components/schemas/TxpoolContentByAddress' + queued: + title: queued transactions + description: Transactions with nonce gaps awaiting preceding transactions before they can be executed + $ref: '#/components/schemas/TxpoolContentByAddress' + diff --git a/src/txpool/pool.yaml b/src/txpool/pool.yaml new file mode 100644 index 000000000..e4ef27a61 --- /dev/null +++ b/src/txpool/pool.yaml @@ -0,0 +1,104 @@ +- name: txpool_status + summary: Returns the number of pending and queued transactions in the pool. + description: | + Returns an object containing the count of transactions currently pending for + inclusion in the next block(s), as well as ones that are scheduled for future + execution (transactions with nonce gaps). + params: [] + result: + name: Transaction pool status + schema: + $ref: '#/components/schemas/TxpoolStatus' + examples: + - name: txpool_status example + params: [] + result: + name: Transaction pool status + value: + pending: '0xa' + queued: '0x7' + +- name: txpool_content + summary: Returns the contents of the transaction pool. + description: | + Returns an object containing all pending and queued transactions in the pool, + grouped by origin address and sorted by nonce. Pending transactions are ready + for inclusion in the next block(s). Queued transactions have nonce gaps and + are scheduled for future execution. + params: [] + result: + name: Transaction pool content + schema: + $ref: '#/components/schemas/TxpoolContent' + examples: + - name: txpool_content example + params: [] + result: + name: Transaction pool content + value: + pending: + '0x0216d5032f356960cd3749c31ab34eeff21b3395': + '806': + blockHash: null + blockNumber: null + from: '0x0216d5032f356960cd3749c31ab34eeff21b3395' + gas: '0x5208' + gasPrice: '0xba43b7400' + hash: '0xaf953a2d01f55cfe080c0c94150a60105e8ac3d51153058a1f03dd239dd08586' + input: '0x' + nonce: '0x326' + to: '0x7f69a91a3cf4be60020fb58b893b7cbb65376db8' + transactionIndex: null + value: '0x19a99f0cf456000' + queued: + '0x0216d5032f356960cd3749c31ab34eeff21b3395': + '808': + blockHash: null + blockNumber: null + from: '0x0216d5032f356960cd3749c31ab34eeff21b3395' + gas: '0x5208' + gasPrice: '0xba43b7400' + hash: '0x593f723c6f7abc7878c4927d2f1a0e6c5a6c0b4c9f5a7b2e3d4c5f6a7b8c9d0e' + input: '0x' + nonce: '0x328' + to: '0x7f69a91a3cf4be60020fb58b893b7cbb65376db8' + transactionIndex: null + value: '0x19a99f0cf456000' + +- name: txpool_contentFrom + summary: Returns the transactions in the pool from a specific address. + description: | + Returns an object containing pending and queued transactions from the specified + address, grouped by nonce. This is a filtered version of txpool_content. + params: + - name: address + required: true + schema: + $ref: '#/components/schemas/address' + result: + name: Transaction pool content from address + schema: + $ref: '#/components/schemas/TxpoolContentFromResult' + examples: + - name: txpool_contentFrom example + params: + - name: address + value: '0x0216d5032f356960cd3749c31ab34eeff21b3395' + result: + name: Transaction pool content from address + value: + pending: + '806': + blockHash: null + blockNumber: null + from: '0x0216d5032f356960cd3749c31ab34eeff21b3395' + gas: '0x5208' + gasPrice: '0xba43b7400' + hash: '0xaf953a2d01f55cfe080c0c94150a60105e8ac3d51153058a1f03dd239dd08586' + input: '0x' + nonce: '0x326' + to: '0x7f69a91a3cf4be60020fb58b893b7cbb65376db8' + transactionIndex: null + value: '0x19a99f0cf456000' + queued: {} + diff --git a/tests/txpool_content/get-content.io b/tests/txpool_content/get-content.io new file mode 100644 index 000000000..8daf68796 --- /dev/null +++ b/tests/txpool_content/get-content.io @@ -0,0 +1,4 @@ +// retrieves the transaction pool content +// speconly: client response is only checked for schema validity. +>> {"jsonrpc":"2.0","id":1,"method":"txpool_content"} +<< {"jsonrpc":"2.0","id":1,"result":{"pending":{"0x0c2c51a0990AeE1d73C1228de158688341557508":{"0":{"blockHash":null,"blockNumber":null,"blockTimestamp":null,"from":"0x0c2c51a0990aee1d73c1228de158688341557508","gas":"0x61a8","gasPrice":"0x5763d65","hash":"0x66734e85ef096167acb887cf445946a1ed57b90b66ffe38af87e11294febbfa9","input":"0x5544","nonce":"0x0","to":"0xaa00000000000000000000000000000000000000","transactionIndex":null,"value":"0xa","type":"0x0","chainId":"0xc72dd9d5e883e","v":"0x18e5bb3abd109f","r":"0xc8e3b4a0087357bd49d80a0ac24daf0c91191e71086c1e355fc62cfab2218873","s":"0x74f4636f740fa4d1697b6e736e5982b700be2c8b63031a24fa531ae4814b3af8"},"1":{"blockHash":null,"blockNumber":null,"blockTimestamp":null,"from":"0x0c2c51a0990aee1d73c1228de158688341557508","gas":"0xea60","gasPrice":"0x5763f58","maxFeePerGas":"0x5763f58","maxPriorityFeePerGas":"0x1f4","hash":"0xfa245384e9eb7d6a4a40f3bf4bf70f1f44929d8bfcdf75762ce1a015389449e3","input":"0x3d602d80600a3d3981f3363d3d373d3d3d363d734d11c446473105a02b5c1ab9ebe9b03f33902a295af43d82803e903d91602b57fd5bf3","nonce":"0x1","to":null,"transactionIndex":null,"value":"0x2a","type":"0x2","accessList":[],"chainId":"0xc72dd9d5e883e","v":"0x1","r":"0xfe6d380224a516b802717755d2f640163e81bae64a4ab5adbcf741267f20ad66","s":"0x15d9ceb9fecb47b342be00782b2485f42ab53715006d208897cc969d7c05ab67","yParity":"0x1"},"2":{"blockHash":null,"blockNumber":null,"blockTimestamp":null,"from":"0x0c2c51a0990aee1d73c1228de158688341557508","gas":"0x15f90","gasPrice":"0x5763f58","hash":"0xd07a55a00aeb93c7825d1ca42238abdc3bc225de097ee1b8b2a4a9240ae55f9c","input":"0x010203","nonce":"0x2","to":"0x7dcd17433742f4c0ca53122ab541d0ba67fc27df","transactionIndex":null,"value":"0x0","type":"0x1","accessList":[{"address":"0x7dcd17433742f4c0ca53122ab541d0ba67fc27df","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000"]}],"chainId":"0xc72dd9d5e883e","v":"0x0","r":"0xf9dc42e8bab0a70132fb8399cf03cf38e1c12cc47f736d19e6e7728356d97db3","s":"0x53daf342acd24da15073f5dac02bec0501a0716165984aab2df9694882b91fac","yParity":"0x0"},"3":{"blockHash":null,"blockNumber":null,"blockTimestamp":null,"from":"0x0c2c51a0990aee1d73c1228de158688341557508","gas":"0x13880","gasPrice":"0x5763f58","maxFeePerGas":"0x5763f58","maxPriorityFeePerGas":"0x1f4","hash":"0x0baf604666cbc4d04263bbc98c048000451b8c188d93ec87ca5a86b044fd956c","input":"0x01020304","nonce":"0x3","to":"0x7dcd17433742f4c0ca53122ab541d0ba67fc27df","transactionIndex":null,"value":"0x0","type":"0x2","accessList":[{"address":"0x7dcd17433742f4c0ca53122ab541d0ba67fc27df","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000"]}],"chainId":"0xc72dd9d5e883e","v":"0x0","r":"0xe56d869d8b32f767582fdcb03d1d9d3bcc47f3c7ae08984feafdcd57f2f205f5","s":"0x74134e4bf0fb11ff606b47259aff0d01bf7cb9ec68cb179b62576b9dd6631cf0","yParity":"0x0"}},"0x14e46043e63D0E3cdcf2530519f4cFAf35058Cb2":{"0":{"blockHash":null,"blockNumber":null,"blockTimestamp":null,"from":"0x14e46043e63d0e3cdcf2530519f4cfaf35058cb2","gas":"0x5208","gasPrice":"0x5763f58","maxFeePerGas":"0x5763f58","maxPriorityFeePerGas":"0x1f4","hash":"0x5acd356e2e5dcd345a43e8a714c0bba3138856c25ac0f9cec51f4ae6aad77c4d","input":"0x","nonce":"0x0","to":"0x7dcd17433742f4c0ca53122ab541d0ba67fc27df","transactionIndex":null,"value":"0x3e8","type":"0x2","accessList":[],"chainId":"0xc72dd9d5e883e","v":"0x0","r":"0x2ff0582cbfd9034c5fa5081d8e87689fca126ef89e764ed75b9377a5abc17174","s":"0x3f88569a957315fa1204dcc026fcc50cef44c6268642990b8f05b226d8f60a40","yParity":"0x0"}}},"queued":{}}} diff --git a/tests/txpool_contentFrom/get-content-from-address.io b/tests/txpool_contentFrom/get-content-from-address.io new file mode 100644 index 000000000..c37abe339 --- /dev/null +++ b/tests/txpool_contentFrom/get-content-from-address.io @@ -0,0 +1,4 @@ +// retrieves pending transactions from a specific address +// speconly: client response is only checked for schema validity. +>> {"jsonrpc":"2.0","id":1,"method":"txpool_contentFrom","params":["0x0000000000000000000000000000000000000000"]} +<< {"jsonrpc":"2.0","id":1,"result":{"pending":{},"queued":{}}} diff --git a/tests/txpool_status/get-status.io b/tests/txpool_status/get-status.io new file mode 100644 index 000000000..a07867f31 --- /dev/null +++ b/tests/txpool_status/get-status.io @@ -0,0 +1,4 @@ +// retrieves the transaction pool status +// speconly: client response is only checked for schema validity. +>> {"jsonrpc":"2.0","id":1,"method":"txpool_status"} +<< {"jsonrpc":"2.0","id":1,"result":{"pending":"0x6","queued":"0x0"}} diff --git a/tools/cmd/rpctestgen/client.go b/tools/cmd/rpctestgen/client.go index 47e1b0ca4..2e7a0fe71 100644 --- a/tools/cmd/rpctestgen/client.go +++ b/tools/cmd/rpctestgen/client.go @@ -107,7 +107,7 @@ func (g *gethClient) Start(ctx context.Context, verbose bool) error { "--gcmode=archive", "--nodiscover", "--http", - "--http.api=admin,eth,debug,net,testing", + "--http.api=admin,eth,debug,net,txpool,testing", fmt.Sprintf("--http.addr=%s", HOST), fmt.Sprintf("--http.port=%s", PORT), fmt.Sprintf("--authrpc.port=%s", AUTHPORT), diff --git a/tools/testgen/generators.go b/tools/testgen/generators.go index a7095aa82..3de97a475 100644 --- a/tools/testgen/generators.go +++ b/tools/testgen/generators.go @@ -93,6 +93,9 @@ var AllMethods = []MethodTests{ EthBlobBaseFee, NetVersion, TestingBuildBlockV1, + TxpoolStatus, + TxpoolContent, + TxpoolContentFrom, // -- gas price tests are disabled because of non-determinism // EthGasPrice, @@ -6716,3 +6719,72 @@ func hex2Bytes(str string) *hexutil.Bytes { rpcBytes := hexutil.Bytes(common.Hex2Bytes(str)) return &rpcBytes } + +// TxpoolStatus stores a list of all tests against the method. +var TxpoolStatus = MethodTests{ + "txpool_status", + []Test{ + { + Name: "get-status", + About: "retrieves the transaction pool status", + SpecOnly: true, + Run: func(ctx context.Context, t *T) error { + var result struct { + Pending hexutil.Uint `json:"pending"` + Queued hexutil.Uint `json:"queued"` + } + if err := t.rpc.CallContext(ctx, &result, "txpool_status"); err != nil { + return err + } + return nil + }, + }, + }, +} + +// TxpoolContent stores a list of all tests against the method. +var TxpoolContent = MethodTests{ + "txpool_content", + []Test{ + { + Name: "get-content", + About: "retrieves the transaction pool content", + SpecOnly: true, + Run: func(ctx context.Context, t *T) error { + var result struct { + Pending map[common.Address]map[string]any `json:"pending"` + Queued map[common.Address]map[string]any `json:"queued"` + } + if err := t.rpc.CallContext(ctx, &result, "txpool_content"); err != nil { + return err + } + return nil + }, + }, + }, +} + +// TxpoolContentFrom stores a list of all tests against the method. +var TxpoolContentFrom = MethodTests{ + "txpool_contentFrom", + []Test{ + { + Name: "get-content-from-address", + About: "retrieves pending transactions from a specific address", + SpecOnly: true, + Run: func(ctx context.Context, t *T) error { + var result struct { + Pending map[string]any `json:"pending"` + Queued map[string]any `json:"queued"` + } + // Use a known address from the test chain + addr := common.HexToAddress("0x0000000000000000000000000000000000000000") + if err := t.rpc.CallContext(ctx, &result, "txpool_contentFrom", addr); err != nil { + return err + } + return nil + }, + }, + }, +} + diff --git a/wordlist.txt b/wordlist.txt index ca45f67f0..3916e273e 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -74,6 +74,7 @@ ssz statev statusv teku +txpool txs txt uint