著者:Tia、Techub News

ブロックチェーンはその非中央集権の設計のために効率を犠牲にしているため、実行速度の向上は常に解決が急務の問題の一つです。ブロックチェーンの「実行層」は、各トランザクションを処理し、チェーンに追加する重要な部分です。処理能力を加速するために、実行層での向上は主要な戦略の一つとなり、並行実行はこの面での重要な突破口です。

従来のブロックチェーンは通常、シリアル方式でトランザクションを逐次処理します。これにより、トランザクション速度が大きく制限され、特にトランザクションが集中しているネットワークでは混雑を引き起こす可能性があります。しかし、並行実行を通じて、複数のトランザクションを同時に処理することで、実行効率を大幅に向上させ、チェーン上の負担を軽減することができます。

並行性とは何かをよりよく理解するために、まず実行から始めて、マージ後のPBSモードでのイーサリアムの例を用いて実行が何であるかを説明し、実行がトランザクションライフサイクル全体の中でどの位置にあるかを示します。

トランザクション実行の具体的な段階

  1. トランザクションはメモリプールに入り、フィルタリングとソートされます:これはトランザクションが提出された後の前処理段階であり、Mempool、Searcher、Builderの相互作用が含まれ、トランザクションのフィルタリングとソートが完了します。

  2. Builderはブロックを構築する(ただし実行はしない):Builderは利益のあるトランザクションを並べて1つのブロックを作成し、トランザクションのパッケージ化とソートを完了します。

  3. Proposerはブロックを検証し提出します:ブロックが構築された後、Builderはブロックの提案をProposerに送信します。Proposerはブロックの構造とトランザクション内容を検証し、その後正式にブロックをネットワークに提出して実行を開始します。

  4. トランザクションを実行する:ブロックが提出された後、ノードはブロック内のトランザクションを逐次実行します。これは状態更新の重要な段階であり、各トランザクションはスマートコントラクト呼び出し、アカウント残高の変化、または状態の変更を引き起こします。

  5. 証人の証明:検証者はブロックの実行結果と状態ルートを証明し、最終確認としてこれを行います。これにより、ブロックの実行層の真実性と有効性が確保され、不整合を防ぎます。

  6. 状態同期:各ノードはブロックの実行結果(アカウント残高、契約状態更新など)を自身のローカル状態に同期し、各トランザクションの実行後、ノードは新しい状態ルートを計算して保存し、次のブロックの初期状態として使用します。

もちろん、これはブロック単位のトランザクションの状態同期に過ぎません。最新のチェーン上の状態を維持するために、通常、ノードはブロックごとにデータを同期し、ブロックと状態を継続的に検証します。しかし、POSメカニズム下での最終性を達成するためには、アグリゲーターが各スロットの証人の署名を集約して完全な署名を生成し、それを次のスロットの提案者に渡す必要があります。また、検証者はエポックを経た後、投票数に基づいてそのエポック内のすべてのブロックの状態を確認し、一時的なコンセンサス状態チェックポイントを形成します。連続する2つのエポックが大多数の検証者の証人支持を得た後にのみ、ブロックとトランザクションは最終性を達成します。

トランザクションのライフサイクル全体から見ると、実行はProposerがBuilderから送られたブロックの構造とトランザクション内容を検証した後に発生します。実際の実行プロセスでは、トランザクションを一つずつ処理し、関連するアカウントまたは契約状態を更新します。すべてのトランザクションが完了した後、Proposerは新しい状態ルート(メルクルルート)を計算します。これは現在のブロック内のすべてのトランザクションの実行結果と最終的なグローバル状態の要約です。一般的に言えば、完全なブロック実行プロセスは、イーサリアムが前の状態から次の状態に変わる過程で完了すべき計算の一連の作業を含み、各トランザクションの実行からメルクルルートの計算までを含みます。

順次実行

並行と対照的なのは順次実行であり、これは現在のブロックチェーンで一般的な実行方法です。通常、トランザクションは順番に逐次実行されます。一つのトランザクションが実行を完了すると、イーサリアムはアカウントの状態と関連情報(たとえば残高、契約ストレージデータ)をアカウント状態ツリーに更新し、新しいアカウント状態ハッシュを生成します。すべてのアカウント状態ツリーが更新されると、状態メルクルルートと呼ばれる状態ツリーのルートノードハッシュが形成されます。状態メルクルルート、トランザクションメルクルルート、レシートメルクルルートが完成した後、ブロックヘッダーはハッシュ計算を行い、そのブロックのブロックハッシュを生成します。

