Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

アカウントモデルとEVM

アカウントモデルとEVM 1枚まとめ

ここではEthereumの実行レイヤー(Execution Layer)の仕組みを、アカウント → トランザクション → 実行 → ブロックの順に見ていく。合意形成(PoS)については Proof of Stake を参照。

アカウント

Ethereumには2種類のアカウントがある。どちらも20バイトのアドレスで識別され、アドレスを見ただけではどちらの種類か区別できない。

EOA(Externally Owned Account)CA(Contract Account)
管理者人(またはブロックチェーン外のソフトウェア)なし(ブロックチェーン上のプログラム)
秘密鍵ありなし
コードなしあり(スマートコントラクトのバイトコード)
トランザクションの発行できるできない(EOAからの呼び出しを起点に、他のコントラクトを呼ぶことはできる)

アドレスの導出

  • EOA:秘密鍵 → 公開鍵(secp256k1、Bitcoinと同じ)→ 公開鍵(64バイト)のKeccak-256ハッシュの 末尾20バイト。Bitcoinと違いBase58Checkは使わず、0x + 16進数40桁で表す。大文字・小文字の混在でチェックサムを表現する方式(EIP-55)がある

  • CA(CREATEの場合):デプロイしたアカウントのアドレスと、その時点のnonceをRLPエンコードしてKeccak-256した末尾20バイト

  • CA(CREATE2の場合):デプロイ者のアドレス、任意のsalt、コードのハッシュから計算。デプロイ前にアドレスを予測できる

ワールドステート

Ethereumは、全アカウントの状態の集まりである ワールドステート(world state) を管理する。トランザクションはワールドステートを遷移させる関数と見なせる。

σt+1=Υ(σt,T)\sigma_{t+1} = \Upsilon(\sigma_t, T)

各アカウントは次の4つのフィールドを持つ。

フィールドEOACA
nonce送信したトランザクション数作成したコントラクト数
balanceETH残高(wei)ETH残高(wei)
codeHash空のハッシュコントラクトのバイトコードのハッシュ
storageRoot空コントラクトの変数を格納したストレージのルートハッシュ

ワールドステートとストレージは マークル・パトリシア・トライ(Merkle Patricia Trie, MPT) に格納される。MPTはキーで値を検索できるトライ(パトリシア木)にマークルツリーのハッシュを組み合わせたもので、

  • アドレス(のハッシュ)をキーにアカウントを効率的に検索・更新できる

  • ルートハッシュ(stateRoot)だけで全状態を要約でき、任意のアカウントの状態をマークルプルーフで証明できる

という性質を持つ。

フローとストック

ブロックチェーンに記録されるのはトランザクション(フロー)で、その実行結果であるステート(ストック)自体はブロックの外、各フルノードのデータベースに保持される。ブロックにはステートの要約であるstateRootだけが記録される。

Bitcoinはフロー(UTXO形式のトランザクション)だけでシステムを成立させていたのに対し、Ethereumはストックも管理している点が大きな違い。

トランザクション

2種類の操作

EOAが送るトランザクションは、操作の種類で2つに分けられる。

  • コントラクト作成(contract creation):宛先を空にし、data にコントラクトの初期化コードを入れて送る。コントラクトをブロックチェーン上で使えるようにすることを デプロイ と呼ぶ

  • メッセージコール(message call):EOAへの送金、またはコントラクトの関数呼び出し

どちらも、mempoolにあるうちはまだ実行されておらず、ブロックに取り込まれて初めて実行される。

トランザクションのフィールド(EIP-1559形式)

フィールド内容
chain_idチェーンの識別子(メインネットは1)。別チェーンでのリプレイを防ぐ
nonce送信者にとって何番目のトランザクションか。同じトランザクションの二重実行を防ぐ
max_priority_fee_per_gasブロック提案者へのチップの上限(gasあたり)
max_fee_per_gas支払うgas単価の上限
gas_limitこのトランザクションに使ってよいgasの上限
to宛先アドレス(EOAまたはCA)
value送金するETHの量(wei)
dataコントラクトに渡す関数セレクタと引数(calldata)
access_list事前にアクセスを宣言するアドレスとストレージ(EIP-2930)
v, r, sECDSA署名

送信元アドレスはトランザクションに含まれず、署名から復元する。

例tovaluedata
Aさんに3 ETH送金AさんのEOA3 ETH空
ERC-20トークンを5枚送るトークンのCA0transfer(Aさんのアドレス, 5) をエンコードしたもの

トランザクションは RLP(Recursive Length Prefix) という方式でバイト列にシリアライズされる。トランザクションの種類は先頭の1バイトで区別する(EIP-2718)。

タイプ名称導入
0Legacy初期
1Access list(EIP-2930)Berlin(2021)
2Dynamic fee(EIP-1559)London(2021)
3Blob(EIP-4844)Dencun(2024)
4Set code(EIP-7702)Pectra(2025)

Gas

gasとは

Ethereumはチューリング完全なので、無限ループするプログラムも書けてしまう。そこで、EVMの命令(オペコード)ごとに計算コストを gas という単位で定め、実行した分のgas代を送信者に支払わせる。これにより

  • 無限ループや重い処理でネットワークを止める攻撃を防ぐ(停止性問題を経済的に回避する)

  • ブロック提案者に報酬を与える

ことができる。

トラックの運送に例えると、gasはガソリンの量、gas単価(fee per gas)はガソリンのリッター価格、gas limitは積んでおくガソリンの上限にあたる。

主なオペコードのgasコスト(evm.codes で一覧できる):

操作gas備考
ADD, SUB3
MUL, DIV5
KECCAK25630 + 6/ワード
SLOAD2,100(cold)/ 100(warm)トランザクション内で初回アクセスかどうか(EIP-2929)
SSTORE最大22,100ゼロから非ゼロへの書き込みが最も高い
CALL2,600(cold)/ 100(warm)+ α
CREATE32,000
LOG375 + 375/トピック + 8/バイト

ストレージへの書き込みは、全フルノードが永続的に保持する必要があるため非常に高い。

手数料の計算(EIP-1559)

2021年のLondonアップグレード以降、gas単価は2つの部分からなる。

fee per gas=base fee per gas+priority fee per gas\text{fee per gas} = \text{base fee per gas} + \text{priority fee per gas}
手数料=fee per gas×gas used\text{手数料} = \text{fee per gas} \times \text{gas used}
要素決め方行き先
base feeプロトコルがブロックの混雑度から自動で決める焼却(burn) される
priority fee(チップ)送信者が決めるブロック提案者の報酬

実際には送信者は max_fee_per_gas と max_priority_fee_per_gas を指定し、

priority fee=min⁡(max priority fee, max fee−base fee)\text{priority fee} = \min(\text{max priority fee},\ \text{max fee} - \text{base fee})

となる。

base feeは、直前のブロックの使用gasがターゲット(ブロックgas上限の半分)をどれだけ上回ったかで、ブロックごとに最大12.5%ずつ上下する。

bt+1=bt(1+18⋅gt−g∗g∗)b_{t+1} = b_t \left(1 + \frac{1}{8} \cdot \frac{g_t - g^*}{g^*}\right)

ここで gtg_t はブロック tt の使用gas、g∗g^* はターゲット。ブロックが満杯(gt=2g∗g_t = 2g^*)なら12.5%上昇し、空なら12.5%下落する。

base feeを焼却することで、

  • 提案者が自分でトランザクションを詰めて手数料を吊り上げる誘因をなくす

  • 手数料の見積もりがしやすくなる(オークションで競り合う必要が減る)

  • ネットワークの利用が多いとETHの供給が減る(発行量より焼却量が多ければデフレになる)

という効果がある。

<Figure size 700x350 with 2 Axes>

gas limitとintrinsic gas

トランザクションに必要なgasは実行してみるまで正確には分からないので、送信者は上限(gas limit)を指定する。ただし、実行前でも「最低限これだけは必要」という intrinsic gas は分かる。

  • すべてのトランザクションに21,000 gas

  • コントラクト作成ならさらに32,000 gas

  • calldataのバイト数に応じたgas(ゼロバイトは4、非ゼロバイトは16 gas/バイト)

gas limitがintrinsic gasに満たないトランザクションは絶対に実行できないので、検証の段階で弾かれる。

gas不足のとき

実行中にgas limitを使い切ると、トランザクションは 失敗(revert) し、ステートの変更はすべて取り消される。ただし 消費したgas代は返ってこない。失敗したトランザクションもブロックに含まれ、全ノードで「gas不足で失敗した」ことが検証される。

失敗してもgas代を取らないと、重い処理を投げて途中で失敗させるだけのスパムが可能になってしまうため。

EVM(Ethereum Virtual Machine)

概要

EVM は、スマートコントラクトのバイトコードを実行する仮想マシン。すべてのフルノードが同じEVMで同じトランザクションを実行し、同じ結果(ステート)になることを確認しあう。

そのため、EVMは 決定論的 でなければならない。

  • 浮動小数点演算がない(環境によって丸め誤差が異なりうるため)

  • 真の乱数がない(ブロックのRANDAO値などで代用)

  • 外部のネットワークやファイルにアクセスできない(外部データはオラクル経由で書き込む)

アーキテクチャ

EVMは256bit(32バイト)のワードを単位とする スタックマシン。

