Cosmos IBC如何异构跨链

1. 引言

Datachain团队致力于开发通过IBC(Inter-Blockchain Communication)异构跨链的模块和框架——“YUI”:

相关代码库有:

2. IBC 总览

IBC为不同账本间的互操作协议,初始为Cosmos的一个核心模块,使得多个基于tendermint 或 不基于tendermint的账本之间可相互连通。
理论上,任何链都可通过IBC相互通讯。
IBC以Interchain standard (ICS)进行了标准化:

3. IBC 架构设计

IBC协议采用分层设计,主要分为2层:

  • IBC/TAO:底层的transport,authentication and ordering层。
  • IBC/APP:基于TAO构建的上层应用层。

实现IBC协议的大多数工作集中在TAO层。一旦目标ledger的TAO层已实现,则很容易在TAO层之上实现不同的APP层协议。
在这里插入图片描述

3.1 IBC/TAO层(合约)

IBC/TAO层的主要作用为:在两链之间以reliable,ordered and authenticated方式relay packets。

  • reliable:是指 源链仅发送一个packet,目标链仅接收一次,二者无需信任任何第三方。事实上,链之间并不相互通信。因此,需要“relayer”来relay packets from one to another,但是relayer是无需许可的,任何人都可运行relayer。
  • ordered:是指 目标链接收packet的顺序与源链发送packet的顺序一致。
  • authenticated:是指 IBC relay packets采用“channel”抽象,channel的每个end都专门分配给特定的智能合约。因此,若目标链通过channel收到了一个packet,则可说明 源链端分配给该channel的特定智能合约发送了该packet。任何其他智能合约都无法使用该channel来发送packet。

IBC/TAO实现为智能合约,在通过IBC相互连接的两个区块链上运行,这些智能合约称为“IBC/TAO模块”。 智能合约(IBC/TAO模块)中包含了以下元素:

  • on-chain light client:为IBC/TAO的基础,在无需信任第三方的情况下,验证某状态确实存在对方链上。
  • connection abstraction
  • channel abstraction

在on-chain light client的基础之上,定义了connection abstraction和channel abstraction,用于连接2条链上的智能合约,并在二者之间relay packets。

3.1.1 IBC/TAO合约中的on-chain light client

IBC/TAO合约中的on-chain light client语法和语义 可参看 ICS-2标准

当“对方链”上有新的区块头时,relayer会query该区块头,并将该区块头提交到“本地链”的IBC/TAO合约。然后IBC/TAO合约中会运行“对方链”的 light client协议 来 验证该协议是否有效,若有效,则更新其 ClientState 以反应“对方链”的状态。

ClientState 更新为“对方链”的最新区块头之后,IBC/TAO合约可检查在“对方链”是否存在某presented state。
比如,若“对方链”采用merkle tree来存储其world state,并在每个区块头中包含该merkle tree的root hash(与以太坊类似),可采用merkle proof来证明该tree中的智能合约状态。
在这里插入图片描述

3.1.2 IBC/TAO合约中的connection abstraction

IBC/TAO合约中的connection语义语法可参看 ICS-3

IBC上下文中的"connection"表示为不同链上的2个ClientState组成的connected pair。
在开始用“channel” relay packets之前,两条链上的IBC/TAO需确定并验证要与之通信的ClientState。connection abstraction就用于此目的。链之间的connection建立机制类似于TCP的3-way handshake。
在这里插入图片描述
connection握手过程的状态变化如下:【所有操作都是由relayer发起交易来触发】

  • 1)connOpenInit:在发起链上会创建并存储a new connection in INIT status。
  • 2)connOpenTry:对方链 若验证 发起链 上该connection为INIT status,则在自身链上创建并存储a new connection in TRYOPEN status。
  • 3)connOpenAck:发起链 若验证 对方链 上该connection为TRYOPEN status,则将自身链上该connection的状态由INIT更新为OPEN。
  • 4)connOpenConfirm:对方链 若验证 发起链 上该connection状态已由INIT更新为OPEN,则将自身链上该connection状态由TRYOPEN更新为OPEN。

在这里插入图片描述

3.1.3 IBC/TAO合约中的channel abstraction

IBC/TAO合约中的channel语义语法可参看 ICS-4

