🔭

IBC(ブロックチェーン間通信)の概要

2022/07/18に公開

はじめに

前提知識

  • PoW(Proof of Work), PoS(Proof of Stake)のざっくりとした知識
  • マークル木とマークル証明についてのざっくりとした知識

この記事で扱うこと

  • IBCとは何か?
  • IBCのセキュリティ
  • IBCのざっくりとした動作原理・規格
  • ローカルチェーンとパブリックチェーンをIBCでつなげる方法

IBCとは

IBC(Inter-Blockchain Communication, ブロックチェーン間通信)とは、2つのステートマシン間で任意のデータをやり取りするための通信プロトコルです。

主にCosmos Networkでのトークン転送に利用されますが、Cosmos Networkに限らず、さまざまなブロックチェーンで(更に言うと、ブロックチェーンに限らずステートマシンであれば)IBCを利用することができます。

チェーンAとチェーンBがIBCを用いて通信を行う際、通信を仲介するリレイヤーと呼ばれるノードが必要です。リレイヤーはチェーンA(B)の情報をクエリし、チェーンB(A)にトランザクションを送るということを繰り返すことでチェーン間の通信を仲介します。特筆すべきは、リレイヤーを一切信頼する必要がないということです。リレイヤーのうち、一つでも善良なノードがあればIBCは正しく動きます。リレイヤーを行うすべてのノードに悪意があっても、最悪IBCが停止するだけです。

ブロックチェーン間の通信はリレイヤーが仲介する。

IBCのこのトラストレスな性質はどのようにして可能になっているのでしょうか?

それはチェーンA(B)が、チェーンB(A)の簡易ノード(ライトクライアント)を内部で立ち上げ、リレーされてきたチェーンB(A)のデータの正当性を検証することで可能になっています。

ライトクライアントとは

フルノードはブロックチェーンのすべてのデータを保存するため、非常に大きなリソースが必要です。例えば、Cosmos-hubというブロックチェーンでは、フルノードを動かすために現在1TB以上の保存領域が必要になります。

一方でライトクライアントは、主にブロックヘッダー(ブロックのハッシュ値)のみを保存するので非常に軽量です。あるトランザクションがブロックに含まれるかどうかは、マークル証明[1]を利用してブロックヘッダーだけで検証することができるので、ライトクライアントでも証明さえ提示されればトランザクションがチェーン内に取り込まれているかどうかが分かります。

ライトクライアントは、ある信頼できるブロックヘッダーとブロック高[2]を起点として、それ以降の

  • ブロックヘッダー
  • ブロック高
  • バリデータ(PoSコンセンサスに参加しているノード)の公開鍵とそのVoting power(ステークした量に依存した投票力)のペア(バリデータセット)。
  • Commit (バリデータのブロックヘッダーに対する署名)
  • 次ブロックでのバリデータセットのハッシュ値

を順次ダウンロードします[3]

あるブロック高nでの上記の情報を信用するなら、n+1における情報は次のように検証することができます。

  1. ブロック高nでの「次ブロックのバリデータセットのハッシュ値」がブロック高n+1でのバリデータセットのハッシュ値に等しいことを確認する。これによってブロック高n+1でのバリデータセットが正しいことが保証される。
  2. ブロック高n+1のCommitが正しく、十分な投票(全投票力の2/3以上)が集まっていることを確認する。これによってブロック高n+1のブロックヘッダーが、コンセンサスの取れた正しいものであることが保証される。
  3. マークル証明を用いて、ブロック高n+1における「次ブロックでのバリデータセットのハッシュ値」が正しいことを証明する。

これを帰納的に繰り返すことで、現在までの正しいブロックヘッダーのシークエンスを得ることができます。このプロセスは情報源の信頼性に依存しません。すなわち、最初に入力したブロックヘッダーとブロック高さえ信頼すれば、赤の他人から上記の情報を教えてもらったとしても、得られたブロックヘッダーのシークエンスを信頼することができます。

ただし、この方式では通信回数が多いので、Tendermintライトクライアント(IBCで最もよく用いられるライトクライアント)では検証を効率的にスキップすることで通信回数を抑えているようです。詳しくは下の記事をご覧ください。
https://medium.com/tendermint/everything-you-need-to-know-about-the-tendermint-light-client-f80d03856f98

