Ethereum Source Code Analysis: Blocks, Transactions, Contracts, and the Virtual Machine

·

Ethereum has emerged as one of the most influential blockchain platforms, not only for its native cryptocurrency Ether but also for enabling decentralized applications (dApps) and smart contracts. While high-level overviews of Ethereum are widely available, this article dives deep into the Go implementation of the Ethereum protocol — specifically go-ethereum — to uncover the core architectural decisions, data structures, and execution mechanisms that power the network.

We’ll explore how blocks and transactions are structured, how gas governs computation, how digital signatures secure transactions, and how the Ethereum Virtual Machine (EVM) executes smart contracts. Whether you're a developer, researcher, or blockchain enthusiast, this technical walkthrough aims to deliver meaningful insights grounded in actual source code.


Core Concepts in Ethereum’s Architecture

Before analyzing transaction execution and contract logic, it's essential to understand the foundational components used throughout the Ethereum codebase.

SHA-3 Hashing and RLP Encoding

At the heart of Ethereum’s data integrity lies cryptography, primarily through SHA-3 (Keccak-256) hashing. Unlike SHA-1 and SHA-2, SHA-3 uses a different internal structure called the sponge construction, offering resilience against known cryptographic attacks. In Ethereum, every object — from transactions to blocks — is hashed using SHA-3 to produce a unique 32-byte identifier.

However, before hashing, objects must be serialized. This is where RLP (Recursive Length Prefix) encoding comes in. RLP serializes nested arrays of bytes into a flat byte sequence, making complex data structures suitable for hashing and storage.

The combination of RLP + SHA-3 forms what’s known as an RLP hash, which serves as a globally unique key for storing data in Ethereum’s underlying key-value database (typically LevelDB).

For example:

// Pseudocode: RLP hash of a transaction
encodedTx := rlp.Encode(transaction)
hash := sha3.Sum256(encodedTx)

This design ensures immutability: any change in the original data alters the RLP encoding and thus the final hash.

👉 Discover how developers interact with Ethereum’s execution layer using modern tools.


Key Data Types: Hash, Address, and Big Integers

Ethereum defines several custom types to ensure consistency across the system:

// common/types.go
const (
    HashLength  = 32      // 256 bits
    AddressLength = 20    // 160 bits
)

type Hash [HashLength]byte
type Address [AddressLength]byte

Additionally, Ethereum uses big.Int from Go’s standard library to handle large integers — especially important when dealing with Ether (measured in wei, where 1 Ether = 10¹⁸ wei) and gas calculations.

Example usage:

value := new(big.Int).SetUint64(1000000)

These types are used consistently across all core operations, ensuring precision and preventing overflow issues.


Understanding Gas and Ether

Two fundamental concepts in Ethereum are Gas and Ether, often confused but serving distinct purposes.

ConceptPurpose
GasUnit measuring computational effort required to execute operations (e.g., memory allocation, arithmetic). Each operation has a predefined gas cost.
Ether (ETH)Native cryptocurrency used to pay for gas. Users specify a gas price (in wei per unit of gas), determining transaction priority.

Every transaction includes:

Total fee = GasUsed × GasPrice

This mechanism prevents infinite loops and resource abuse while incentivizing miners (or validators in PoS) to process transactions.


Blocks as Ordered Containers of Transactions

In Ethereum, a block is more than just a container — it's a unit of consensus and execution.

The block structure in go-ethereum looks like this:

// core/types/block.go
type Block struct {
    header       *Header
    transactions Transactions
    // ...
}

type Header struct {
    ParentHash common.Hash
    Number     *big.Int
    // ...
}

Key points:

Each transaction (Transaction) contains critical fields:

type txdata struct {
    AccountNonce uint64
    Price        *big.Int          // Gas price
    GasLimit     *big.Int          // Max gas allowed
    Recipient    *common.Address   // To address (nil for contract creation)
    Amount       *big.Int          // Value transferred
    Payload      []byte            // Data or init code
    V, R, S      *big.Int          // Signature values
}

Notably, the sender address is not stored directly — it's derived cryptographically from the signature during validation.


How Transactions Are Executed

Transaction execution is orchestrated by the StateProcessor, which processes all transactions in a block and updates the global state accordingly.

The Execution Flow: From Block to Receipts

Execution begins in Process():

func (p *StateProcessor) Process(block *Block, statedb *StateDB, cfg vm.Config) {
    for _, tx := range block.Transactions() {
        receipt, _, err := ApplyTransaction(/*...*/)
        receipts = append(receipts, receipt)
    }
    return receipts, logs, totalGasUsed, nil
}

