Geth Source Code Series: Transaction Design and Implementation

·

The Ethereum execution layer operates as a transaction-driven state machine, where transactions are the sole mechanism for altering network state. This article dives deep into the architecture and implementation of transaction processing in Geth—the most widely used Ethereum client—covering everything from signature validation and dynamic fee mechanisms to P2P propagation, EVM execution, and state modification. We’ll explore core components such as the dual-layer transaction pool (LegacyPool and BlobPool), EIP-1559’s base fee adjustment logic, and recent upgrades like SetCodeTxType introduced in the Pectra hard fork.

Whether you're analyzing DDoS-resistant eviction strategies or tracing how ApplyTransaction modifies StateDB, this guide offers a comprehensive, code-level understanding of Ethereum’s transaction engine.

Transaction Lifecycle Overview

Every Ethereum transaction follows a standardized lifecycle:

This structured flow ensures consistency, security, and decentralization.

👉 Discover how real-time blockchain analytics enhance transaction monitoring

Evolution of Ethereum Transaction Structure

Ethereum’s transaction format has evolved significantly to improve security, scalability, and flexibility. These changes reflect key protocol upgrades that shaped modern Ethereum.

Preventing Cross-Chain Replay Attacks (EIP-155)

Originally, Ethereum used a simple RLP-encoded structure:

RLP([nonce, gasPrice, gasLimit, to, value, data, v, r, s])

This design lacked chain specificity, allowing transactions from one chain to be replayed on another. EIP-155 solved this by embedding the chainId into the v value during signing. Now, each network (e.g., Mainnet = 1) enforces unique signatures, eliminating cross-chain replay risks.

Core Keyword: EIP-155, transaction replay protection

Standardizing Transaction Extensions (EIP-2718 & EIP-2930)

As use cases expanded, Ethereum needed a scalable way to introduce new transaction types. EIP-2718 introduced a typed transaction envelope:
TransactionType || TransactionPayload.

This allows up to 128 distinct transaction types while maintaining backward compatibility. It also paved the way for EIP-2930, which added Access Lists—predefined sets of contracts and storage keys accessed during execution. This reduces gas costs by eliminating cold storage penalties.

These changes were activated during the Berlin upgrade.

Core Keywords: EIP-2718, typed transactions, access list

Revamping Economic Model with EIP-1559

The London upgrade introduced EIP-1559, transforming Ethereum’s fee market. Instead of pure first-price auctions, users now specify:

The actual fee is calculated as:
(Base Fee + Priority Fee) × Gas Used

Crucially, Base Fee is burned, reducing ETH supply over time and contributing to deflationary pressure. The Base Fee adjusts dynamically based on block congestion:

This creates a self-regulating “target” mechanism that smooths network load.

Core Keywords: EIP-1559, base fee, gas efficiency

Introducing Scalability-Focused Transactions

Recent upgrades have expanded transaction capabilities for Layer 2 and account abstraction.

EIP-4844: Blob Transactions

Blob-carrying transactions allow large data payloads at low cost, ideal for rollups. Each blob can carry up to 128 KB of data and expires after ~18 days. Blob fees operate under a separate market with its own target-based adjustment logic, similar to EIP-1559 but optimized for data availability.

EIP-7702: SetCode Transactions

This upcoming feature enables externally owned accounts (EOAs) to temporarily gain smart contract functionality through a SetCodeTxType. It supports account abstraction by letting users execute pre-approved contract logic without migrating funds.

Core Keywords: blob transaction, account abstraction, EIP-4844, EIP-7702

Core Transaction Types in Geth

Geth currently supports five transaction types, all unified under the TxData interface:

TypePurposeKey Features
LegacyTxTypeOriginal formatFirst-price auction; rarely used today
AccessListTxTypeCold storage optimizationPreloads contract access paths
DynamicFeeTxTypeDefault modern txSupports EIP-1559 base/priority fees
BlobTxTypeL2 data postingCarries blobs; separate fee market
SetCodeTxTypeAccount abstractionConverts EOAs to temporary contracts

All types implement the same interface, enabling modular handling without modifying core logic when new types emerge.

Transaction Pool Architecture

The transaction pool (txpool) holds pending transactions before they’re included in blocks. Geth uses a dual-pool design:

LegacyPool (Legacy TxPool)

Handles all non-blob transactions. Uses an in-memory structure with two per-account queues:

Each account is limited in the number of slots it can occupy (txSlotSize = 32KB). Node-wide caps prevent resource exhaustion.

BlobPool (Blob TxPool)

Dedicated to blob transactions due to their large size. Unlike LegacyPool:

This separation prevents memory bloat and ensures efficient syncing.

👉 Learn how advanced wallets leverage transaction type flexibility

Key Data Structures in Geth

Unified Transaction Representation

In core/types/transaction.go, the Transaction struct wraps all types:

type Transaction struct {
    inner TxData     // Interface for type-specific logic
    time  time.Time  // Timestamp when received
}

The TxData interface defines methods every transaction must implement—such as gas(), value(), to(), and sigHash()—enabling polymorphic handling across different types.

Lazy Loading for Efficiency

To optimize memory usage, Geth uses LazyTransaction in core/txpool/subpool.go:

type LazyTransaction struct {
    Hash        common.Hash
    Tx          *types.Transaction
    GasFeeCap   *uint256.Int
    // ... other metadata
}

It stores only essential metadata until full resolution is needed (e.g., during execution). This reduces memory pressure during peak traffic.

Gas and Fee Mechanisms

Gas remains central to Ethereum’s security and economics.

Gas Usage and Limits

Each EVM operation consumes gas:

Blocks have dynamic gas limits (~30M currently), adjustable via consensus. Proposals exist to raise it to 60M to increase throughput without compromising decentralization.

Dynamic Fee Markets

EIP-1559 Base Fee Adjustment

Base Fee changes per block using this formula:

baseFee = previousBaseFee * (1 + (gasUsed - target) / target / 8)

This elastic pricing stabilizes demand and improves UX by reducing volatility in fee estimation.

Blob Fee Market

Blob transactions use a parallel mechanism:

This dual-market approach isolates data availability costs from compute costs.

Transaction Processing Flow in Geth

Submission via RPC

Users submit transactions via:

Both call SubmitTransaction() in internal/ethapi/api.go, which performs initial checks:

Broadcasting Over P2P Network

New transactions trigger events via SubscribeTransactions():

The P2P handler listens on h.txsCh and broadcasts:

Peers then request full transactions via GetPooledTransactionsMsg.

Defense Against DDoS Attacks

Geth employs multiple safeguards:

These prevent spam attacks while preserving legitimate traffic.

Block Building and Payload Generation

When a validator is selected:

  1. Consensus layer calls engine_forkchoiceUpdated
  2. Execution layer starts building payload via miner.BuildPayload
  3. Worker spawns goroutine to fill transactions via fillTransactions()

Local transactions (configured in Locals) receive priority inclusion—encouraging node operators to prioritize their own activity.

Transactions are sorted:

  1. By origin (local first)
  2. By effective gas price (descending)

Finally, ApplyTransaction() executes each tx in the EVM context:

func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, ...) {
    msg := TransactionToMessage(tx, signer, baseFee)
    evm.IntraBlockState().ApplyMessage(msg)
}

👉 Explore tools that visualize real-time transaction flows and mempool dynamics

State Modification and Storage Tracking

During execution, StateDB tracks changes through layered storage:

After each transaction, receipts record:

Upon block finalization, dirty states become canonical.

Frequently Asked Questions

Q: Why are there two separate transaction pools in Geth?
A: Blob transactions carry large data payloads that require persistent storage. Keeping them separate from regular transactions prevents memory bloat and allows independent scaling and fee control.

Q: How does EIP-1559 improve user experience?
A: It replaces volatile first-price auctions with predictable base fees and optional tips. Users get clearer fee estimates and avoid overpaying during congestion spikes.

Q: What is the purpose of LazyTransaction in Geth?
A: It delays full transaction deserialization until necessary—reducing memory usage and improving performance when managing thousands of pending transactions.

Q: Can old legacy transactions still be used?
A: Yes, Geth maintains backward compatibility. However, most wallets now default to EIP-1559 transactions due to better UX and cost efficiency.

Q: How do local transactions get priority?
A: Addresses marked in the node’s Locals config bypass certain filters and are prioritized during block building—giving node operators control over their own transaction inclusion.

Q: What happens if a transaction fails during execution?
A: The transaction still consumes all its gas (no refunds), but no state changes are applied. The receipt shows status = failed and logs any revert reason.

Conclusion

From its foundational role as the only state-changing primitive to its evolution through EIPs like 1559, 4844, and 7702, the Ethereum transaction system exemplifies adaptive protocol design. Geth’s implementation—through typed transactions, lazy loading, dual pools, and dynamic fee markets—ensures robustness, efficiency, and forward compatibility.

Understanding these mechanics empowers developers, validators, and researchers to build better tooling, optimize gas usage, and anticipate future upgrades like full account abstraction. As Ethereum continues evolving, its transaction engine remains at the heart of innovation.