Cosmos IBC がチェーン間でどのように異質であるか

1 はじめに

Datachain チームは、IBC (Inter-Blockchain Communication) - "YUI" を通じて異種クロスチェーン モジュールとフレームワークの開発に取り組んでいます。

関連するコードベースは次のとおりです。

2. IBC の概要

IBC は、異なる台帳間の相互運用性プロトコルであり、当初は Cosmos のコア モジュールであり、複数のテンダーミント ベースまたは非テンダーミント ベースの台帳が相互に通信できるようにしていました。
理論的には、どのチェーンも IBC を介して相互に通信できます。
IBC はインターチェーン標準 (ICS) で標準化されています。

3. IBC アーキテクチャ設計

IBC プロトコルは階層化された設計を採用しており、主に 2 つの層に分かれています。

  • IBC/TAO: 基礎となるトランスポート、認証、および順序付けレイヤー。
  • IBC/APP: TAO に基づく上位アプリケーション層。

IBC プロトコルを実装する作業のほとんどは、TAO 層に集中しています。ターゲット台帳の TAO 層が実装されると、TAO 層の上にさまざまな APP 層プロトコルを実装するのは簡単です。
ここに画像の説明を挿入

3.1 IBC/TAO層(コントラクト)

IBC/TAO 層の主な機能は、2 つのチェーン間で信頼性が高く、順序付けられ、認証された方法でパケットを中継することです。

  • 信頼できる: ソース チェーンは 1 つのパケットのみを送信し、ターゲット チェーンはそれを 1 回だけ受信することを意味し、この 2 つは第三者を信頼する必要はありません。実際、チェーンは互いに通信しません。したがって、パケットを別のパケットに中継するには「リレイヤー」が必要ですが、リレイヤーはパーミッションレスであり、誰でもリレイヤーを実行できます。
  • 順序付き: ターゲット リンクがパケットを受信する順序が、ソース リンクがパケットを送信する順序と同じであることを意味します。
  • 認証済み: IBC リレー パケットが「チャネル」抽象化を採用し、チャネルの各端が特定のスマート コントラクトに特別に割り当てられることを意味します。したがって、ターゲット チェーンがチャネルを介してパケットを受信した場合は、ソース チェーン エンドでチャネルに割り当てられた特定のスマート コントラクトがパケットを送信したことを意味します。他のスマート コントラクトは、このチャネルを使用してパケットを送信できません。

IBC/TAO は、IBC を介して相互に接続された 2 つのブロックチェーン上で動作するスマート コントラクトとして実装され、これらのスマート コントラクトは「IBC/TAO モジュール」と呼ばれます。スマート コントラクト (IBC/TAO モジュール) には、次の要素が含まれています。

  • オンチェーン ライト クライアント: IBC/TAO の基盤として、第三者を信頼することなく、相手のチェーンに特定の状態が存在することを検証できます。
  • 接続の抽象化
  • チャネルの抽象化

オンチェーン ライト クライアントに基づいて、接続の抽象化とチャネルの抽象化が定義され、2 つのチェーンのスマート コントラクトを接続し、それらの間でパケットを中継するために使用されます。

3.1.1 IBC/TAO コントラクトにおけるオンチェーン ライト クライアント

IBC/TAO コントラクトにおけるオンチェーン ライト クライアントの構文とセマンティクスは、ICS-2 標準に記載されています。

「反対側のチェーン」に新しいブロック ヘッダーがある場合、中継者はブロック ヘッダーを照会し、ブロック ヘッダーを「ローカル チェーン」の IBC/TAO コントラクトに送信します。次に、IBC/TAO コントラクトは「逆チェーン」のライト クライアント プロトコルを実行して、契約が有効かどうかを確認し、有効な場合は、その ClientState を更新して「チェーン」の状態を反映します。

ClientState が「対戦相手チェーン」の最新のブロック ヘッダーに更新された後、IBC/TAO コントラクトは「対戦相手チェーン」に提示された状態があるかどうかを確認できます。
たとえば、「逆チェーン」がマークル ツリーを使用してその世界の状態を保存し、各ブロック ヘッダーにマークル ツリーのルート ハッシュを含める場合 (イーサリアムと同様)、マークル プルーフを使用して、その状態を証明できます。ツリー内のスマート コントラクト。
ここに画像の説明を挿入

3.1.2 IBC/TAO コントラクトにおける接続の抽象化

IBC/TAO コントラクトにおける接続のセマンティック構文については、ICS-3を参照してください。

IBC コンテキストの「接続」は、異なるチェーン上の2 つのClientStatesで構成される接続されたペアとして表されます。
「チャネル」リレー パケットの使用を開始する前に、両方のチェーンの IBC/TAO は、 と通信するClientStateを決定して検証する必要があります。この目的のために、接続の抽象化が使用されます。チェーン間の接続確立メカニズムは、TCP の 3 ウェイ ハンドシェイクに似ています。
ここに画像の説明を挿入
接続ハンドシェーク プロセスの状態変化は次のとおりです。 [すべての操作は、リレーによって開始されたトランザクションによってトリガーされます]

  • 1) connOpenInit: INIT ステータスの新しい接続が作成され、開始チェーンに格納されます。
  • 2) connOpenTry: カウンターパーティ チェーンが開始チェーンの接続が INIT ステータスであることを確認すると、それ自体のチェーンに TRYOPEN ステータスの新しい接続を作成して保存します。
  • 3) connOpenAck: 開始チェーンが、反対側のチェーンの接続が TRYOPEN ステータスであることを確認すると、自身のチェーンの接続ステータスを INIT から OPEN に更新します。
  • 4) connOpenConfirm: 開始チェーンの接続ステータスが INIT から OPEN に更新されたことをピア チェーンが確認すると、ピア チェーンは自身のチェーンの接続ステータスを TRYOPEN から OPEN に更新します。