その中で、トランザクションの実行順序は非常に重要です。メルクルツリーはハッシュ値の二分木であり、異なる順序で形成されるメルクルルートの値は異なります。

並行実行

並行実行環境下では、ノードはブロック内のトランザクションを並行処理しようとします。トランザクションを一つ一つ順番に実行するのではなく、異なる「実行経路」にトランザクションを割り当てて同時に実行できるようにします。並行実行を通じて、システムはブロック内のトランザクションをより効率的に処理し、スループットを向上させることができます。

すべてのトランザクションの実行が完了すると、ノードは実行結果(すなわちトランザクションが影響を与えた状態の更新)を集計し、新しいブロック状態を形成します。この状態はブロックチェーンに追加され、チェーン上の最新のグローバル状態を表します。

状態衝突

並行処理は異なる経路でトランザクションを同時に処理するため、並行処理の大きな難点は状態衝突です。つまり、複数のトランザクションが同じ時間にブロックチェーンの同じデータ(状態)に対して読み取りまたは書き込み操作を行う可能性がある場合です。この状況を適切に処理しないと、実行結果が不確定になる可能性があります。状態の更新順序が異なれば、最終的な計算結果も異なるからです。たとえば、

2つのトランザクション、トランザクションAとトランザクションBがあり、両方が同じアカウントの残高を更新しようとしています:

  • トランザクションA:アカウント残高を10増加させる。

  • トランザクションB:アカウント残高を20増加させる。

アカウントの初期残高は100です。

シリアル実行を行うと、実行順序の結果は決定的です:

1. トランザクションAを先に実行し、次にトランザクションBを実行します:

  • アカウント残高は最初に10増加し、110になります。

  • さらに20増加し、最終的に130になります。

2. トランザクションBを先に実行し、その後トランザクションAを実行します:

  • アカウント残高は最初に20増加し、120になります。

  • さらに10を加え、最終的に130になります。

この2つの順序では、最終的な残高はどちらも130であり、システムはトランザクション実行の順序の一貫性を確保します。

しかし、並行実行環境では、トランザクションAとトランザクションBが同時に初期残高100を読み取り、それぞれの計算を行う可能性があります:

  1. トランザクションAは残高100を読み取り、計算後に残高を110に更新します。

  2. トランザクションBも残高100を読み取り、計算後に残高を120に更新します。

この場合、トランザクションが同時に実行されたため、最終的な残高は120にしか更新されず、130にはなりません。これは、トランザクションAとトランザクションBの操作が互いの結果を「覆い」、状態衝突が発生したためです。

この種の状態衝突問題は通常「データオーバーライド」と呼ばれます。すなわち、トランザクションが同じデータを同時に変更しようとして、互いの計算結果を覆してしまうことがあります。その結果、最終的な状態が正しくない可能性があります。別のタイプの状態衝突が引き起こす可能性のある問題は、実行順序を保証できなくなることです。複数のトランザクションが異なる時間に操作を完了するため、異なる実行順序が生じることがあります。順序が異なれば、異なる計算結果を引き起こし、結果の不確定性を生じさせることがあります。

この不確実性を避けるために、ブロックチェーンの並行実行システムは通常、いくつかの衝突検出とロールバックメカニズムを導入するか、トランザクションの依存性分析を事前に行い、最終的な状態の一貫性に影響を与えずに並行実行されることを確保します。

楽観的並行と決定的並行

状態衝突の可能性がある問題への対処方法には、決定的並行と楽観的並行の2つのアプローチがあります。これらの2つの方式は、効率性と設計の複雑さにおいてそれぞれ異なるトレードオフを持っています。

決定的並行処理では、状態アクセスを事前に宣言する必要があります。検証者またはシーケンサーは、トランザクションの順序を決定する際に宣言された状態アクセスをチェックします。複数のトランザクションが同じ状態に書き込もうとする場合、これらのトランザクションは衝突としてマークされ、同時実行を避けます。異なるチェーンは、状態アクセスを事前に宣言する具体的な方法が異なりますが、一般的には以下のような方法が含まれます。

  • 契約仕様による制約:開発者はスマートコントラクト内で状態アクセスの範囲を直接指定します。たとえば、ERC-20トークンの転送には、送信者と受信者の残高フィールドにアクセスする必要があります。

  • トランザクション構造化データ宣言:トランザクションに特別なフィールドを追加して状態アクセスを示します。

  • コンパイラ分析による:高級言語のコンパイラは契約コードを静的に分析し、状態アクセスのセットを自動生成できます。

  • フレームワークによる強制宣言:特定のフレームワークは、開発者に関数を呼び出す際にアクセスする必要のある状態を明示的に指定するよう求めます。

