Skip to content

title: VM & Submodules description: Overview of the internal EVM, blockchain, state, receipts, and more

VM & Submodules

Tevm Node is built on a modular architecture that separates concerns into distinct submodules. This guide covers the internal components and their APIs.

Overview

The main submodules are:

1. EVM (Ethereum Virtual Machine) - Core execution engine 2. Blockchain - Block and chain state management 3. StateManager - Account and storage state management 4. TxPool - Transaction mempool 5. ReceiptsManager - Transaction receipts and logs

EVM Module

The EVM module handles bytecode execution and state transitions. It is based on @ethereumjs/evm.

const vm = await node.getVm()
const evm = await vm.evm
 
// Direct EVM execution
const result = await evm.runCall({
  to: '0x...',
  data: '0x...',
  value: 0n,
  caller: '0x...',
})
 
// Full transaction execution (recommended)
const txResult = await vm.runTx({
  tx: transaction,
  block: block,
})

Key Features

- State Management: Handles account state, storage, and code execution - Gas Metering: Tracks gas consumption during execution - Precompiles: Built-in contract implementations - EIP Support: Implements various Ethereum Improvement Proposals

Common Operations

// Check execution result
if (result.execResult.exceptionError) {
  console.error('Execution failed:', result.execResult.exceptionError)
} else {
  console.log('Return value:', result.execResult.returnValue)
  console.log('Gas used:', result.execResult.executionGasUsed)
}
 
// Access logs from execution
for (const log of result.execResult.logs) {
  console.log('Event:', {
    address: log.address,
    topics: log.topics,
    data: log.data
  })
}

Blockchain Module

The blockchain module manages blocks and chain state. It is based on @ethereumjs/blockchain.

const chain = (await node.getVm()).blockchain
 
// Get block by number or hash
const block = await chain.getBlock('latest')
const blockByHash = await chain.getBlock(blockHash)
 
// Add new block
await chain.putBlock(block)
 
// Delete block
await chain.delBlock(blockHash)
 
// Get block details
console.log({
  number: block.header.number,
  hash: block.hash(),
  parentHash: block.header.parentHash,
  stateRoot: block.header.stateRoot
})

Fork Support

// When forking, blocks are fetched from the parent chain
const forkedBlock = await chain.getBlock(blockNumber)
 
// Local blocks override forked blocks
await chain.putBlock(localBlock)

StateManager

The StateManager handles account state and storage. It is based on @ethereumjs/statemanager.

const state = (await node.getVm()).stateManager
 
// Account operations
const account = await state.getAccount(address)
await state.putAccount(address, account)
 
// Contract operations
await state.putContractCode(address, bytecode)
const code = await state.getContractCode(address)
 
// Storage operations
await state.putContractStorage(address, key, value)
const value = await state.getContractStorage(address, key)
 
// State snapshots
const snapshot = await state.dumpCanonicalGenesis()
await state.commit()
await state.revert()

Working with Accounts

// Create or update account
const account = {
  nonce: 1n,
  balance: 100n,
  storageRoot: EMPTY_ROOT,
  codeHash: EMPTY_HASH
}
await state.putAccount(address, account)
 
// Delete account
await state.deleteAccount(address)

Transaction Pool

The TxPool manages pending transactions. It is based on @ethereumjs/tx-pool.

const pool = await node.getTxPool()
 
// Add transaction
await pool.add(transaction)
 
// Get pending transactions
const pending = await pool.getPendingTransactions()
 
// Remove transaction
await pool.removeByHash(txHash)
 
// Get transactions ordered by price
const ordered = await pool.txsByPriceAndNonce({
  baseFee: 10n
})

Transaction Validation

// Validate transaction before adding
try {
  await pool.validateTx(transaction)
  await pool.add(transaction)
} catch (error) {
  console.error('Invalid transaction:', error)
}

ReceiptsManager

The ReceiptsManager handles transaction receipts and event logs.

const receipts = await node.getReceiptsManager()
 
// Get receipts for block
const blockReceipts = await receipts.getReceipts(blockHash)
 
// Get receipt by transaction hash
const txReceipt = await receipts.getReceiptByTxHash(txHash)
 
// Query logs
const logs = await receipts.getLogs({
  fromBlock: 0n,
  toBlock: 'latest',
  address: contractAddress,
  topics: [eventSignature]
})

Working with Logs

// Process event logs
for (const log of logs) {
  console.log({
    address: log.address,
    topics: log.topics,
    data: log.data,
    blockNumber: log.blockNumber,
    transactionHash: log.transactionHash,
    logIndex: log.logIndex
  })
}

Best Practices

1. Use High-Level APIs: Prefer vm.runTx() over direct EVM execution for full transaction support.

2. State Management: - Use checkpoints for atomic operations - Clean up state after tests - Handle reverts properly

3. Gas Optimization: - Estimate gas before execution - Monitor gas usage in hot paths - Use appropriate gas limits

4. Error Handling: - Check execution results - Handle exceptions gracefully - Validate inputs

Related Topics

- JSON-RPC Support - Managing State - Transaction Pool - Receipts & Logs - EVM Opcodes Reference - Ethereum Yellow Paper - ethereumjs/ethereumjs-monorepo

Up Next
- JSON-RPC Guide - Advanced Usage