ここに画像の説明を挿入

3.1.3 IBC/TAO コントラクトにおけるチャネルの抽象化

IBC/TAO コントラクトのチャネル セマンティック構文については、ICS-4を参照してください。

IBC のチャネル抽象化は、異なるチェーン上の 2 つのスマート コントラクトの接続ペアを表すために使用されます。
チャネルによって確立されるハンドシェイク メカニズムは、接続のハンドシェイク メカニズムと似ています。確立されたチャネルは、チェーン間でパケットをリレーするために使用できます。
パケット リレー プロセス自体も、TCP と同様の 3 ウェイ ハンドシェイク メカニズムを使用します。
ここに画像の説明を挿入
ここに画像の説明を挿入

チャネル ハンドシェイク プロセスの状態変化は次のとおりです。

  • 1) chanOpenInit: INIT ステータスの新しいチャネルが作成され、開始チェーンに格納されます。
  • 2) chanOpenTry: カウンターパーティ チェーンが、開始チェーンのチャネルが INIT ステータスであることを確認すると、新しいチャネルを作成し、独自のチェーンに TRYOPEN ステータスで保存します。
  • 3) chanOpenAck: 開始側のチェーンが、反対側のチェーンのチャネルが TRYOPEN ステータスであることを確認すると、自身のチェーンのチャネルのステータスを INIT から OPEN に更新します。
  • 4) chanOpenConfirm: カウンターパーティ チェーンが、開始チェーンのチャネル ステータスが INIT から OPEN に更新されたことを確認すると、自身のチェーンのチャネル ステータスを TRYOPEN から OPEN に更新します。
    ここに画像の説明を挿入

3.1.4 パケット中継

チャネルが確立されると、2 つのスマート コントラクトはパケットを送受信できます (パケットの内容は任意のバイト シーケンスです)。

  • 1) sendPacket: シーケンス番号が N= nextSequenceNumberである新しいパケットをソース チェーンに作成して保存します。次に、nextSequenceNumber が1 増加します。[チェーン外のエンティティによって直接トリガーされるのではなく、App コントラクトによってトリガーされます。
  • 2) recvPacket: ターゲット チェーンは、パケットがソース チェーンで実際に送信 (作成) されたことを確認すると、シーケンス番号 N の新しいパケットを作成し、独自のチェーンに格納します。[中継者によるトリガー。
  • 3) 確認パケット: シーケンス番号が N のパケットを削除します。[中継者によるトリガー。

ここに画像の説明を挿入

3.2 IBC/APP層(コントラクト)

単一のシンプルなパケット リレー メカニズム (IBC/TAO) が、あらゆるクロスチェーン プロトコルをサポートします。IBC/TAO の上に構築されたアプリケーション プロトコルは、まとめて IBC/APP と呼ばれます。
ここに画像の説明を挿入
クロスチェーン トークン転送の例:
ICS-20は、クロスチェーン トークン転送をサポートする IBC/APP プロトコルの例です。IBC/APP の実装者は、チェーン全体の相互運用メカニズムを設計または実装する必要はなく、sendPacketおよびrecvPacket関連acknowledgePacketするプラグインを実装するだけで済みます。
ICS-20 を介した chainA から chainB へのトークン転送のプロセスは次のとおりです。

  • 1) chainA のアトムは、次の操作を実行します。
    • ICS-20 モジュールでトークンをロックします。
    • 次に、sendPacketロックされたトークンの量と額面を指定するパケットの操作を実行します。
  • 2) chainB のアトムは、次の操作を実行します。
    • recvPacketパケットの操作を実行します。
    • その後、ICS-20 モジュールのロックされたトークンに相当するバウチャー トークンを作成します。
  • 3) chainA で正常に実行されますacknowledgePacket

ここに画像の説明を挿入
chainB から chainA にトークンを転送するプロセスは次のとおりです。

  • 1) chainB のアトムは、次の操作を実行します。
    • ICS-20モジュールでバウチャートークンを焼きます。
    • 次に、sendPacketロックされたトークンの量と額面を指定するパケットの操作を実行します。
  • 2) chainA のアトムは、次の操作を実行します。
    • recvPacketパケットの操作を実行します。
    • その後、ICS-20 モジュールで焼かれたバウチャーに相当するバウチャー トークンのロックを解除します。
  • 3) chainB で正常に実行されますacknowledgePacket

ここに画像の説明を挿入
IBC/TAO が実装されている場合、上記の ICS-20 モジュールには次の機能のみが必要です。

  • トークンのロックとロック解除
  • バウチャーの発行と燃焼

4. yui-ibc-solidity IBC コントラクトの分析

https://github.com/hyperledger-labs/yui-ibc-solidityの IBC コントラクトの分析[Solidity EVM と互換性のある Ethereum およびその他のチェーンをサポートできます]:

  • 1) IBC/TAO 層のコントラクトには以下が含まれます。

    • IBCIdentifier: ライブラリ用。主に keccak256 操作であり、クライアント、コンセンサス、接続、チャネル、パケット、packetAcknowledgment に関連するコミットメント キー、clientState に関連する Commitment Slot、consensusState、connection、channel、packet、packetAcknowledgment、および part および part に関連する機能パスを生成するために使用されます。チャネル。
    • IBCHeight: ライブラリ用。主に Height.Data.revision_number に対するさまざまな操作用です。
    library Height {
      //struct definition
      struct Data {
        uint64 revision_number;
        uint64 revision_height;
      }
    }
    
    • IBCMsgs: ライブラリ用。ICS-026で、クライアント、接続ハンドシェイク、チャネル ハンドシェイク、チャネル クロージング、パケット リレーなどの関連するメッセージ構造を定義します。
    • IClient: インターフェイス用。getTimestampAtHeight、getLatestHeight、checkHeaderAndUpdateState、verifyClientState、verifyClientConsensusState、verifyConnectionState、verifyChannelState、verifyPacketCommitment、およびverifyPacketAcknowledgmentなどのインターフェース関数が定義されています。
    • MockClient: コントラクト用。IClient インターフェイスのモック実装では、検証は実際には行われず、シナリオのテストに使用されます。
    • IBFT2Client: コントラクトです。Hyperledger Besu の IBFT 2.0 (Proof-of-Authority (PoA) Byzantine-Fault-Tolerant (BFT) ) コンセンサス アルゴリズムを検証するためのライト クライアントです。[Hyperledger Besu は、動的バリデータ セットをサポートする Ethereum コンソーシアム チェーン ソリューションです。ブロックには、コンセンサス結果を格納するための追加のデータ フィールドがあります。[32 bytes Vanity, List<Validators>, Votes, Round number, Commit Seals]ここで、2 番目の要素は、このブロックのバリデータ セット内の各アドレスのリストです。5 番目の要素は、バリデータ セットによるこのブロックへのコミット シールです。ブロック ノードは、コミット シールを検証して、アルゴリズム 1に従ってブロックを検証します
    • IBCHost: コントラクト用。デプロイが完了したら、所有者は後でそれを呼び出す必要がありsetIBCModule、パラメーターは IBCHandler コントラクト アドレスです。使得generateClientIdentifier、generateConnectionIdentifier、generateChannelIdentifier、setClientImpl、setClientType、setClientState、setConsensusState、setProcessedTime、setProcessedHeight、setConnection、setChannel、setNextSequenceSend、setNextSequenceRecv、setNextSequenceAck、setPacketCommitment、deletePacketCommitment、setPacketAcknowledgementCommitment、setPacketReceipt、setExpectedTimePerBlock、claimCapability、authenticateCapability等操作仅能由IBCHandler合约调用.
    • IBCClient: ライブラリ用。実装されたクライアント関連の関数。作成や更新などの操作は、IBCHandler コントラクトによってのみ呼び出すことができます。
    • IBCConnection: ライブラリ用。IBCHandler コントラクトによってのみ呼び出される接続関連の関数を実装しました。そして、さまざまな状態、コミットメント、ACK 検証機能。
    • IBCChannel: ライブラリ用。IBCHandler コントラクトによってのみ呼び出される sendPacket および recvPacket、writeAcknowledgment およびacknowledgePacket 関数と同様に、チャネル関連の関数を実装しました。
    • IBCHandler: コントラクト用。デプロイ時には、IBCHost コントラクト アドレスを指定する必要があります。registerClient (モックや ibft2.0 クライアントなどの登録済みのクライアント タイプで、所有者が呼び出す必要があります) と、IBCMsgs の各メッセージに対する応答関数の実装を提供します。
    • IBCModule: インターフェイスの場合、onChanOpenInit、onChanOpenTry、onChanOpenAck、onChanOpenConfirm、onChanCloseInit、onChanCloseConfirm、onRecvPacket、および onAcknowledgmentPacket インターフェイス関数を抽象化します。
  • 2) IBC/APP 層のコントラクトには次のものがあります。 [主に 3 つのコントラクトがあります: ICS20Bank、ICS20TransferBank、SimpleToken コントラクト。

    • ICS20Bank: ICS20Bank コントラクトは APP コントラクトで、IICS20Bank で定義された抽象インターフェイスを実装するだけでなく、入金および引き出し機能も実装します。
    • ICS20Transfer: IBCHost および IBCHandler コントラクト アドレスは、ICS20TransferBank のサブ コントラクトである構築中に指定する必要があります。IBCModule で定義された抽象インターフェイスを実装するだけでなく、IICS20Transfer で sendTransfer 関数も実装します。
    • ICS20TransferBank: IBCHost、IBCHandler、および ICS20Bank コントラクト アドレスは、構築中に指定する必要があります。これは、App コントラクトの ICS20TransferBank コントラクトです。
    • IICS20Bank: インターフェイスについては、transferFrom、mint、burn インターフェイス関数を抽象化します。
    • IICS20Transfer: sendTransfer インターフェース機能を抽象化したインターフェースです。
    • SimpleToken: APP コントラクトのトークン コントラクトです。ここではERC20コントラクトとして定義されています。

sendPacket の内容は次のとおりです。

library Packet {
  //struct definition
  struct Data {
    uint64 sequence;
    string source_port;
    string source_channel;
    string destination_port;
    string destination_channel;
    bytes data;
    Height.Data timeout_height;
    uint64 timeout_timestamp;
  }
  .......
}

4.1 yui-ibc-solidity IBC/TAO 層コントラクト

IBC/TAO レイヤー コントラクト IBCHost、IBCHandler、IBCClient、IBCConnection、IBCChannel、IBFT2Client、MockClient、IClient、IBCHeight、IBCModule、IBCMsgs、IBCIdentifier の関係は次のとおりです。 IBC/TAO レイヤーには、主に IBCHost コントラクト、IBCHandler の 3 つのコントラクトがあります。 contract
ここに画像の説明を挿入
ここに画像の説明を挿入
、IBFT2Client コントラクト (および/または) MockClient テスト コントラクト)。

作る:

//portID
const PortTransfer = "transfer"
//light client类型
const BesuIBFT2ClientType = "hyperledger-besu-ibft2"
const MockClientType = "mock-client"