IBC中的channel abstraction用于表示不同链上2个智能合约的connect pair。
channel建立的握手机制与connection类似。一旦建立,channel可用于在链之间进行packet relay。
packet relay过程本身也采用类似TCP的3-way handshake机制。
在这里插入图片描述
在这里插入图片描述

channel握手过程的状态变化如下:【所有操作都是由relayer发起交易来触发】

  • 1)chanOpenInit:在发起链上会创建并存储a new channel in INIT status。
  • 2)chanOpenTry:对方链 若验证 发起链 上该channel为INIT status,则在自身链上创建并存储a new channel in TRYOPEN status。
  • 3)chanOpenAck:发起链 若验证 对方链 上该channel为TRYOPEN status,则将自身链上该channel的状态由INIT更新为OPEN。
  • 4)chanOpenConfirm:对方链 若验证 发起链 上该channel状态已由INIT更新为OPEN,则将自身链上该channel状态由TRYOPEN更新为OPEN。
    在这里插入图片描述

3.1.4 packet relay

一旦channel建立,2个智能合约就可发送和接收packets(packet内容为任意bytes sequences):

  • 1)sendPacket:在源链上创建并存储了一个sequence number为 N=nextSequenceNumber 的新packet。然后nextSequenceNumber 会加1。【不是由链外实体直接触发,而是由App合约触发。
  • 2)recvPacket:目标链 若验证 源链 上确实发送(创建)了该packet,则会在自身链上创建并存储一个sequence number为N的新packet。【由relayer触发。】
  • 3)acknowledgePacket:删除sequence number为N的packet。【由relayer触发。】

在这里插入图片描述

3.2 IBC/APP层(合约)

单一且简单的packet relay 机制(IBC/TAO)支持任意跨链协议。建立在IBC/TAO之上的应用程序协议统称为IBC/APP。
在这里插入图片描述
Cross-chain token transfer举例:
ICS-20 为IBC/APP protocol例子,支持跨链token transfer。IBC/APP implementers无需设计或实现整个链的互操作机制,仅需实现sendPacketrecvPacketacknowledgePacket相关插件。
token由chainA通过ICS-20 transfer 到 chainB的流程为:

  • 1)chainA上原子执行如下操作:
    • Locking tokens in the ICS-20 module。
    • 然后执行a sendPacket operation for a packet that specifies the amount and denomination of the locked tokens。
  • 2)chainB上原子执行如下操作:
    • 运行recvPacket operation for the packet。
    • 然后mint voucher tokens that is equivalent to the locked tokens in the ICS-20 module。
  • 3)chainA上正常运行acknowledgePacket

在这里插入图片描述
将该token由chainB再transfer转回chainA的流程为:

  • 1)chainB上原子执行如下操作:
    • Burning voucher tokens in the ICS-20 module。
    • 然后执行a sendPacket operation for a packet that specifies the amount and denomination of the locked tokens。
  • 2)chainA上原子执行如下操作:
    • 运行recvPacket operation for the packet。
    • 然后unlock voucher tokens that is equivalent to the burned vouchers in the ICS-20 module。
  • 3)chainB上正常运行acknowledgePacket

在这里插入图片描述
若已实现了IBC/TAO,则以上提到的ICS-20 module仅需具有如下函数:

  • locking and unlocking tokens
  • minting and burning vouchers

4. yui-ibc-solidity IBC合约解析

