はじめに: EVM バイトコードのメルケル化
編集者注: この記事は以下から引用しましたUnitimes(ID:Uni-times)編集者注: この記事は以下から引用しました
要約: ステートレス クライアントは、ブロック監視の一部としてブロック内で呼び出されるスマート コントラクト コードを送信する必要があります。コントラクト コードは、ステートレス ブロック帯域幅のオーバーヘッドの 2 番目に大きな原因です。コードのマークル化は、このオーバーヘッドの削減に役立つと考えられています。この記事では、コントラクトコードをブロックに分割し、これらのブロックをマークル化し(著者注:これらのブロックをマークルツリーにすることを指します)、トランザクションの実行に必要なブロックのみを送信する方法について詳しく説明します。最近のメインネット ブロックで行われた実験に基づくと、このアプローチによりコード転送が合計 40 ~ 60% 節約されることがわかります。
副題
ステートレス ブロックが大きい
ステートレス モデルでは、(少なくとも一部の) ノードは状態を保存する必要がなく、他のノード (マイナーなど) に依存して、必要なすべての状態 (コントラクト コードを含む) とこれらの状態の有効性を証明するマークル証明をブロックにパッケージ化します。これは、以前よりもはるかに大きなネットワーク帯域幅を意味します。 Alexey Akhunov とturbo-geth[5] チームは、歴史的なメインネット ブロックのブロック監視サイズを測定する実験を行ってきました。以下は、過去 50,000 ブロックの測定値です。赤い線は、ステートレス ブロックで送信する必要があるコントラクト コードの量を追跡します。これは、ブロック監視サイズの 2 番目に大きなソースです。イーサリアムが現在の 16 進トライからバイナリ トライに移行すると、これらの証人のハッシュ部分は最大 3 倍に縮小し、コントラクト コードが証人サイズの主なソースになります。
データはgithub[6]から。最近の 50,000 のメインネット ブロックのステートレス ブロック監視構成を示すグラフ。これらの値は、128 ブロックのウィンドウにわたる移動平均です。
副題
完全なコードを送信する必要はありません
これがどのように機能するかを正確に確認するために、新しいコントラクトがデプロイされるところを想像してみましょう。コントラクト コードをスキャンし、基本ブロック [7] を特定します (アルゴリズム [8] を参照) (著者注、基本ブロックは、順次実行されるコードの一部として理解できます)。クライアントは、JUMPDEST 分析のためにコードを 1 回スキャンするだけでよいため、高いオーバーヘッドが発生しないことに注意してください。これらの基本ブロックには 2 つの特徴があります。
画像の説明
架空のバイトコードの基本ブロック
各基本ブロックはインデックス 0 または JUMPDEST から始まります。これは、ステートレス クライアントが JUMPDEST 分析を安全に実行できるようにするためです (これについては後で詳しく説明します)。
各基本ブロックは制御フローを変更しません (例: JUMP なし)。したがって、基本ブロックの実行を開始すると、最後まで実行されるか、ガスがなくなるかのどちらかであることがわかります。このソリューションはより効率的であると想定していますが、比較のための代替案はテストしていません。
効率性を高めるため、各基本ブロックが最小長 128 バイトになるまで、隣接する基本ブロックがマージされます (これは構成可能なパラメーターです)。次に、最初のバイトのインデックスをキーとして使用して、それらがトライに挿入されます。クライアントは最終的に、コントラクトを記録する新しく作成されたアカウントにこのトライのルートを保存します。以下に示すように、コード トライは実際には状態トライのサブツリーになります (ストレージ トライと同様)。
コントラクトを呼び出すトランザクションを送信して、これをテストしてみましょう。マイナーはトランザクションを実行し、実行中にタッチされたブロック (この場合はブロック 1 と 3) にマークを付けます。ブロックを公開するとき、マイナーは契約アカウントの状態の証明と、コード ブロックに触れたことのターボ証明を含めます [9]。
画像の説明
ブロックを受信した後、ステートレス クライアントは、コントラクトがステートの一部であり、正しいプロパティ (バランス、ノンス値、ステート ルート、およびコード ルート) を持っていることを検証できます。その後、コード ブロックとそのキーをコード ルートに対して検証できます。上記の情報は、クライアントがこれらのブロックからバイトコードの一部を再構築し、他のブロックを空のままにするのに十分です。ここで使用するブロック分割アルゴリズムによれば、クライアントはすべてのブロック (最初のブロックを除く) が JUMPDEST で始まることを認識しているため、安全にジャンプを実行できることに注意してください。
副題
実験
実験
副題
どこへ行く
どこへ行く
覚えているかもしれませんが、各ブロックの最小長は構成可能なパラメータです (上記の 128 バイトに設定します)。このパラメータを変更すると、ブロック監視のサイズに対して 2 つの相反する効果が生じます。たとえば、32 バイトに減らすとブロックの粒度が細かくなり、送信する必要があるコードの総量が減ります。しかし同時に、トライ木の深さも増加し、最終的には証明に必要なハッシュ数の増加につながります。次のステップでは、最小ブロック サイズ設定をより徹底的に分析して、最もスペース効率の高い値があるかどうかを確認します。最小ブロック サイズの値に関係なく、16 進トライから 2 進トライに切り替えると、証明に必要なハッシュが 1/4 減少し (ここを参照 [11])、ブロック監視のサイズがさらに減少します。
このプロトタイプでは、コードを基本ブロックに分割することを選択しましたが、他にもさまざまな分割アルゴリズムが存在し、より単純なものもあれば、より複雑なものもあります。最も簡単な方法は、コードを固定サイズのブロック (ブロックあたり 32 バイトごとなど) に分割することです。現時点で、このソリューションに関する唯一の問題は、PUSH データと JUMPDEST 分析に関係しています。
これに基づいて拡張すると、バイトコードを任意の境界で分割すると、PUSH オペコードおよび将来導入されるその他のマルチバイト オペコードのオペランドが、ブロックを受信するクライアントによって JUMPDEST (0x5b) と誤認される可能性があります。以下に示すように、完全なコードを持つクライアントは、JUMP が無効であることを認識し (0x5b は PUSH1 のオペランドであるため)、実行を停止できます。ただし、ブロック 6 と 8 は受信するが、ブロック 7 は受信しないクライアントは、ロケーション 41 にジャンプし、コントラクトを別の方法で解析します。この問題を回避し、任意の境界をサポートするスキームについては後で簡単に説明します。
この問題を解決するために、Martin Holst Swende は、ヘッダーの何バイトが PUSH のオペランドであるかを指定するメタデータを各ブロックに追加することを提案しました。検証者は、JUMPDEST 分析中にこれらのバイトをスキップできます。 Alexey が模索しているもう 1 つの手段は、EVM での動的ジャンプを無効にすることであり (少なくともコードのマークル化を希望する契約では)、コード実行中の各パスではなく、デプロイメント時に 1 つのパスで静的にジャンプを分析できるようになります。 Alex Beregszaszi は、契約制御フロー グラフの使用によりメルケル化をより適切に導くことができると提案しました。一方、Christian Reitweissner は、契約の制御フロー グラフからマークル化 DAG を作成する実行証明スキームを提案しました。この記事では彼の考え方を客観的に評価することはできませんが、今後さらに詳しく説明してくれることを期待しています。
異なるセグメンテーション アルゴリズムでは、結果では効率の向上が無視できる程度しか示されない可能性があります。この場合、最も単純なアルゴリズムが最も賢明な選択になります。良いニュースは、初期のデータでは、ステートレス ブロックで転送されるコードの量を大幅に削減すると思われるアルゴリズムが少なくとも 1 つあることです。
元のリンク:
元のリンク:
参考リンク:


