Skip to content

Commit

Permalink
Add txpool_content rpc method for pending txs fetch (#2410)
Browse files Browse the repository at this point in the history
* Add txpool_content rpc method for pending txs fetch

* Fix rpc export

* Add tests for `txpool_content`

* address feedback

* Include vm by default in rpc tests

* Missed commits from shandong

Co-authored-by: acolytec3 <17355484+acolytec3@users.noreply.github.com>
  • Loading branch information
g11tech and acolytec3 authored Nov 9, 2022
1 parent 9d470d3 commit 6d23fd0
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 37 deletions.
32 changes: 32 additions & 0 deletions packages/client/lib/rpc/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { bigIntToHex, bufferToHex, intToHex } from '@ethereumjs/util'

import type { Block } from '@ethereumjs/block'
import type { JsonRpcTx, TypedTransaction } from '@ethereumjs/tx'

/**
* Returns tx formatted to the standard JSON-RPC fields
*/
export const jsonRpcTx = (tx: TypedTransaction, block?: Block, txIndex?: number): JsonRpcTx => {
const txJSON = tx.toJSON()
return {
blockHash: block ? bufferToHex(block.hash()) : null,
blockNumber: block ? bigIntToHex(block.header.number) : null,
from: tx.getSenderAddress().toString(),
gas: txJSON.gasLimit!,
gasPrice: txJSON.gasPrice ?? txJSON.maxFeePerGas!,
maxFeePerGas: txJSON.maxFeePerGas,
maxPriorityFeePerGas: txJSON.maxPriorityFeePerGas,
type: intToHex(tx.type),
accessList: txJSON.accessList,
chainId: txJSON.chainId,
hash: bufferToHex(tx.hash()),
input: txJSON.data!,
nonce: txJSON.nonce!,
to: tx.to?.toString() ?? null,
transactionIndex: txIndex !== undefined ? intToHex(txIndex) : null,
value: txJSON.value!,
v: txJSON.v!,
r: txJSON.r!,
s: txJSON.s!,
}
}
36 changes: 2 additions & 34 deletions packages/client/lib/rpc/modules/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from '@ethereumjs/util'

import { INTERNAL_ERROR, INVALID_PARAMS, PARSE_ERROR } from '../error-code'
import { jsonRpcTx } from '../helpers'
import { middleware, validators } from '../validation'

import type { EthereumClient } from '../..'
Expand All @@ -24,12 +25,7 @@ import type { RpcTx } from '../types'
import type { Block, JsonRpcBlock } from '@ethereumjs/block'
import type { Log } from '@ethereumjs/evm'
import type { Proof } from '@ethereumjs/statemanager'
import type {
FeeMarketEIP1559Transaction,
JsonRpcTx,
Transaction,
TypedTransaction,
} from '@ethereumjs/tx'
import type { FeeMarketEIP1559Transaction, Transaction, TypedTransaction } from '@ethereumjs/tx'
import type { Account } from '@ethereumjs/util'
import type { PostByzantiumTxReceipt, PreByzantiumTxReceipt, TxReceipt, VM } from '@ethereumjs/vm'

Expand Down Expand Up @@ -77,34 +73,6 @@ type JsonRpcLog = {
// (e.g. Deposit(address,bytes32,uint256)), except you declared the event with the anonymous specifier.)
}

/**
* Returns tx formatted to the standard JSON-RPC fields
*/
const jsonRpcTx = (tx: TypedTransaction, block?: Block, txIndex?: number): JsonRpcTx => {
const txJSON = tx.toJSON()
return {
blockHash: block ? bufferToHex(block.hash()) : null,
blockNumber: block ? bigIntToHex(block.header.number) : null,
from: tx.getSenderAddress().toString(),
gas: txJSON.gasLimit!,
gasPrice: txJSON.gasPrice ?? txJSON.maxFeePerGas!,
maxFeePerGas: txJSON.maxFeePerGas,
maxPriorityFeePerGas: txJSON.maxPriorityFeePerGas,
type: intToHex(tx.type),
accessList: txJSON.accessList,
chainId: txJSON.chainId,
hash: bufferToHex(tx.hash()),
input: txJSON.data!,
nonce: txJSON.nonce!,
to: tx.to?.toString() ?? null,
transactionIndex: txIndex !== undefined ? intToHex(txIndex) : null,
value: txJSON.value!,
v: txJSON.v!,
r: txJSON.r!,
s: txJSON.s!,
}
}

/**
* Returns block formatted to the standard JSON-RPC fields
*/
Expand Down
3 changes: 2 additions & 1 deletion packages/client/lib/rpc/modules/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export const list = ['Eth', 'Engine', 'Web3', 'Net', 'Admin']
export const list = ['Eth', 'Engine', 'Web3', 'Net', 'Admin', 'TxPool']

export * from './admin'
export * from './engine'
export * from './eth'
export * from './net'
export * from './txpool'
export * from './web3'
44 changes: 44 additions & 0 deletions packages/client/lib/rpc/modules/txpool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { jsonRpcTx } from '../helpers'
import { middleware } from '../validation'

import type { EthereumClient } from '../..'
import type { FullEthereumService } from '../../service'
import type { TxPool as Pool } from '../../service/txpool'
import type { VM } from '@ethereumjs/vm'

/**
* web3_* RPC module
* @memberof module:rpc/modules
*/
export class TxPool {
private _txpool: Pool
private _vm: VM
/**
* Create web3_* RPC module
* @param client Client to which the module binds
*/
constructor(client: EthereumClient) {
const service = client.services.find((s) => s.name === 'eth') as FullEthereumService
this._txpool = service.txPool
this._vm = service.execution.vm
this.content = middleware(this.content.bind(this), 0, [])
}

/**
* Returns the contents of the transaction pool
* @param params An empty array
*/
content(_params = []) {
const pending = new Map()
for (const pool of this._txpool.pool) {
const pendingForAcct = new Map<bigint, any>()
for (const tx of pool[1]) {
pendingForAcct.set(tx.tx.nonce, jsonRpcTx(tx.tx))
}
if (pendingForAcct.size > 0) pending.set('0x' + pool[0], Object.fromEntries(pendingForAcct))
}
return {
pending: Object.fromEntries(pending),
}
}
}
4 changes: 2 additions & 2 deletions packages/client/test/rpc/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type WithEngineMiddleware = { jwtSecret: Buffer; unlessFn?: (req: IncomingMessag

export function startRPC(
methods: any,
opts: StartRPCOpts = { port: 3000 },
opts: StartRPCOpts = { port: 3001 },
withEngineMiddleware?: WithEngineMiddleware
) {
const { port, wsServer } = opts
Expand Down Expand Up @@ -103,7 +103,7 @@ export function createClient(clientOpts: any = {}) {
}

let execution
if (clientOpts.includeVM === true) {
if (!(clientOpts.includeVM === false)) {
const metaDB: any = clientOpts.enableMetaDB === true ? new MemoryLevel() : undefined
execution = new VMExecution({ config, chain, metaDB })
}
Expand Down
84 changes: 84 additions & 0 deletions packages/client/test/rpc/txpool/content.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Block, BlockHeader } from '@ethereumjs/block'
import { Blockchain } from '@ethereumjs/blockchain'
import { Chain, Common, Hardfork } from '@ethereumjs/common'
import { TransactionFactory } from '@ethereumjs/tx'
import { randomBytes } from 'crypto'
import * as tape from 'tape'

import { baseRequest, createClient, createManager, params, startRPC } from '../helpers'

import type { FullEthereumService } from '../../../lib/service'

const method = 'txpool_content'

tape(`${method}: call with valid arguments`, async (t) => {
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Istanbul })
const blockchain = await Blockchain.create({
common,
validateBlocks: false,
validateConsensus: false,
})

const client = createClient({ blockchain, commonChain: common, includeVM: true })
const manager = createManager(client)
const server = startRPC(manager.getMethods())
const { execution } = client.services.find((s) => s.name === 'eth') as FullEthereumService
t.notEqual(execution, undefined, 'should have valid execution')
const { vm } = execution
await vm.eei.generateCanonicalGenesis(blockchain.genesisState())
const gasLimit = 2000000
const parent = await blockchain.getCanonicalHeadHeader()
const block = Block.fromBlockData(
{
header: {
parentHash: parent.hash(),
number: 1,
gasLimit,
},
},
{ common, calcDifficultyFromHeader: parent }
)

let ranBlock: Block | undefined = undefined
vm.events.once('afterBlock', (result: any) => (ranBlock = result.block))
await vm.runBlock({ block, generate: true, skipBlockValidation: true })
await vm.blockchain.putBlock(ranBlock!)
const service = client.services[0] as FullEthereumService
service.execution.vm._common.setHardfork('london')
service.chain.config.chainCommon.setHardfork('london')
const headBlock = await service.chain.getCanonicalHeadBlock()
const londonBlock = Block.fromBlockData(
{
header: BlockHeader.fromHeaderData(
{
baseFeePerGas: 1000000000n,
number: 2n,
parentHash: headBlock.header.hash(),
},
{
common: service.chain.config.chainCommon,
skipConsensusFormatValidation: true,
calcDifficultyFromHeader: headBlock.header,
}
),
},
{ common: service.chain.config.chainCommon }
)

vm.events.once('afterBlock', (result: any) => (ranBlock = result.block))
await vm.runBlock({ block: londonBlock, generate: true, skipBlockValidation: true })
await vm.blockchain.putBlock(ranBlock!)
;(service.txPool as any).validate = () => {}
await service.txPool.add(TransactionFactory.fromTxData({ type: 2 }, {}).sign(randomBytes(32)))

const req = params(method, [])
const expectedRes = (res: any) => {
t.equal(
Object.keys(res.body.result.pending).length,
1,
'received one pending transaction back from response'
)
}

await baseRequest(t, server, req, 200, expectedRes)
})

0 comments on commit 6d23fd0

Please sign in to comment.