diff --git a/neo/How_to_become_neo_relayer_cn.md b/neo/How_to_become_neo_relayer_cn.md new file mode 100644 index 0000000..d4d554f --- /dev/null +++ b/neo/How_to_become_neo_relayer_cn.md @@ -0,0 +1,40 @@ +# neo-relayer + +[English](./How_to_become_neo_relayer_en.md) | 中文 + +## 架构 + +一方面,neo-relayer实现了对Neo网络的监听,会持续扫描每个Neo区块并将切换了下一轮共识地址的关键区块头同步到中继链上,同时能识别并转发跨链交易,向中继链提交区块的状态根和交易的梅克尔证明。 +另一方面,neo-relayer也实现了对中继链的监听,会持续扫描每个中继链区块并将切换了共识地址的关键区块头同步到Neo链上,中继链收到其他链的跨链信息会构造到Neo链的返回交易,neo-relayer会将这些交易所在的中继链区块头同步到Neo链上因为其中包含了状态根,同时转发这些交易的梅克尔证明及跨链信息。 +只要存在一个neo-relayer在工作,那么整个Neo跨链生态就可以持续运作。 + +## 准备 + +1. 申请中继链钱包 +申请一个中继链的钱包,如果有本体的钱包可以直接使用,二者钱包是通用的。 + +2. 注册Relayer +然后向中继链注册自己的地址为Relayer。 + +## 启动Relayer + +1. 下载neo-relayer的可执行文件,解压至特定位置 +2. 更改配置信息,例如: + +```json +{ + "RelayJsonRpcUrl": "http://138.91.6.125:40336", // 中继链rpc地址 + "RelayChainID": 0, // 中继链编码 + "WalletFile": "./wallet.dat", // 中继链钱包 + "NeoWalletFile": "TBD", // 待定,后续版本可能采用钱包文件以替代钱包wif + "NeoWalletWIF": "L3Hab7wL43SbWLnkfnVCp6dT99xzfB4qLZxeR9dFgcWWPirwKyXp", // Neo钱包wif + "NeoJsonRpcUrl": "http://47.89.240.111:11332", // Neo链rpc地址 + "NeoChainID": 4, // Neo链编码 + "NeoCCMC": "b0d4f20da68a6007d4fb7eac374b5566a5b0e229", // Neo链的跨链管理合约 + "ScanInterval": 2, // 扫描中继链的时间间隔,单位为秒 + "GasPrice": 0, + "GasLimit": 200000 +} +``` + +3. 输入./main --loglevel 0 --cliconfig “path” 执行Relayer可执行文件以运行neo-relayer diff --git a/neo/How_to_become_neo_relayer_en.md b/neo/How_to_become_neo_relayer_en.md new file mode 100644 index 0000000..59a621d --- /dev/null +++ b/neo/How_to_become_neo_relayer_en.md @@ -0,0 +1,45 @@ +# neo-relayer + +English | [中文](./How_to_become_neo_relayer_CN.md) + +## Structure + +On one hand, neo-relayer monitors Neo network continuously, scans each Neo block and synchronizes those key block headers which contains different "NextConsensus" field to the relay chain. At the same time, neo-relayer can recognize cross-chain transactions, and provides the "StateRoot" of the block containing those transactions together with the "MerkleProof" of those transactions to the relay chain. +On the other hand, neo-relayer also monitors the relay chain, scans each relay chain block and synchronizes those key block headers which has changed next consensus addresses to Neo chain. Moreover, the relay chain creates cross-chain transactions to Neo chain when it receives cross-chain transactions from other chains. Then neo-relayer relays the "StateRoot" of the block containing those transactions together with the "MerkleProof" of those transactions to Neo chain. +The entire Neo cross-chain ecosystem can continue to function normally even with just one active neo-relayer. + +## Setup + +1. Create a relay chain wallet +Create a wallet to be used on the relay chain. If you have an existing Ontology wallet it can be used on the relay chain. The two wallets are mutually interchangeable. + +2. Register the neo-relayer +Register your address as the **relayer** on the relay chain. + +## Start Up + +1. Download the executable file of the relayer and extract it to a specific directory. + +2. Modify the configuration settings. Here is a sample configuration: + +```json +{ + "RelayJsonRpcUrl": "http://138.91.6.125:40336", // the rpc address of the relay chain + "RelayChainID": 0, // the relay chain id + "WalletFile": "./wallet.dat", // the wallet used on the relay chain + "NeoWalletFile": "TBD", // to be determined, future versions may replace WIF with wallet file of Neo network + "NeoWalletWIF": "L3Hab7wL43SbWLnkfnVCp6dT99xzfB4qLZxeR9dFgcWWPirwKyXp", // private key in WIF format + "NeoJsonRpcUrl": "http://47.89.240.111:11332", // the rpc address of the Neo chain + "NeoChainID": 4, // Neo chain id + "NeoCCMC": "b0d4f20da68a6007d4fb7eac374b5566a5b0e229", // cross chain management contract of Neo chain + "ScanInterval": 2, // the time interval to scan the relay chain, in seconds + "GasPrice": 0, + "GasLimit": 200000 +} +``` + +3. Use the following command with the configuration file path to enable the relayer: + +```bash +./main --loglevel 0 --cliconfig "path" +``` diff --git a/neo/How_to_cross_NEP5_asset_cn.md b/neo/How_to_cross_NEP5_asset_cn.md new file mode 100644 index 0000000..49b9da2 --- /dev/null +++ b/neo/How_to_cross_NEP5_asset_cn.md @@ -0,0 +1,44 @@ +# 如何将NEP5资产跨到其他链上 + +[English](How_to_cross_NEP5_asset_en.md) | 中文 + +## 收集资产在链上对应的合约地址 + +以某个NEP5资产为例,这种资产在链上的合约地址为: + +-Neo链: 4da43995ee75be3931fd3abae06a3e6447d2febf (小端序) + +-Ethereum链: 0x86B7Be11D77043E5aDe3858f2F694E4f89d4a941 + +## 执行跨链操作 + +### 集成跨链功能的NEP5资产 + +如果该资产集成了跨链的Lock和Unlock接口,则可以直接调用该资产的Lock接口进行跨链资产转移: + +```C# +public static bool Lock(byte[] fromAddress, BigInteger toChainId, byte[] toAddress, BigInteger amount) +``` + +**参数设置:** + +- fromAddress: 5392ebf880d9a7119b17b2f1adaebc5f71c28e75 (Neo链地址APPmjituYcgfNxjuQDy9vP73R2PmhFsYJR的script hash,小端序) +- toChainId: 2 (Ethereum链的编码) +- toAddress: 0x0B24aBDd39185055311aaa27082F9dEb294A7255 (Ethereum链地址) +- amount: 10000 (跨链数量) + +### 未集成跨链功能的NEP5资产 + +如果该资产未集成跨链的Lock和Unlock接口,则需要调用该资产对应的代理合约的Lock接口: + +```C# +public static bool Lock(byte[] fromAssetHash, byte[] fromAddress, BigInteger toChainId, byte[] toAddress, BigInteger amount) +``` + +**参数设置:** + +- fromAssetHash: 4da43995ee75be3931fd3abae06a3e6447d2febf (NEP5资产的script hash,小端序) +- fromAddress: 5392ebf880d9a7119b17b2f1adaebc5f71c28e75 (Neo链地址APPmjituYcgfNxjuQDy9vP73R2PmhFsYJR的script hash,小端序) +- toChainId: 2 (Ethereum链的编码) +- toAddress: 0x0B24aBDd39185055311aaa27082F9dEb294A7255 (Ethereum链地址) +- amount: 10000 (跨链数量) diff --git a/neo/How_to_cross_NEP5_asset_en.md b/neo/How_to_cross_NEP5_asset_en.md new file mode 100644 index 0000000..544f569 --- /dev/null +++ b/neo/How_to_cross_NEP5_asset_en.md @@ -0,0 +1,44 @@ +# How to Transfer NEP5 Assets to Other Chains + +English | [中文](./How_to_cross_NEP5_asset_cn.md) + +## Collect the corresponding contract addresses for the asset on different chains + +For example, an NEP5 asset has the following contract addresses: + +-Neo chain: 4da43995ee75be3931fd3abae06a3e6447d2febf (little endian) + +-Ethereum chain: 0x86B7Be11D77043E5aDe3858f2F694E4f89d4a941 + +## Start cross chain operation + +### NEP5 assets with cross chain features + +If the asset has embedded the "Lock" and "Unlock" API methods, cross chain transactions can be carried out by invoking the "Lock" method directly: + +```C# +public static bool Lock(byte[] fromAddress, BigInteger toChainId, byte[] toAddress, BigInteger amount) +``` + +**Parameters setup:** + +- fromAddress: 5392ebf880d9a7119b17b2f1adaebc5f71c28e75 (script hash of a Neo chain address APPmjituYcgfNxjuQDy9vP73R2PmhFsYJR in little endian) +- toChainId: 2 (Ethereum chain id) +- toAddress: 0x0B24aBDd39185055311aaa27082F9dEb294A7255 (an address on Ethereum chain) +- amount: 10000 (cross chain amount) + +### NEP5 assets without cross chain features + +If the asset doesn't have the "Lock" and "Unlock" API methods natively, then the "Lock" method of its corresponding proxy contract is invoked to create cross chain transactions: + +```C# +public static bool Lock(byte[] fromAssetHash, byte[] fromAddress, BigInteger toChainId, byte[] toAddress, BigInteger amount) +``` + +**Parameters setup:** + +- fromAssetHash: 4da43995ee75be3931fd3abae06a3e6447d2febf (script hash of the NEP5 asset in little endian) +- fromAddress: 5392ebf880d9a7119b17b2f1adaebc5f71c28e75 (script hash of a Neo chain address APPmjituYcgfNxjuQDy9vP73R2PmhFsYJR in little endian) +- toChainId: 2 (Ethereum chain id) +- toAddress: 0x0B24aBDd39185055311aaa27082F9dEb294A7255 (an address on Ethereum chain) +- amount: 10000 (cross chain amount) diff --git a/neo/Neo_cross_chain_contract_dev_cn.md b/neo/Neo_cross_chain_contract_dev_cn.md new file mode 100644 index 0000000..3676923 --- /dev/null +++ b/neo/Neo_cross_chain_contract_dev_cn.md @@ -0,0 +1,399 @@ +# Neo跨链合约开发 + +[English](Neo_cross_chain_contract_dev_en.md) | 中文 + +Neo多链生态是基于跨链合约构建的,所谓跨链合约就是合约的逻辑贯穿两条甚至多条链,例如: + +在Neo上发布的某种NEP5资产可以和Ethereum上的某种ERC20资产相对应并互相流转。 + +## 跨链合约概述 + +跨链合约实际上是由多本合约构成的,例如dApp开发者需要在链A和链B上实现跨链业务,此时开发者需要分别在链A和链B上部署智能合约(智能合约A和智能合约B)。 + +跨链合约的开发总的来说可以分为两部分,业务部分和跨链部分: + +业务部分是指在某条链中运行的逻辑代码,按照标准的智能合约开发方式开发,完成合约在该链中的业务。 + +跨链部分主要是指跨链管理合约,当需要跨链时,如链A的逻辑执行完,接下来需要执行链B的逻辑,那么就需要用到跨链管理合约中的跨链接口。 + +## 跨链接口 + +合约的跨链对开发者来说只需要关注一个跨链接口,也就是跨链管理合约的`CrossChain`接口,该接口将链A上已经执行的业务存入梅克尔树,会有矿工生成该跨链交易的梅克尔证明,并将其提交到链B的跨链管理合约中,该跨链管理合约会验证梅克尔证明,并按照参数调用智能合约B中对应的方法。 + +## 跨链合约开发示例(新发行的NEP5资产) + +某开发者想在链A和链B上发行资产,但是希望链A和链B上的资产互通,也就是说其需要发行一种资产,这种资产能够在链A和链B上同时使用,并且可以在链A和链B上自由转移。 + +为了使资产可以在链A和链B之间互相转移,在NEP5标准接口的基础上,需要增加`Lock`和`Unlock`接口,用户在链A中调用`Lock`接口将资产锁定在智能合约A中,该接口同时调用跨链管理合约实现跨链调用智能合约B中的`Unlock`接口,并在链B中将智能合约B中的资产释放给该用户。反之亦然。 + +`Lock`接口实现: + +```C# +public static bool Lock(byte[] fromAddress, BigInteger toChainId, byte[] toAddress, BigInteger amount) +{ + // check parameters + if (!IsAddress(fromAddress)) + { + Runtime.Notify("The parameter fromAddress SHOULD be a legal address."); + return false; + } + if (toAddress.Length == 0) + { + Runtime.Notify("The parameter toAddress SHOULD not be empty."); + return false; + } + if (amount < 0) + { + Runtime.Notify("The parameter amount SHOULD not be less than 0."); + return false; + } + // more checks + if (!Runtime.CheckWitness(fromAddress)) + { + Runtime.Notify("Authorization failed."); + return false; + } + if (IsPaused()) + { + Runtime.Notify("The contract is paused."); + return false; + } + + // lock asset + StorageMap asset = Storage.CurrentContext.CreateMap(nameof(asset)); + var balance = asset.Get(fromAddress).AsBigInteger(); + if (balance < amount) + { + Runtime.Notify("Not enough balance to lock."); + return false; + } + StorageMap contract = Storage.CurrentContext.CreateMap(nameof(contract)); + var totalSupply = contract.Get("totalSupply").AsBigInteger(); + if (totalSupply < amount) + { + Runtime.Notify("Not enough supply to lock."); + return false; + } + asset.Put(fromAddress, balance - amount); + contract.Put("totalSupply", totalSupply - amount); + + // construct args for the corresponding asset contract on target chain + var inputBytes = SerializeArgs(toAddress, amount); + var toContract = GetContractAddress(toChainId); + // constrct params for CCMC + var param = new object[] { toChainId, toContract, "unlock", inputBytes }; + + // dynamic call CCMC + var ccmc = (DynCall)CCMCScriptHash.ToDelegate(); + var success = (bool)ccmc("CrossChain", param); + if (!success) + { + Runtime.Notify("Failed to call CCMC."); + return false; + } + + LockEvent(toChainId, fromAddress, toAddress, amount); + return true; +} +``` + +**参数设置:** + +- fromAddress: 跨链调用发起地址,矿工费从该地址扣除 +- toChainId: 目标链的链ID +- toAddress: 目标链上的用户地址 +- amount: 跨链资产数量 + +在上面的代码中可以看到该接口先冻结用户在链A上的需要跨链的数量的资产,并缩减该资产总量(因为该部分资产将锁定而不再流通),然后调用Neo跨链管理合约的`CrossChain`接口,该方法接受四个参数: + +```C# +public static bool CrossChain(BigInteger toChainID, byte[] toChainAddress, byte[] functionName, byte[] args) +``` + +**参数设置:** + +- toChainId: 目标链的链ID +- toChainAddress: 跨链需要调用的目标链上的合约 +- functionName: 跨链资产数量 +- args: 序列化之后的目标合约输入参数 + +在`Lock`接口的代码中可以看到该方法调用了目标合约的`Unlock`接口,`Unlock`接口实现: + +```C# +private static bool Unlock(byte[] inputBytes, byte[] fromContract, BigInteger fromChainId, byte[] caller) +{ + //only allowed to be called by CCMC + if (caller.AsBigInteger() != CCMCScriptHash.AsBigInteger()) + { + Runtime.Notify("Only allowed to be called by CCMC"); + return false; + } + + byte[] storedContract = GetContractAddress(fromChainId); + + // check the fromContract is stored, so we can trust it + if (fromContract.AsBigInteger() != storedContract.AsBigInteger()) + { + Runtime.Notify(fromContract); + Runtime.Notify(fromChainId); + Runtime.Notify(storedContract); + Runtime.Notify("From contract address not found."); + return false; + } + + // parse the args bytes constructed in source chain proxy contract, passed by multi-chain + object[] results = DeserializeArgs(inputBytes); + var toAddress = (byte[])results[0]; + var amount = (BigInteger)results[1]; + if (!IsAddress(toAddress)) + { + Runtime.Notify("ToChain Account address SHOULD be a legal address."); + return false; + } + if (amount < 0) + { + Runtime.Notify("ToChain Amount SHOULD not be less than 0."); + return false; + } + + // unlock asset + StorageMap asset = Storage.CurrentContext.CreateMap(nameof(asset)); + var balance = asset.Get(toAddress).AsBigInteger(); + StorageMap contract = Storage.CurrentContext.CreateMap(nameof(contract)); + var totalSupply = contract.Get("totalSupply").AsBigInteger(); + asset.Put(toAddress, balance + amount); + contract.Put("totalSupply", totalSupply + amount); + + UnlockEvent(toAddress, amount); + return true; +} +``` + +**参数设置:** + +- inputBytes: 序列化之后的合约参数 +- fromContract: 发起跨链的源链上的合约 +- fromChainId: 源链ID +- caller: 该方法的调用者,不用传入,合约自动补充该参数 + +该接口会先校验调用是不是来自跨链管理合约,然后校验源链上的合约地址是否可信,接着反序列化出所需参数,最后解锁链B上的资产给用户及增加该资产总量。 + +## 跨链合约开发示例(已发行的NEP5资产) + +对于已经存在的NEP5资产来说,其合约代码是不能修改并添加`Lock`、`Unlock`方法的,因此需要额外实现一个代理合约,相当于原先NEP5合约的补充功能,实现了跨链协议中的主要接口,也可以使用现有的代理合约,实现跨链,避免重复开发。 + +以下为代理合约必须实现的接口: + +### BindProxyHash + +```C# +public static bool BindProxyHash(BigInteger toChainId, byte[] targetProxyHash) +{ + if (!Runtime.CheckWitness(Operator)) return false; + StorageMap proxyHash = Storage.CurrentContext.CreateMap(nameof(proxyHash)); + proxyHash.Put(toChainId.AsByteArray(), targetProxyHash); + return true; +} +``` + +**参数设置:** + +- toChainId: 目标链的链ID +- targetProxyHash: 跨链需要调用的目标链上的代理合约 + +该接口绑定目标链上的代理合约hash,或者实现了跨链接口的合约hash,在NEP5朝其他链跨链的时候会将该目标链代理合约作为中转站,进而将NEP5转到目标链的映射合约,当然在跨链开始前应该预先部署好映射合约。 + +### BindAssetHash + +```C# +public static bool BindAssetHash(byte[] fromAssetHash, BigInteger toChainId, byte[] toAssetHash, BigInteger initialAmount) +{ + if (!Runtime.CheckWitness(Operator)) return false; + StorageMap assetHash = Storage.CurrentContext.CreateMap(nameof(assetHash)); + assetHash.Put(fromAssetHash.Concat(toChainId.AsByteArray()), toAssetHash); + + if (GetAssetBalance(fromAssetHash) != initialAmount) + { + Runtime.Notify("Initial amount incorrect."); + return false; + } + + StorageMap lockedAmount = Storage.CurrentContext.CreateMap(nameof(lockedAmount)); + lockedAmount.Put(fromAssetHash, initialAmount); + BindAssetHashEvent(fromAssetHash, toChainId, toAssetHash, initialAmount); + return true; +} +``` + +**参数设置:** + +- fromAssetHash: 源链上的资产合约hash +- toChainId: 目标链的链ID +- toAssetHash: 目标链上的资产合约hash +- initialAmount: 该资产初始锁定数量 + +该接口绑定NEP5代币合约地址和目标链映射合约,比如USDT地址,并设置初始锁定数量。 + +### Lock + +```C# +public static bool Lock(byte[] fromAssetHash, byte[] fromAddress, BigInteger toChainId, byte[] toAddress, BigInteger amount) +{ + // check parameters + if (fromAssetHash.Length != 20) + { + Runtime.Notify("The parameter fromAssetHash SHOULD be 20-byte long."); + return false; + } + if (fromAddress.Length != 20) + { + Runtime.Notify("The parameter fromAddress SHOULD be 20-byte long."); + return false; + } + if (toAddress.Length == 0) + { + Runtime.Notify("The parameter toAddress SHOULD not be empty."); + return false; + } + if (amount < 0) + { + Runtime.Notify("The parameter amount SHOULD not be less than 0."); + return false; + } + + // get the corresbonding asset on target chain + var toAssetHash = GetAssetHash(fromAssetHash, toChainId); + if (toAssetHash.Length == 0) + { + Runtime.Notify("Target chain asset hash not found."); + return false; + } + + // get the proxy contract on target chain + var toContract = GetProxyHash(toChainId); + if (toContract.Length == 0) + { + Runtime.Notify("Target chain proxy contract not found."); + return false; + } + + // transfer asset from fromAddress to proxy contract address, use dynamic call to call nep5 token's contract "transfer" + byte[] currentHash = ExecutionEngine.ExecutingScriptHash; // this proxy contract hash + var nep5Contract = (DynCall)fromAssetHash.ToDelegate(); + bool success = (bool)nep5Contract("transfer", new object[] { fromAddress, currentHash, amount }); + if (!success) + { + Runtime.Notify("Failed to transfer NEP5 token to proxy contract."); + return false; + } + + // construct args for proxy contract on target chain + var inputBytes = SerializeArgs(toAssetHash, toAddress, amount); + // constrct params for CCMC + var param = new object[] { toChainId, toContract, "unlock", inputBytes }; + // dynamic call CCMC + var ccmc = (DynCall)CCMCScriptHash.ToDelegate(); + success = (bool)ccmc("CrossChain", param); + if (!success) + { + Runtime.Notify("Failed to call CCMC."); + return false; + } + + // update locked amount + StorageMap lockedAmount = Storage.CurrentContext.CreateMap(nameof(lockedAmount)); + BigInteger old = lockedAmount.Get(fromAssetHash).ToBigInteger(); + lockedAmount.Put(fromAssetHash, old + amount); + + LockEvent(fromAssetHash, toChainId, toAssetHash, fromAddress, toAddress, amount); + + return true; +} +``` + +**参数设置:** + +- fromAssetHash: 源链上的NEP5资产合约hash +- fromAddress: 跨链调用发起地址,矿工费从该地址扣除 +- toChainId: 目标链的链ID +- toAddress: 目标链上的用户地址 +- amount: 跨链资产数量 + +该接口会把NEP5资产锁定到代理合约账户中,并调用跨链管理合约的`CrossChain`接口从而发出跨链请求。 + +### Unlock + +```C# +private static bool Unlock(byte[] inputBytes, byte[] fromProxyContract, BigInteger fromChainId, byte[] caller) +{ + //only allowed to be called by CCMC + if (caller.AsBigInteger() != CCMCScriptHash.AsBigInteger()) + { + Runtime.Notify("Only allowed to be called by CCMC"); + return false; + } + + byte[] storedProxy = GetProxyHash(fromChainId); + + // check the fromContract is stored, so we can trust it + if (fromProxyContract.AsBigInteger() != storedProxy.AsBigInteger()) + { + Runtime.Notify(fromProxyContract); + Runtime.Notify(fromChainId); + Runtime.Notify(storedProxy); + Runtime.Notify("From proxy contract not found."); + return false; + } + + // parse the args bytes constructed in source chain proxy contract, passed by multi-chain + object[] results = DeserializeArgs(inputBytes); + var assetHash = (byte[])results[0]; + var toAddress = (byte[])results[1]; + var amount = (BigInteger)results[2]; + if (assetHash.Length != 20) + { + Runtime.Notify("ToChain Asset script hash SHOULD be 20-byte long."); + return false; + } + if (toAddress.Length != 20) + { + Runtime.Notify("ToChain Account address SHOULD be 20-byte long."); + return false; + } + if (amount < 0) + { + Runtime.Notify("ToChain Amount SHOULD not be less than 0."); + return false; + } + + // transfer asset from proxy contract to toAddress + byte[] currentHash = ExecutionEngine.ExecutingScriptHash; // this proxy contract hash + var nep5Contract = (DynCall)assetHash.ToDelegate(); + bool success = (bool)nep5Contract("transfer", new object[] { currentHash, toAddress, amount }); + if (!success) + { + Runtime.Notify("Failed to transfer NEP5 token to toAddress."); + return false; + } + + // update locked amount + StorageMap lockedAmount = Storage.CurrentContext.CreateMap(nameof(lockedAmount)); + BigInteger old = lockedAmount.Get(assetHash).ToBigInteger(); + lockedAmount.Put(assetHash, old - amount); + + UnlockEvent(assetHash, toAddress, amount); + + return true; +} +``` + +**参数设置:** + +- inputBytes: 序列化之后的合约参数 +- fromProxyContract: 发起跨链的源链上的代理合约hash +- fromChainId: 源链ID +- caller: 该方法的调用者,不用传入,合约自动补充该参数 + +该接口会被跨链管理合约调用,为用户释放原先锁定的NEP5资产,即从其他链返回的NEP5代币。 + +所以代理合约主要实现的是NEP5的注册、锁定与解锁,锁定就是用户将NEP5转到合约中,解锁只有跨链管理合约可以调用。代理合约可以自己部署,也可以使用现有的合约,多个NEP5资产可以使用一本代理合约。 diff --git a/neo/Neo_cross_chain_contract_dev_en.md b/neo/Neo_cross_chain_contract_dev_en.md new file mode 100644 index 0000000..49eee97 --- /dev/null +++ b/neo/Neo_cross_chain_contract_dev_en.md @@ -0,0 +1,397 @@ +# Neo Cross Chain Contract Development + +English | [中文](Neo_cross_chain_contract_dev_cn.md) + +The Neo multi-chain ecosystem is based on cross chain contracts which have the capability to interact with two or more chains. For example: an NEP5 asset released on Neo can be transferred to Ethereum as a corresponding ERC20 asset, or a dApp contract which has some of its business logic taking place on Neo and other carried out on Ethereum. + +## Cross chain contract overview + +The cross chain system actually consists of multiple smart contracts. For example, if a dApp needs to achieve cross chain business logic on Chain A and Chain B, the dApp developers need to deploy smart contract A on Chain A and smart contract B on Chain B. Both smart contracts have part of the dApp business logic. + +Cross chain contract development is composed of two parts: the business logic part and the cross chain supporting part. + +The business logic part is nothing special and should be developed as a conventional smart contract application. It runs its business logic code on the chain where it is deployed. + +The cross chain supporting part is mainly about the cross chain management contract (CCMC). When the business logic is finished on Chain A and the business logic on Chain B is required to be executed, then those interfaces in the CCMC are used. + +## Cross chain interface + +Cross chain contract developers only need to focus on one interface which is the `CrossChain` method in the CCMC. This method stores the business logic execution result on Chain A into a merkle tree. A miner then generates the merkle proof of this cross chain transaction and sends it to the CCMC on Chain B. This CCMC verifies the merkle proof and invokes the corresponding contract methods on Chain B based on the parameters passed by. + +## Cross chain contract development sample (for new released NEP5 asset) + +A developer intends to release a new asset on Chain A and Chain B and wants the asset on both chains are interchangeable which means the asset can be used and transferred between two chains conveniently. + +In order to achieve the interoperability of the asset on both chains, two additional methods `Lock` and `Unlock` are added to the standard NEP5 contracts. The user invokes the `Lock` method on Chain A and the contract locks the cross chain amount of the asset. At the same time, the method then invokes the CCMC on Chain A which invokes the `Unlock` method of the contract on Chain B to release the corresponding amount of asset back to the user, and vice versa. + +Sample `Lock` method implementation in C#: + +```C# +public static bool Lock(byte[] fromAddress, BigInteger toChainId, byte[] toAddress, BigInteger amount) +{ + // check parameters + if (!IsAddress(fromAddress)) + { + Runtime.Notify("The parameter fromAddress SHOULD be a legal address."); + return false; + } + if (toAddress.Length == 0) + { + Runtime.Notify("The parameter toAddress SHOULD not be empty."); + return false; + } + if (amount < 0) + { + Runtime.Notify("The parameter amount SHOULD not be less than 0."); + return false; + } + // more checks + if (!Runtime.CheckWitness(fromAddress)) + { + Runtime.Notify("Authorization failed."); + return false; + } + if (IsPaused()) + { + Runtime.Notify("The contract is paused."); + return false; + } + + // lock asset + StorageMap asset = Storage.CurrentContext.CreateMap(nameof(asset)); + var balance = asset.Get(fromAddress).AsBigInteger(); + if (balance < amount) + { + Runtime.Notify("Not enough balance to lock."); + return false; + } + StorageMap contract = Storage.CurrentContext.CreateMap(nameof(contract)); + var totalSupply = contract.Get("totalSupply").AsBigInteger(); + if (totalSupply < amount) + { + Runtime.Notify("Not enough supply to lock."); + return false; + } + asset.Put(fromAddress, balance - amount); + contract.Put("totalSupply", totalSupply - amount); + + // construct args for the corresponding asset contract on target chain + var inputBytes = SerializeArgs(toAddress, amount); + var toContract = GetContractAddress(toChainId); + // constrct params for CCMC + var param = new object[] { toChainId, toContract, "unlock", inputBytes }; + + // dynamic call CCMC + var ccmc = (DynCall)CCMCScriptHash.ToDelegate(); + var success = (bool)ccmc("CrossChain", param); + if (!success) + { + Runtime.Notify("Failed to call CCMC."); + return false; + } + + LockEvent(toChainId, fromAddress, toAddress, amount); + return true; +} +``` + +**Parameters setup:** + +- fromAddress: the user account address starting the cross chain operation where mining fee is deducted +- toChainId: the target chain ID +- toAddress: the user address on target chain +- amount: cross chain asset amount + +In the above code, the method first locks the specified amount of the user's asset on Chain A, reduces the asset total supply (since that amount of asset is locked and not circulating), and then invokes the `CrossChain` of the Neo CCMC which takes four parameters: + +```C# +public static bool CrossChain(BigInteger toChainID, byte[] toChainAddress, byte[] functionName, byte[] args) +``` + +**Parameters setup:** + +- toChainId: the target chain ID +- toChainAddress: the target chain contract address whose `Unlock` method needs to be invoked +- functionName: the method name of the target chain contract, in this case it's "unlock" +- args: the serialized parameters for the target chain contract + +In the above code of `Lock` method, the method name parameter passed to the CCMC is "unlock", so here is the `Unlock` method: + +```C# +private static bool Unlock(byte[] inputBytes, byte[] fromContract, BigInteger fromChainId, byte[] caller) +{ + //only allowed to be called by CCMC + if (caller.AsBigInteger() != CCMCScriptHash.AsBigInteger()) + { + Runtime.Notify("Only allowed to be called by CCMC"); + return false; + } + + byte[] storedContract = GetContractAddress(fromChainId); + + // check the fromContract is stored, so we can trust it + if (fromContract.AsBigInteger() != storedContract.AsBigInteger()) + { + Runtime.Notify(fromContract); + Runtime.Notify(fromChainId); + Runtime.Notify(storedContract); + Runtime.Notify("From contract address not found."); + return false; + } + + // parse the args bytes constructed in the source chain proxy contract, passed by multi-chain + object[] results = DeserializeArgs(inputBytes); + var toAddress = (byte[])results[0]; + var amount = (BigInteger)results[1]; + if (!IsAddress(toAddress)) + { + Runtime.Notify("ToChain Account address SHOULD be a legal address."); + return false; + } + if (amount < 0) + { + Runtime.Notify("ToChain Amount SHOULD not be less than 0."); + return false; + } + + // unlock asset + StorageMap asset = Storage.CurrentContext.CreateMap(nameof(asset)); + var balance = asset.Get(toAddress).AsBigInteger(); + StorageMap contract = Storage.CurrentContext.CreateMap(nameof(contract)); + var totalSupply = contract.Get("totalSupply").AsBigInteger(); + asset.Put(toAddress, balance + amount); + contract.Put("totalSupply", totalSupply + amount); + + UnlockEvent(toAddress, amount); + return true; +} +``` + +**Parameters setup:** + +- inputBytes: serialized parameters for business logic +- fromContract: the contract on the source chain which invokes the cross chain operation +- fromChainId: the source chain ID +- caller: the caller of this method, no need to pass by, the contract adds this parameter automatically + +This method first checks if it's invoked by the CCMC, then checks the "from" contract parameter is same as what's stored in its storage, deserializes the parameters to run business logic and finally unlocks the user's asset on Chain B and increases the asset total supply. + +## Cross chain contract development sample (for existing NEP5 asset) + +Existing NEP5 assets cannot be modified and implement the `Lock` and `Unlock` methods. So an additional proxy contract is needed to add the cross chain functionality to those NEP5 contracts. Also, off the shelf proxy contracts are ready to be used to avoid duplicate development. + +The proxy contract must implement the following methods: + +### BindProxyHash + +```C# +public static bool BindProxyHash(BigInteger toChainId, byte[] targetProxyHash) +{ + if (!Runtime.CheckWitness(Operator)) return false; + StorageMap proxyHash = Storage.CurrentContext.CreateMap(nameof(proxyHash)); + proxyHash.Put(toChainId.AsByteArray(), targetProxyHash); + return true; +} +``` + +**Parameters setup:** + +- toChainId: the target chain ID +- targetProxyHash: the proxy contract hash on target chain + +This method binds the target chain proxy contract hash with chain ID, and acts as the intermerdiary before NEP5 assets are transferred to the token contract on the target chain which is deployed in advance. + +### BindAssetHash + +```C# +public static bool BindAssetHash(byte[] fromAssetHash, BigInteger toChainId, byte[] toAssetHash, BigInteger initialAmount) +{ + if (!Runtime.CheckWitness(Operator)) return false; + StorageMap assetHash = Storage.CurrentContext.CreateMap(nameof(assetHash)); + assetHash.Put(fromAssetHash.Concat(toChainId.AsByteArray()), toAssetHash); + + if (GetAssetBalance(fromAssetHash) != initialAmount) + { + Runtime.Notify("Initial amount incorrect."); + return false; + } + + StorageMap lockedAmount = Storage.CurrentContext.CreateMap(nameof(lockedAmount)); + lockedAmount.Put(fromAssetHash, initialAmount); + BindAssetHashEvent(fromAssetHash, toChainId, toAssetHash, initialAmount); + return true; +} +``` + +**Parameters setup:** + +- fromAssetHash: the asset contract hash on the source chain +- toChainId: the target chain ID +- toAssetHash: the asset contract hash on the target chain +- initialAmount: initial locked amount + +This method binds the NEP5 token contract hash with the corresponding asset hash on the target chain, such as the USDT contract hash, and sets the cross chain amount limit. + +### Lock + +```C# +public static bool Lock(byte[] fromAssetHash, byte[] fromAddress, BigInteger toChainId, byte[] toAddress, BigInteger amount) +{ + // check parameters + if (fromAssetHash.Length != 20) + { + Runtime.Notify("The parameter fromAssetHash SHOULD be 20-byte long."); + return false; + } + if (fromAddress.Length != 20) + { + Runtime.Notify("The parameter fromAddress SHOULD be 20-byte long."); + return false; + } + if (toAddress.Length == 0) + { + Runtime.Notify("The parameter toAddress SHOULD not be empty."); + return false; + } + if (amount < 0) + { + Runtime.Notify("The parameter amount SHOULD not be less than 0."); + return false; + } + + // get the corresbonding asset on target chain + var toAssetHash = GetAssetHash(fromAssetHash, toChainId); + if (toAssetHash.Length == 0) + { + Runtime.Notify("Target chain asset hash not found."); + return false; + } + + // get the proxy contract on target chain + var toContract = GetProxyHash(toChainId); + if (toContract.Length == 0) + { + Runtime.Notify("Target chain proxy contract not found."); + return false; + } + + // transfer asset from fromAddress to proxy contract address, use dynamic call to call nep5 token's contract "transfer" + byte[] currentHash = ExecutionEngine.ExecutingScriptHash; // this proxy contract hash + var nep5Contract = (DynCall)fromAssetHash.ToDelegate(); + bool success = (bool)nep5Contract("transfer", new object[] { fromAddress, currentHash, amount }); + if (!success) + { + Runtime.Notify("Failed to transfer NEP5 token to proxy contract."); + return false; + } + + // construct args for proxy contract on target chain + var inputBytes = SerializeArgs(toAssetHash, toAddress, amount); + // constrct params for CCMC + var param = new object[] { toChainId, toContract, "unlock", inputBytes }; + // dynamic call CCMC + var ccmc = (DynCall)CCMCScriptHash.ToDelegate(); + success = (bool)ccmc("CrossChain", param); + if (!success) + { + Runtime.Notify("Failed to call CCMC."); + return false; + } + + // update locked amount + StorageMap lockedAmount = Storage.CurrentContext.CreateMap(nameof(lockedAmount)); + BigInteger old = lockedAmount.Get(fromAssetHash).ToBigInteger(); + lockedAmount.Put(fromAssetHash, old + amount); + + LockEvent(fromAssetHash, toChainId, toAssetHash, fromAddress, toAddress, amount); + + return true; +} +``` + +**Parameters setup:** + +- fromAssetHash: the NEP5 contract hash on the source chain +- fromAddress: the user account address starting the cross chain operation where mining fee is deducted +- toChainId: the target chain ID +- toAddress: the user account address on the target chain +- amount: the cross chain asset amount + +This method locks the NEP5 asset in the contract address, and invokes the `CrossChain` method of the CCMC to start a cross chain operation. + +### Unlock + +```C# +private static bool Unlock(byte[] inputBytes, byte[] fromProxyContract, BigInteger fromChainId, byte[] caller) +{ + //only allowed to be called by CCMC + if (caller.AsBigInteger() != CCMCScriptHash.AsBigInteger()) + { + Runtime.Notify("Only allowed to be called by CCMC"); + return false; + } + + byte[] storedProxy = GetProxyHash(fromChainId); + + // check the fromContract is stored, so we can trust it + if (fromProxyContract.AsBigInteger() != storedProxy.AsBigInteger()) + { + Runtime.Notify(fromProxyContract); + Runtime.Notify(fromChainId); + Runtime.Notify(storedProxy); + Runtime.Notify("From proxy contract not found."); + return false; + } + + // parse the args bytes constructed in source chain proxy contract, passed by multi-chain + object[] results = DeserializeArgs(inputBytes); + var assetHash = (byte[])results[0]; + var toAddress = (byte[])results[1]; + var amount = (BigInteger)results[2]; + if (assetHash.Length != 20) + { + Runtime.Notify("ToChain Asset script hash SHOULD be 20-byte long."); + return false; + } + if (toAddress.Length != 20) + { + Runtime.Notify("ToChain Account address SHOULD be 20-byte long."); + return false; + } + if (amount < 0) + { + Runtime.Notify("ToChain Amount SHOULD not be less than 0."); + return false; + } + + // transfer asset from proxy contract to toAddress + byte[] currentHash = ExecutionEngine.ExecutingScriptHash; // this proxy contract hash + var nep5Contract = (DynCall)assetHash.ToDelegate(); + bool success = (bool)nep5Contract("transfer", new object[] { currentHash, toAddress, amount }); + if (!success) + { + Runtime.Notify("Failed to transfer NEP5 token to toAddress."); + return false; + } + + // update locked amount + StorageMap lockedAmount = Storage.CurrentContext.CreateMap(nameof(lockedAmount)); + BigInteger old = lockedAmount.Get(assetHash).ToBigInteger(); + lockedAmount.Put(assetHash, old - amount); + + UnlockEvent(assetHash, toAddress, amount); + + return true; +} +``` + +**Parameters setup:** + +- inputBytes: serialized parameters for business logic +- fromProxyContract: the proxy hash starting the cross chain operation on the source chain +- fromChainId: the source chain ID +- caller: the caller of this method, no need to pass by, the contract adds this parameter automatically + +This method is only called by the CCMC and returns the previously locked NEP5 asset to the user. + +In conclusion, the proxy contract is mainly used to bind, lock and unlock the NEP5 asset. "lock" means users transfer their asset to the proxy contract address and "unlock" does the opposite. Developers can either deploy their own proxy contracts, or use the existing ones. Multiple NEP5 assets can share a proxy contract. diff --git a/neo/README.md b/neo/README.md new file mode 100644 index 0000000..7d505f6 --- /dev/null +++ b/neo/README.md @@ -0,0 +1,61 @@ +# Design of Neo Cross Chain + +English | [中文](README_CN.md) + +## Overview + +Cross chain is the procedure of how information interacts between two or more block chains. The information can be the business logic of an dApp, or an asset released on one chain and etc. Currently there are two ways to achieve cross chain operation on Neo: one is to use full nodes like Neo-CLI and Neo-GUI to invoke contracts and send out cross chain transactions, the other is to construct cross chain transactions with SDK. + +## Cross chain implementation + +Take asset cross chain as an example: users should be able to lock their assets on the source chain and release corresponding assets on the target chain, they also can request to withdraw their assets on the target chain and finally unlock their assets from the source chain. To achieve this goal, it is required that what has happened on the source chain must be verifiable by the target chain, in this case, which means the target chain can verify that the cross chain transactions do exist and certain amount of assets are locked on the source chain. + +It is same for other cross chain information. The target chain needs to verify the information change on the source chain and make corresponding actions. + +Currently such verification procedures are carried out in the form of merkle proof. The source chain stores cross chain information and constrct a merkle tree, and then send the merkle tree root and the merkle proof of the information to the target chain. The target chain verify the proof using the merkle root and confirm the validity of the cross chain information. + +## Cross chain structure + +
+ +The above picture shows the structure of Neo cross chain ecosystem which consists of Neo chain, neo-relayer, the relay chain Poly, relayer of the target chain and the target chain. Briefly speaking, the merkle proof of the cross chain transactions created by on Neo chain is delivered to Poly by neo-relayer, and then delivered to the target chain by its relayers. The target chain verifies the proof and make corresponding actions and vice versa. + +The entities involved in the ecosystem are defined as below: + +- [**Poly**](https://github.com/PolygonNetwork/docs/blob/master/poly/How_to_join_cross_chain.md):The relay chain is one of the crucial components of the cross chain ecosystem. Each node of it is deployed and maintained by different individuals or organizations. The relay chain has its unique governance and trust mechanism and is responsible for connecting different chains and relay cross chain information. +- [**Relayer**](How_to_become_neo_relayer_en.md):Every chain has its own relays which basically transmit the cross chain transaction information from or to the relay chain, thus connecting the relay chain with the outside world. Relayers collect small incentives during this procedure. +- [**Applications**](Neo_cross_chain_contract_dev_en.md):Applications refer to people or organizations who develop and implement cross chain business. Anyone can deploy cross chain contracts to start cross chain business. +- [**Users**](How_to_cross_NEP5_asset_en.md):Users are those who use cross chain applications to launch their business across multiple chains. + +## Block header synchronization between Neo chain and the relay chain + +
+ +The above picture shows the procedure of Block header synchronization between Neo chain and the relay chain: + +On one hand, the consensus nodes of Neo chain are voted and elected by NEO holders for each block, so the block validators can be changed in any future block. If the block validators are not changed, then there's no need to sync this block header. Only key headers (which changes validators, are also called epoch changing blocks) are required to sync. This greatly reduces the amount of headers needed to sync. And the merkle root is not included in Neo's block headers, instead is signed and broadcast by the consensus nodes independently. So if a cross chain transaction occurs, neo-relayer sends the merkle root, merkle proof and block height to the relay chain, and relay chain updates the current synced Neo block height and relays the root and proof to the target chain. + +On the other hand, the relay chain block headers are synced to Neo chain by neo-relayers. Then the cross chain management contract of Neo chain validates and stores these block headers. Any contracts on Neo chain can read these stored headers. + +## Cross chain transactions between Neo chain and the relay chain + +
+ +The cross chain procedure from Neo chain to the target chain: + +1. Users create cross chain transactions in business contracts on Neo chain; +2. Business contracts invoke the cross chain method of the cross chain management contract, which handles these transactions and assigns a unique ID for each transaction; +3. The consensus nodes then generates the merkle root of the block containing the cross chain transaction and merkle proof of that transaction; +4. The neo-relayers get the merkle root and proof through RPC and send them to the relay chain; +5. The relay chain verifies the validity of the merkle proof and broadcasts the cross chain information as events. The relayers of the target chain monitors these events, catches those are related to their chain and sends the information to the target chain; +6. The cross chain management contract of the target chain verifies the validity of the merkle proof of the relay chain. Successful verification means the cross chain information from the source chain is valid. The cross chain management contract of the target chain invokes corresponding business contract. + +
+ +The cross chain procedure from the target chain back to Neo chain: + +1. Users create cross chain transactions in business contracts on the target chain; +2. The relayers of the target chain send the cross chain information to the relay chain and invoke the cross chain management contract of the relay chain; +3. The cross chain management contract verifies and stores the cross chain information, and builds a new merkle tree, adds the root into the block header of the relay chain, and generates the merkle proof for the cross chain information; +4. The relayers of Neo monitors the relay chain events, and sends the related block header and proof to Neo chain; +5. The cross chain management contract of Neo chain verifies the merkle proof and invoke corresponding business contract to finish the business logic. diff --git a/neo/README_CN.md b/neo/README_CN.md new file mode 100644 index 0000000..169337a --- /dev/null +++ b/neo/README_CN.md @@ -0,0 +1,61 @@ +# Neo跨链设计 + +[English](README.md) | 中文 + +## 概述 + +所谓跨链,即是信息在两条甚至多条链之间交互的过程,这里的信息可以是一个dApp的业务逻辑,也可以是某一条链上的一种资产,等等。目前在Neo上有两种方式可以实现跨链操作:一种是通过节点如Neo-CLI和Neo-GUI等调用合约发送跨链交易,另一种是通过SDK构造跨链交易。 + +## 跨链的实现 + +以资产跨链为例,用户把资产在原链上锁定,之后在目标链上发行映射资产,同时可以在目标链上申请提现,最后在原链上解锁的过程。要实现该过程,则需要目标链可以验证原链上发生的行为,即验证原链上确实锁定了一定数量的原资产。 + +非资产类的信息跨链也是如此,需要目标链验证原链上的信息变化,从而做出相应的变化。 + +这种验证过程目前都是通过merkle证明的形式实现的,即原链将其上发生的行为存储下来,并构造一棵merkle tree,然后将merkle tree的树根和该行为的merkle proof提交给目标链。目标链根据提交的merkle root验证proof的合法性,从而确定原链上发生的行为。 + +## 架构 + +
+ +上图显示了Neo跨链生态的架构,从上到下分别是Neo链、Neo链的Relayer、中继链Poly、目标链的Relayer和目标链。简单来说,用户在Neo链上发出的跨链交易的证明会经由Relayer传递到Poly,再由目标链的Relayer传递到目标链,目标链验证Neo链上的交易证明并执行相应的交易。反之亦然。 + +生态中的角色定义如下: + +- [**中继链 Poly**](https://github.com/PolygonNetwork/docs/blob/master/poly/How_to_join_cross_chain_cn.md):中继链是整个生态中的重要部分,每个节点由不同的个人或组织运行,有自己独特的治理模式和信任机制,它负责将各个链连接到一起和跨链信息的传递。 +- [**Relayer**](how_to_become_neo_relayer_cn.md):每条链都有自己的Relayer,它们负责把交易等跨链信息搬运到中继链或从中继链搬运到源链,并且它们会在这个过程中获取收益。 +- [**应用**](Neo_cross_chain_contract_dev_cn.md):应用是指开发跨链业务的人或组织,任何人都可以部署跨链合约来构建跨链应用,然后把应用公开出去招揽用户。 +- [**用户**](how_to_cross_NEP5_asset_cn.md):对跨链生态来说,最重要的就是用户,通过调用具有跨链功能的应用,实现Neo到以太坊等链的跨链业务。 + +## Neo链和中继链之间的区块头同步 + +
+ +上图展示了Neo链和中继链之间同步区块头的具体流程: + +一方面,Neo链的共识节点由Neo持有者实时投票选举产生,理论上每个区块的验证者集合都可能变化。若验证者集合保持不变,则不需要同步该区块头,只需要同步关键区块头(即包含切换了验证者集合后的区块头),这样可以大大减少需要同步的区块头数量。同时由于Neo的merkle root不包含在区块头中,而是由共识节点独立签名并广播,所以当有跨链交易发生时,Relayer会把这些交易所在区块的merkle root和proof以及区块高度转发给中继链,中继链仅更新一下当前已同步的Neo链的高度并转发merkle root和proof。 + +另一方面,中继链区块头由Relayer同步到Neo链,Neo链的跨链管理合约会验证区块头的合法性,并存储区块头,该链的其它任何合约都可以从该合约中读取同步的区块头。 + +## Neo链和中继链之间的跨链交易 + +
+ +从Neo链到目标链的跨链流程如下: + +1. 用户在Neo链上的业务合约中发起跨链交易; +2. 业务合约调用跨链管理合约的跨链接口,跨链管理合约处理跨链请求,分配唯一自增ID,发出跨链事件; +3. 共识节点会生成该跨链交易所在区块的merkle root和该交易的merkle proof; +4. Neo的Relayer捕获跨链事件后通过RPC调用获取上述merkle root和merkle proof以及区块高度并提交到中继链; +5. 中继链验证merkle proof的合法性,将跨链交易的信息以事件的形式广播,目标链的Relayer会监听这些事件并把发往自己链的交易捕获到,然后转发到对应目标链; +6. 目标链的跨链管理合约验证中继链merkle proof的合法性,验证通过则说明原链上的跨链信息合法,目标链的跨链管理合约会调用相应的业务合约,执行目标链上的业务逻辑。 + +
+ +从目标链返回到Neo链的跨链流程如下: + +1. 用户在目标链上的业务合约中发起跨链交易; +2. 目标链的Relayer将跨链交易信息转发到中继链,调用中继链的跨链管理合约; +3. 跨链管理合约验证交易信息后,将验证后的信息存下来,并构造新的merkle tree,将树根写入中继链的区块头中,并生成跨链信息的merkle proof; +4. Neo的Relayer会监听中继链,将区块头和proof提交到Neo链; +5. Neo链的跨链管理合约会读取中继链区块头,验证中继链的proof,然后调用业务合约执行对应的逻辑。 diff --git a/neo/resources/CrossChainStructure.png b/neo/resources/CrossChainStructure.png new file mode 100644 index 0000000..49c7969 Binary files /dev/null and b/neo/resources/CrossChainStructure.png differ diff --git a/neo/resources/HeaderSync.png b/neo/resources/HeaderSync.png new file mode 100644 index 0000000..f21f914 Binary files /dev/null and b/neo/resources/HeaderSync.png differ diff --git a/neo/resources/Neo2Relay.png b/neo/resources/Neo2Relay.png new file mode 100644 index 0000000..42f4fb5 Binary files /dev/null and b/neo/resources/Neo2Relay.png differ diff --git a/neo/resources/Relay2Neo.png b/neo/resources/Relay2Neo.png new file mode 100644 index 0000000..8fc6718 Binary files /dev/null and b/neo/resources/Relay2Neo.png differ