IBC/TAO レイヤーの展開と呼び出しの基本的なプロセスは次のとおりです。

  • 1) IBFT2Client コントラクトをデプロイします。
  • 2) IBCHost コントラクトをデプロイします。
  • 3) IBCHandler コントラクトを展開する 展開するときは、IBCHost コントラクト アドレスを指定する必要があります。
  • 4)IBCHost の所有者は、それ自体のsetIBCModule関数を使用して、ibcModule を IBCHandler に設定し、generateClientIdentifier、generateConnectionIdentifier、generateChannelIdentifier、setClientImpl、setClientType、setClientState、setConsensusState、setProcessedTime、setProcessedHeight、setConnection、setChannel、setNextSequenceSend、setNextSequenceRecv、setNextSequenceAck、setPacket を取得します。 、deletePacketCommitment、setPacketAcknowledgmentCommitment、setPacketReceipt、setExpectedTimePerBlock、claimCapability、authenticateCapability、およびその他の操作は、IBCHandler コントラクトによってのみ呼び出すことができます。
  • 5) IBCHandler コントラクト所有者は独自のbindPort関数を呼び出します。パラメーターは portID と DAPP ICS20TransferBank コントラクト アドレスです。機能は次のとおりです。IBCHost コントラクトを呼び出し、capabilities[portID]ICS20TransferBanke コントラクト アドレスを配列に挿入します。[同じポート ID の機能配列は、複数の DAPP アプリケーション コントラクト アドレスの構成をサポートします。しかし、getModuleOwner では、デフォルトで最初の DAPP アドレスのみが返され、実際にはポート ID が DAPP コントラクトに対応します。】【チャネル ハンドシェイク プロトコルでは、capabilities[portID]対応する DAPP アプリケーション コントラクトの onChanOpenInit、channelOpenTry、channelOpenAck、onChanOpenConfirm、onChanCloseInit、onChanCloseConfirm およびその他の関数が呼び出されます。
  • 6) IBCHandler コントラクト オーナーは独自のregisterClient関数を呼び出します。パラメータはライト クライアント タイプ (BesuIBFT2ClientType など) と対応するライト クライアント コントラクト アドレス (IBFT2Client コントラクト アドレスなど) です。関数は次のとおりです。IBCHost コントラクトを呼び出しますclientRegistry[clientType]=clientAddress【各クライアント種別の登録は1回のみ】

4.2 yui-ibc-solidity IBC/APP 層のコントラクト

IBC/APP レイヤ コントラクト ICS20Bank、ICS20Transfer、ICS20TransferBank、IICS20Bank、IICS20Transfer、SimpleToken 間の関係は次のとおりです。 [下図の IBCAppModule は ICS20TransferBank コントラクトに対応します。
ここに画像の説明を挿入
IBC/APP 層のコントラクトには以下が含まれます。 【主に 3 つのコントラクトがあります: ICS20Bank、ICS20TransferBank、および SimpleToken コントラクト。

  • ICS20Bank: ICS20Bank コントラクトは APP コントラクトで、IICS20Bank で定義された抽象インターフェイスを実装するだけでなく、入金および引き出し機能も実装します。
  • ICS20Transfer: IBCHost および IBCHandler コントラクト アドレスは、ICS20TransferBank のサブ コントラクトである構築中に指定する必要があります。IBCModule で定義された抽象インターフェイスを実装するだけでなく、IICS20Transfer で sendTransfer 関数も実装します。
  • ICS20TransferBank: IBCHost、IBCHandler、および ICS20Bank コントラクト アドレスは、構築中に指定する必要があります。これは、App コントラクトの ICS20TransferBank コントラクトです。
  • IICS20Bank: インターフェイスについては、transferFrom、mint、burn インターフェイス関数を抽象化します。
  • IICS20Transfer: sendTransfer インターフェース機能を抽象化したインターフェースです。
  • SimpleToken: APP コントラクトのトークン コントラクトです。ここではERC20コントラクトとして定義されています。
const (
	DefaultChannelVersion        = "ics20-1"
	BlockTime             uint64 = 1000 * 1000 * 1000 // 1[sec]
	DefaultDelayPeriod    uint64 = 3 * BlockTime
	DefaultPrefix                = "ibc"
	TransferPort                 = "transfer"

	RelayerKeyIndex uint32 = 0
)

yui-ibc-solidity/tests/e2e/chains_test.goの場合SetupTest:

func (suite *ChainTestSuite) SetupTest() {
    
    
	//信任的A链FullNode,对应的RPC接口为http://127.0.0.1:8645
	chainClientA, err := client.NewBesuClient("http://127.0.0.1:8645", clienttypes.BesuIBFT2Client)
	suite.Require().NoError(err)
	//信任的B链FullNode,对应的RPC接口为http://127.0.0.1:8745
	chainClientB, err := client.NewBesuClient("http://127.0.0.1:8745", clienttypes.BesuIBFT2Client)
	suite.Require().NoError(err)
	// A链和B链之间的ibcID
	ibcID := uint64(time.Now().UnixNano())
	// 2018/3018为全局的chainID。chainA和chianB分别维护各链信任的FullNode信息,以及各自所部署的TAO和APP合约信息。
	suite.chainA = ibctesting.NewChain(suite.T(), 2018, *chainClientA, testchain0.Contract, mnemonicPhrase, ibcID)
	suite.chainB = ibctesting.NewChain(suite.T(), 3018, *chainClientB, testchain1.Contract, mnemonicPhrase, ibcID)
	// 会分别对链A和链B进行UpdateHeader操作
	suite.coordinator = ibctesting.NewCoordinator(suite.T(), suite.chainA, suite.chainB)
}

UpdateHeaderプロセスは次のとおりです。

  • 1) 30 秒のタイミング - 「GetIBFT2ContractState: 信頼された FullNode の現在のブロックを読み取り、get_ethProofstate.ethProof 構造に格納されている accountProof と storageProof を含む、ブロックの対応するプルーフを呼び出して読み取ります。