https://github.com/hyperledger-labs/yui-ibc-solidity【可支持以太坊等兼容solidity EVM的链】中IBC合约解析:

  • 1)IBC/TAO层合约有:【主要有3个合约:light client合约、IBCHost合约 和 IBCHandler合约。】

    • IBCIdentifier:为library。主要为keccak256运算,用于生成client、consensus、connection、channel、packet、packetAcknowledgement 相关的commitment key,以及clientState、consensusState、connection、channel、packet、packetAcknowledgement 相关的Commitment Slot,和 part、channel 相关的capability path。
    • IBCHeight:为library。主要为对Height.Data.revision_number的各种运算。
    library Height {
      //struct definition
      struct Data {
        uint64 revision_number;
        uint64 revision_height;
      }
    }
    
    • IBCMsgs:为library。定义了ICS-026中的client、connection handshake、channel handshake、channel closing 以及 packet relay等相关消息结构体。
    • IClient:为interface。定义了getTimestampAtHeight、getLatestHeight、checkHeaderAndUpdateState、verifyClientState、verifyClientConsensusState、verifyConnectionState、verifyChannelState、verifyPacketCommitment 和 verifyPacketAcknowledgement等接口函数。
    • MockClient:为合约。为对IClient接口的mock实现,实际并未做任何验证,用于测试场景。
    • IBFT2Client:为合约。为对 Hyperledger Besu的IBFT 2.0(即Proof-of-Authority (PoA) Byzantine-Fault-Tolerant (BFT) )共识算法 进行验证的light client。【 Hyperledger Besu为以太坊联盟链方案,支持动态validator set。在区块中有额外的data field存储共识结果:[32 bytes Vanity, List<Validators>, Votes, Round number, Commit Seals],其中第二个元素为a list of each address in a validator set of this block,第5个元素为commit seals to this block by the validator set,每个区块节点会verifies the commit seals to validates the block according to Algorithm 1。】
    • IBCHost:为合约。部署完成后,owner后续需调用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:为library。实现了client相关函数,create和update等操作仅能由IBCHandler合约调用。
    • IBCConnection:为library。实现了connection相关函数,仅由IBCHandler合约调用。以及各种state、commitment和ack的verify函数。
    • IBCChannel:为library。实现了channel相关函数,以及sendPacket和recvPacket,writeAcknowledgement和acknowledgePacket函数,仅由IBCHandler合约调用。
    • IBCHandler:为合约。部署时,需指定IBCHost合约地址。提供了registerClient(注册client类型,如mock还是ibft2.0 client,需由owner调用)、以及对IBCMsgs各消息的响应函数的实现。
    • IBCModule:为interface,抽象了onChanOpenInit、onChanOpenTry、onChanOpenAck、onChanOpenConfirm、onChanCloseInit、onChanCloseConfirm、onRecvPacket和onAcknowledgementPacket接口函数。
  • 2)IBC/APP层合约有:【主要有3个合约:ICS20Bank、ICS20TransferBank 和 SimpleToken合约。】

    • ICS20Bank:为APP合约 之 ICS20Bank合约,除具体实现了IICS20Bank中定义的抽象接口之外,还额外实现了deposit和withdraw函数。
    • ICS20Transfer:构建时需指定IBCHost和IBCHandler合约地址,为ICS20TransferBank的子合约,除具体实现了IBCModule中定义的抽象接口之外,还额外实现了IICS20Transfer中的sendTransfer函数。
    • ICS20TransferBank:构建时需指定IBCHost、IBCHandler以及ICS20Bank合约地址,为App合约 之 ICS20TransferBank合约。
    • IICS20Bank:为interface,抽象了transferFrom、mint、burn接口函数。
    • IICS20Transfer:为interface,抽象了sendTransfer接口函数。
    • SimpleToken:为APP合约 之 token合约。此处定义为了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层主要有3个合约:IBCHost合约、IBCHandler合约、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合约owner 调用其自身的setIBCModule函数,将ibcModule设置为IBCHandler合约地址,使得其generateClientIdentifier、generateConnectionIdentifier、generateChannelIdentifier、setClientImpl、setClientType、setClientState、setConsensusState、setProcessedTime、setProcessedHeight、setConnection、setChannel、setNextSequenceSend、setNextSequenceRecv、setNextSequenceAck、setPacketCommitment、deletePacketCommitment、setPacketAcknowledgementCommitment、setPacketReceipt、setExpectedTimePerBlock、claimCapability、authenticateCapability等操作仅能由IBCHandler合约调用。
  • 5)IBCHandler合约owner 调用其自身的bindPort函数,参数为portID 和 DAPP ICS20TransferBank合约地址。作用为:调用IBCHost合约,将capabilities[portID]数组中插入ICS20TransferBanke合约地址。【同一portID的capabilities数组支持配置多个DAPP应用合约地址。但是在getModuleOwner中默认只返回第一个DAPP地址,实际还是一个portID对应一个DAPP合约。】【在channel握手协议中,会根据capabilities[portID]调用相应的DAPP应用合约中的onChanOpenInit、channelOpenTry、channelOpenAck、onChanOpenConfirm、onChanCloseInit、onChanCloseConfirm等函数。】
  • 6)IBCHandler合约owner 调用其自身的registerClient函数,参数为 light client 类型(如BesuIBFT2ClientType)和 相应的light client合约地址(如IBFT2Client合约地址)。作用为:调用IBCHost合约,设置clientRegistry[clientType]=clientAddress。【每种client类型仅能注册一次】