Each transaction results in a Receipt, which records:

These receipts are crucial for light clients and event indexing.


Gas Mechanics: Consumption, Refunds, and Miner Rewards

The actual work happens inside ApplyTransaction()TransitionDb().

Here’s a step-by-step breakdown:

  1. Buy Gas: Deduct GasLimit × GasPrice from sender’s balance.
  2. Intrinsic Gas Cost: Calculate base cost based on transaction size and payload (non-zero vs zero bytes).
  3. Execute via EVM: Run contract code or transfer funds.
  4. Refund Unused Gas: Return unused gas plus bonuses for storage cleanup.
  5. Reward Miner: Pay GasUsed × GasPrice to the block proposer.
💡 Why refunds? They encourage efficient use of storage by rewarding users who free up space (e.g., deleting data).

Miner rewards create economic incentives for securing the network — a cornerstone of Ethereum’s decentralization model.


Digital Signatures: Securing Transaction Origin

Since sender addresses aren’t explicitly declared, they’re recovered from digital signatures using ECDSA (Elliptic Curve Digital Signature Algorithm).

A signature consists of three parts:

When processing a transaction:

  1. Concatenate R, S, V into a 65-byte signature.
  2. Use ECDSA recovery to derive the public key.
  3. Hash the public key (Keccak-256) and take last 20 bytes → sender address.

This process is encapsulated in the Signer interface:

type Signer interface {
    Sender(tx *Transaction) (common.Address, error)
    Hash(tx *Transaction) common.Hash
}

Once recovered, the sender is cached to avoid recomputation.

👉 Learn how wallets validate Ethereum transactions securely.


Inside the Ethereum Virtual Machine (EVM)

The EVM is a stack-based virtual machine responsible for executing smart contracts. It runs in isolation, ensuring deterministic behavior across nodes.

Execution Context and State Management

The EVM operates within a context containing:

Account state is managed via stateObject, cached in memory and committed only upon block finalization.


Contract Creation vs. Call

Two primary entrypoints:

Both functions:

  1. Transfer value (if applicable).
  2. Initialize a Contract struct.
  3. Execute code via run().

Difference:

After deployment, runtime code is stored under the new contract’s address using SetCode().


Precompiled Contracts for Efficiency

Certain cryptographic operations are implemented as precompiled contracts, bypassing interpreter overhead.

Located at specific addresses (e.g., 0x01 for ECDSA recovery), they offer fixed logic with predictable gas costs:

type PrecompiledContract interface {
    RequiredGas(input []byte) uint64
    Run(input []byte) ([]byte, error)
}

Examples include:

Developers can extend this set for performance-critical applications.


Interpreter: Executing Smart Contract Instructions

Non-precompiled contracts are interpreted byte-by-byte using an opcode table (JumpTable). Each opcode maps to an operation struct defining:

Common opcodes:

Logs generated by LOGn instructions become part of the receipt and enable dApps to listen for events like token transfers.


Summary: Ethereum’s Design Philosophy

Ethereum builds upon Bitcoin’s foundation but introduces programmability at scale. Key takeaways:

By combining cryptographic rigor with flexible execution semantics, Ethereum supports everything from DeFi protocols to NFT marketplaces — all built on open-source primitives.


Frequently Asked Questions

Q: Why does Ethereum use RLP instead of JSON or Protocol Buffers?
A: RLP ensures canonical encoding — there's exactly one way to encode any given data structure. This prevents ambiguity in hashing and consensus-critical operations.

Q: Can a transaction run out of gas mid-execution?
A: Yes. If gas is exhausted, execution halts immediately, changes are reverted (except gas payment), and the transaction fails — though miners still receive fees.

Q: How is the sender address recovered without being stored?
A: Using ECDSA’s public key recovery feature. From (R,S,V) and the signed message hash, we can reconstruct the public key and derive the address.

Q: What happens to unused gas?
A: It’s refunded to the sender after execution. This encourages accurate gas estimation without penalizing overestimation.

Q: Are all EVM operations equally expensive?
A: No. Opcodes are priced according to computational intensity. For example, SHA3 costs more than ADD. Prices are updated via EIPs to reflect real-world costs.

Q: How do precompiled contracts improve performance?
A: They skip interpretation overhead and execute native Go code directly, making them significantly faster for common cryptographic tasks.

👉 Explore real-time Ethereum network metrics and contract interactions.