コンパイル:デンリンク翻訳計画ロールアップの仕組みについて詳しく知りたいと思ったことはありませんか? 理論は良いですが、実践的な経験が常に望ましいです。 残念ながら、既存のプロジェクトでは、何が起こっているのかを常に簡単に確認できるとは限りません。 そのため、BYOR (独自のロールアップの構築) を作成しました。 これは、コードを読みやすく理解しやすくすることに重点を置いて、最小限の機能を備えたソブリンロールアップです。このプロジェクトの動機は、部外者と内部者の両方の人々が、私たちの周りのロールアップが実際に何をしているのかをよりよく理解することです。 HoleskyのデプロイされたBYORで遊んだり、GitHubでソースコードを読んだりすることができます。## BYORとは何ですか?BYOR プロジェクトは、ソブリン ロールアップの簡易バージョンです。 ロールアップの楽観的でゼロ知識の証明とは対照的に、ソブリンロールアップはイーサリアムの状態ルートを検証せず、イーサリアムのデータの可用性とコンセンサスのみに依存します。 これにより、L1 と BYOR の間の信頼最小化ブリッジが防止されますが、コードが大幅に簡素化され、教育目的に最適です。コードベースは、スマートコントラクト、ノード、ウォレットの3つのプログラムで構成されています。 一緒に展開すると、エンド ユーザーはネットワークと対話できます。 興味深いことに、ネットワークの状態は完全にオンチェーンデータによって決定されるため、複数のノードが実際に実行できます。 各ノードは、シーケンサーとして個別にデータを公開することもできます。BYORに実装されている機能の完全なリストは次のとおりです。*手数料の仕分け* ステータスを L1 にポストし、L1 からステータスを取得*無効なトランザクションを破棄する*アカウントの残高を確認する*トランザクションを送信する*取引状況を確認する## ウォレットを使用するウォレットアプリでは、ネットワークのフロントエンドとして機能し、ユーザーはトランザクションを送信し、アカウントのステータスまたはトランザクションのステータスを確認できます。 ランディング ページには、ロールアップの現在のステータスに関する統計情報を提供する概要が表示され、その後にアカウントのステータスが表示されます。 ほとんどの場合、選択したウォレットに接続するためのボタンは1つしかなく、トークンの蛇口に関するニュースがあります。 以下に、誰かのアドレスまたはトランザクションハッシュを貼り付けてL2の現在の状態を調べることができる検索バーがあります。 最後に、トランザクションのリストは 2 つあり、1 つ目は L2 mempool 内のトランザクションのリストで、2 つ目は L1 にパブリッシュされたトランザクションのリストです。開始するには、ウォレットコネクトボタンを使用してウォレットを接続します。 接続すると、ウォレットが間違ったネットワークに接続されているという通知を受け取る場合があります。 アプリケーションがネットワーク切り替えをサポートしている場合は、[ネットワークの切り替え] ボタンをクリックして Holesky テスト ネットワークに切り替えます。 それ以外の場合は、手動で切り替えます。これで、受信者のアドレス、送信するトークンの数、および必要な料金を提供することで、トークンを誰かに送信できます。 送信されると、ウォレットアプリはメッセージに署名するように求められます。 正常に署名されると、メッセージは L2 ノードのメモリ プールに送信され、L1 へのパブリッシュを待機します。 トランザクションがバッチリリースにバンドルされるまでにかかる時間は異なる場合があります。 10 秒ごとに、L2 ノードは公開するコンテンツをチェックします。 手数料の高い取引が先に送信されるため、手数料を低く指定し、取引トラフィックが多い場合は、待ち時間が長くなる可能性があります。## 仕組み ロールアップ アーキテクチャ図 ## テクノロジ スタック各コンポーネントは、次の手法を使用して構築しました。* 节点: Node.js, Type, tRPC, Postgres, viem, drizzle-orm*ウォレット:タイプ、tRPC、ネクスト.js、ウォレットコネクト## コードのドリルダウンBYORコードは、コードベースを見て簡単に理解できるように設計されています。 私たちのコードベースを自由に探索してください! 最初に* README.md *を読んで、プロジェクトの構造を理解するために、* ARCHITECTURE.md*ファイルを読んでください。コードの興味深いハイライトを次に示します。## スマートコントラクトSPDX ライセンス識別子: MITプラグマの堅牢性^ 0.8.0; 契約入力 {イベントバッチ追加(アドレス送信者);関数 appendBatch(bytes calldata) external {require(msg.sender == tx.origin);emit BatchAppended(msg.sender);}}これは必要な唯一のスマートコントラクトです。 その名前は、入力が状態遷移関数に格納されるという事実に由来しています。 この契約の唯一の目的は、すべてのトランザクションを便利に保存することです。 シリアル化されたバッチは、calldata としてこのスマート コントラクトに発行され、バッチ発行元のアドレスを含む BatchAppended イベントを発行します。 コントラクトではなくEOAに直接トランザクションを公開するようにシステムを設計することもできますが、イベントを発行することでJSON-RPCを介してデータを簡単に取得できます。 このスマートコントラクトの唯一の要件は、別のスマートコントラクトからではなく、EOAから直接呼び出すことです。## データベーススキーマテーブル アカウントの作成 (アドレス テキスト 主キーが NULL ではありません。バランス整数デフォルト0はNULLではありません、nonce 整数 デフォルト 0 NULL ではない); テーブルトランザクションの作成 (id整数、テキストから NULL ではなく、テキストに NOT NULL、値 整数 NOT NULL の場合、ノンス整数 NULL ではありません。料金 整数 NOT NULL 、料金受信テキスト NOT NULL 、l1SubmittedDate integer NOT NULLハッシュ テキスト NULL ではありません主キー(差出人、ナンス)); -- このテーブルには 1 つの行がありますテーブルフェッチャー状態の作成 (チェーン ID 整数 主キーが NULL ではありません。lastFetchedBlock integer デフォルト 0 NOT NULL);これは、ロールアップに関する情報を格納するために使用されるデータベース スキーマ全体です。 必要なデータがすべてL1に保存されているのに、なぜデータベースが必要なのか疑問に思われるかもしれません。 これは事実ですが、データをローカルに保存すると、重複した取得を回避できるため、時間とリソースを節約できます。 このスキーマに格納されているすべてのデータを、ステータスのメモ、トランザクションハッシュ、およびその他の計算情報として扱います。fetcherStates テーブルは、BatchAppended イベントを検索するときに最後にフェッチしたブロックを追跡するために使用されます。 これは、ノードをシャットダウンして再起動するときに役立ちます。 検索を再開する場所がわかっています。## 状態遷移関数定数 DEFAULT_ACCOUNT = { 残高: 0, ナンス: 0 } function uteTransaction(state, tx, feeRecipient) {const fromAccount = getAccount(state, tx.from, DEFAULT_ACCOUNT)const toAccount = getAccount(state, tx.to, DEFAULT_ACCOUNT)ステップ1:ノンスを更新するfromAccount.nonce = tx.nonceステップ2:価値の転送fromAccount.balance -= tx.valuetoAccount.balance += tx.valueステップ3 料金を支払うfromAccount.balance -= tx.feefeeRecipientAccount.balance += tx.fee}上記の関数は、BYORの状態遷移メカニズムの中心です。 これは、定義された支払いを行うのに十分なナンスと十分な残高で、トランザクションが安全に実行できることを前提としています。 この前提のため、この関数内にはエラー処理や検証の手順はありません。 代わりに、これらの手順は、関数が呼び出される前に実行されます。 各アカウントの状態はマップに格納されます。 このマッピングにアカウントがまだ存在しない場合は、コード リストの上部に表示される既定値に設定されます。 使用される3つのアカウントのうち、ナンスが更新され、残高が割り当てられます。## トランザクション署名 トランザクション署名:EIP-712標準を使用して、型指定されたデータに署名します。 これにより、ユーザーが署名している内容を明確に示すことができます。 上記のように、トランザクションを送信するときに、受信者、金額、および手数料をユーザーフレンドリーな方法で表示できます。## L1 イベント取得関数 getNewStates() {const lastBatchBlock = getLastBatchBlock()const events = getLogs(lastBatchBlock)const calldata = getCalldata(events)const timestamp = getTimestamps(events)const posters = getTransactionPosters(events)updateLastFetchedBlock(lastBatchBlock)zipを返す(ポスター、タイムスタンプ、コールデータ)}新しいイベントを取得するには、入力コントラクトから最後にフェッチされたブロックからすべての BatchAddended イベントを取得します。 取得するイベントの最大数は、最新のチャンクまたは最後にフェッチされたチャンクにバッチサイズ制限を加えたものです。 すべてのイベントを取得した後、各トランザクションから calldata、タイムスタンプ、およびパブリッシャー アドレスを抽出します。 フェッチする最後のブロックを、フェッチする最後のブロックに更新します。 抽出された calldata、タイムスタンプ、およびパブリッシャーは一緒にパッケージ化され、さらに処理するために関数から返されます。## メモリプールとそのコストソートfunction popNHighestFee(txPool, n) {txPool.sort((a, b) => b.fee - a.fee))return txPool.splice(0, n)}mempool は、署名されたトランザクションの配列を管理するオブジェクトです。 最も興味深い側面は、トランザクションが L1 に転記される順序を決定する方法です。 上記のコードに示されているように、トランザクションは手数料に従ってソートされます。 これにより、システム内の手数料価格の中央値は、チェーン上の活動に基づいて変動します。高い手数料を指定した場合でも、現在の状態に追加する必要がある場合は、トランザクションが有効な状態を生成する必要があります。 したがって、手数料が高いという理由だけで無効な取引を送信することはできません。## BYORは本当にイーサリアムをスケーリングしますか?楽観主義とZKロールアップは、公開された状態ルートが状態遷移関数とそれらがコミットするデータと一致していることを証明するシステムを構築しましたが、ソブリンロールアップはそうではありません。 したがって、このタイプのロールアップでイーサリアムをスケーリングできないことは、最初は直感に反するように思えるかもしれません。 ただし、他の種類のロールアップでは L1 のみを使用して、公開された状態ルートが正しいことを証明できることがわかれば、これは合理的になります。 ソブリンロールアップのデータが正しいかどうかを区別するには、L1ノードと追加のソフトウェアを実行してL2ノードを形式化し、状態遷移関数を実行する必要があるため、計算負荷が増加します。## 今後の展望このプロジェクトの構築は私たちにとって素晴らしい学習経験であり、私たちの努力も貴重であることを願っています。 将来的にはBYORに戻り、不正防止システムを追加したいと考えています。 これは真に楽観的なロールアップになり、私たちが毎日使用するシステムの内部動作の教訓になります。
独自のロールアップを作成する – BYOR プロジェクトのリスト
コンパイル:デンリンク翻訳計画
ロールアップの仕組みについて詳しく知りたいと思ったことはありませんか? 理論は良いですが、実践的な経験が常に望ましいです。 残念ながら、既存のプロジェクトでは、何が起こっているのかを常に簡単に確認できるとは限りません。 そのため、BYOR (独自のロールアップの構築) を作成しました。 これは、コードを読みやすく理解しやすくすることに重点を置いて、最小限の機能を備えたソブリンロールアップです。
このプロジェクトの動機は、部外者と内部者の両方の人々が、私たちの周りのロールアップが実際に何をしているのかをよりよく理解することです。 HoleskyのデプロイされたBYORで遊んだり、GitHubでソースコードを読んだりすることができます。
BYORとは何ですか?
BYOR プロジェクトは、ソブリン ロールアップの簡易バージョンです。 ロールアップの楽観的でゼロ知識の証明とは対照的に、ソブリンロールアップはイーサリアムの状態ルートを検証せず、イーサリアムのデータの可用性とコンセンサスのみに依存します。 これにより、L1 と BYOR の間の信頼最小化ブリッジが防止されますが、コードが大幅に簡素化され、教育目的に最適です。
コードベースは、スマートコントラクト、ノード、ウォレットの3つのプログラムで構成されています。 一緒に展開すると、エンド ユーザーはネットワークと対話できます。 興味深いことに、ネットワークの状態は完全にオンチェーンデータによって決定されるため、複数のノードが実際に実行できます。 各ノードは、シーケンサーとして個別にデータを公開することもできます。
BYORに実装されている機能の完全なリストは次のとおりです。
*手数料の仕分け
ウォレットを使用する
ウォレットアプリでは、ネットワークのフロントエンドとして機能し、ユーザーはトランザクションを送信し、アカウントのステータスまたはトランザクションのステータスを確認できます。 ランディング ページには、ロールアップの現在のステータスに関する統計情報を提供する概要が表示され、その後にアカウントのステータスが表示されます。 ほとんどの場合、選択したウォレットに接続するためのボタンは1つしかなく、トークンの蛇口に関するニュースがあります。 以下に、誰かのアドレスまたはトランザクションハッシュを貼り付けてL2の現在の状態を調べることができる検索バーがあります。 最後に、トランザクションのリストは 2 つあり、1 つ目は L2 mempool 内のトランザクションのリストで、2 つ目は L1 にパブリッシュされたトランザクションのリストです。
開始するには、ウォレットコネクトボタンを使用してウォレットを接続します。 接続すると、ウォレットが間違ったネットワークに接続されているという通知を受け取る場合があります。 アプリケーションがネットワーク切り替えをサポートしている場合は、[ネットワークの切り替え] ボタンをクリックして Holesky テスト ネットワークに切り替えます。 それ以外の場合は、手動で切り替えます。
これで、受信者のアドレス、送信するトークンの数、および必要な料金を提供することで、トークンを誰かに送信できます。 送信されると、ウォレットアプリはメッセージに署名するように求められます。 正常に署名されると、メッセージは L2 ノードのメモリ プールに送信され、L1 へのパブリッシュを待機します。 トランザクションがバッチリリースにバンドルされるまでにかかる時間は異なる場合があります。 10 秒ごとに、L2 ノードは公開するコンテンツをチェックします。 手数料の高い取引が先に送信されるため、手数料を低く指定し、取引トラフィックが多い場合は、待ち時間が長くなる可能性があります。
仕組み
各コンポーネントは、次の手法を使用して構築しました。
コードのドリルダウン
BYORコードは、コードベースを見て簡単に理解できるように設計されています。 私たちのコードベースを自由に探索してください! 最初に* README.md を読んで、プロジェクトの構造を理解するために、 ARCHITECTURE.md*ファイルを読んでください。
コードの興味深いハイライトを次に示します。
スマートコントラクト
SPDX ライセンス識別子: MIT プラグマの堅牢性^ 0.8.0;
契約入力 { イベントバッチ追加(アドレス送信者); 関数 appendBatch(bytes calldata) external { require(msg.sender == tx.origin); emit BatchAppended(msg.sender); } }
これは必要な唯一のスマートコントラクトです。 その名前は、入力が状態遷移関数に格納されるという事実に由来しています。 この契約の唯一の目的は、すべてのトランザクションを便利に保存することです。 シリアル化されたバッチは、calldata としてこのスマート コントラクトに発行され、バッチ発行元のアドレスを含む BatchAppended イベントを発行します。 コントラクトではなくEOAに直接トランザクションを公開するようにシステムを設計することもできますが、イベントを発行することでJSON-RPCを介してデータを簡単に取得できます。 このスマートコントラクトの唯一の要件は、別のスマートコントラクトからではなく、EOAから直接呼び出すことです。
データベーススキーマ
テーブル アカウントの作成 ( アドレス テキスト 主キーが NULL ではありません。 バランス整数デフォルト0はNULLではありません、 nonce 整数 デフォルト 0 NULL ではない );
テーブルトランザクションの作成 ( id整数、 テキストから NULL ではなく、 テキストに NOT NULL、 値 整数 NOT NULL の場合、 ノンス整数 NULL ではありません。 料金 整数 NOT NULL 、 料金受信テキスト NOT NULL 、 l1SubmittedDate integer NOT NULL ハッシュ テキスト NULL ではありません 主キー(差出人、ナンス) );
-- このテーブルには 1 つの行があります テーブルフェッチャー状態の作成 ( チェーン ID 整数 主キーが NULL ではありません。 lastFetchedBlock integer デフォルト 0 NOT NULL );
これは、ロールアップに関する情報を格納するために使用されるデータベース スキーマ全体です。 必要なデータがすべてL1に保存されているのに、なぜデータベースが必要なのか疑問に思われるかもしれません。 これは事実ですが、データをローカルに保存すると、重複した取得を回避できるため、時間とリソースを節約できます。 このスキーマに格納されているすべてのデータを、ステータスのメモ、トランザクションハッシュ、およびその他の計算情報として扱います。
fetcherStates テーブルは、BatchAppended イベントを検索するときに最後にフェッチしたブロックを追跡するために使用されます。 これは、ノードをシャットダウンして再起動するときに役立ちます。 検索を再開する場所がわかっています。
状態遷移関数
定数 DEFAULT_ACCOUNT = { 残高: 0, ナンス: 0 }
function uteTransaction(state, tx, feeRecipient) { const fromAccount = getAccount(state, tx.from, DEFAULT_ACCOUNT) const toAccount = getAccount(state, tx.to, DEFAULT_ACCOUNT) ステップ1:ノンスを更新する fromAccount.nonce = tx.nonce ステップ2:価値の転送 fromAccount.balance -= tx.value toAccount.balance += tx.value ステップ3 料金を支払う fromAccount.balance -= tx.fee feeRecipientAccount.balance += tx.fee }
上記の関数は、BYORの状態遷移メカニズムの中心です。 これは、定義された支払いを行うのに十分なナンスと十分な残高で、トランザクションが安全に実行できることを前提としています。 この前提のため、この関数内にはエラー処理や検証の手順はありません。 代わりに、これらの手順は、関数が呼び出される前に実行されます。 各アカウントの状態はマップに格納されます。 このマッピングにアカウントがまだ存在しない場合は、コード リストの上部に表示される既定値に設定されます。 使用される3つのアカウントのうち、ナンスが更新され、残高が割り当てられます。
トランザクション署名
L1 イベント取得
関数 getNewStates() { const lastBatchBlock = getLastBatchBlock() const events = getLogs(lastBatchBlock) const calldata = getCalldata(events) const timestamp = getTimestamps(events) const posters = getTransactionPosters(events) updateLastFetchedBlock(lastBatchBlock) zipを返す(ポスター、タイムスタンプ、コールデータ) }
新しいイベントを取得するには、入力コントラクトから最後にフェッチされたブロックからすべての BatchAddended イベントを取得します。 取得するイベントの最大数は、最新のチャンクまたは最後にフェッチされたチャンクにバッチサイズ制限を加えたものです。 すべてのイベントを取得した後、各トランザクションから calldata、タイムスタンプ、およびパブリッシャー アドレスを抽出します。 フェッチする最後のブロックを、フェッチする最後のブロックに更新します。 抽出された calldata、タイムスタンプ、およびパブリッシャーは一緒にパッケージ化され、さらに処理するために関数から返されます。
メモリプールとそのコストソート
function popNHighestFee(txPool, n) { txPool.sort((a, b) => b.fee - a.fee)) return txPool.splice(0, n) }
mempool は、署名されたトランザクションの配列を管理するオブジェクトです。 最も興味深い側面は、トランザクションが L1 に転記される順序を決定する方法です。 上記のコードに示されているように、トランザクションは手数料に従ってソートされます。 これにより、システム内の手数料価格の中央値は、チェーン上の活動に基づいて変動します。
高い手数料を指定した場合でも、現在の状態に追加する必要がある場合は、トランザクションが有効な状態を生成する必要があります。 したがって、手数料が高いという理由だけで無効な取引を送信することはできません。
BYORは本当にイーサリアムをスケーリングしますか?
楽観主義とZKロールアップは、公開された状態ルートが状態遷移関数とそれらがコミットするデータと一致していることを証明するシステムを構築しましたが、ソブリンロールアップはそうではありません。 したがって、このタイプのロールアップでイーサリアムをスケーリングできないことは、最初は直感に反するように思えるかもしれません。 ただし、他の種類のロールアップでは L1 のみを使用して、公開された状態ルートが正しいことを証明できることがわかれば、これは合理的になります。 ソブリンロールアップのデータが正しいかどうかを区別するには、L1ノードと追加のソフトウェアを実行してL2ノードを形式化し、状態遷移関数を実行する必要があるため、計算負荷が増加します。
今後の展望
このプロジェクトの構築は私たちにとって素晴らしい学習経験であり、私たちの努力も貴重であることを願っています。 将来的にはBYORに戻り、不正防止システムを追加したいと考えています。 これは真に楽観的なロールアップになり、私たちが毎日使用するシステムの内部動作の教訓になります。