領域永続性特徴
スタック実行中のみ最大1024要素。演算の中間値
メモリ呼び出し中のみバイト単位でアクセスできる一時領域。拡張するとgasが二次関数的に増える
ストレージ永続32バイトのキーと値のマップ。コントラクトの状態変数。読み書きが高い
transient storageトランザクション中のみトランザクション終了時に消える(EIP-1153, 2024年〜)。再入防止のロックなどに使う
calldata読み取り専用トランザクションの入力データ
コード不変コントラクトのバイトコード(最大24,576バイト)

スマートコントラクトは通常 Solidity などの高級言語で書き、コンパイルしたバイトコードをデプロイする(→スマートコントラクト)。

contract HelloWorld {
    function print() public pure returns (string memory) {
        return "Hello World!";
    }
}

これをコンパイルすると 6080604052348015... のようなバイトコードになる。先頭の 60 80 60 40 52 は PUSH1 0x80 PUSH1 0x40 MSTORE(メモリ上の空き領域ポインタの初期化)で、Solidityが生成するほぼすべてのコントラクトの先頭に現れる。

EVM互換チェーン

EVMの仕様とツールチェーン(Solidity、ウォレット、開発ツール)はEthereum以外でも広く採用されている。

  • L2:Arbitrum, Optimism, Base, zkSync Era, Scroll など

  • 他のL1:BNB Chain, Avalanche C-Chain, Polygon PoS など

  • コンソーシアム・プライベート型:Hyperledger Besu, Quorum など

同じコントラクトのコードが、異なる信頼モデル(パブリック、コンソーシアム、プライベート)の上でそのまま動くことはEVMの大きな強み。

トランザクションの実行

ブロック提案者(およびブロックを検証する全ノード)は、各トランザクションについて次の処理を行う。

  1. 送信者のnonceを1増やす

  2. gas_limit × fee_per_gas を送信者の残高から前払いで差し引く

  3. 送金と、EVM上でのコントラクト実行を行い、ステートを更新する

  4. 未使用gas分を払い戻す

  5. base fee分を焼却し、priority fee分を提案者の fee_recipient に送る

  6. 実行結果の レシート を作る

レシートとイベント

レシートには実行結果が記録される。

フィールド内容
status成功なら1、失敗なら0
cumulative_gas_usedブロック内でそのトランザクションまでに使われたgasの累計
logsコントラクトが発行(emit)したイベントの記録
logs_bloomlogsを高速に検索するためのBloomフィルター

コントラクトはイベント(ログ)を発行でき、DAppsのフロントエンドやインデクサーはこれを監視してオンチェーンの出来事を検知する。ログはコントラクトからは読めないが、ストレージより安く記録できる。

ブロック

ブロックヘッダーの主なフィールド

フィールド内容
parentHash親ブロックのハッシュ(Keccak-256を1回)
stateRootブロック内の全トランザクション実行後のワールドステートのルート
transactionsRootブロック内のトランザクションのMPTのルート
receiptsRootブロック内のレシートのMPTのルート
logsBloomブロック内の全ログのBloomフィルター
gasLimitブロック全体のgas上限
gasUsedブロック内のトランザクションの使用gasの合計
baseFeePerGasこのブロックのbase fee
feeRecipientpriority feeの受取アドレス
withdrawalsRootステーキングの引き出し情報のルート(Shapella以降)

ブロックの容量はバイト数ではなく gas上限 で決まる。提案者は、gas上限に収まる範囲で、priority feeの高いトランザクションを選んで詰める。

ブロックのgas上限は、2021年から長らく3,000万gasだったが、2025年に段階的に引き上げられ、6,000万gasになった。

ブロックの検証

ブロックを受け取ったノードは、構造・署名・gasの整合性などに加えて、ブロック内の全トランザクションを自分でも実行し、得られたstateRoot・receiptsRootがヘッダーの値と一致するかを確認する。

一致すれば、提案者が正しく計算したことが分かる。「他人の計算結果を信用せず、自分で再計算して答え合わせする」ことがEthereumの安全性の土台になっている。

ノードとクライアント

ノードの種類保持するデータストレージの目安
ライトノードブロックヘッダーのみ小
フルノード全ブロックと直近のステート(古いステートは削除)1〜2TB
アーカイブノード全ブロックと、全時点のステート十数TB以上

PoS移行後、フルノードを動かすには 実行クライアント(Execution Client) と コンセンサスクライアント(Consensus Client) の両方が必要になった。

種類役割主な実装
実行クライアントトランザクションの実行、ステートの管理Geth(Go), Nethermind(C#), Besu(Java), Erigon(Go), Reth(Rust)
コンセンサスクライアントPoSの合意形成、バリデータの管理Lighthouse(Rust), Prysm(Go), Teku(Java), Nimbus(Nim), Lodestar(TypeScript)

複数の独立した実装が存在する クライアント多様性(client diversity) は、1つの実装にバグがあってもネットワーク全体が止まったり誤った状態に確定したりしないために重要とされている。