IBCでは両方のチェーンが相手チェーンのライトクライアントを立ち上げることで、相手のチェーン状態の検証を行います。

両方のチェーンが相手チェーンのライトクライアントを立ち上げる。

IBCの大まかな流れ

IBCを用いてチェーンAからチェーンBにトークンを転送する際は、大まかに次のような流れで行われます。

  1. チェーンA, Bはリレイヤーを介して、それぞれが相手チェーンのライトクライアントインスタンスを立ち上げたのち、ハンドシェイクを行いコネクションを確立する。
  2. 両チェーンのtransferポートを接続するチャンネルを、ハンドシェイクを行い開く。
  3. チェーンA上の利用者は、チェーンB上にトークンを送金したいという旨のトランザクションをチェーンAに送る。送金予定のトークンはロックされる。
  4. リレイヤーはチェーンAの状態変化を察知し、そのマークル証明が含まれたトランザクションをチェーンBへ送る。
  5. チェーンBはライトクライアントを用いてマークル証明を検証し、確認が取れたら送金された額と同額のバウチャートークンを発行する。

コネクション, チャンネル, ポートとは

コネクションはIBCの基礎レイヤーですが、ブロックチェーンアプリケーションはその一つ上のレイヤーであるチャンネルを介して通信を行います。TCP/IPに例えれば、コネクションはトランスポート層、チャンネルはアプリケーション層に対応します。

IBCでは、コネクションが属するレイヤーをIBC:TAO (IBC Transport, Authentication, and Ordering of packets), チャンネルが属するレイヤーをIBC:APP(IBC Application)と呼びます。

アプリケーションのモジュールごとに固有のport-idが割り当てられており、例えばトークンを扱うモジュールbankport-idtransferと決められています。チャンネルは両チャンネルのポート同士を接続します。コネクションやチャンネルのオープンは初回通信時にのみ行われます。その際、立ち上げられたライトクライアントインスタンスには固有のclient-idが、コネクションにはconnection-idが、チャンネルにはchannel-idが付与され、(リレイヤーが変わっても)永久に変わることがありません。

注意として、チャンネルのIDchannel-idはチェーンAとチェーンBで独立に割り当てられるので、基本的に一致しません。

コネクション、ポート、チャンネルの概念図。

バウチャートークンとは

バウチャー(受領証明書などの意味)トークンとは、同額のトークンがチェーンAでロックされていることを証明する代替性トークンです。チェーンBからチェーンAに送り返す際には、バウチャートークンはバーンされ、それと同額のトークンがチェーンAでアンロックされます。

IBCはトラストレスなプロトコルですが、チェーンAとチェーンBへの信頼は仮定しています。したがって、チェーンAが実はロックしなかったとか、チェーンBが実はバーンしなかったなどの場合はIBCは破綻します。しかしながら、両チェーンへの信頼はブリッジプロトコルとして必要最低限の仮定です。信頼できないブロックチェーンは、そもそも使うべきではありません。

正しいチェーンと接続していることの確認

ブロックチェーンはchain-idと呼ばれる識別子を持っているのですが、既存のチェーンと同じchain-idを持つチェーンは簡単に作成することができます。したがって、接続先のチェーンの真偽はchain-idではなくchannel-id(あるいはconnection-idclient-id)を用いて判断する必要があります。では本物のチェーンのchannel-idはどのように調べることができるのでしょうか?

残念なことに、現時点ではブロックエクスプローラーなどで調べるか、両方のチェーンのノードを立ててチェックするしかないようです。この方法には不満があるので、将来的にはCosmos-hubにCNS(Chain Name Service)をホストし、分散的に管理することが検討されているようです。

IBCの詳細な流れ

コネクションの確立

ライトクライアントインスタンスの生成と更新

コネクションの確立に先立ち、両チェーンのCreateClientメソッドを呼び出し、ライトクライアントインスタンスを生成する必要があります。
https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/core/02-client/keeper/client.go#L17-L57
CreateClientclient-idをレスポンスとして返します。client-id07-tendermint-831のような形式を取ります。この場合、07-tendermintはライトクライアントのバージョンで831はclientインスタンスのIDです。

UpdateClientメソッドでclientの状態を更新し、最新のブロックに同期することができます。
https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/core/02-client/keeper/client.go#L60-L144

ハンドシェイク