4.2 yui-ibc-solidity IBC/APP层合约

IBC/APP层合约 ICS20Bank、ICS20Transfer、ICS20TransferBank、IICS20Bank、IICS20Transfer、SimpleToken之间的关系为:【下图中的IBCAppModule 对应为 ICS20TransferBank合约。】
在这里插入图片描述
IBC/APP层合约有:【主要有3个合约:ICS20Bank、ICS20TransferBank 和 SimpleToken合约。】

  • ICS20Bank:为APP合约 之 ICS20Bank合约,除具体实现了IICS20Bank中定义的抽象接口之外,还额外实现了deposit和withdraw函数。
  • ICS20Transfer:构建时需指定IBCHost和IBCHandler合约地址,为ICS20TransferBank的子合约,除具体实现了IBCModule中定义的抽象接口之外,还额外实现了IICS20Transfer中的sendTransfer函数。
  • ICS20TransferBank:构建时需指定IBCHost、IBCHandler以及ICS20Bank合约地址,为App合约 之 ICS20TransferBank合约。
  • IICS20Bank:为interface,抽象了transferFrom、mint、burn接口函数。
  • IICS20Transfer:为interface,抽象了sendTransfer接口函数。
  • SimpleToken:为APP合约 之 token合约。此处定义为了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.goSetupTest为:

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_ethProof读取该区块相应的proof证明,包含accountProof和storageProof,存储在state.ethProof结构体中:
type ETHProof struct {
	AccountProofRLP []byte
	StorageProofRLP [][]byte
}
  • 2)解析当前区块头信息,存入state.ParsedHeader;验证当前区块头中有超过2/3 validator签名,将每个validator的seal信息拼接在一起,存入state.CommitSeals。
  • 3)若当前区块高度大于链上light client合约中记录的header高度,则更新chain.LastContractState值为当前state。(第一次启动时,light client合约header信息为空,则直接更新chain.LastContractState为当前state)【此时并未调用light client合约更新。】

UpdateClient的流程为:

  • 1)ConstructIBFT2MsgUpdateClient:调用IBCHost合约的getClientState函数,读取链上存储的clientState的高度作为trustedHeight,将对方链上的新header信息打包:
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)调用IBCHandler合约的updateClient函数,更新相应的consensusState和root。【其中checkHeaderAndUpdateState会分别对storageProof和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条链上分别创建对方链的client:【会返回在链上分配的clientId:clientA 和 clientB】
    • 1.1)ConstructIBFT2MsgCreateClient:记录对方链的chainID、IBCHost合约地址,以及在UpdateHeader过程中记录在chain.LastContractState中的header、root以及validators信息。
    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合约的createClient函数,会在IBCHost合约中生成相应的clientId,并基于该clientId,在IBCHost合约中存储相应的clientType、clientState、ConsensusState、block.timestamp、block.number等信息。
    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,遵循connection握手协议:
    • 2.1)调用链A的IBCHandler合约的ConnectionOpenInit函数,会在IBCHost合约中生成相应的connectionId,并在IBCHost合约中存储相应的connection信息。【通过监听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链的light client合约中。
    • 2.2)查询链A上的proofConnection和proofClient,作为参数打包,调用链B的IBCHandler合约的connectionOpenTry函数,会做相应的connectionState和clientState验证,在IBCHost合约中生成相应的connectionId,并在IBCHost合约中存储相应的connection信息。【通过监听GeneratedClientIdentifier事件,来获取所创建的connectionId】【verifyConnectionState和verifyClientState可再细看】
      UpdateHeader:以确保B链上成功创建了connectionId,获取新的区块,更新程序本地B链状态。
      UpdateClient:将B链状态更新至A链的light client合约中。

    • 2.3)查询链B上的proofConnection和proofClient,作为参数打包,调用链A的IBCHandler合约的connectionOpenAck函数,会做相应的connectionState和clientState验证,并在IBCHost合约中更新相应的connection信息。【verifyConnectionState和verifyClientState可再细看】
      UpdateHeader:以确保A链上交易成功,获取新的区块,更新程序本地B链状态。
      UpdateClient:将A链状态更新至B链的light client合约中。

    • 2.4)查询链A上的proofConnection,作为参数打包,调用链B的IBCHandler合约的connectionOpenConfirm函数,会做相应的connectionState验证,并在IBCHost合约中更新相应的connection信息。【verifyConnectionState可再细看】
      UpdateHeader:以确保B链上交易成功,获取新的区块,更新程序本地A链状态。
      UpdateClient:将B链状态更新至A链的light client合约中。

  • 3)CreateChannel:总体流程与CreateConnection类似,只是调用的为合约中channel相关函数。【与connection握手不同之处:在channel握手协议中,会根据capabilities[portID]调用相应的DAPP应用合约中的onChanOpenInit、channelOpenTry、channelOpenAck、onChanOpenConfirm、onChanCloseInit、onChanCloseConfirm等函数。目前作用是设置该channel的escrowAddress为ICS20TransferBank合约地址。