楽観的並行処理は、トランザクションを先に処理し、衝突が発生したときに影響を受けたトランザクションを順に再実行します。衝突状況をできるだけ回避するために、楽観的並行処理の設計の核心は、過去のデータや静的分析を通じて状態を迅速に予測し、仮定することです。すなわち、システムは完全な検証を行わずに、ある操作や状態更新が有効であると仮定し、すべての検証プロセスを待つことなく、性能とスループットを向上させることを目指します。

楽観的並行は、状態の迅速な予測と仮定を通じて衝突の発生をできるだけ避けることができますが、特に契約実行やクロスチェーントランザクションに関しては避けられない課題がいくつか存在します。衝突が頻繁に発生する場合、再実行はシステムの性能を大幅に低下させ、計算リソースの消費を増加させる可能性があります。

決定的並行は、トランザクション前に状態依存性チェックを行うことで、楽観的並行で発生する可能性のある衝突状況を回避しますが、トランザクション提出前に正確に状態依存を宣言する必要があるため、開発者に高い要件を課し、実装の複雑性を増加させます。

EVM並行のジレンマ

状態衝突に対処する方法には、決定的と楽観的の2つがあります。これら2つのモードは、効率と設計の複雑性の面でそれぞれトレードオフがあります。並行処理の具体的なプロセスの実現にあたっては、チェーンのデータベースアーキテクチャの観点からも考慮する必要があります。並行処理における状態衝突問題は、メルクルツリーアーキテクチャの下でのEVMでは特に困難です。メルクルツリーは階層的なハッシュ構造であり、トランザクションがある状態データを変更するたびに、メルクルツリーのルートハッシュも更新する必要があります。この更新プロセスは再帰的であり、葉ノードから上に向かって層ごとに計算します。ハッシュは逆転できないため、下層のデータが変更されるまで上層を計算することができません。この特性により、並行して更新することは非常に難しいです。

2つのトランザクションが並行して実行され、同じ状態(たとえばアカウント残高)にアクセスしようとすると、メルクルツリーのノードに衝突が生じます。この衝突を解決するためには、通常、追加のトランザクション管理メカニズムが必要であり、複数の分岐の中で一貫したルートハッシュを確保する必要があります。これはEVMにとって簡単ではなく、並行化と状態の一貫性の間でトレードオフを行う必要があるからです。

非EVM並行ソリューション

Solana

イーサリアムのグローバル状態ツリーとは異なり、Solanaはアカウントモデルを採用しています。各アカウントは独立したストレージスペースであり、台帳に保存されているため、パスの競合の問題を回避します。

Solanaは決定的並行処理です。Solanaでは、各トランザクションが提出時に明示的にアクセスするアカウントと必要なアクセス権(読み取り専用または読み書き)を宣言する必要があります。この設計により、ブロックチェーンノードはトランザクション実行前に、各トランザクションがアクセスするリソースを事前に分析できます。トランザクションが開始される前にすべてのアカウント依存関係が明示されているため、ノードはどのトランザクションが同じアカウントにアクセスするか、どのトランザクションが安全に並行実行できるかを判断できるため、スマートスケジューリングを実現し、衝突を回避し、並行スケジューリングの基礎を確立します。

各トランザクションが実行前にアクセスするアカウントと権限を宣言しているため、Solanaはトランザクション間のアカウント依存関係(Sealevelモデル)を確認できます。トランザクション間に共有の読み書きアカウントがない場合、システムはそれらを異なるプロセッサに割り当てて並行実行できます。

Aptos

Aptosの並行実行設計はイーサリアムと大きく異なり、アーキテクチャとメカニズムのいくつかの重要な革新を実現しており、主にアカウントモデルと状態ストレージに反映されています。

イーサリアムはトランザクションを実行する際にグローバル状態ツリー(MPT)を頻繁に更新する必要があります。すべてのアカウントと契約の状態は共有の状態ツリーに保存され、いかなるトランザクションもこの状態ツリーの一部にアクセスして更新する必要があります。一方、Aptosはアカウントを独立した状態ユニットに分割することによって、各オブジェクトを独立したキーバリューペアとして扱い、オブジェクト間は独立して存在し、互いに影響を与えず、明示的な参照関係がある場合にのみ関連します。オブジェクト間には公共のツリーパスがなく、ロック競合が発生せず、完全に並行処理できます。