type ETHProof struct {
	AccountProofRLP []byte
	StorageProofRLP [][]byte
}
  • 2) 現在のブロック ヘッダー情報を解析し、state.ParsedHeader に保存します; 現在のブロック ヘッダーに 2/3 を超えるバリデータ署名があることを確認し、各バリデータのシール情報をつなぎ合わせて、state.CommitSeals に保存します。
  • 3) 現在のブロックの高さが、チェーンのライト クライアント コントラクトに記録されているヘッダーの高さよりも大きい場合は、chain.LastContractState を現在の状態に更新します。(初回起動時、ライトクライアントのコントラクトヘッダー情報が空の場合は、直接 chain.LastContractState を現在の状態に更新する) [このとき、ライトクライアントのコントラクト更新は呼び出されない。

UpdateClientプロセスは次のとおりです。

  • 1) ConstructIBFT2MsgUpdateClient: IBCHost コントラクトの関数を呼び出しgetClientState、チェーンに格納されている clientState の高さを読み取りtrustedHeight、新しいヘッダー情報を相手のチェーンにパックします。
func (chain *Chain) ConstructIBFT2MsgUpdateClient(counterparty *Chain, clientID string) ibchandler.IBCMsgsMsgUpdateClient {
    
    
	trustedHeight := chain.GetIBFT2ClientState(clientID).LatestHeight
	cs := counterparty.LastContractState.(client.IBFT2ContractState)
	var header = ibft2clienttypes.Header{
    
    
		BesuHeaderRlp:     cs.SealingHeaderRLP(),
		Seals:             cs.CommitSeals,
		TrustedHeight:     trustedHeight,
		AccountStateProof: cs.ETHProof().AccountProofRLP,
	}
	bz, err := MarshalWithAny(&header)
	if err != nil {
    
    
		panic(err)
	}
	return ibchandler.IBCMsgsMsgUpdateClient{
    
    
		ClientId: clientID,
		Header:   bz,
	}
}
  • 2) updateClientIBCHandler コントラクトの関数を呼び出して、対応する consensusState とルートを更新します。[ checkHeaderAndUpdateStateStorageProof と stateProof は個別に検証されます。
function updateClient(IBCHost host, IBCMsgs.MsgUpdateClient calldata msg_) external {
        host.onlyIBCModule();
        bytes memory clientStateBytes;
        bytes memory consensusStateBytes;
        Height.Data memory height;
        bool found;
    
        (clientStateBytes, found) = host.getClientState(msg_.clientId);
        require(found, "clientState not found");

        (clientStateBytes, consensusStateBytes, height) = getClient(host, msg_.clientId).checkHeaderAndUpdateState(host, msg_.clientId, clientStateBytes, msg_.header);
    
         persist states 
        host.setClientState(msg_.clientId, clientStateBytes);
        host.setConsensusState(msg_.clientId, height, consensusStateBytes);
        host.setProcessedTime(msg_.clientId, height, block.timestamp);
        host.setProcessedHeight(msg_.clientId, height, block.number);
    }

TestChannelプロセスは次のとおりです。

  • 1) SetupClients: 2 つのチェーンに他のチェーンのクライアントを作成します: [チェーンに割り当てられた clientId が返されます: clientA と clientB]
    • 1.1) ConstructIBFT2MsgCreateClient: 相手側のチェーンの chainID、IBCHost コントラクト アドレス、およびUpdateHeaderプロセス中に chain.LastContractState に記録されたヘッダー、ルート、およびバリデーター情報を記録します。
    func (chain *Chain) ConstructIBFT2MsgCreateClient(counterparty *Chain) ibchandler.IBCMsgsMsgCreateClient {
          
          
    	clientState := ibft2clienttypes.ClientState{
          
          
    		ChainId:         counterparty.ChainIDString(),
    		IbcStoreAddress: counterparty.ContractConfig.GetIBCHostAddress().Bytes(),
    		LatestHeight:    ibcclient.NewHeightFromBN(counterparty.LastHeader().Number),
    	}
    	consensusState := ibft2clienttypes.ConsensusState{
          
          
    		Timestamp:  counterparty.LastHeader().Time,
    		Root:       counterparty.LastHeader().Root.Bytes(),
    		Validators: counterparty.LastContractState.(client.IBFT2ContractState).Validators(),
    	}
    	clientStateBytes, err := MarshalWithAny(&clientState)
    	if err != nil {
          
          
    		panic(err)
    	}
    	consensusStateBytes, err := MarshalWithAny(&consensusState)
    	if err != nil {
          
          
    		panic(err)
    	}
    	return ibchandler.IBCMsgsMsgCreateClient{
          
          
    		ClientType:          ibcclient.BesuIBFT2Client,
    		Height:              clientState.LatestHeight.ToCallData(),
    		ClientStateBytes:    clientStateBytes,
    		ConsensusStateBytes: consensusStateBytes,
    	}
    }
    
    • 1.2) このチェーンの IBCHandler コントラクトの関数を呼び出すと、createClientIBCHost コントラクトに対応する clientId が生成され、clientId に基づいて、対応する clientType、clientState、ConsensusState、block.timestamp、block.number およびその他の情報が IBCHost に格納されます。契約。
    function createClient(IBCHost host, IBCMsgs.MsgCreateClient calldata msg_) external {
        host.onlyIBCModule();
        (, bool found) = getClientByType(host, msg_.clientType);
        require(found, "unregistered client type");
    
        string memory clientId = host.generateClientIdentifier(msg_.clientType);
        host.setClientType(clientId, msg_.clientType);
        host.setClientState(clientId, msg_.clientStateBytes);
        host.setConsensusState(clientId, msg_.height, msg_.consensusStateBytes);
        host.setProcessedTime(clientId, msg_.height, block.timestamp);
        host.setProcessedHeight(clientId, msg_.height, block.number);
    }
    
  • 2) CreateConnection: チェーン A とチェーン B の間に、チェーン A に clientB があり、チェーン B に clientA があり、接続ハンドシェイク プロトコルに従います。
    • 2.1) チェーン A の IBCHandler コントラクトの関数を呼び出すとConnectionOpenInit、対応する connectionId が IBCHost コントラクトに生成され、対応する接続​​情報が IBCHost コントラクトに格納されます。[GeneratedClientIdentifier イベントをリッスンして、作成された connectionId を取得]
    function connectionOpenInit(IBCHost host, IBCMsgs.MsgConnectionOpenInit memory msg_) public returns (string memory) {
        host.onlyIBCModule();
        ConnectionEnd.Data memory connection = ConnectionEnd.Data({
            client_id: msg_.clientId,
            versions: getVersions(),
            state: ConnectionEnd.State.STATE_INIT,
            delay_period: msg_.delayPeriod,
            counterparty: msg_.counterparty
        });
        string memory connectionId = host.generateConnectionIdentifier();
        host.setConnection(connectionId, connection);
        return connectionId;
    }
    
    UpdateHeader: A チェーンで connectionId が正常に作成されるようにするには、新しいブロックを取得し、プログラムのローカル A チェーンの状態を更新します。
    UpdateClient: チェーン A の状態をチェーン B のライト クライアント コントラクトに更新します。
    • 2.2) チェーン A の ProofConnection と ProofClient をクエリし、それらをパラメーターとしてパッケージ化し、チェーン B の IBCHandler コントラクトの関数を呼び出し、connectionOpenTry対応する connectionState と clientState の検証を行い、対応する connectionId を IBCHost コントラクトに生成し、対応する IBCHost コントラクトを保存します。 IBCHost コントラクト接続情報。[GeneratedClientIdentifier イベントをリッスンして、作成された connectionId を取得] [verifyConnectionState和verifyClientState詳細を確認できます]
      UpdateHeader: connectionId が B チェーンで正常に作成されたことを確認し、新しいブロックを取得して、プログラムのローカル B チェーンのステータスを更新します。
      UpdateClient: チェーン B の状態をチェーン A のライト クライアント コントラクトに更新します。

    • 2.3) チェーン B のproofConnection とproofClient をクエリし、それらをパラメーターとしてパッケージ化し、チェーン A の IBCHandler コントラクトの関数を呼び出し、connectionOpenAck対応する connectionState と clientState を検証し、IBCHost コントラクトの対応する接続​​情報を更新します。[verifyConnectionState和verifyClientState詳しく見ることができます]
      UpdateHeader: A チェーンでのトランザクションの成功を確実にするために、新しいブロックを取得し、プログラムのローカル B チェーンの状態を更新します。
      UpdateClient: チェーン A の状態をチェーン B のライト クライアント コントラクトに更新します。

    • 2.4) チェーン A のproofConnection をクエリし、それをパラメーターとしてパックし、チェーン B の IBCHandler コントラクトの関数を呼び出し、connectionOpenConfirm対応する connectionState 検証を行い、IBCHost コントラクトの対応する接続​​情報を更新します。[verifyConnectionState詳しく見ることができます]
      UpdateHeader: B チェーンでのトランザクションの成功を確実にするために、新しいブロックを取得し、プログラムのローカル A チェーンのステータスを更新します。
      UpdateClient: チェーン B の状態をチェーン A のライト クライアント コントラクトに更新します。

  • 3) CreateChannel: 全体的なプロセスは CreateConnection と似ていますが、コントラクト内のチャネル関連の関数が呼び出される点が異なります。[接続ハンドシェイクとの違い: チャネル ハンドシェイク プロトコルでは、capabilities[portID]対応する DAPP アプリケーション コントラクトの onChanOpenInit、channelOpenTry、channelOpenAck、onChanOpenConfirm、onChanCloseInit、onChanCloseConfirm およびその他の関数が呼び出されます。現在の機能は、チャネルの escrowAddress を ICS20TransferBank コントラクト アドレスに設定することです。

yui-ibc-solidity/tests/e2e/chains_test.go例では:

  • 1) SimpleToken コントラクトを chainA にデプロイすると、デフォルトですべてのトークンがデプロイヤー deployerA に割り当てられます。
  • 2) ICS20Bank コントラクトを展開します。
  • 3) ICS20TransferBank コントラクトをデプロイするには、デプロイ時に IBCHost、IBCHandler、および ICS20Bank コントラクト アドレスを指定する必要があります。
  • 4) ICS20Bank コントラクト デプロイヤーはsetOperator関数を呼び出し、ICS20TransferBank コントラクト アドレスを OPERATOR ロールとして設定します。
  • 5) IBCBank コントラクト アドレスが deployerA に代わって 100 トークンを使用することを承認します。
  • 6) SimpleToken コントラクト デプロイヤー deployerA は、depositICS20Bank コントラクトの関数を呼び出し、100 トークンを ICS20Bank コントラクト アドレスに入金します. ICS20Banke コントラクトでは、aliceA の残高が維持されます: _balances[id][account] += amount;.
  • 7) aliceA はsendTransferICS20TransferBank コントラクトの関数を呼び出し、chanA.PortID、chanA.ID (sourcePort および sourceChannel) を介して chainA の 100 個のトークンを chainB の bobB に転送します。timeoutHeight を に設定します当前区块高度+1000
    sendTransfer
    • 7.1) 着信 denom パラメータが単純な SimpleToken コントラクト アドレスであるか、または sourcePort+sourceChannel のプレフィックスが付いているかを判断します。単純なコントラクト アドレスであれば、100 個のトークンを sourceChannel の escrowAddress (つまり、ICS20TransferBank コントラクト アドレス) に直接転送し、実際には ICS20Bank コントラクトの状態を維持します; プレフィックスがあれば、トークン_balances[id][account]が転送される前に受け取った他のチェーンの 100 トークンは、ICS20Bank コントラクトから直接差し引かれます_balances[id][account]
    • 7.2) カプセル化FungibleTokenPacketData、およびさらにカプセル化するとPacket.DatasendPacketIBCHandler コントラクトの関数が呼び出されます。以前の DAPP コントラクトのアドレスを確認しbindPort、チャネルと接続のアクセス許可が正常であることを確認し、IBCHost の sequenceSend 番号を更新し、IBCHost コントラクトのコミット マップを [パケットが処理された場合、削除されます] に設定しますcommitments[IBCIdentifier.packetCommitmentKey(portId, channelId, sequence)] = makePacketCommitment(packet);。リプレイ攻撃を防ぐために]。最終的に sendPacket イベントが解放されます。
    function _sendPacket(FungibleTokenPacketData.Data memory data, string memory sourcePort, string memory sourceChannel, uint64 timeoutHeight) virtual internal {
        (Channel.Data memory channel, bool found) = ibcHost.getChannel(sourcePort, sourceChannel);
        require(found, "channel not found");
        ibcHandler.sendPacket(Packet.Data({
            sequence: ibcHost.getNextSequenceSend(sourcePort, sourceChannel),
            source_port: sourcePort,
            source_channel: sourceChannel,
            destination_port: channel.counterparty.port_id,
            destination_channel: channel.counterparty.channel_id,
            data: FungibleTokenPacketData.encode(data),
            timeout_height: Height.Data({revision_number: 0, revision_height: timeoutHeight}),
            timeout_timestamp: 0
        }));
    }
    function sendPacket(IBCHost host, Packet.Data calldata packet) external {
        host.onlyIBCModule();
        Channel.Data memory channel;
        ConnectionEnd.Data memory connection;
        IClient client;
        Height.Data memory latestHeight;
        uint64 latestTimestamp;
        uint64 nextSequenceSend;
        bool found;
    
        channel = mustGetChannel(host, packet.source_port, packet.source_channel);
        require(channel.state == Channel.State.STATE_OPEN, "channel state must be OPEN");
        require(hashString(packet.destination_port) == hashString(channel.counterparty.port_id), "packet destination port doesn't match the counterparty's port");
        require(hashString(packet.destination_channel) == hashString(channel.counterparty.channel_id), "packet destination channel doesn't match the counterparty's channel");
        connection = mustGetConnection(host, channel);
        client = IBCClient.getClient(host, connection.client_id);
        (latestHeight, found) = client.getLatestHeight(host, connection.client_id);
        require(packet.timeout_height.isZero() || latestHeight.lt(packet.timeout_height), "receiving chain block height >= packet timeout height");
        (latestTimestamp, found) = client.getTimestampAtHeight(host, connection.client_id, latestHeight);
        require(found, "consensusState not found");
        require(packet.timeout_timestamp == 0 || latestTimestamp < packet.timeout_timestamp, "receiving chain block timestamp >= packet timeout timestamp");
    
        nextSequenceSend = host.getNextSequenceSend(packet.source_port, packet.source_channel);
        require(nextSequenceSend > 0, "sequenceSend not found");
        require(packet.sequence == nextSequenceSend, "packet sequence != next send sequence");
    
        nextSequenceSend++;
        host.setNextSequenceSend(packet.source_port, packet.source_channel, nextSequenceSend);
        host.setPacketCommitment(packet.source_port, packet.source_channel, packet.sequence, packet);
    
        // TODO emit an event that includes a packet
    }
    
  • 8) UpdateHeader&UpdateClient: プログラムのローカル ステータスを更新し、チェーン B のライト クライアント コントラクトのステータスを更新します。
  • 9) チェーン A の IBCHost コントラクトを呼び出し、最新の sequenceSend を読み取り、これに基づいて、IBCHandler コントラクトによってリリースされた sendPacket イベントをリッスンし、対応するシーケンス番号、sourcePort および sourceChannel に一致する sendPacket イベントを除外します。
  • 10) DelayPeriod (ここでは 1S) 待機します。次のステップへの直接操作を待機しないと、エラーが報告されます。
  • 11)eth_getProofチェーン A のパケットの格納証明を取得するために呼び出します。次に、チェーン B の IBCHandler コントラクトの関数を呼び出しますrecvPacket。これは、ICS20TransferBank コントラクトの関数を呼び出しonRecvPacket、対応する金額を受信者に発行し、同時に対応する確認を返します。次に、チャネル、接続、およびパケットが有効かどうかを確認し、packetCommitment を確認し、IBCHost コントラクトで packetReceipt を設定し、IBCHost コントラクトで PacketAcknowledgmentCommitment を設定し、WriteAcknowledgment イベントと RecvPacket イベントを解放します。
	function onRecvPacket(Packet.Data calldata packet) external virtual override returns (bytes memory acknowledgement) {
        FungibleTokenPacketData.Data memory data = FungibleTokenPacketData.decode(packet.data);
        strings.slice memory denom = data.denom.toSlice();
        strings.slice memory trimedDenom = data.denom.toSlice().beyond(
            _makeDenomPrefix(packet.source_port, packet.source_channel)
        );
        if (!denom.equals(trimedDenom)) { // receiver is source chain
            return _newAcknowledgement(
                _transferFrom(_getEscrowAddress(packet.destination_channel), data.receiver.toAddress(), trimedDenom.toString(), data.amount)
            );
        } else {
            string memory prefixedDenom = _makeDenomPrefix(packet.destination_port, packet.destination_channel).concat(denom);
            return _newAcknowledgement(
                _mint(data.receiver.toAddress(), prefixedDenom, data.amount)
            );
        }
    }
  • 12) UpdateHeader&UpdateClient: プログラムのローカル状態を更新し、チェーン B のライト クライアント コントラクト状態を更新します。
  • 13) DelayPeriod (ここでは 1S) 待機します。次のステップへの直接操作を待機しないと、エラーが報告されます。
  • 14)チェーン B のpacketAcknowledgmentCommitment に対応する storageProof をeth_getProof取得するために呼び出します。(packet.DestinationPort, packet.DestinationChannel, packet.Sequence)チェーン A の IBCHandler コントラクトの関数を呼び出しますacknowledgePacket: ICS20TransferBank コントラクトの関数が呼び出されますonAcknowledgementPacket. 確認応答に失敗情報が含まれている場合は、トークンの転送が失敗したことを意味し、トークンは送信者に返されます; 成功した場合は、何も行われません。次に、チャネルと接続を確認します。packetAcknowledgment を確認します。ORDERED チャネルの場合は、sequenceAck 番号を更新します。処理された packetCommitment を削除して、リプレイ攻撃を防ぎます。