yui-ibc-solidity/tests/e2e/chains_test.go 示例中:

  • 1)在chainA上部署SimpleToken合约时,默认会给部署者deployerA分配所有的token。
  • 2)部署ICS20Bank合约。
  • 3)部署ICS20TransferBank合约,部署时需指定IBCHost、IBCHandler以及ICS20Bank合约地址。
  • 4)ICS20Bank合约部署者,调用setOperator函数,将ICS20TransferBank合约地址设置为OPERATOR角色。
  • 5)授权IBCBank合约地址,可代deployerA花费100 token。
  • 6)SimpleToken合约部署者deployerA调用 ICS20Bank合约的deposit函数,往ICS20Bank合约地址中存入100token,在ICS20Banke合约中,会维护aliceA的balance:_balances[id][account] += amount;
  • 7)aliceA调用ICS20TransferBank合约的sendTransfer函数,将其在chainA的100个token 通过chanA.PortID, chanA.ID(sourcePort和sourceChannel) 转给 chainB的bobB。设置的timeoutHeight为当前区块高度+1000
    sendTransfer中:
    • 7.1)会判断传入的denom参数是单纯的SimpleToken合约地址,还是前缀有sourcePort+sourceChannel。若为单纯的合约地址,则将那100token 直接转给sourceChannel的escrowAddress(即ICS20TransferBank合约地址),实际维护的是ICS20Bank合约的_balances[id][account]状态;若有前缀,说明是之前收到的其它链的token再转出,会直接将那100个token 从 ICS20Bank合约的_balances[id][account]中减去。
    • 7.2)封装FungibleTokenPacketData,再进一步封装Packet.Data,调用IBCHandler合约的sendPacket函数。会验证之前已bindPort本DAPP合约地址;确保channel、connection权限状态正常;更新IBCHost的sequenceSend序号,同时设置IBCHost合约中的commits map为commitments[IBCIdentifier.packetCommitmentKey(portId, channelId, sequence)] = makePacketCommitment(packet);【若该packet已处理完成,会删除,防止replay攻击】。最终会释放sendPacket event。
    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上的light client合约状态。
  • 9)调用链A的IBCHost合约,读取当前最新的sequenceSend,基于此监听IBCHandler合约释放的sendPacket事件,过滤出符合相应序号、sourcePort和sourceChannel的sendPacket事件。
  • 10)等待DelayPeriod(此处为1S),若不等待直接操作下一步会报错。
  • 11)调用eth_getProof,获取链A该packet的storage proof。然后调用链B的IBCHandler合约的recvPacket函数:会调用ICS20TransferBank 合约的onRecvPacket函数,mint相应的金额给receiver,同时返回相应的acknowledgement。然后验证channel、connection和packet是否在有效期;验证packetCommitment;在IBCHost合约中设置packetReceipt;在IBCHost合约中设置PacketAcknowledgementCommitment;释放 WriteAcknowledgement事件 和 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上的light client合约状态。
  • 13)等待DelayPeriod(此处为1S),若不等待直接操作下一步会报错。
  • 14)调用eth_getProof,获取链B上(packet.DestinationPort, packet.DestinationChannel, packet.Sequence)的packetAcknowledgementCommitment 对应的storageProof。调用链A的IBCHandler合约的acknowledgePacket函数:会调用ICS20TransferBank合约的onAcknowledgementPacket函数,若acknowledgement中包含了失败信息,即意味着token转移失败,将token返回给sender;若成功,则什么都不做。然后验证channel、connection;验证packetAcknowledgement;若为ORDERED channel,则更新sequenceAck序号;删除该已处理的packetCommitment,防止replay攻击。

