Understanding Ethereum's Account Model

·

Ethereum's account model is a foundational concept in blockchain development, serving as the backbone for how users interact with the network and how smart contracts operate. Unlike Bitcoin’s UTXO (Unspent Transaction Output) model, Ethereum employs a more intuitive account-based model, where each user and contract has a persistent state. This structure simplifies balance tracking and enables powerful programmability through smart contracts.

In this guide, we'll explore the core components of Ethereum’s account system, including external and contract accounts, address generation mechanisms, how to distinguish between account types, internal data structures, storage operations, and the strengths and limitations of this design.


Types of Accounts on Ethereum

Ethereum supports two primary types of accounts: External Accounts and Contract Accounts. Both hold balances and can send transactions, but they differ significantly in control mechanisms and functionality.

External Accounts (EOAs)

External accounts, also known as Externally Owned Accounts (EOAs), are controlled by private keys. These are typically user wallets like MetaMask or hardware wallets such as Ledger.

Despite being unable to run code, EOAs are essential for triggering state changes on the blockchain.

👉 Discover how wallet interactions shape blockchain activity

Contract Accounts

Contract accounts are smart contracts deployed on the Ethereum Virtual Machine (EVM). They are created when an EOA sends a transaction containing initialization code.

Once deployed, a contract account cannot be altered—its code is immutable.

Key Differences Between EOAs and Contract Accounts

FeatureExternal Account (EOA)Contract Account
Controlled byPrivate keyCode execution
Has associated code?NoYes
Can hold balance?YesYes
Can create contracts?YesYes (if coded to do so)
Supports multi-sig?Only via contractYes (programmatically)

Understanding these distinctions helps developers design secure and efficient decentralized applications (dApps).


How Contract Addresses Are Generated

Ethereum uses deterministic methods to generate contract addresses, ensuring predictability and consistency across nodes.

Method 1: Sender Address + Nonce

The traditional method combines the creator's address and transaction nonce:

Keccak256(rlp([sender, nonce]))[12:]

This means:

Example implementation in Go:

func CreateAddress(b common.Address, nonce uint64) common.Address {
    data, _ := rlp.EncodeToBytes([]interface{}{b, nonce})
    return common.BytesToAddress(Keccak256(data)[12:])
}

Method 2: CREATE2 Opcode (EIP-1014)

To enable predictable deployment at a fixed address—even before deployment—Ethereum introduced CREATE2:

keccak256(0xff ++ address ++ salt ++ keccak256(init_code))[12:]

This allows:

Notable standards leveraging this include EIP-1820 (interface detection) and EIP-2470 (singleton contracts).


Detecting Contract vs. External Accounts

Determining whether an address belongs to a contract is crucial for security and logic enforcement.

Using EXTCODESIZE in Solidity

The EVM provides EXTCODESIZE, an opcode that returns the size of code at a given address:

function isContract(address addr) internal view returns (bool) {
    uint256 size;
    assembly { size := extcodesize(addr) }
    return size > 0;
}

If the size is greater than zero, it's a contract.

Via Web3.js or JSON-RPC

You can query directly using:

web3.eth.getCode("0x8415A51d68e80aebb916A6f5Dafb8af18bFE2F9d")

Returns:

⚠️ Note: An empty response doesn’t guarantee it’s an EOA—code might not yet be deployed.

Common Use Case: Restricting Access to EOAs Only

To prevent contracts from calling your smart contract (e.g., to block flash loan attacks):

require(tx.origin == msg.sender, "No contracts allowed");

While effective, this pattern has trade-offs in composability and future-proofing.


Internal Account Data Structure

All Ethereum accounts share the same underlying structure within the state trie:

type Account struct {
    Nonce     uint64
    Balance   *big.Int
    Root      common.Hash
    CodeHash  []byte
}

Breakdown of Fields

👉 Learn how real-time balance tracking powers modern dApps


Simulating Account State Management

Below is a simplified simulation showing how account states are managed in Ethereum’s state database:

func main() {
    statadb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()))
    
    acct1 := toAddr("0x0bB141C2F7d4d12B1D27E62F86254e6ccEd5FF9a")
    acct2 := toAddr("0x77de172A492C40217e48Ebb7EEFf9b2d7dF8151B")

    statadb.AddBalance(acct1, big.NewInt(100))
    statadb.AddBalance(acct2, big.NewInt(888))

    contract := crypto.CreateAddress(acct1, statadb.GetNonce(acct1))
    statadb.CreateAccount(contract)
    statadb.SetCode(contract, []byte("contract code bytes"))
    statadb.SetNonce(contract, 1)

    statadb.SetState(contract, toHash([]byte("owner")), toHash(acct1.Bytes()))
    statadb.SetState(contract, toHash([]byte("name")), toHash([]byte("ysqi")))
    statadb.SetState(contract, toHash([]byte("online")), toHash([]byte{})) // Deletes key

    statadb.Commit(true)
    fmt.Println(string(statadb.Dump()))
}

After committing, Dump() outputs full state data in JSON format—showing balances, codes, and storage entries. Only contract accounts contain non-empty code and storage.


Advantages and Limitations of the Account Model

✅ Advantages

  1. High Programmability: Contracts store both code and state, enabling complex logic like DeFi protocols and NFT marketplaces.
  2. Simplified UX: Users can easily track balances without parsing transaction history.
  3. Efficient Batch Operations: Payment systems (e.g., mining pools) can use single contract calls to distribute funds.

❌ Limitations

  1. Replay Attacks: Transactions aren’t inherently tied to chains; safeguards like chain ID are required.
  2. Scalability Challenges: Off-chain scaling solutions like Plasma or Lightning Network require complex proof systems due to shared account states.
  3. State Bloat: Persistent account storage increases node storage requirements over time.

Frequently Asked Questions (FAQ)

Q: Can a contract have a private key?
A: No. Contracts are controlled by code logic, not private keys. Only EOAs possess private keys.

Q: Is it safe to use tx.origin for access control?
A: It works but is discouraged due to potential proxy bypass risks. Prefer msg.sender with explicit checks.

Q: How does nonce prevent double-spending?
A: Each transaction from an EOA must have a unique, sequential nonce. Nodes reject out-of-order or duplicate nonces.

Q: Why do EOAs have a CodeHash field?
A: For structural uniformity. EOAs have a fixed CodeHash value representing an empty string.

Q: Can I change a contract’s code after deployment?
A: No. Code is immutable. Use upgradeable patterns (e.g., proxy contracts) for flexibility.

Q: What happens if I send ETH to a non-existent address?
A: As long as the address format is valid, funds are sent—but recoverable only if you own the private key.


By understanding Ethereum’s account model deeply, developers can build more secure, efficient, and scalable applications. Whether you're deploying tokens or designing complex financial instruments, this knowledge forms the bedrock of blockchain innovation.

👉 Explore tools that simplify Ethereum development workflows