参考文献

[1]ブロックチェーン間の相互運用性を実現する Cosmos の IBC の仕組み
[2] IBFT 2.0 ライト クライアント

付録 A - Hyperledger Besu の IBFT2.0 ライト クライアント

Hyperledger Besu は、動的バリデータ セットをサポートする Ethereum コンソーシアム チェーン ソリューションです。
Hyperledger Besu は、さまざまなコンセンサス アルゴリズムをサポートしています。その中で、IBFT 2.0 (Proof-of-Authority (PoA) Byzantine-Fault-Tolerant (BFT)) コンセンサス アルゴリズムが最も人気があります。

[32 bytes Vanity, List<Validators>, Votes, Round number, Commit Seals]IBFT2.0 コンセンサスを使用する場合、コンセンサス結果を格納するための追加のデータ フィールドがブロックに存在します:バリデータセットによるこのブロックでは、各ブロックノードはコミットシールを検証して、アルゴリズム 1に従ってブロックを検証します

付録 A.1 ライト クライアントの信頼できるソースと信頼期間

IBFT2.0 Light Client を初期化するときは、信頼できるソースが必要です。

バリデータ セットが更新されることを考慮して、信頼期間が導入されます。つまり、高さのバリデータ セットの期間を信頼できます。

  • 新しいブロックは、T現在の時間より前の期間内に生成されたブロックの検証セットによって検証される必要があります。

