Datachain团队致力于开发通过IBC(Inter-Blockchain Communication)异构跨链的模块和框架——“YUI”:
- https://github.com/hyperledger-labs/yui-docs
相关代码库有:
- https://github.com/hyperledger-labs/yui-fabric-ibc
- https://github.com/hyperledger-labs/yui-ibc-solidity【可支持以太坊等兼容solidity EVM的链】
- https://github.com/hyperledger-labs/yui-corda-ibc
- https://github.com/hyperledger-labs/yui-relayer
IBC为不同账本间的互操作协议,初始为Cosmos的一个核心模块,使得多个基于tendermint 或 不基于tendermint的账本之间可相互连通。 理论上,任何链都可通过IBC相互通讯。 IBC以Interchain standard (ICS)进行了标准化:
- https://github.com/cosmos/ibc
IBC协议采用分层设计,主要分为2层:
- IBC/TAO:底层的transport,authentication and ordering层。
- IBC/APP:基于TAO构建的上层应用层。
实现IBC协议的大多数工作集中在TAO层。一旦目标ledger的TAO层已实现,则很容易在TAO层之上实现不同的APP层协议。
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 clientIBC/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中的智能合约状态。
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。
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。
一旦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触发。】
单一且简单的packet relay 机制(IBC/TAO)支持任意跨链协议。建立在IBC/TAO之上的应用程序协议统称为IBC/APP。 Cross-chain token transfer举例: ICS-20 为IBC/APP protocol例子,支持跨链token transfer。IBC/APP implementers无需设计或实现整个链的互操作机制,仅需实现
sendPacket
,recvPacket
和acknowledgePacket
相关插件。 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
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, 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类型仅能注册一次】
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.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_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); }
- 1.1)ConstructIBFT2MsgCreateClient:记录对方链的chainID、IBCHost合约地址,以及在
- 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合约中。
- 2.1)调用链A的IBCHandler合约的
- 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 }
- 7.1)会判断传入的denom参数是单纯的SimpleToken合约地址,还是前缀有sourcePort+sourceChannel。若为单纯的合约地址,则将那100token 直接转给sourceChannel的escrowAddress(即ICS20TransferBank合约地址),实际维护的是ICS20Bank合约的
- 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 ClientHyperledger Besu为以太坊联盟链方案,支持动态validator set。 Hyperledger Besu支持多种共识算法,其中IBFT 2.0(即Proof-of-Authority (PoA) Byzantine-Fault-Tolerant (BFT) )共识算法最受欢迎。
当采用IBFT2.0共识时,在区块中有额外的data field存储共识结果:[32 bytes Vanity, List, 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。
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。
当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>0且m>0】
- 1)当前时间在最新trusted block的trusting period:
B
T
n
<
N
o
w
(
)
<
B
T
n
+
T
P
BT_n
关注打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?