Aptosの基盤データ構造はJellyfish Merkle Treeです。各オブジェクトの状態は最終的にJMTに保存され、独立したキーバリューペアとして扱われます。イーサリアムのMPTとは異なり、Jellyfish Merkle Treeは完全な二分木構造を採用しており、この形式によってノードのストレージパスとクエリパスが簡略化され、検証時間が大幅に短縮されます。また、各アカウントのツリー内での位置は固定されており、ツリー内のノードは独立して保存されているため、複数のアカウントの更新と検索が並行して行えます。

Aptosは楽観的並行処理であり、すべてのアカウントの依存関係を事前に提供する必要はありません。これを実現するために、AptosはBlock-STMを使用し、Block-STMは事前に設定されたトランザクションの順序を利用して依存関係を推定することで、中断の回数を減らします。

並行EVM

非EVM並行と比較して、並行EVMは状態依存性、衝突検出、Gas管理、ロールバックメカニズムなどの問題を処理する際に、技術的な難しさが大きいです。これをよりよく理解するために、いくつかの並行EVMプロジェクト(たとえばSui、Monad、Canto)がこれらの問題をどのように解決しているかを参考にできます。

Sui

SuiとAptosのように、Monadも状態を処理するためにオブジェクトモデルを使用し、各オブジェクト(アカウント、スマートコントラクトの状態など)を独立したリソースとして扱います。これらのオブジェクトはオブジェクトの一意識別子を通じて区別されます。トランザクションが異なるオブジェクトに関与する場合、これらのトランザクションは並行して処理できます。なぜなら、異なる状態に対して操作を行い、直接的な衝突を生じないからです。

Suiはオブジェクトモデルを使用して状態を管理していますが、EVMとの互換性を保つために、Suiのアーキテクチャは追加のアダプタレイヤーや抽象メカニズムを通じてオブジェクトモデルとEVMのアカウントモデルを橋渡しします。

Suiでは、トランザクションのスケジューリングに楽観的並行戦略を使用し、トランザクション間に衝突がないと仮定します。衝突が発生した場合、システムはロールバックメカニズムを使用して状態を復元します。

Suiはオブジェクトモデルと状態隔離技術を使用しており、状態依存性の問題を効果的に回避できます。各オブジェクトは独立したリソースとして扱われ、異なるトランザクションは並行実行できるため、スループットと効率が向上します。しかし、この方法のトレードオフはオブジェクトモデルの複雑性とロールバックメカニズムのオーバーヘッドです。トランザクション間で衝突が発生した場合、一部の状態をロールバックする必要があり、システムの負担が増加し、並行処理の効率に影響を与える可能性があります。非EVM並行システム(たとえばSolana)と比較して、Suiは効率的な並行性を維持するためにより多くの計算およびストレージリソースを必要とします。

モナド

Suiと同様に、Monadも楽観的並行処理を採用しています。しかし、Monadの楽観的並行処理は、具体的なトランザクション実行前に依存関係のあるトランザクションを予測します。予測は主にMonadの静的コード分析器を通じて行われます。予測には状態へのアクセスが必要ですが、イーサリアムデータベースに保存されている状態の方法は状態へのアクセスを非常に困難にします。状態の読み取りプロセスでの並行性を高めるために、Monadはデータベースを再構築しました。

Monadの状態ツリーはパーティションによって分割され、各パーティションは独自の状態サブツリーを維持します。更新時には関連するシャードのみを変更すればよく、全体の状態ツリーを再構築する必要はありません。状態インデックステーブルを使用してパーティション内の状態を迅速に特定し、パーティション間の相互作用を減少させます。

小結

並行の核心は多経路実行の方法で実行層の実行効率を向上させることであり、多経路実行を実現するために、チェーンは衝突検出やロールバックメカニズムなどを実施して、最終的な状態の一貫性に影響を与えずに並行実行できるようにし、データベースに一定の改良を加える必要があります。

もちろん、実行層の効率を向上させる方法は並行処理だけではありません。実行段階の最適化は、トランザクションがデータベースに必要とする読み書き操作を減少させることでも実現できます。そして、全体のチェーン速度の向上には、共通の層の効率向上も含まれます。

各技術の背後には特有の制約条件があります。並行処理は効率を向上させる方法の一つに過ぎず、最終的にその技術を使用するかどうかは、開発者にとっての使いやすさや、非中央集権を犠牲にせずに達成できるかどうかを考慮する必要があります。技術のスタックは多ければ多いほど良いわけではなく、少なくともイーサリアムに関しては、並行処理はそれほど魅力的ではありません。効率を向上させる観点から見ても、並行処理を導入することはイーサリアムにとって最適解ではなく、シンプルさやイーサリアムの現在のRollup中心のロードマップを考慮してもそうです。