コネクションのオープンはTCPと同じようにハンドシェイクによって行われます。

ConnOpenInit

コネクションのプロセス開始をチェーンAに告知するためのメソッドです。client-idを引数として渡すと、 connectionIDが新たに割り当てられ、返り値として受け取ります。チェーンAはINIT状態になります。

func (k Keeper) ConnOpenInit(
	ctx sdk.Context,
	clientID string,
	counterparty types.Counterparty, // counterpartyPrefix, counterpartyClientIdentifier
	version *types.Version,
	delayPeriod uint64,
) (string, error) {...

ConnOpenTry

「チェーンAがINIT状態になり、〇〇というconnectionIDが割り当てられた」ということをチェーンBがライトクライアントで検証するためのメソッドです。チェーンB側でもconnectionIDが新たに割り当てられ、返り値で返します。チェーンBはTRYOPEN状態になります。このメソッドを呼ぶ前にリレイヤーはUpdateClientメソッドを両チェーンに対して呼び、ライトクライアントを最新の状態に更新する必要があります。

func (k Keeper) ConnOpenTry(
	ctx sdk.Context,
	counterparty types.Counterparty, // counterpartyConnectionIdentifier, counterpartyPrefix and counterpartyClientIdentifier
	delayPeriod uint64,
	clientID string, // clientID of chainA
	clientState exported.ClientState, // clientState that chainA has for chainB
	counterpartyVersions []exported.Version, // supported versions of chain A
	proofInit []byte, // proof that chainA stored connectionEnd in state (on ConnOpenInit)
	proofClient []byte, // proof that chainA stored a light client of chainB
	proofConsensus []byte, // proof that chainA stored chainB's consensus state at consensus height
	proofHeight exported.Height, // height at which relayer constructs proof of A storing connectionEnd in state
	consensusHeight exported.Height, // latest height of chain B which chain A has stored in its chain B client
) (string, error) {...

ConnOpenAck

「チェーンBがINIT状態になり、〇〇というconnectionIDが割り当てられた」ということをチェーンAがライトクライアントで検証するためのメソッドです。チェーンAはOPEN状態になります。このメソッドを呼ぶ前にリレイヤーはUpdateClientメソッドを両チェーンに対して呼び、ライトクライアントを最新の状態に更新する必要があります。

func (k Keeper) ConnOpenAck(
	ctx sdk.Context,
	connectionID string,
	clientState exported.ClientState, // client state for chainA on chainB
	version *types.Version, // version that ChainB chose in ConnOpenTry
	counterpartyConnectionID string,
	proofTry []byte, // proof that connectionEnd was added to ChainB state in ConnOpenTry
	proofClient []byte, // proof of client state on chainB for chainA
	proofConsensus []byte, // proof that chainB has stored ConsensusState of chainA on its client
	proofHeight exported.Height, // height that relayer constructed proofTry
	consensusHeight exported.Height, // latest height of chainA that chainB has stored on its chainA client
) error {...

ConnOpenConfirm

「チェーンAがOPEN状態になった」ということをチェーンBに伝えるためのメソッドです。チェーンBはOPEN状態となり、コネクションが確立されます。

func (k Keeper) ConnOpenConfirm(
    ctx sdk.Context,
    connectionID string,
    proofAck []byte, // proof that connection opened on Chain A during ConnOpenAck
    proofHeight exported.Height, // height that relayer constructed proofAck
)...

チャンネルのオープン

チャンネルのオープンはコネクションの確立と同様にChannelOpenInit, ChannelOpenTry, ChannelOpenAck, ChannelOpenConfirmによるハンドシェイクで行います。コネクションとの違いは、port-idを指定する必要があること程度です。
詳しくは
https://tutorials.cosmos.network/academy/4-ibc/channels.html
をご参照ください。

チャンネルによるパケット送信

チャンネルが開かれると、アプリケーションは通信を行うことが可能になります。通信はIBC/TAO層が行ってくれるので、アプリケーションは通信そのものを実装する必要はなく、代わりに{port-id}/{channel-id}で指定されるパスにパケットをコミットすることでIBC/TAO層に通信が必要であることを伝えます。

パケットとは

パケットとは、一回のIBCリレーで伝達されるデータの塊のことです。適切なアプリケーションへ伝達するためのルーティング(ポートやチャンネル)や、タイムアウトを含みます。

func NewPacket(
	data []byte,
	sequence uint64, sourcePort, sourceChannel,
	destinationPort, destinationChannel string,
	timeoutHeight clienttypes.Height, timeoutTimestamp uint64,
) Packet {
	return Packet{
		Data:               data,
		Sequence:           sequence,
		SourcePort:         sourcePort,
		SourceChannel:      sourceChannel,
		DestinationPort:    destinationPort,
		DestinationChannel: destinationChannel,
		TimeoutHeight:      timeoutHeight,
		TimeoutTimestamp:   timeoutTimestamp,
	}
}

パケットの流れ

パッケットは下の図のように流れます。実線は状態変化を伴う関数の実行を、点線は状態変化を伴わないクエリを表しています。

パケットの流れ

1. SendPacket

パケットを{port-id}/{channel-id}で指定されるパスへコミットするメソッドです。

func (k Keeper) SendPacket(
	ctx sdk.Context,
	channelCap *capabilitytypes.Capability,
	packet exported.PacketI,
) error {

2. QueryPacket

リレイヤーがコミットされたパケットを読み取るためのメソッドです。

3. RecvPacket

リレイヤーがチェーンBにパケットを渡すためのメソッドです。

func (k Keeper) RecvPacket(
	ctx sdk.Context,
	chanCap *capabilitytypes.Capability,
	packet exported.PacketI,
	proof []byte,
	proofHeight exported.Height,
) error {

4. onRecvPacket

受信したパケットはモジュールにルーティングされ、このメソッドが呼び出されます。

5. WriteAcknowledgement

もしチェーンAにacknowledgementを返す必要があれば、このメソッドが呼ばれ、acknowledgeパケットが{port-id}/{channel-id}にコミットされます。

func (k Keeper) WriteAcknowledgement(
	ctx sdk.Context,
	chanCap *capabilitytypes.Capability,
	packet exported.PacketI,
	acknowledgement exported.Acknowledgement,
) error {

6. QueryAcknowledgement

リレイヤーがチェーンBのacknowledgeパケットを読み取るためのメソッドです。

7. AcknowledgePacket

リレイヤーがチェーンAにacknowledgeパケットを渡すためのメソッドです。

func (k Keeper) AcknowledgePacket(
	ctx sdk.Context,
	chanCap *capabilitytypes.Capability,
	packet exported.PacketI,
	acknowledgement []byte,
	proof []byte,
	proofHeight exported.Height,
) error {

8. onAcknowledgePacket

acknowledgeパケットは元のモジュールにルーティングされ、このメソッドが呼ばれます。

タイムアウトになった場合の処理

パケットに指定された期限を過ぎてもリレーされない場合はタイムアウトとなります。タイムアウトの場合はリレイヤーがチェーンBにリレーされなかったことの証明をチェーンAに提出することで、チェーンA側の処理をキャンセルします。

タイムアウトになった場合

TimeoutPacket

タイムアウトになった場合にリレイヤーが呼び出すメソッドです。パスにコミットされたパケットを削除します。

func (k Keeper) TimeoutPacket(
	ctx sdk.Context,
	packet exported.PacketI,
	proof []byte,
	proofHeight exported.Height,
	nextSequenceRecv uint64,
) error {

onTimeoutPacket

タイムアウトになった場合に呼ばれるモジュールのメソッドです。IBC送金の場合はトークンのロックを解除し、送金者に返送するなどのロジックを実装します。

ローカルチェーンとパブリックチェーンを接続する

Igniteを利用してローカルチェーンを作成し、パブリックチェーン(cosmos-hub theta testnet)に接続してみます。Cosmos-hunメインネットとも同様の手順で接続できますが、トランザクション手数料が馬鹿にならない(0.3atom程度かかる)のでテストネットでまず試すのがおすすめです。

Igniteのインストール

以下のページなどを参考に、ignite(ブロックチェーンを作ることができるツール)をインストールしてください。
https://docs.ignite.com/guide/install

Igniteを動かすにはGolangが必要です。
まだインストールされていない方は、以下のページなどを参考にインストールしてください。

https://go.dev/doc/install

また、GOPATHをPATHに追加する必要があります。

export GOPATH=$(go env GOPATH)
export PATH=$PATH:$(go env GOPATH)/bin

ブロックチェーンの作成

ignite scaffold chainコマンドでチェーンの雛形を作りましょう。--address-prefixでアドレスのプレフィックスを指定できます。指定しない場合cosmosになりますが、今回はtestnetのアドレスと区別をつけやすくするため、指定することをおすすめします。

ignite scaffold chain mylocalchain --address-prefix my

mylocalchain/ディレクトリに次のような内容のconfig.ymlが生成されているはずです。

accounts:
  - name: alice
    coins: ["20000token", "200000000stake"]
  - name: bob
    coins: ["10000token", "100000000stake"]
validator:
  name: alice
  staked: "100000000stake"
client:
  openapi:
    path: "docs/static/openapi.yml"
  vuex:
    path: "vue/src/store"
faucet:
  name: bob
  coins: ["5token", "100000stake"]

config.ymlgenesis.jsonの内容をyml形式で指定することができます。genesis.jsonはブロックチェーンのパラメータを指定するファイルで、チェーン誕生時に誰がどれだけのトークンを持っているかなども指定することができます。この場合、Aliceは20000token200000000stake、Bobは10000token100000000stake持つことが分かります。

この状態でignite chain serveを実行すると、ブロックチェーンがビルドされ起動します。簡単ですね!

~/D/d/mylocalchain ❯❯❯ ignite chain serve 

Cosmos SDK's version is: stargate - v0.45.4

🔄 Resetting the app state...
🛠️  Building proto...
📦 Installing dependencies...
🛠️  Building the blockchain...
💿 Initializing the app...
🙂 Created account "alice" with address "my19m8xlmnjduvp0xdd5afq4x7dsu7g05vraajmda" with mnemonic: "junior metal supreme list close retreat token fish leg version legal canvas raven behave drip label memory awful main pretty proof lesson silver aim"
🙂 Created account "bob" with address "my1g22und2uf9lrf6hwv49eejunyr9jq85q3xd96c" with mnemonic: "add south brief outside rural display young token wait virus better smoke client symbol lamp meat note wet fragile submit now kitchen people wall"
🌍 Tendermint node: http://0.0.0.0:26657
🌍 Blockchain API: http://0.0.0.0:1317
🌍 Token faucet: http://0.0.0.0:4500

同時に$GOPATH/binブロックチェーン名+dのCLIツールがインストールされます。別のターミナルを開き、mylocalchaind keys listで登録された鍵を見てみましょう。

~ ❯❯❯ mylocalchaind keys list
- name: alice
  type: local
  address: my19m8xlmnjduvp0xdd5afq4x7dsu7g05vraajmda
  pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A10eltvKGC7ZgpmT4E0p6SPW0qcw0eGnN7IEkfIlb58p"}'
  mnemonic: ""
- name: bob
  type: local
  address: my1g22und2uf9lrf6hwv49eejunyr9jq85q3xd96c
  pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"Av9TYS4DhA/JuFCa7SjEGVrUJfrToaY8ihsuDWT5lyLj"}'
  mnemonic: ""

mylocalchaindの操作方法は、Cosmosh-hubのCLIツールgaiadとほぼ同じなので、そちらのドキュメンを参考にするのがおすすめです。

https://hub.cosmos.network/main/hub-tutorials/gaiad.html

リレイヤーの構成

リレイヤーの機能もigniteが提供しています。
ignite relayer configureでリレイヤーを構成しましょう。基本的にデフォルトの設定で大丈夫ですが、Target RPCはtestnetを指定したいので、testnetのRPChttps://rpc.sentry-01.theta-testnet.polypore.xyz:443を指定しておきます。Source Address Prefixも作成したブロックチェーンのプレフィックスに合わせておきます。Source Faucethttp://0.0.0.0:4500を指定します。

~ ❯❯❯ ignite relayer configure

------
Setting up chains
------

? Source Account default
? Target Account default
? Source RPC http://localhost:26657
? Target RPC https://rpc.sentry-01.theta-testnet.polypore.xyz:443 <---変更する!
? Target Faucet (optional) 
? Source Gas Price 0.00025stake
? Target Gas Price 0.025uatom
? Source Gas Limit 300000
? Target Gas Limit 300000
? Source Address Prefix my <---変更する!
? Target Address Prefix cosmos

🔐  Account on "source" is default(my1juzrpdf8dfgsn9e7wq0tjdputcfx3re0lpw0ml)
 
received coins from a faucet
 |· (balance: 100000stake,5token)

🔐  Account on "target" is default(cosmos1juzrpdf8dfgsn9e7wq0tjdputcfx3re0hmqacn)
 
no faucet available, please send coins to the address
 |· (balance: -)

⛓  Configured chains: mylocalchain-theta-testnet-001

※リレイヤーを再構成する場合は、rm -rf ~/.ignite/relayer/で前回のデータを削除しておかないとおかしくなるみたいです。

トランザクション手数料の入金

ローカルチェーン側のアカウントはmy1juzrpdf8dfgsn9e7wq0tjdputcfx3re0lpw0mlで、パブリックチェーン側のアカウントはcosmos1juzrpdf8dfgsn9e7wq0tjdputcfx3re0hmqacnであることが分かりました(もちろん実行環境によってこの値は変わります)。リレイヤーになるためにはこれらのアカウントに、トランザクション手数料を支払うのに十分な資金がないといけません。

ローカルチェーン側

Source Faucetを指定しておくと自動的に入金されますが、もし設定ミスなどで入金されていない場合は、Aliceのアカウントから送金します。

mylocalchaind tx bank send alice my1juzrpdf8dfgsn9e7wq0tjdputcfx3re0lpw0ml 100000stake -y

パブリックチェーン側

Cosmos NetworkのDiscordの#testnet-faucetチャンネルにfaucetがあります。

https://discord.com/invite/cosmosnetwork

リレイヤーを立ち上げる

ignite relayer connectでリレイヤーを立ち上げます。

~ ❯❯❯ ignite relayer connect

------
Paths
------

mylocalchain-theta-testnet-001:
    mylocalchain      > (port: transfer) (channel: channel-0)
    theta-testnet-001 > (port: transfer) (channel: channel-508)

------
Listening and relaying packets between chains...
------

Block exploreで見てみましょう。
https://cosmoshub-testnet.mintscan.io/cosmoshub-testnet

ハンドシェイクの結果、チャンネルが開かれたのが見えました!

パブリックチェーンへ送金

別のターミナルウィンドウで、mylocalchaind tx ibc-transfer transfer [src-port] [src-channel] [receiver] [amount]を実行し、送金を行います。

mylocalchaind tx ibc-transfer transfer transfer channel-0 cosmos1juzrpdf8dfgsn9e7wq0tjdputcfx3re0hmqacn 10000stake --from alice

クライアントが更新されたのち、トークンを受け取っていることが分かります。

IBCを利用したブロックチェーンアプリケーションの実装方法に関しては, igniteのチュートリアルがおすすめです。https://docs.ignite.com/guide/ibc

参考にした記事

https://docs.ignite.com/

https://tutorials.cosmos.network/academy/4-ibc/

https://docs.tendermint.com/v0.34/tendermint-core/light-client.html

https://medium.com/tendermint/everything-you-need-to-know-about-the-tendermint-light-client-f80d03856f98

https://www.youtube.com/watch?v=-AuExRijtrA

脚注
  1. 解説記事https://prsarahevans.com/page-629/page-656/などをご参照ください ↩︎

  2. ブロックエクスプローラーで調べたり、複数のノードにクエリすることで取得します。これはPoSチェーンの「弱い主観性」と呼ばれる性質から手動で行う必要があります。複数のフォークが存在する場合、PoWでは、現在のブロックまでに最も多くの計算リソースを消費したチェーンが「正統な」チェーンであるという客観的基準が存在するため、初めてノードを立ち上げた場合でも自動的に「正統な」チェーンと同期されますが、PoSではそのような客観的基準が存在せず、どのチェーンが「正統」かはソーシャルメディアなどで得られる「主観的な」情報に依存します。ただし、一度「正統な」チェーンを選べば、ハードフォークが起きるまではノードが自動的に「正統な」チェーンを選び続けるという意味で客観性があるため「弱い主観性」と呼ばれています。詳しくはhttps://blog.ethereum.org/2014/11/25/proof-stake-learned-love-weak-subjectivityをご参照ください ↩︎

  3. 他のフィールドも取得します。tendermintライトクライアントが実際にどのようなフィールドを取得するかはhttps://github.com/tendermint/spec/tree/master/spec/light-client/verificationをご参照ください ↩︎

Discussion