UNPKG

@tevm/actions

Version:

A typesafe library for writing forge scripts in typescript

167 lines (154 loc) 5.86 kB
import { createAddress } from '@tevm/address' import { createImpersonatedTx } from '@tevm/tx' import { bytesToHex, hexToBigInt } from '@tevm/utils' import { forkAndCacheBlock } from '../internal/forkAndCacheBlock.js' import { serializeTraceResult } from '../internal/serializeTraceResult.js' import { traceCallHandler } from './traceCallHandler.js' /** * Creates a JSON-RPC procedure handler for the `debug_traceBlock` method * * This handler traces the execution of all transactions in a block, providing * detailed traces for each transaction. It handles both block hash and block number * identifiers and supports multiple tracer types. * * @param {import('@tevm/node').TevmNode} client - The TEVM node instance * @returns {import('./DebugProcedure.js').DebugTraceBlockProcedure} A handler function for debug_traceBlock requests * @throws {Error} If the block cannot be found * @throws {Error} If the parent block's state root is not available and cannot be forked * * @example * ```javascript * import { createTevmNode } from '@tevm/node' * import { debugTraceBlockJsonRpcProcedure } from '@tevm/actions' * * // Create a node with automatic mining * const node = createTevmNode({ miningConfig: { type: 'auto' } }) * * // Get a block * const blockNumber = await node.getBlockNumber() * * // Create the debug procedure handler * const debugProcedure = debugTraceBlockJsonRpcProcedure(node) * * // Trace the block * const trace = await debugProcedure({ * jsonrpc: '2.0', * method: 'debug_traceBlock', * params: [ * blockNumber, // Or block hash as a hex string * { * tracer: 'callTracer', // Or 'prestateTracer' * tracerConfig: { * // diffMode: true // Only for prestateTracer * } * } * ], * id: 1 * }) * * console.log('Block transaction traces:', trace.result) * ``` */ export const debugTraceBlockJsonRpcProcedure = (client) => { /** * @template {'callTracer' | 'prestateTracer'} TTracer * @template {boolean} TDiffMode * @param {import('./DebugJsonRpcRequest.js').DebugTraceBlockJsonRpcRequest<TTracer, TDiffMode>} request * @returns {Promise<import('./DebugJsonRpcResponse.js').DebugTraceBlockJsonRpcResponse<TTracer, TDiffMode>>} */ return async (request) => { // Parse parameters from the request const { tracer, timeout, tracerConfig, block: blockParam, blockTag, blockHash, blockNumber } = request.params[0] if (timeout !== undefined) { client.logger.warn('Warning: timeout is currently respected param of debug_traceBlock') } client.logger.debug({ blockTag, tracer, tracerConfig }, 'debug_traceBlock: executing with params') const vm = await client.getVm() const block = await vm.blockchain.getBlockByTag(blockParam ?? blockTag ?? blockHash ?? blockNumber ?? 'latest') // If no transactions in the block, return empty array if (block.transactions.length === 0) { return { jsonrpc: '2.0', method: request.method, ...(request.id !== undefined ? { id: request.id } : {}), result: /** @type {any} */ ([]), } } const parentBlock = await vm.blockchain.getBlock(block.header.parentHash) // Ensure the parent block's state root is available const hasStateRoot = await vm.stateManager.hasStateRoot(parentBlock.header.stateRoot) if (!hasStateRoot && client.forkTransport) { await forkAndCacheBlock(client, parentBlock) } else if (!hasStateRoot) { return { jsonrpc: '2.0', method: request.method, ...(request.id !== undefined ? { id: request.id } : {}), error: { code: '-32602', message: 'State root not available for parent block', }, } } // Clone the VM and set initial state const vmClone = await vm.deepCopy() await vmClone.stateManager.setStateRoot(parentBlock.header.stateRoot) // Array to store trace results for each transaction /** @type {import('./DebugResult.js').DebugTraceBlockResult} */ const traceResults = [] // Trace each transaction in the block for (let i = 0; i < block.transactions.length; i++) { const blockTx = block.transactions[i] if (!blockTx) continue const impersonatedTx = createImpersonatedTx( { ...blockTx, gasPrice: null, impersonatedAddress: createAddress(blockTx.getSenderAddress()), }, { freeze: false, common: vmClone.common.ethjsCommon, allowUnlimitedInitCodeSize: true, }, ) // Trace the transaction const txParams = impersonatedTx.toJSON() const traceResult = await traceCallHandler({ ...client, getVm: () => Promise.resolve(vmClone) })({ tracer, from: impersonatedTx.getSenderAddress().toString(), blockTag: bytesToHex(block.header.hash()), ...(txParams.to !== undefined ? { to: txParams.to } : {}), ...(txParams.gasLimit !== undefined ? { gas: hexToBigInt(txParams.gasLimit) } : {}), ...(txParams.gasPrice !== undefined ? { gasPrice: hexToBigInt(txParams.gasPrice) } : {}), ...(txParams.value !== undefined ? { value: hexToBigInt(txParams.value) } : {}), ...(txParams.data !== undefined ? { data: txParams.data } : {}), ...(timeout !== undefined ? { timeout } : {}), .../** @type {any} */ (tracerConfig !== undefined ? { tracerConfig } : {}), }) // Add to results traceResults.push({ txHash: bytesToHex(blockTx.hash()), txIndex: i, result: traceResult, }) // Actually run the call to update the vmClone state await vmClone.runTx({ block, skipNonce: true, skipBalance: true, skipHardForkValidation: true, skipBlockGasLimitValidation: true, tx: impersonatedTx, }) } return { jsonrpc: '2.0', method: request.method, result: /** @type {any} */ ( traceResults.map((trace) => ({ ...trace, result: serializeTraceResult(trace.result) })) ), ...(request.id !== undefined ? { id: request.id } : {}), } } }