参考资料

[1] How Cosmos’s IBC Works to Achieve Interoperability Between Blockchains
[2] IBFT 2.0 Light Client

附录A——Hyperledger Besu的IBFT2.0 Light Client

Hyperledger Besu为以太坊联盟链方案,支持动态validator set。
Hyperledger Besu支持多种共识算法,其中IBFT 2.0(即Proof-of-Authority (PoA) Byzantine-Fault-Tolerant (BFT) )共识算法最受欢迎。

当采用IBFT2.0共识时,在区块中有额外的data field存储共识结果:[32 bytes Vanity, List<Validators>, Votes, Round number, Commit Seals],其中第二个元素为a list of each address in a validator set of this block,第5个元素为commit seals to this block by the validator set,每个区块节点会verifies the commit seals to validates the block according to Algorithm 1

附录A.1 light client trusted source & trusting period

IBFT2.0 Light Client 初始化时,需要一个trusted source。

考虑到validator set会更新,引入了trusting period,即a period of the validator set of height can be trusted:

  • The new block must be verified by the validator set of a block generated within time duration T before the current time.

因此,在light client初始化时,需指定trusting period参数为T,若T=0,则指信任的validator set不会变,可一直信任。每个区块,仅可增加或减少1个validator。

附录A.2 light client verification

当light client初始化了trusting period 和 a header from trusted source,需要基于 该trusting header 和 相应的trusted validators 来验证 incoming header。
对新提交的区块,validation function需验证以下条件:【假设 B h B_h Bh为高度为 h h h的区块, V h V_h Vh为区块 B h B_h Bh的validator set, B T h BT_h BTh为区块 B h B_h Bh的timestamp, N o w ( ) Now() Now()为当前时间, T P TP TP为trusting period。
假设当前的trusted block高度为 n n n,untrusted block高度为 n + m n+m n+m n > 0 且 m > 0 n>0且m>0 n>0m>0

  • 1)当前时间在最新trusted block的trusting period: B T n < N o w ( ) < B T n + T P BT_n<Now()<BT_n+TP BTn<Now()<BTn+TP
  • 2) B n + m B_{n+m} Bn+m具有 V n V_n Vn的1/3签名。因为最大拜占庭数量为 f ( n ) = ( n − 1 ) / 3 f(n)=(n-1)/3 f(n)=(n1)/3,该要求可保证至少有1个honest validator。
  • 3) B n + m B_{n+m} Bn+m具有 V N + M V_{N+M} VN+M的2/3+ 签名。可保证 n + m n+m n+m高度的固化区块是valid的。
  • 4)此外,还会verifyStorageProof。具体见yui-ibc-solidity/contracts/lib/TrieProofs.sol,参考了https://github.com/lorenzb/proveth/blob/master/onchain/ProvethVerifier.sol

附录A.3 light client liveness analysis

light client的liveness取决于validator set的变换。IBFT2.0支持Dynamic ValidatorSet,每个block仅可增加或减少1个validator。

因此,需确保存在某高度,上一节中的validation function可验证该高度的区块是由IBFT2.0协议生成的。显然,对于validator set数量增加的情况,总是存在a verifiable height。接下来仅考虑validator set 数量减少的情况:
令validator set为 V V V,减少量为 Δ \Delta Δ,有 Δ ⊆ V \Delta\subseteq V ΔV
∣ V ∧ V − Δ ∣ ≥ ∣ V ∣ ∗ 1 / 3 |V \wedge V-\Delta|\geq |V| * 1/3 VVΔV1/3

若该方程式成立,则总是存在a block height with a validator set that can validate the block to be validated。在IBFT2.0中,该方程式将成立,因为 ∣ V − Δ ∣ ≥ 1 |V-\Delta|\geq 1 VΔ1 Δ = 1 \Delta=1 Δ=1

附录A.4 light client fork detection

若the failure model of IBFT 2.0 is violated and there are more than (n-1) / 3 failed nodes, multiple valid confirmed blocks may be generated。
light client应能检测到这样的失败或攻击。这部分工作未来将实现。

猜你喜欢

转载自blog.csdn.net/mutourend/article/details/122576435