したがって、ライト クライアントを初期化するときは、信頼期間パラメーターを として指定する必要がありますT。 の場合T=0、信頼できるバリデータ セットは変更されず、常に信頼できることを意味します。各ブロックは、1 つのバリデータのみを増減できます。

付録 A.2 簡易クライアント検証

軽量クライアントが信頼期間と信頼できるソースからのヘッダーを初期化するとき、信頼するヘッダーと対応する信頼できるバリデーターに基づいて受信ヘッダーを検証する必要があります。
新しく提出されたブロックについては、validation function次の条件を検証する必要があります: [ B h B_hと仮定します。B時間高さhhhのブロックV h V_h時間ブロックB h B_hの場合B時間のバリデータセット,BT h BT_hB T時間ブロックB h B_hの場合B時間的タイムスタンプ,Now ( ) Now()Now ( )現在時刻、TP TPT P は信頼期間です。
現在のトラステッド ブロックの高さがnnn、信頼できないブロックの高さはn + m n+mn+mn > 0 および m > 0 n>0 および m>0n>0m>0

  • 1) 現在の時刻が最新の信頼済みブロックの信頼期間内にある: BT n < N ow ( ) < BT n + TP BT_n<Now()<BT_n+TPB Tn<( ) _ _<B Tn+T P
  • 2)B n + m B_{n+m}Bn + mV n V_nn署名の 1/3。最大ビザンチン数はf ( n ) = ( n − 1 ) / 3 なので f(n)=(n-1)/3f ( n )=( n1 ) / 3、この要件により、少なくとも 1 つの正直なバリデーターが存在することが保証されます。
  • 3)B n + m B_{n+m}Bn + mVN + M V_{N+M}N + Mの 2/3+ 署名。保証n + m n+mn+高さm の硬化ブロックが有効です。
  • 4) さらに、StorageProof も検証します。詳細についてはyui-ibc-solidity/contracts/lib/TrieProofs.solhttps://github.com/lorenzb/proveth/blob/master/onchain/ProvethVerifier.solを参照してください。

