diff --git a/packages/procedures/src/eth/ethGetTransactionCountProcedure.js b/packages/procedures/src/eth/ethGetTransactionCountProcedure.js index c5fb88ed1c..4f66ac20f1 100644 --- a/packages/procedures/src/eth/ethGetTransactionCountProcedure.js +++ b/packages/procedures/src/eth/ethGetTransactionCountProcedure.js @@ -1,17 +1,18 @@ import { createAddress } from '@tevm/address' -import { bytesToHex, getAddress, hexToBigInt, hexToBytes, numberToHex } from '@tevm/utils' +import { ForkError, InternalEvmError} from '@tevm/errors' +import { hexToBigInt, hexToBytes, numberToHex } from '@tevm/utils' /** * Request handler for eth_getFilterLogs JSON-RPC requests. - * @param {import('@tevm/node').TevmNode} client + * @param {import('@tevm/node').TevmNode} node * @returns {import('./EthProcedure.js').EthGetTransactionCountJsonRpcProcedure} */ -export const ethGetTransactionCountProcedure = (client) => { +export const ethGetTransactionCountProcedure = (node) => { return async (request) => { const [address, tag] = request.params const block = await (async () => { - const vm = await client.getVm() + const vm = await node.getVm() if (tag.startsWith('0x') && tag.length === 66) { return vm.blockchain.getBlock(hexToBytes(/** @type {import('@tevm/utils').Hex}*/ (tag))) } @@ -42,23 +43,64 @@ export const ethGetTransactionCountProcedure = (client) => { const pendingCount = tag === 'pending' ? await (async () => { - const txPool = await client.getTxPool() + const txPool = await node.getTxPool() const pendingTx = await txPool.getBySenderAddress(createAddress(address)) return BigInt(pendingTx.length) })() : 0n const includedCount = await (async () => { - const vm = await client.getVm() - // TODO we can optimize this by not deep copying once we are more confident it's safe - const root = vm.stateManager._baseState.stateRoots.get(bytesToHex(block.header.stateRoot)) - if (!root) { - // todo we might want to throw an error hre - return 0n + const vm = await node.getVm() + if (!(await vm.stateManager.hasStateRoot(block.header.stateRoot))) { + return undefined } - return root[getAddress(address)]?.nonce ?? 0n + const stateCopy = await vm.stateManager.deepCopy() + stateCopy.setStateRoot(block.header.stateRoot) + const account = await stateCopy.getAccount(createAddress(address)) + return account?.nonce ?? 0n })() + if (includedCount === undefined && node.forkTransport) { + try { + /** + * @type {import('@tevm/utils').Hex} + */ + const result = await node.forkTransport.request(request) + return { + ...(request.id ? { id: request.id } : {}), + method: request.method, + jsonrpc: request.jsonrpc, + result: numberToHex(hexToBigInt(result) + pendingCount), + } + } catch(e) { + const err = new ForkError('Unable to resolve eth_getTransactionCount with fork', {cause: /** @type {any}*/(e)}) + return { + ...(request.id ? { id: request.id } : {}), + method: request.method, + jsonrpc: request.jsonrpc, + error: { + code: err.code, + message: err.message, + }, + + } + } + + } + if (includedCount === undefined) { + const err = new InternalEvmError(`No state root found for block tag ${tag} in eth_getTransactionCountProcedure`) + node.logger.error(err) + return { + ...(request.id ? { id: request.id } : {}), + method: request.method, + jsonrpc: request.jsonrpc, + error: { + code: err.code, + message: err.message, + }, + } + } + return { ...(request.id ? { id: request.id } : {}), method: request.method, diff --git a/packages/procedures/src/eth/ethGetTransactionCountProcedure.spec.ts b/packages/procedures/src/eth/ethGetTransactionCountProcedure.spec.ts new file mode 100644 index 0000000000..8ad8665266 --- /dev/null +++ b/packages/procedures/src/eth/ethGetTransactionCountProcedure.spec.ts @@ -0,0 +1,102 @@ +import { callHandler } from '@tevm/actions' +import { createAddress } from '@tevm/address' +import { createTevmNode } from '@tevm/node' +import { transports } from '@tevm/test-utils' +import { numberToHex, parseEther } from '@tevm/utils' +import { describe, expect, it } from 'vitest' +import { ethGetTransactionCountProcedure } from './ethGetTransactionCountProcedure.js' + +const address = '0xb5d85CBf7cB3EE0D56b3bB207D5Fc4B82f43F511' as const + +describe(ethGetTransactionCountProcedure.name, () => { + it('should work', async () => { + const node = createTevmNode({ + fork: { + transport: transports.mainnet, + blockTag: 20743493n, + }, + }) + expect( + await ethGetTransactionCountProcedure(node)({ + jsonrpc: '2.0', + id: 1, + method: 'eth_getTransactionCount', + params: [address, 'latest'], + }), + ).toMatchInlineSnapshot(` + { + "id": 1, + "jsonrpc": "2.0", + "method": "eth_getTransactionCount", + "result": "0x8e96b4", + } + `) + }) + it('should work with past block tags', async () => { + const node = createTevmNode({ + fork: { + transport: transports.mainnet, + blockTag: 20743493n, + }, + }) + expect( + await ethGetTransactionCountProcedure(node)({ + jsonrpc: '2.0', + id: 1, + method: 'eth_getTransactionCount', + params: [address, numberToHex(20700000n)], + }), + ).toMatchInlineSnapshot(` + { + "id": 1, + "jsonrpc": "2.0", + "method": "eth_getTransactionCount", + "result": "0x8df90f", + } + `) + }) + it('should work with pending tx', async () => { + const node = createTevmNode({ + fork: { + transport: transports.mainnet, + blockTag: 20743493n, + }, + }) + await callHandler(node)({ + from: address, + to: createAddress(5).toString(), + value: parseEther('0.1'), + createTransaction: true, + }) + expect( + await ethGetTransactionCountProcedure(node)({ + jsonrpc: '2.0', + id: 1, + method: 'eth_getTransactionCount', + params: [address, 'latest'], + }), + ).toMatchInlineSnapshot(` + { + "id": 1, + "jsonrpc": "2.0", + "method": "eth_getTransactionCount", + "result": "0x8e96b4", + } + `) + expect( + await ethGetTransactionCountProcedure(node)({ + jsonrpc: '2.0', + id: 1, + method: 'eth_getTransactionCount', + params: [address, 'pending'], + }), + ).toMatchInlineSnapshot(` + { + "id": 1, + "jsonrpc": "2.0", + "method": "eth_getTransactionCount", + "result": "0x8e96b5", + } + `) + }) +})