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:
- Submission: A user submits a signed transaction via
SendRawTransaction. - Validation: The node verifies cryptographic signatures, gas limits, and chain ID.
- Pooling: Valid transactions enter the local transaction pool.
- Propagation: Transactions are broadcast across the P2P network.
- Selection & Execution: Validators pick high-fee transactions for inclusion in new blocks.
- Packaging: Transactions are finalized into a block and validated by peers.
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:
maxFeePerGas: Maximum total fee they’re willing to pay.maxPriorityFeePerGas: Tip offered to validators.
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:
- If block usage > 50% of gas limit → Base Fee increases
- If block usage < 50% → Base Fee decreases
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:
| Type | Purpose | Key Features |
|---|---|---|
LegacyTxType | Original format | First-price auction; rarely used today |
AccessListTxType | Cold storage optimization | Preloads contract access paths |
DynamicFeeTxType | Default modern tx | Supports EIP-1559 base/priority fees |
BlobTxType | L2 data posting | Carries blobs; separate fee market |
SetCodeTxType | Account abstraction | Converts 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:
- Pending: Executable transactions (nonce matches current state)
- Queue: Future nonce transactions held temporarily
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:
- Blob data is stored off-chain using persistent storage (
billy.Database) - Only metadata resides in memory
- Implements separate tracking for blob gas usage
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:
- Transfer: 21,000 gas
- Storage write: ~20,000 gas
- Contract creation: variable
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:
- Target: Half of maximum allowed blobs per block
- Adjustment: Proportional to excess usage
- No separate priority fee; relies on EIP-1559 tip
This dual-market approach isolates data availability costs from compute costs.
Transaction Processing Flow in Geth
Submission via RPC
Users submit transactions via:
eth_sendTransaction: Signs locally using node-managed keyseth_sendRawTransaction: Broadcasts pre-signed transactions (used by MetaMask, Rabby)
Both call SubmitTransaction() in internal/ethapi/api.go, which performs initial checks:
- Validates signature format
- Ensures compliance with EIP-155
- Checks fee reasonableness against RPC cap
Broadcasting Over P2P Network
New transactions trigger events via SubscribeTransactions():
- LegacyPool: Single event feed (
txFeed) - BlobPool: Two feeds:
discoverFeed(new txs) andinsertFeed(reorgs included)
The P2P handler listens on h.txsCh and broadcasts:
- Small transactions → Full content sent
- Large/blob txs → Hash-only propagation
Peers then request full transactions via GetPooledTransactionsMsg.
Defense Against DDoS Attacks
Geth employs multiple safeguards:
- Slot limits: Caps per account and globally
- Size restrictions: Max 128KB per tx
- Lazy loading: Reduces memory footprint
- Hash-only sync: Minimizes bandwidth abuse
These prevent spam attacks while preserving legitimate traffic.
Block Building and Payload Generation
When a validator is selected:
- Consensus layer calls
engine_forkchoiceUpdated - Execution layer starts building payload via
miner.BuildPayload - 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:
- By origin (local first)
- 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:
originStorage: Original state pre-blockpendingStorage: Changes within current blockdirtyStorage: Active modifications during tx execution
After each transaction, receipts record:
- Status (success/failure)
- Gas used
- Logs emitted
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.