付録 A.3 光クライアントの活性分析

ライト クライアントの活性は、バリデータ セットの変換に依存します。IBFT2.0 は Dynamic ValidatorSet をサポートしており、各ブロックは 1 つのバリデータのみを増減できます。

そのため、ある程度の高さを確保する必要があり、前節でこの高さを検証できるブロックをvalidation functionIBFT2.0プロトコルで生成しています。明らかに、バリデータセットの数の増加には常に検証可能な高さが存在します。次に、バリデータ セットの数を減らすことだけを考えます。
バリデータ セットをVVとします。V、減少はΔ \DeltaΔ,有Δ ⊆ V \Delta\subseteq VDV
∣ V ∧ V − Δ ∣ ≥ ∣ V ∣ ∗ 1 / 3 |V \wedge V-\Delta|\geq |V| * 1/3VΔ∣ _V 1 / 3

この式が成り立つ場合、検証対象のブロックを検証できるバリデータ セットを持つブロックの高さが常に存在します。IBFT2.0 では、 ∣ V − Δ ∣ ≥ 1 |V-\Delta|\geq 1であるため、この式が成り立ちます。VΔ∣ _ =1 \Delta=1D=1

付録 A.4 ライト クライアントのフォーク検出

IBFT 2.0 の障害モデルに違反し、(n-1) / 3 を超える障害ノードがある場合、複数の有効な確認済みブロックが生成される可能性があります。
ライト クライアントは、このような障害や攻撃を検出できる必要があります。作業のこの部分は、将来実現される予定です。

おすすめ

転載: blog.csdn.net/mutourend/article/details/122576435