Vitalik 新作速読:多次元ガス価格設定
編訳:Karen,Foreisght News
イーサリアムでは、リソースは最近まで限られており、「Gas」と呼ばれる単一のリソースによって価格が設定されていました。Gasは、特定のトランザクションやブロックを処理するために必要な「計算量」(computational effort)を測定する単位です。Gasはさまざまなタイプの「計算量」を統合しており、最も重要なものには以下が含まれます:
原始計算(Raw computation、例えばADD、MULTIPLY);
イーサリアムストレージの読み書き(例えばSSTORE、SLOAD、ETH転送);
データ帯域幅;
ブロック生成のためのZK-SNARK証明のコスト。
例えば、私が送信したこのトランザクションは合計で47,085 Gasを消費しました。その内訳は:(i)基本コストが21,000 Gas、(ii)トランザクションの一部として含まれるcalldataバイトが1,556 Gasを消費し、(iii)ストレージの読み書きが16,500 Gasを消費し、(iv)ログ生成が2,149 Gasを消費し、残りはEVMの実行に使用されました。ユーザーが支払うトランザクション手数料は、トランザクションが消費したGasに比例します。1つのブロックには最大で3,000万Gasを含めることができ、Gas価格はEIP-1559のターゲティングメカニズムによって継続的に調整され、各ブロックに平均して1,500万Gasが含まれるようにしています。
この方法には主な利点があります:すべての内容が1つの仮想リソースに統合されているため、市場設計が非常にシンプルです。コストを最小化するためにトランザクションを最適化するのは簡単で、できるだけ高い手数料を徴収するためにブロックを最適化するのも比較的簡単です(MEVを除く)、また、いくつかのトランザクションを他のトランザクションとバンドルして手数料を節約するような奇妙なインセンティブメカニズムはありません。
しかし、この方法には非効率性もあります:異なるリソースを相互に変換可能なものとして扱っているため、実際の基盤となる制約は異なります。この問題を理解するために、まず以下の図を見てください:
Gas制限は制約条件を課します:
実際の基盤となる安全制約は通常、次のように近いです:
この違いは、Gas制限が実際の安全なブロックを無意味に排除したり、実際には安全でないブロックを受け入れたり、またはその両方を引き起こすことになります。
もしn種類のリソースが異なる安全制約を持っている場合、一次元のGasはスループットを最大でn倍低下させる可能性があります。したがって、長い間、多次元Gasの概念に関心が寄せられてきましたが、EIP-4844を通じて、私たちは現在実際にイーサリアム上で多次元Gasを実現しています。本稿では、このアプローチの利点と、さらなる強化の展望について探ります。
Blob:Dencunにおける多次元Gas
今年初め、平均ブロックサイズは150kBでした。その大部分はRollupデータです:Layer2プロトコルがチェーン上にデータを保存します。これらのデータは非常に高価です:Rollup上のトランザクションコストは、イーサリアムL1上の対応するトランザクションの5-10倍ですが、それでもこのコストは多くのユースケースにとって高すぎます。
では、なぜRollupを安くするためにcalldataのGasコストを下げないのでしょうか(現在、非ゼロバイトは16Gas、ゼロバイトは4Gas)?以前にこれを行ったことがあり、今も再び行うことができます。しかし、ここでの答えは、ブロックの最大サイズは30,000,000/16=1,875,000非ゼロバイトであり、ネットワークはそのようなサイズのブロックをほとんど処理できないか、まったく処理できないということです。コストをさらに4倍下げると、最大値は7.5MBに増加し、安全性に大きなリスクをもたらします。
この問題は、各ブロックに独立したRollupフレンドリーなデータスペース(Blobと呼ばれる)を導入することで最終的に解決されました。
これら2つのリソースは異なる価格と制限を持っています:Dencunハードフォークの後、1つのイーサリアムブロックには最大で(i)3,000万Gasと(ii)6つのBlobが含まれ、各Blobは約125kBのcalldataを含むことができます。これら2つのリソースはそれぞれ独自の価格を持ち、EIP-1559に類似した独自の価格設定メカニズムによって調整され、目標は各ブロックで平均1,500万Gasと3つのBlobを使用することです。
その結果、Rollupのコストは100倍削減され、Rollup上のトランザクション量は3倍以上増加しましたが、理論的な最大ブロックサイズはわずかに増加しました:約1.9MBから約2.6MBに。
注:Rollupトランザクション手数料、Growthepie.xyz提供。Dencunフォークは2024年3月13日に発生し、多次元価格設定Blobを導入しました。
多次元Gasと無状態クライアント
近い将来、無状態クライアント(stateless clients)のストレージ証明も同様の問題に直面します。無状態クライアントは、新しいタイプのクライアントで、大量または全くデータをローカルに保存することなくチェーンを検証できるようになります。無状態クライアントは、ブロック内のトランザクションがアクセスする必要があるイーサリアムの状態の特定部分の証明を受け入れることでこれを実現します。
上の図は、無状態クライアントがブロックを受け取り、そのブロックの実行によって触れられた状態の特定部分(例えば、アカウントの残高、コード、ストレージ)の現在の値の証明を示しています。これにより、ノードはストレージなしでブロックを検証できるようになります。
ストレージの読み取りには2,100-2,600 Gasがかかり、具体的には読み取りの種類によりますが、ストレージの書き込みコストはさらに高くなります。平均して、1つのブロックは約1,000回のストレージの読み書き操作(ETH残高のチェック、SSTOREおよびSLOAD呼び出し、コントラクトコードの読み取りなど)を実行します。しかし、理論的な最大値は30,000,000/2,100=14,285回の読み取りです。無状態クライアントの帯域幅負荷はこの数字に比例します。
現在の計画は、イーサリアムのState tree設計をMerkle Patricia treesからVerkle treesに変更することで無状態クライアントをサポートすることです。しかし、Verkle treesは量子耐性を持たず、より新しいSTARK証明システムにとって最適な選択肢ではありません。したがって、多くの人々は、バイナリMerkle treesとSTARKsを使用して無状態クライアントをサポートすることに興味を持っており、Verkleを完全にスキップするか、Verkleが数年後にSTARKがより成熟するまで移行することを考えています。
バイナリハッシュツリーに基づくSTARK証明は多くの利点を持っていますが、その重要な弱点は証明を生成するのに時間がかかることです:Verkleツリーは1秒あたり10万以上の値を証明できますが、ハッシュに基づくSTARKsは通常1秒あたり数千のハッシュしか証明できず、各値を証明するには多くのハッシュの「ブランチ」を含める必要があります。
今日、BiniusやPlonky3などの超最適化された証明システムやVision-Mark-32などの専用ハッシュから予測される数字を考慮すると、私たちはしばらくの間、実用的な範囲にいるようです。つまり、1秒あたり1,000の値を証明することは可能ですが、14,285の値を証明することは不可能です。平均的なブロックには問題ありませんが、潜在的な最悪のケースのブロック(攻撃者によって発行されたもの)はネットワークを破壊します。
このような状況に対処するためのデフォルトの方法は再価格設定です:ストレージの読み取りコストを引き上げて、各ブロックの最大値をより安全なレベルに減少させます。しかし、私たちはこれを何度も行ってきましたし、再度行うとあまりにも多くのアプリケーションが高価になってしまいます。より良い方法は多次元Gasです:ストレージアクセスをそれぞれ制限し、料金を設定し、平均使用量を各ブロックで1,000回のストレージアクセスに保ちながら、各ブロックの上限を設定することです(例えば2,000回)。
多次元Gasの普遍性
考慮すべきもう1つのリソースは、状態サイズの増加です:すなわち、イーサリアムの状態サイズを増加させる操作であり、これらの操作の後にはフルノードが保存する必要があります。状態サイズの増加の独自性は、それを制限する理由が完全に長期的な持続的使用に由来することであり、ピークではないということです。
したがって、状態サイズを増加させる操作(例えば、zero-to-non-zero SSTORE、コントラクト作成)に対して独自のGas次元を追加することは価値があるかもしれませんが、目標は異なります:特定の平均使用量をターゲットにするために浮動価格を設定できますが、各ブロックの制限を完全に設定しないことができます。
これは多次元Gasの強力な特性を示しています:それは私たちに各リソースに対して、(i)理想的な平均使用量は何か?(ii)各ブロックの安全な最大使用量は何か?を尋ねることを可能にします。各ブロックの最大値に基づいてGas価格を設定し、平均使用量がそれに続くのとは異なり、私たちは2nの自由度を持って2nのパラメータを設定し、ネットワークの安全性に基づいて各パラメータを調整できます。
より複雑な状況、例えば2つのリソースの安全性の考慮が部分的に加算される場合は、オペコードまたはリソースが複数のタイプのGasを消費するようにすることで対処できます(例えば、zero-to-non-zero SSTOREは5000の無状態クライアント証明Gasと20000のストレージ拡張Gasを消費する可能性があります)。
各トランザクションのMax(データまたは計算の消費が大きい方を選択)
𝑥1をデータのGasコスト、𝑥2を計算Gasコストとすると、一次元GasシステムではトランザクションのGasコストを次のように書くことができます:
このスキームでは、トランザクションのGasコストを次のように定義します:
つまり、トランザクションはデータと計算の合計ではなく、消費した2つのリソースのうちどちらか多い方に基づいて料金が発生します。これは、より多くの次元をカバーするように簡単に拡張できます(例えば、𝑚𝑎𝑥(…,𝑥3∗𝑠𝑡𝑜𝑟𝑎𝑔𝑒_𝑎𝑐𝑐𝑒𝑠𝑠))。
これは、安全性を保証しながらスループットを向上させる方法がどのように機能するかを簡単に示しています。理論的に、ブロック内の最大データ量は依然としてGas LIMIT /𝑥1であり、一次元Gasスキームと完全に同じです。同様に、理論的な最大計算量はGas LIMIT /𝑥2であり、これも一次元Gasスキームと完全に同じです。しかし、データと計算の両方を消費するトランザクションのGasコストは低下します。
これは、提案されたEIP-7623で採用されたスキームの一部であり、最大ブロックサイズを減少させつつ、Blobの数をさらに増加させることを目的としています。EIP-7623の正確なメカニズムはやや複雑ですが、現在のcalldata価格をバイトあたり16Gasに保ちながら、バイトあたり48Gasのフロア価格を追加します;トランザクションは(16 * バイト + 実行_Gas)と(48 * バイト)の高い方を支払います。したがって、EIP-7623はブロック内の理論的な最大トランザクション呼び出しデータを約1.9MBから約0.6MBに減少させつつ、大多数のアプリケーションのコストを変えません。この方法の利点は、現在の一次元Gasスキームに比べて変化が非常に小さいため、非常に実装が容易であることです。
ただし、この方法には2つの欠点があります:
ブロック内の他のすべてのトランザクションがそのリソースをわずかにしか使用していなくても、大量に1つのリソースを占有するトランザクションは依然として不必要に高い料金を請求されること;
データ集約型および計算集約型のトランザクションがコストを節約するために1つのバンドルに統合されることを奨励すること。
私は、EIP-7623のようなルールは、トランザクションcalldataや他のリソースに対して十分な利益をもたらすと考えています。これらの欠点があっても、それは価値があると思います。
しかし、もし私たちが(かなり高い)開発努力を投入することを望むなら、より理想的な方法が現れるでしょう。
多次元EIP-1559:より困難だが理想的な戦略
まず、従来のEIP-1559の動作を振り返りましょう。私たちは、EIP-4844でBlobに対して導入されたバージョンに焦点を当てます。なぜなら、それは数学的により優雅だからです。
私たちはパラメータexcess_blobsを追跡します。各ブロックの間に、次のように設定します:
excessblobs \<-- max (excessblobs + len(block.blobs) - TARGET, 0)
ここで、TARGET = 3です。つまり、あるブロックのBlobの数が目標を超えると、excessblobsが増加し、あるブロックのBlobの数が目標を下回ると、excessblobsが減少します。次に、blobbasefee = exp(excessblobs / 25.47)を設定します。ここで、expは指数関数𝑒𝑥𝑝(𝑥)=2.71828\^𝑥の近似値です。
つまり、excessblobsが約25増加するたびに、blob基本料金は約2.7倍増加します。Blobが高くなりすぎると、平均使用量が減少し、excessblobsが減少し始め、価格が自動的に再び下がります。Blobの価格は継続的に調整され、平均してブロックが半分満たされるように、つまり各ブロックに平均して3つのBlobが含まれるようにします。
使用量に短期的なピークがある場合、制限が発生します:各ブロックには最大で6つのBlobしか含められず、この場合、トランザクションは優先手数料を引き上げることで互いに競争できます。しかし、通常は、各Blobはblob_basefeeに少量の追加優先手数料を支払うだけで済みます。
このGas価格設定は、イーサリアムにおいて何年も存在しています:2020年にはEIP-1559が非常に似たメカニズムを導入しました。EIP-4844を通じて、GasとBlobsに対して2つの独立した浮動価格を設定しました。
注:2024年5月8日1時間内のGas基本料金、単位はgwei。出典:ultrasound.money
原則として、ストレージの読み取りや他のタイプの操作に対してさらに独立した浮動料金を追加することができますが、次のセクションで注意が必要な問題について詳しく説明します。
ユーザーにとって、この体験は今日と非常に似ています:基本料金(basefee)を支払うのではなく、2つの基本料金を支払いますが、あなたのウォレットはそれを抽象化し、予想される支払いと最高料金だけを表示できます。
ブロックビルダーにとって、ほとんどの場合、最適な戦略は今日と同じです:有効なコンテンツをすべて含めることです。ほとんどのブロックは満杯ではありません------GasでもBlobでも。十分なGasまたは十分なBlobがブロック制限を超える場合、ビルダーは潜在的に多次元ナップサック問題を解決して利益を最大化する必要があります。しかし、かなり良い近似アルゴリズムが存在しても、この場合、独自のアルゴリズムを策定して利益を最適化することから得られる利益は、MEVを使用して同じ操作を行うことから得られる利益よりもはるかに小さいです。
開発者にとっての主な課題は、現在は単一の価格と単一の制限に基づいて設計されているEVMおよびその関連インフラストラクチャの機能を再設計する必要があることです。これを複数の価格と複数の制限に適応できる設計に変更する必要があります。
アプリケーション開発者が直面する問題の1つは、最適化がやや困難になることです:特定の状況では、AがBよりも効率的であるとは明確に言えなくなります。なぜなら、Aがより多くのcalldataを使用し、Bがより多くの実行を使用する場合、calldataが安価なときはAが安価であり、calldataが高価なときはAが高価になるからです。
しかし、開発者は依然として長期的な歴史的平均価格に基づいて最適化を行うことで、かなり良い結果を得ることができます。
多次元価格設定、EVM、およびサブコール
Blobにおいては発生しない問題があり、EIP-7623やcalldataに対する完全な多次元価格設定の実装でも発生しませんが、状態アクセスや他のリソースに対して個別に価格設定を試みると、この問題が発生します:すなわち、サブコール(sub-calls)におけるGas制限です。
EVMにおけるGas制限は2つの場所に存在します。まず、各トランザクションにはGas制限(Gas Limit)が設定され、そのトランザクションで使用できるGasの総量が制限されます。次に、あるコントラクトが別のコントラクトを呼び出すとき、その呼び出しは独自のGas制限を設定できます。これにより、コントラクトは信頼できない他のコントラクトを呼び出し、呼び出し後に他の計算を実行するために残りのGasを保証できます。
注:アカウント抽象トランザクションのトレースで、1つのアカウントが別のアカウントを呼び出し、呼び出された側に限られた量のGasを提供して、呼び出された側が割り当てられた全てのGasを消費しても、外部呼び出しが続行できるようにします。
課題は、異なるタイプの実行間で多次元Gasを実現するには、サブコールが各タイプのGasに対して複数の制限を提供する必要があるように思えることです。これにはEVMに非常に深い変更が必要であり、既存のアプリケーションと互換性がありません。
これが、多次元Gas提案が通常2つの次元(データと実行)に留まる理由の1つです。データ(トランザクションcalldataまたはBlob)はEVMの外部でのみ割り当てられるため、EVM内部を変更せずにcalldataまたはBlobを個別に価格設定できます。
私たちは「EIP-7623スタイルの解決策」を考え出すことができます。これはシンプルな実装です:実行中にストレージ操作に4倍の料金を請求します;分析を簡素化するために、各ストレージ操作に10,000Gasがかかると仮定します。トランザクションが終了すると、返金はmin(7500 * storageoperations, executionGas)です。結果として、返金を差し引いた後、ユーザーは以下の料金を支払う必要があります:
executionGas + 10,000 * storageoperations - min(7500 * storageoperations, executionGas)
これは次のように等しいです:
max(executionGas + 2500 * storageoperations, 10,000 * storage_operations)
これはEIP-7623の構造を反映しています。別の方法は、ストレージ操作とexecutionGasをリアルタイムで追跡し、その時点でmax(executionGas + 2500 * storageoperations, 10,000 * storageoperations)がどれだけ上昇するかに基づいて2500または10,000を請求することです。この方法は、トランザクションがGasを過剰に割り当てる必要がなく、これらのGasが主に返金を通じて回収されることを避けます。
私たちはサブコールの細かい許可を得ていません:サブコールはトランザクションの全てのアローワンスを消費して安価なストレージ操作を行う可能性があります。
しかし、私たちはサブコールを行うコントラクトが制限を設定し、サブコールが実行された後にメインコールが必要な後処理(post-processing)を行うために十分なGasを持つことを保証するという、十分に良いものを得ています。
私が考える最もシンプルな「完全な多次元価格設定ソリューション」は、サブコールのGas制限を比例的に扱うことです。つまり、𝑘種類の異なる実行タイプがあり、各トランザクションが多次元制限𝐿1…𝐿𝑘を設定していると仮定します。現在の実行ポイントで、残りのGasが𝑔1…𝑔𝑘であると仮定します。CALLオペコードを呼び出し、サブコールGas制限𝑆を使用します。𝑠1=𝑆とし、次に𝑠2=𝑠1/𝑔1∗𝑔2、𝑠3=𝑠1/𝑔1∗𝑔3とします。
つまり、最初のタイプのGas(実際にはVM実行)を特別な「アカウント単位」として扱い、他のタイプのGasを割り当てて、サブコールが各タイプのGasにおいて同じ割合の利用可能なGasを得るようにします。この方法はやや見栄えが悪い(ugly)ですが、後方互換性を最大限に保証します。
もし私たちが後方互換性を犠牲にすることなく、このスキームを異なるタイプのGas間でより「中立」にしたい場合、サブコールのGas制限パラメータを現在のコンテキスト内の残りGasの一部として表現することができます(例えば、[1…63]/64)。
しかし、どちらの場合でも、一度多次元実行Gasを導入し始めると、固有の複雑性(ugliness)が増加することは避けられないようです。
したがって、私たちの課題は、EVMレベルでのある程度の複雑性(ugliness)の増加を受け入れるかどうか、そしてそれによってL1の顕著なスケーラビリティの向上を安全に解除することができるか、もしそうなら、どの具体的な提案がプロトコル経済とアプリケーション開発者に最も効果的かという複雑なトレードオフを行うことです。私が上で言及した2つの提案が最良のものでない可能性が高いですが、より優雅で良い提案をする余地はまだあります。
特に、Ansgar Dietrichs、Barnabe Monnot、Davide Crapisのフィードバックとレビューに感謝します。