├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── apps └── tics-030-non-fungible-token-transfer │ └── README.md ├── client ├── tics-007-tendermint-client │ └── README.md ├── tics-008-bsc-client │ └── README.md └── tics-009-eth-client │ └── README.md ├── core ├── tics-002-client-semantics │ └── README.md ├── tics-004-port-and-packet-semantics │ └── README.md ├── tics-024-Host-Environments │ └── README.md └── tics-026-packet-routing │ └── README.md ├── relayer └── tics-018-relayer-algorithms │ ├── README.md │ ├── tibc-relayer-architecture.png │ ├── tibc-relayer-one-hop.png │ └── tibc-relayer-two-hops.png └── zh ├── apps └── tics-030-non-fungible-token-transfer │ └── README.md ├── client ├── tics-007-tendermint-client │ └── README.md ├── tics-008-bsc-client │ └── README.md └── tics-009-eth-client │ └── README.md ├── core ├── tics-002-client-semantics │ └── README.md ├── tics-004-port-and-packet-semantics │ └── README.md ├── tics-024-Host-Environments │ └── README.md └── tics-026-packet-routing │ └── README.md └── relayer └── tics-018-relayer-algorithms ├── README.md ├── relayer架构.png ├── tibc-relayer-一跳跨链时序图.png └── tibc-relayer-两跳跨链时序图.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for considering making contributions to TIBC! 4 | 5 | Contributing to this repo can mean many things such as participated in 6 | discussion or proposing code changes. To ensure a smooth workflow for all 7 | contributors, the general procedure for contributing has been established: 8 | 9 | 1. either [open](https://github.com/bianjieai/tibc/issues/new) or 10 | [find](https://github.com/bianjieai/tibc/issues) an issue you'd like to help with, 11 | 2. participate in thoughtful discussion on that issue, 12 | 3. if you would then like to contribute code: 13 | 1. if the issue is a proposal, ensure that the proposal has been accepted, 14 | 2. ensure that nobody else has already begun working on this issue, if they have 15 | make sure to contact them to collaborate, 16 | 3. if nobody has been assigned the issue and you would like to work on it 17 | make a comment on the issue to inform the community of your intentions 18 | to begin work, 19 | 4. follow standard github best practices: fork the repo, 20 | if the issue if a bug fix, branch from the 21 | tip of `develop`, make some commits, and submit a PR to `develop`; if the issue is a new feature, branch from the tip of `feature/XXX`, make some commits, and submit a PR to `feature/XXX` 22 | 5. include `WIP:` in the PR-title to and submit your PR early, even if it's 23 | incomplete, this indicates to the community you're working on something and 24 | allows them to provide comments early in the development process. When the code 25 | is complete it can be marked as ready-for-review by replacing `WIP:` with 26 | `R4R:` in the PR-title. 27 | 28 | Note that for very small or blatantly obvious problems (such as typos) it is 29 | not required to an open issue to submit a PR, but be aware that for more complex 30 | problems/features, if a PR is opened before an adequate design discussion has 31 | taken place in a github issue, that PR runs a high likelihood of being rejected. 32 | 33 | ## Pull Requests 34 | 35 | To accommodate review process we suggest that PRs are categorically broken up. 36 | Ideally each PR addresses only one single issue. Additionally, as much as possible 37 | code refactoring and cleanup should be submitted as a separate PRs. And the feature branch `feature/XXX` should be synced with `develop` regularly. 38 | 39 | ### PR Targeting 40 | 41 | Ensure that you base and target your PR on the correct branch: 42 | 43 | - `release/vxx.yy` for a merge into a release candidate 44 | - `master` for a merge of a release 45 | - `develop` in the usual case 46 | 47 | All feature additions should be targeted against `feature/XXX`. Bug fixes for an outstanding release candidate 48 | should be targeted against the release candidate branch. Release candidate branches themselves should be the 49 | only pull requests targeted directly against master. 50 | 51 | ### Development Procedure 52 | 53 | - the latest state of development is on `develop` 54 | - no --force onto `develop` (except when reverting a broken commit, which should seldom happen) 55 | 56 | ### Pull Merge Procedure 57 | 58 | - ensure `feature/XXX` is rebased on `develop` 59 | - ensure pull branch is rebased on `feature/XXX` 60 | - merge pull request 61 | - push `feature/XXX` into `develop` regularly 62 | 63 | ### Release Procedure 64 | 65 | - start on `develop` 66 | - prepare changelog/release issue 67 | - merge to `master` 68 | - tag on `master` 69 | 70 | ### Hotfix Procedure 71 | 72 | - checkout `hotfix/vX.X.X` rebase on `vX.X.X` 73 | - make the required changes 74 | - these changes should be small and an absolute necessity 75 | - add a note to CHANGELOG.md 76 | - tag on `hotfix/vX.X.X` 77 | - merge `hotfix/vX.X.X` to master if necessary 78 | - merge `hotfix/vX.X.X` to develop if necessary 79 | - delete `hotfix/vX.X.X` 80 | 81 | 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 Shanghai Bianjie Technology Ltd. 上海边界智能科技有限公司 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TIBC 2 | Terse Interchain Standards (TICS) for the Cosmos network & interchain ecosystem. 3 | 4 | ## Synopsis 5 | 6 | This repository is the canonical location for development and documentation of the terse inter-blockchain communication protocol (TIBC). 7 | 8 | TIBC protocol, based on IBC protocol, simplifies and updates some IBC designs and implementations to reduce complexity of connections with heterogeneous blockchains and to enhance the abilities of cross-chain NFT, smart-contract, and services interactions. 9 | 10 | This repository shall be used to consolidate design rationale, protocol semantics, and encoding descriptions for TIBC, including both the core transport, authentication, & ordering layer (TIBC/TAO) and the application layers describing packet encoding & processing semantics (TIBC/APP). 11 | 12 | Contributions are welcome. 13 | 14 | ## Standardisation 15 | Please see [ICS 1](https://github.com/cosmos/ibc/blob/master/spec/ics-001-ics-standard/README.md) for a description of what a standard entails. 16 | 17 | To propose a new standard, open an issue. 18 | 19 | To start a new standardisation document, copy the template and open a PR. 20 | 21 | See PROCESS.md for a description of the standardisation process. 22 | 23 | See STANDARDS_COMMITTEE.md for the membership of the core standardisation committee. 24 | 25 | See CONTRIBUTING.md for contribution guidelines. 26 | 27 | ## Interchain Standards 28 | 29 | All standards at or past the "Draft" stage are listed here in order of their ICS/TICS numbers, sorted by category. 30 | 31 | ### Meta 32 | 33 | | Interchain Standard Number | Standard Title | Stage | 34 | | ---------------------------------------- | -------------------------- | ----- | 35 | | [1](https://github.com/cosmos/ibc/blob/master/spec/ics-001-ics-standard/README.md) | ICS Specification Standard | N/A | 36 | 37 | ### Core 38 | 39 | | Terse Interchain Standard Number | Standard Title | Stage | 40 | | ------------------------------------------------------------- | -------------------------- | ----- | 41 | | [2](core/tics-002-client-semantics/README.md) | Client Semantics | Candidate | 42 | | [4](core/tics-004-port-and-packet-semantics/README.md) | Port & Packet Semantics | Candidate | 43 | | [24](core/tics-024-Host-Environments/README.md) | Host Environments | Candidate | 44 | | [26](core/tics-026-packet-routing/README.md) | Packet Routing | Candidate | 45 | 46 | ### Client 47 | 48 | | Terse Interchain Standard Number | Standard Title | Stage | 49 | | ---------------------------------------------------- | ------------------- | --------- | 50 | | [7](client/tics-007-tendermint-client/README.md) | Tendermint Client | Candidate | 51 | | [8](client/tics-008-bsc-client/README.md) | BSC Client | Candidate | 52 | | [9](client/tics-009-eth-client/README.md) | Ethereum Client | Candidate | 53 | 54 | 55 | ### Relayer 56 | 57 | | Terse Interchain Standard Number | Standard Title | Stage | 58 | | ---------------------------------------------------------------- | -------------------------- | ----- | 59 | | [18](relayer/tics-018-relayer-algorithms/README.md) | Relayer Algorithms | Candidate | 60 | 61 | ### App 62 | 63 | | Terse Interchain Standard Number | Standard Title | Stage | 64 | | -------------------------------------------------------- | ----------------------- | ----- | 65 | | [30](apps/nft/tics-030-non-fungible-token-transfer.md) | Non-fungible Token Transfer | Candidate | 66 | -------------------------------------------------------------------------------- /apps/tics-030-non-fungible-token-transfer/README.md: -------------------------------------------------------------------------------- 1 | ## Synopsis 2 | 3 | This standard document specifies packet data structure, state machine handling logic, and encoding details for the transfer of NFT over a TIBC channel between two modules on separate chains. 4 | 5 | ## Motivation 6 | 7 | Users of a set of chains connected over the IBC protocol might wish to utilise an asset issued on one chain on another chain, perhaps to make use of additional features such as exchange or privacy protection, while retaining non-fungibility with the original asset on the issuing chain. This application-layer standard describes a protocol for transferring non-fungible tokens between chains connected with IBC which preserves asset non-fungibility, preserves asset ownership, limits the impact of Byzantine faults, and requires no additional permissioning. 8 | 9 | ## Definitions 10 | 11 | The TIBC handler interface & TIBC routing module interface are as defined in ICS 25 and TICS 26, respectively. 12 | 13 | ## Desired Properties 14 | 15 | - Preservation of non-fungibility 16 | - Preservation of total supply 17 | 18 | ## Technical Specification 19 | 20 | ### Data Structures 21 | 22 | Only one packet data type is required: NonFungibleTokenPacketData, which specifies class, id, uri, sending chain, receiving chain, source chain, destination chain, and whether the NFT has departed from the source chain. 23 | 24 | ```go 25 | interface NonFungibleTokenPacketData { 26 | class: string // nft class 27 | id: string 28 | uri: string // must have value 29 | sender: string 30 | receiver: string 31 | awayFromOrigin:bool // has departed from the source chain or not 32 | } 33 | ``` 34 | 35 | As `nft` is cross-chain transferred using the `TICS-030` protocol, it begins to record the transfers. This information is encoded into the `class` field. 36 | 37 | `class` field is implemented in the form of `{prefix}/{sourceChain}/class}` , wherein `prefix = "tibc/nft"`. The sending chain is the source chain of NFT when there's no `prefix` and `sourceChain` in the field; if the field contains a `prefix` and a `sourceChain`, then the NFT is transferred from the `sourceChain`. As in the case of `class` is `tibc/nft/A/B/nftClass`, if the NFT now exists on Chain `C`, then the NFT is transferred from Chain `A` to Chain `B` and then to Chain `C`. If Chain `C` wishes to return the NFT to its original chain, it must return the NFT to Chain `B` first to update it to `tibc/nft/A`, and then return it to Chain `A` via Chain `B`. 38 | 39 | The `awayFromOrigin` is specified when transferred from the source chain of the NFT to the destination chain or returned from the destination chain to the source chain. The `awayFromOrigin` field of the packet constructed in the hops is the `awayFromOrigin` field data in the received packet. 40 | 41 | The acknowledgment data type describes whether the transfer succeeded or failed, and the reason for failure (if any). 42 | 43 | ```go 44 | type NonFungibleTokenPacketAcknowledgement = NonFungibleTokenPacketSuccess | NonFungibleTokenPacketError; 45 | 46 | interface NonFungibleTokenPacketSuccess { 47 | success: "AQ==" 48 | } 49 | 50 | interface NonFungibleTokenPacketError { 51 | error: string 52 | } 53 | ``` 54 | 55 | ### Sub-protocols 56 | 57 | The sub-protocols described herein should be implemented in an "NFT transfer bridge" module with access to the NFT module and the packet module. 58 | 59 | #### Port Setup 60 | 61 | The setup function must be called exactly once when the module is created (perhaps when the blockchain itself is initialized) to bind to the appropriate port and create an escrow address (owned by the module). 62 | 63 | ```go 64 | function setup() { 65 | routingModule.bindPort("nftTransfer", ModuleCallbacks{ 66 | onRecvPacket, 67 | onAcknowledgePacket, 68 | onCleanPacket, 69 | }) 70 | } 71 | ``` 72 | 73 | Once the setup function has been called, channels can be created through the TIBC routing module between instances of the nft transfer module on separate chains. 74 | 75 | #### Routing module callbacks 76 | 77 | between chain A and Chain B: 78 | 79 | - When the NFT on Chain `A` is departing from the source chain, the nft transfer module escrows the existing NFT asset on Chain `A` and mints NFT on Chain `B`. 80 | - When NFT on Chain `B` is approaching Chain `A`, the nft transfer module burns local vouchers on Chain `B` and unescrows the NFT asset on Chain `A`. 81 | - Acknowledgment data is used to handle failures, such as invalid destination accounts. Returning an acknowledgment of failure is preferable to aborting the transaction since it enables the sending chain to take appropriate action based on the nature of the failure. 82 | 83 | `createOutgoingPacket` must be called by a transaction handler in the module which performs appropriate signature checks, specific to the account owner on the host state machine. 84 | 85 | ```go 86 | function createOutgoingPacket( 87 | class:string 88 | id: string 89 | uri: string 90 | sender: string, 91 | receiver: string, 92 | awayFromOrigin: boolean, 93 | destChain:string, 94 | relayChain:string, 95 | { 96 | if awayFromSource { 97 | // escrow source NFT (assumed to fail if balance insufficient) 98 | nft.lock(sender, escrowAccount, class, id, uri) 99 | } else { 100 | // // receiver is source chain, burn NFT 101 | nft.BurnNFT(sender, class, id) 102 | } 103 | NonFungibleTokenPacketData data = NonFungibleTokenPacketData{class, id, uri, awayFromOrigin, sender, receiver} 104 | handler.sendPacket(Packet{sequence, port, relayChain, destChain}) 105 | } 106 | ``` 107 | 108 | `onRecvPacket` is called by the routing module when a packet addressed to this module has been received. 109 | 110 | ```go 111 | function onRecvPacket(packet: Packet) { 112 | NonFungibleTokenPacketData data = packet.data 113 | // construct default acknowledgement of success 114 | NonFungibleTokenPacketAcknowledgement ack = NonFungibleTokenPacketAcknowledgement{true, null} 115 | if awayFromSource { 116 | if string.hasPrefix(packet.class){ 117 | // tibc/nft/A/class -> tibc/nft/A/B/class 118 | newClass = packet.sourceChain + packet.class 119 | } else{ 120 | // class -> tibc/nft/A/class 121 | newClass = prefix + packet.sourceChain + packet.class 122 | } 123 | nft.MintNft(newClass, id, nftName, uri, nftData, sender) 124 | } else { 125 | if packet.class.split("/").length > 4{ 126 | // tibc/nft/A/B/class -> tibc/nft/A/class 127 | newClass = packet.class - packet.destChain 128 | }else{ 129 | // tibc/nft/A/class -> class 130 | newClass = packet.class - packet.destChain - prefix 131 | } 132 | nft.unlock(escrowAccount, data.receiver, newClass, id) 133 | } 134 | } 135 | ``` 136 | 137 | `onAcknowledgePacket` is called by the routing module when a packet sent by this module has been acknowledged. 138 | 139 | ```go 140 | function onAcknowledgePacket( 141 | packet: Packet, 142 | acknowledgement: bytes) { 143 | // if the transfer failed, refund the NFT 144 | if (!ack.success) 145 | refundTokens(packet) 146 | } 147 | ``` 148 | 149 | `refundTokens` is called by `onAcknowledgePacket` to refund NFT to the original sender. 150 | 151 | ```go 152 | function refundTokens(packet: Packet) { 153 | NonFungibleTokenPacketData data = packet.data 154 | if awayFromSource { 155 | // sender was source chain, unescrow NFT back to sender 156 | nft.unlock(escrowAccount, data.receiver, class,id) 157 | } else { 158 | // receiver was source chain, mint NFT back to sender 159 | nft.MintNft(class, id, nftName, uri, nftData, sender) 160 | } 161 | } 162 | ``` 163 | 164 | ## Example of cross-chain single-hop 165 | 166 | ### Chain `A` transfers NFT to Chain `B` 167 | 168 | > To transfer NFT from Chain `A` to Chain `B`, the relayer chain should be specified as null. Chain `A` only needs to escrow the NFT, and Chain `B` creates the NFT correspondingly. 169 | 170 | Chain `A` needs to escrow the NFT, then constructs a cross-chain data packet and sends it to Chain `B` 171 | 172 | ```go 173 | NFTPacket: 174 | class: hellokitty 175 | id: id 176 | uri: uri 177 | sender: a... 178 | receiver: c... 179 | awayFromOrigin: true 180 | sourceChain: A 181 | relayChain : "" 182 | destChain: B 183 | ``` 184 | 185 | Chain `B` will `mint` a new NFT, **newClass = prefix + "/" + sourceChain + "/" + class = tibc/nft/A/hellokitty** 186 | 187 | ### Chain B returns the NFT to Chain A 188 | 189 | > To return the NFT from Chain `B` to Chain `A`, Chain `B` burns the NFT, and Chain `A` unescrows the NFT correspondingly 190 | 191 | Chain `B` needs to burn the NFT, and constructs a cross-chain packet 192 | 193 | ```go 194 | NFTPacket: 195 | class: tibc/nft/A/hellokitty 196 | id: id 197 | sender: b... 198 | receiver: a... 199 | awayFromOrigin: false 200 | sourceChain: B 201 | destChain: A 202 | relayChain: "" 203 | ``` 204 | 205 | Chain `A` will unescrow `hellokitty` 206 | 207 | ## Example of cross-chain two-hop 208 | 209 | ### Chain A transfers the NFT to Chain C through Chain B 210 | 211 | > First Chain `A` escrows the NFT under the class field, and mints an NFT on Chain `B` with the class field `tibc/nft/A/hellokitty`, then Chain `B` escrows the NFT and creates a new packet which specifies Chain `C` as the destination chain, then Chain `C` will mint the NFT correspondingly 212 | 213 | Chain `A` needs to escrow the NFT, and constructs a cross-chain data packet and sends it to Chain `B` 214 | 215 | ``` 216 | NFTPacket: 217 | class: hellokitty 218 | id: id 219 | uri: uri 220 | sender: a... 221 | receiver: b... 222 | awayFromOrigin: true 223 | sourceChain: A 224 | relayChain :"" 225 | destChain: B 226 | ``` 227 | 228 | Chain `B` will mint a new NFT, with new `class` = prefix + "/" + sourceChain + "/" + `class` = `tibc/nft/A/hellokitty`, then escrow the new NFT, and reconstruct a new packet: 229 | 230 | 231 | ```go 232 | NFTPacket: 233 | class: tibc/nft/A/hellokitty 234 | id: id 235 | uri: uri 236 | sender: b... 237 | receiver: c... 238 | awayFromOrigin: true 239 | sourceChain: B 240 | relayChain :"" 241 | destChain: C 242 | ``` 243 | 244 | Chain `C` will `mint` a new NFT, and if a prefix already exists, then new `class` = `packet.class` + `packet.sourceChain` = `tibc/nft/A/B/hellokitty` 245 | 246 | ### Chain `C` returns the NFT to Chain `A` through Chain `B` 247 | 248 | > To return the NFT on Chain `C` to Chain `A`, Chain `C` will determine whether it is the source chain of the NFT, if it is not, then Chain `C` burns the NFT and constructs a packet, wherein `class`=`tibc/nft/A/B/hellokitty` (the complete path from `A` to `C`), then Chain `B` unescrows and burns the NFT (`class`=`tibc/nft/A/hellokitty`), and constructs a packet to inform Chain `A` to unescrow the NFT correspondingly to finish the process. 249 | 250 | Chain `C` determines whether the NFT has departed from the source chain, then burns the NFT and constructs a packet and sends it to Chain `B` 251 | 252 | ```go 253 | NFTPacket: 254 | class: tibc/nft/A/B/hellokitty 255 | id: id 256 | sender: c... 257 | receiver: b... 258 | awayFromOrigin: false 259 | sourceChain: C 260 | destChain: B 261 | relayChain: "" 262 | ``` 263 | 264 | Chain `B` receives the packet sent by Chain `C` and removes `packet.descChain` according to the packet, then creates a new `class` and performs a query on itself, if there is any result, Chain `B` unescrows and burns the NFT, constructs a new packet, and sends it to Chain `A` 265 | 266 | 267 | ```go 268 | NFTPacket: 269 | class: tibc/nft/A/hellokitty 270 | id: id 271 | sender: b... 272 | receiver: a... 273 | awayFromOrigin: false 274 | sourceChain: B 275 | destChain: A 276 | realy_chain: "" 277 | ``` 278 | 279 | Chain `A` receives the packet, determines whether the NFT has departed from the source chain, then removes the prefix and checks if the NFT exists on this chain, if it exists, Chain `A` will unescrow it 280 | 281 | ## Backwards Compatibility 282 | 283 | Not applicable 284 | 285 | ## Forwards Compatibility 286 | 287 | ## Example Implementation 288 | 289 | Coming soon. 290 | 291 | ## Other Implementations 292 | 293 | Coming soon. 294 | 295 | ## History 296 | 297 | ## Copyright 298 | 299 | All content herein is licensed under Apache 2.0. 300 | -------------------------------------------------------------------------------- /client/tics-007-tendermint-client/README.md: -------------------------------------------------------------------------------- 1 | | tics | title | stage | category | kind | requires | 2 | | ---- | ----------------- | ----- | -------- | ------------- | -------- | 3 | | 7 | Tendermint Client | draft | TIBC/TAO | instantiation | 2 | 4 | 5 | ## Synopsis 6 | 7 | This specification document describes a client (verification algorithm) for a blockchain using Tendermint consensus. 8 | 9 | ### Motivation 10 | 11 | State machines of various sorts replicated using the Tendermint consensus algorithm might like to interface with other replicated state machines or solo machines over `TIBC`. 12 | 13 | ### Definitions 14 | 15 | Functions & terms are as defined in [TICS 2](../../core/tics-002-client-semantics). 16 | 17 | `currentTimestamp` is as defined in [TICS 24](../../core/tics-024-host-requirements). 18 | 19 | The Tendermint light client uses the generalised Merkle proof format as defined in `ICS 23`. 20 | 21 | `hash` is a generic collision-resistant hash function, and can easily be configured. 22 | 23 | ### Desired Properties 24 | 25 | This specification must satisfy the client interface defined in `TICS-002` . 26 | 27 | ## Technical Specification 28 | 29 | This specification depends on the [Tendermint consensus algorithm](https://github.com/tendermint/spec/blob/master/spec/consensus/consensus.md) and [light client algorithm](https://github.com/tendermint/spec/blob/master/spec/light-client/README.md). 30 | 31 | ### Client state 32 | 33 | The Tendermint client state tracks the current revision, current validator set, trusting period, unbonding period, latest height, latest timestamp (block time), and a possible frozen height. 34 | 35 | ```go 36 | type ClientState struct{ 37 | ChainID string 38 | ValidatorSet List> 39 | TrustLevel Rational 40 | TrustingPeriod uint64 41 | UnbondingPeriod uint64 42 | LatestHeight Height 43 | LatestTimestamp uint64 44 | FrozenHeight Height 45 | MaxClockDrift uint64 46 | ProofSpecs: []ProofSpec 47 | } 48 | ``` 49 | 50 | ### Consensus state 51 | 52 | The Tendermint client tracks the timestamp (block time), validator set, and commitment root for all previously verified consensus states (these can be pruned after the unbonding period has passed, but should not be pruned beforehand). 53 | 54 | ```go 55 | type ConsensusState struct{ 56 | Timestamp uint64 57 | NextValidatorsHash []byte 58 | Root []byte 59 | } 60 | ``` 61 | 62 | ### Height 63 | 64 | The height of a Tendermint client consists of two `uint64`s: the revision number, and the height in the revision. 65 | 66 | ```go 67 | type Height struct{ 68 | RevisionNumber uint64 69 | RevisionHeight uint64 70 | } 71 | ``` 72 | 73 | Comparison between heights is implemented as follows: 74 | 75 | ```go 76 | func Compare(a Height, b Height): Ord { 77 | if (a.RevisionNumber < b.RevisionNumber) 78 | return LT 79 | else if (a.RevisionNumber === b.RevisionNumber) 80 | if (a.RevisionHeight < b.RevisionHeight) 81 | return LT 82 | else if (a.RevisionHeight === b.RevisionHeight) 83 | return EQ 84 | return GT 85 | } 86 | ``` 87 | 88 | This is designed to allow the height to reset to `0` while the revision number increases by `1` in order to preserve timeouts through zero-height upgrades. 89 | 90 | ### Headers 91 | 92 | The Tendermint client headers include the height, the timestamp, the commitment root, the complete validator set, and the signatures by the validators who committed the block. 93 | 94 | ```go 95 | type Header struct{ 96 | SignedHeader SignedHeader 97 | ValidatorSet List> 98 | TrustedValidators List> 99 | TrustedHeight Height 100 | } 101 | ``` 102 | 103 | ### Client initialisation 104 | 105 | `Tendermint` client initialisation requires a (subjectively chosen) latest consensus state, including the full validator set. To clean up expired consensus states, the storage path of every `ConsensusState` need to be recorded for client cleanup. 106 | 107 | ```go 108 | func (cs ClientState) Initialize(clientStore sdk.KVStore,consState ConsensusState) { 109 | clientStore.Set("iterateConsensusStates/{cs.LatestHeight}", "consensusStates/{cs.LatestHeight}") 110 | clientStore.Set("consensusStates/{cs.LatestHeight}/processTime", consState.Timestamp) 111 | return nil 112 | } 113 | ``` 114 | 115 | ### State 116 | 117 | ```go 118 | func (cs ClientState) Status(clientStore sdk.KVStore) Status { 119 | assert(!cs.FrozenHeight.IsZero()) return Frozen 120 | 121 | onsState, err := GetConsensusState(clientStore, cdc, cs.GetLatestHeight()) 122 | assert(err == nil) return Unknown 123 | assert(consState.Timestamp+cs.TrustingPeriod > now()) return Expired 124 | } 125 | ``` 126 | 127 | ### Validity predicate 128 | 129 | Tendermint client validity checking uses the bisection algorithm described in the [Tendermint spec](https://github.com/tendermint/spec/tree/master/spec/consensus/light-client). 130 | 131 | ```go 132 | func (cs ClientState) CheckHeaderAndUpdateState(header Header) { 133 | // assert trusting period has not yet passed 134 | assert(currentTimestamp() - clientState.latestTimestamp < clientState.trustingPeriod) 135 | // assert header timestamp is less than trust period in the future. This should be resolved with an intermediate header. 136 | assert(header.timestamp - clientState.latestTimeStamp < trustingPeriod) 137 | // assert header timestamp is past current timestamp 138 | assert(header.timestamp > clientState.latestTimestamp) 139 | // assert header height is newer than any we know 140 | assert(header.height > clientState.latestHeight) 141 | // call the `verify` function 142 | assert(verify(clientState.validatorSet, clientState.latestHeight, clientState.trustingPeriod, maxClockDrift, header)) 143 | // update validator set 144 | clientState.validatorSet = header.validatorSet 145 | // update latest height 146 | clientState.latestHeight = header.height 147 | // update latest timestamp 148 | clientState.latestTimestamp = header.timestamp 149 | // create recorded consensus state, save it 150 | consensusState = ConsensusState{header.timestamp, header.validatorSet, header.commitmentRoot} 151 | return clientState,consensusState 152 | } 153 | ``` 154 | 155 | ### State verification functions 156 | 157 | Tendermint client state verification functions check a Merkle proof against a previously validated commitment root. 158 | 159 | These functions utilise the `proofSpecs` with which the client was initialised. 160 | 161 | ```go 162 | func (cs ClientState) VerifyPacketCommitment( 163 | height Height, 164 | prefix Prefix, 165 | proof []byte, 166 | sourceChain string, 167 | destChain string, 168 | sequence uint64, 169 | commitmentBytes bytes) { 170 | path = applyPrefix(prefix, "commitments/{sourceChain}/{destChain}/packets/{sequence}") 171 | // check that the client is at a sufficient height 172 | assert(clientState.latestHeight >= height) 173 | // check delay period has passed 174 | if err := verifyDelayPeriodPassed(height, cs.DelayTime(), cs.DelayBlock()); err != nil { 175 | return err 176 | } 177 | // fetch the previously verified commitment root & verify membership 178 | root = get("clients/{clientState.ChainName()}/consensusStates/{height}") 179 | // verify that the provided commitment has been stored 180 | assert(root.verifyMembership(clientState.proofSpecs, path, hash(commitmentBytes), proof)) 181 | } 182 | 183 | func (cs ClientState) VerifyPacketAcknowledgement( 184 | height Height, 185 | prefix Prefix, 186 | proof []byte, 187 | sourceChain string, 188 | destChain string, 189 | sequence uint64, 190 | acknowledgement bytes) { 191 | path = applyPrefix(prefix, "acks/{sourceChain}/{destChain}/acknowledgements/{sequence}") 192 | // check that the client is at a sufficient height 193 | assert(clientState.latestHeight >= height) 194 | // check that the client is unfrozen or frozen at a higher height 195 | assert(clientState.frozenHeight === null || clientState.frozenHeight > height) 196 | // fetch the previously verified commitment root & verify membership 197 | root = get("clients/{clientState.ChainName()}/consensusStates/{height}") 198 | // verify that the provided acknowledgement has been stored 199 | assert(root.verifyMembership(clientState.proofSpecs, path, hash(acknowledgement), proof)) 200 | } 201 | 202 | func (cs ClientState) VerifyPacketCleanCommitment( 203 | height Height, 204 | prefix CommitmentPrefix, 205 | proof []byte, 206 | sourceChain string, 207 | destChain string, 208 | sequence uint64) { 209 | path = applyPrefix(prefix, "cleans/{sourceChain}/clean") 210 | // check that the client is at a sufficient height 211 | assert(clientState.latestHeight >= height) 212 | // check that the client is unfrozen or frozen at a higher height 213 | assert(clientState.frozenHeight === null || clientState.frozenHeight > height) 214 | // fetch the previously verified commitment root & verify membership 215 | root = get("clients/{clientState.ChainName()}/consensusStates/{height}") 216 | // verify that the nextSequenceRecv is as claimed 217 | assert(root.verifyMembership(clientState.proofSpecs, path, sequence, proof)) 218 | } 219 | ``` 220 | -------------------------------------------------------------------------------- /client/tics-008-bsc-client/README.md: -------------------------------------------------------------------------------- 1 | | tics | title | stage | category | kind | requires | 2 | | ---- | ---------- | ----- | -------- | ------------- | -------- | 3 | | 8 | BSC Client | draft | TIBC/TAO | instantiation | 2 | 4 | 5 | ## Synopsis 6 | 7 | This specification document describes a blockchain client (verification algorithm) that uses the BSC `parlia` consensus (hereinafter referred to as the `parlia` consensus). 8 | 9 | ### Motivation 10 | 11 | State machines of various sorts replicated using the BSC `parlia` consensus algorithm might like to interface with other replicated state machines or solo machines over `TIBC`. 12 | 13 | ### Definitions 14 | 15 | Functions & terms are as defined in [TICS 2](../../core/tics-002-client-semantics). 16 | 17 | `currentTimestamp` is as defined in [TICS 24](../../core/tics-024-host-requirements). 18 | 19 | ### Desired Properties 20 | 21 | This specification must satisfy the client interface defined in `TICS-002`. 22 | 23 | ## Technical Specification 24 | 25 | This specification depends on the [`palia` consensus algorithm]. 26 | 27 | ### Headers 28 | 29 | The `BSC` client `Header`s completely adopts the definition of `Ethereum`, except that the `Extra` field is extended, and the signature information of the validators who committed the block is added. 30 | 31 | ```go 32 | type Header struct { 33 | ParentHash [32]byte 34 | UncleHash [32]byte 35 | Coinbase [20]byte 36 | Root [32]byte 37 | TxHash [32]byte 38 | ReceiptHash [32]byte 39 | Bloom [256]byte 40 | Difficulty *big.Int 41 | Number *big.Int 42 | GasLimit uint64 43 | GasUsed uint64 44 | Time uint64 45 | Extra []byte 46 | MixDigest [32]byte 47 | Nonce [8]byte 48 | } 49 | ``` 50 | 51 | In BSC, the DPOS+POA method is adopted for consensus. At present, the validator set is updated every `200` blocks. If the height of a block is a multiple of 200, then it is called an epoch block. The `header.Extra` in the epoch block saves the validator set for the next epoch, and the data format is: 52 | 53 | ```text 54 | extraVanity = [32]byte{} 55 | validateSets = N * [20]byte{} 56 | signature = [65]byte{} 57 | header.Extra = extraVanity + validateSets + signature 58 | ``` 59 | 60 | Non-epoch Blocks: 61 | 62 | ```text 63 | extraVanity = [32]byte{} 64 | signature = [65]byte{} 65 | header.Extra = extraVanity + signature 66 | ``` 67 | 68 | The message signature algorithm of the header is: 69 | 70 | ```go 71 | func encodeSigHeader(w io.Writer, header *types.Header, chainId *big.Int) { 72 | err := rlp.Encode(w, []interface{}{ 73 | chainId, 74 | header.ParentHash, 75 | header.UncleHash, 76 | header.Coinbase, 77 | header.Root, 78 | header.TxHash, 79 | header.ReceiptHash, 80 | header.Bloom, 81 | header.Difficulty, 82 | header.Number, 83 | header.GasLimit, 84 | header.GasUsed, 85 | header.Time, 86 | header.Extra[:len(header.Extra)-65], 87 | header.MixDigest, 88 | header.Nonce, 89 | }) 90 | if err != nil { 91 | panic("can't encode: " + err.Error()) 92 | } 93 | } 94 | 95 | func SealHash(header *types.Header, chainId *big.Int) (hash common.Hash) { 96 | hasher := sha3.NewLegacyKeccak256() 97 | encodeSigHeader(hasher, header, chainId) 98 | hasher.Sum(hash[:0]) 99 | return hash 100 | } 101 | 102 | ``` 103 | 104 | ### Client States 105 | 106 | ```go 107 | type ClientState struct{ 108 | Header Header 109 | ChainId *big.Int 110 | Epoch uint64 111 | Period uint64 112 | Validators map[[32]byte]struct{} 113 | RecentSingers map[uint64][32]byte 114 | ContractAddress [20]byte 115 | TrustingPeriod uint64 116 | } 117 | ``` 118 | 119 | - `ChainId`: The unique identifier of a blockchain 120 | - `Period`: The number of seconds between blocks to be compulsorily executed, which is set as 3 in the current BSC client 121 | - `Epoch`: The number of blocks produced during each epoch, which is set as 200 in the current BSC client 122 | - `Validators`: The validator set of the current epoch 123 | - `RecentSingers`: The validator set of the latest signatures (equal opportunities). 124 | - `ContractAddress`: The addresses of cross-chain management contracts 125 | - `TrustingPeriod`: The trusting period (ns) of the current client 126 | 127 | ### Consensus State 128 | 129 | The `BSC` client saves the timestamp, height and state root information at the current height. 130 | 131 | ```go 132 | type ConsensusState struct{ 133 | Timestamp uint64 134 | Number *big.Int 135 | Root []byte 136 | } 137 | ``` 138 | 139 | ### State 140 | 141 | ```go 142 | func (cs ClientState) Status(clientStore sdk.KVStore) { 143 | onsState, err := GetConsensusState(clientStore, cdc, cs.GetLatestHeight()) 144 | assert(err == nil) return Unknown 145 | assert(consState.Timestamp+cs.TrustingPeriod > now()) return Expired 146 | } 147 | ``` 148 | 149 | ### Merkle Proof 150 | 151 | ```go 152 | type Proof struct { 153 | Address string 154 | Balance string 155 | CodeHash string 156 | Nonce string 157 | StorageHash string 158 | AccountProof []string 159 | StorageProof []StorageResult 160 | } 161 | 162 | // StorageProof ... 163 | type StorageResult struct { 164 | Key string 165 | Value string 166 | Proof []string 167 | } 168 | ``` 169 | 170 | ### Client initialisation 171 | 172 | The `BSC` consensus algorithm incorporates the concept of validators, and is divided into epoch blocks and non-epoch blocks. Only epoch blocks will update the validator set, so epoch blocks must be uploaded during initialization to save the current validator set. 173 | 174 | ```go 175 | func (cs ClientState) Initialize(clientStore sdk.KVStore,consState ConsensusState) { 176 | assert(cs.Header.Number % cs.Epoch != 0, "must be epoch block") 177 | validatorBytes := cs.Header.Extra[extraVanity : len(cs.Header.Extra)-extraSeal] 178 | for _, v := range ParseValidators(validatorBytes) { 179 | cs.Validators[v.Address] = struct{}{} 180 | } 181 | } 182 | ``` 183 | 184 | ### The Block Period of Delayed Acknowledgement 185 | 186 | Any critical application of `BSC` may have to wait for `2/3*N+1` blocks to be relatively safely finalized. 187 | 188 | ```go 189 | func (cs ClientState) DelayBlock() uint64{ 190 | return (2*len(cs.Validators)/3 + 1) 191 | } 192 | ``` 193 | 194 | ### The Time Period of Delayed Acknowledgement 195 | 196 | Returns the time period of delayed acknowledgement of the current light client. 197 | 198 | ```go 199 | func (cs ClientState) DelayTime() uint64 { 200 | return (2*len(cs.Validators)/3 + 1) * cs.Period 201 | } 202 | ``` 203 | 204 | ### Validity predicate 205 | 206 | The validity check of the `BSC` client is basically the same as the Ethereum light client. The difference is that the verification of the BSC block header is based on the epoch. 207 | 208 | 209 | Except for legitimacy of the block header itself, the difficulty coefficient, change of validators, and the mechanism of equal opportunity also need to be verified when verifying block headers. 210 | 211 | - The change of validator set occurrs in the `header.Number % cs.Epoch` block 212 | - The change of validator set takes place in the `header.Number % cs.Epoch == uint64(len(cs.Validators)/2)` block. 213 | - To prevent malicious nodes from continuously producing blocks, `Parlia` specifies that each verified node can only propose one block in a continuous `limit` blocks, what is to say, in each round, at most `len(cs.Validators) - limit` nodes can propose blocks, wherein `limit = floor(len(cs.Validators) / 2) + 1` . 214 | 215 | ```go 216 | 217 | var ( 218 | diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures 219 | diffNoTurn = big.NewInt(1) // Block difficulty for out-of-turn signatures 220 | ) 221 | 222 | func (cs ClientState) CheckHeaderAndUpdateState(header Header) { 223 | // assert header timestamp is past timestamp 224 | assert(header.Time > currentTime) 225 | assert(length(header.Extra) >= 32+65) 226 | assert(header.MixDigest != []byte{}) 227 | assert(header.Difficulty != nil) 228 | assert(header.GasLimit <= uint64(0x7fffffffffffffff)) 229 | assert(header.GasUsed <= header.GasLimit) 230 | assert(cs.Header.Number + 1 = header.Number) 231 | assert(cs.Header.Hash() = header.ParentHash) 232 | verifyCascadingFields(header, cs.Header) 233 | 234 | signer = ecrecover(header, signature, cs.ChainId) 235 | if signer != header.Coinbase { 236 | return errCoinBaseMisMatch 237 | } 238 | 239 | // The validator set change occurs at `header.Number % cs.Epoch == 0` 240 | if header.Number % cs.Epoch == 0 { 241 | validatorBytes := checkpoint.Extra[extraVanity : len(checkpoint.Extra)-extraSeal] 242 | validators = ParseValidators(validatorBytes) 243 | set("clientState/snapshot/{header.Number}",validators) 244 | } 245 | 246 | // Delete the oldest validator from the recent list to allow it signing again 247 | if limit := uint64(len(cs.Validators)/2 + 1); header.Number >= limit { 248 | delete(cs.RecentSingers, header.Number-limit) 249 | } 250 | 251 | cs.RecentSingers[header.Number] = signer 252 | 253 | // The validator set change takes effect on `header.Number % cs.Epoch == len(cs.Validators)/2` 254 | if header.Number % cs.Epoch == uint64(len(cs.Validators)/2){ 255 | epochNumber := header.Number / cs.Epoch 256 | epochHeight := epochNumber * cs.Epoch 257 | validators := get("clientState/snapshot/{epochHeight}") 258 | 259 | newVals := make(map[common.Address]struct{}, len(validators)) 260 | for _, val := range validators { 261 | newVals[val] = struct{}{} 262 | } 263 | 264 | oldLimit := len(clientState.Validators)/2 + 1 265 | newLimit := len(newVals)/2 + 1 266 | if newLimit < oldLimit { 267 | for i := 0; i < oldLimit-newLimit; i++ { 268 | delete(cs.RecentSingers, header.Number-uint64(newLimit)-uint64(i)) 269 | } 270 | } 271 | clientState.Validators := newVals 272 | } 273 | 274 | if _, ok := cs.Validators[signer]; !ok { 275 | return errUnauthorizedValidator 276 | } 277 | 278 | for seen, recent := range cs.RecentSingers { 279 | if recent == signer { 280 | // Signer is among RecentSingers, only fail if the current block doesn't shift it out 281 | if limit := uint64(len(cs.Validators)/2 + 1); seen > number-limit { 282 | return errRecentlySigned 283 | } 284 | } 285 | } 286 | 287 | inturn := cs.inturn(signer) 288 | if inturn && header.Difficulty.Cmp(diffInTurn) != 0 { 289 | return errWrongDifficulty 290 | } 291 | if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 { 292 | return errWrongDifficulty 293 | } 294 | 295 | clientState.Header = header 296 | consensusState = ConsensusState{header.Time, header.Number, header.Root} 297 | return clientState,consensusState 298 | } 299 | 300 | func (cs ClientState) inturn(validator common.Address) bool { 301 | offset := cs.Header.Number % uint64(len(cs.Validators)) 302 | return validators[offset] == validator 303 | } 304 | ``` 305 | 306 | ### State verification functions 307 | 308 | BSC client state verification functions check a Merkle proof against a previously validated commitment root. 309 | 310 | ```go 311 | func (cs ClientState) VerifyPacketCommitment( 312 | height Height, 313 | prefix Prefix, 314 | proof []byte, 315 | sourceChain string, 316 | destChain string, 317 | sequence uint64, 318 | commitmentBytes bytes) { 319 | 320 | assert(cs.DelayBlock() <= cs.Header.Number - height) 321 | path = applyPrefix(prefix, "commitments/{sourceChain}/{destChain}/packets/{sequence}") 322 | assert(sha256(path) == proof.StorageProof[0].Key) 323 | // verify whether it is the management contract address 324 | assert(proof.Address == cs.ContractAddress) 325 | // fetch the previously verified commitment root & verify membership 326 | root = get("clients/{clientState.ChainName()}/consensusStates/{height}") 327 | // verify that the provided commitment has been stored 328 | trie.VerifyProof(root, account, proof.AccountProof) 329 | v := trie.VerifyProof(root, storageHash, proof.StorageProof) 330 | assert(commitmentBytes == v) 331 | } 332 | 333 | func (cs ClientState) VerifyPacketAcknowledgement( 334 | height Height, 335 | prefix Prefix, 336 | proof []byte, 337 | sourceChain string, 338 | destChain string, 339 | sequence uint64, 340 | acknowledgement bytes) { 341 | assert(cs.DelayBlock() <= cs.Header.Number - height) 342 | path = applyPrefix(prefix, "acks/{sourceChain}/{destChain}/acknowledgements/{sequence}") 343 | assert(sha256(path) >= proof.StorageProof[0].Key) 344 | // verify whether it is the management contract address 345 | assert(proof.Address == cs.ContractAddress) 346 | 347 | // check that the client is at a sufficient height 348 | assert(clientState.Header.Height >= height) 349 | // fetch the previously verified commitment root & verify membership 350 | root = get("clients/{clientState.ChainName()}/consensusStates/{height}") 351 | // verify that the provided commitment has been stored 352 | trie.VerifyProof(root, account, proof.AccountProof) 353 | v:= trie.VerifyProof(root, storageHash, proof.StorageProof) 354 | assert(commitmentBytes == v) 355 | } 356 | 357 | func (cs ClientState) VerifyPacketCleanCommitment( 358 | height Height, 359 | prefix CommitmentPrefix, 360 | proof []byte, 361 | sourceChain string, 362 | destChain string, 363 | sequence uint64) { 364 | assert(cs.DelayBlock() <= cs.Header.Number - height) 365 | path = applyPrefix(prefix, "cleans/{sourceChain}/clean") 366 | assert(sha256(path) >= proof.StorageProof[0].Key) 367 | // verify whether it is the management contract address 368 | assert(proof.Address == cs.ContractAddress) 369 | 370 | // check that the client is at a sufficient height 371 | assert(clientState.Header.Height >= height) 372 | // fetch the previously verified commitment root & verify membership 373 | root = get("clients/{clientState.ClientID()}/consensusStates/{height}") 374 | // verify that the provided commitment has been stored 375 | trie.VerifyProof(root, account, proof.AccountProof) 376 | v := trie.VerifyProof(root, storageHash, proof.StorageProof) 377 | assert(sequence == v) 378 | } 379 | ``` 380 | -------------------------------------------------------------------------------- /client/tics-009-eth-client/README.md: -------------------------------------------------------------------------------- 1 | | tics | title | stage | category | kind | requires | 2 | | ---- | ------------ | ----- | -------- | ------ | -------- | 3 | | 9 | ETH light client | draft | TIBC/TAO | instantiation | 2 | 4 | 5 | ## Synopsis 6 | 7 | This specification document describes a client (verification algorithm) for a blockchain using EHT `PoW` consensus, which is hereinafter referred to as `PoW` consensus. 8 | 9 | ### Motivation 10 | 11 | State machines of various sorts replicated using the Ethereum `PoW` consensus algorithm might like to interface with other replicated state machines or solo machines over `TIBC`. 12 | 13 | ### Definitions 14 | 15 | Functions & terms are as defined in [TICS-002](../../core/tics-002-client-semantics). 16 | 17 | ### Desired Properties 18 | 19 | This specification must satisfy the client interface defined in `TICS-002`. 20 | 21 | 22 | ## Technical Specification 23 | 24 | This specification depends on the [PoW consensus algorithm](https://github.com/ethereum/go-ethereum/blob/master/consensus/ethash/consensus.go). 25 | 26 | ### Headers 27 | 28 | The `Header` of the `ETH` client entirely adopts the `Ethereum` definitions. 29 | 30 | ```go 31 | type Header struct { 32 | ParentHash [32]byte 33 | UncleHash [32]byte 34 | Coinbase [20]byte 35 | Root [32]byte 36 | TxHash [32]byte 37 | ReceiptHash [32]byte 38 | Bloom [256]byte 39 | Difficulty *big.Int 40 | Number *big.Int 41 | GasLimit uint64 42 | GasUsed uint64 43 | Time uint64 44 | Extra []byte 45 | MixDigest [32]byte 46 | Nonce [8]byte 47 | BaseFee *big.Int 48 | } 49 | ``` 50 | 51 | ### Client state 52 | 53 | ```go 54 | type ClientState struct{ 55 | Header Header 56 | ChainId *big.Int 57 | ContractAddress [20]byte 58 | TrustingPeriod uint64 59 | TimeDelay uint64 60 | BlockDelay uint64 61 | } 62 | ``` 63 | 64 | - `Header`: The header of the block 65 | - `ChainId`: The unique identifier of the blockchain 66 | - `ContractAddress`: The contract address of the cross-chain packet 67 | - `TrustingPeriod`: The trusting period (ns) of the current light client 68 | - `TimeDelay`: The time period of delayed acknowledgement 69 | - `BlockDelay`: The block period of delayed acknowledgement 70 | 71 | ### Consensus State 72 | 73 | The `ETH` client saves the timestamp, height, and state root information at the current height. 74 | 75 | ```go 76 | type ConsensusState struct{ 77 | Timestamp uint64 78 | Number *big.Int 79 | Root []byte 80 | } 81 | ``` 82 | 83 | ### State 84 | 85 | ```go 86 | func (cs ClientState) Status(clientStore sdk.KVStore) { 87 | onsState, err := GetConsensusState(clientStore, cdc, cs.GetLatestHeight()) 88 | assert(err == nil) return Unknown 89 | assert(consState.Timestamp+cs.GetDelayTime() currentTime) 176 | assert(length(header.Extra) >= 32) 177 | assert(header.MixDigest != []byte{}) 178 | assert(header.Difficulty != nil) 179 | assert(header.GasLimit <= uint64(0x7fffffffffffffff)) 180 | assert(header.GasUsed <= header.GasLimit) 181 | assert(parent.Hash() = header.ParentHash) 182 | 183 | // verify header 1559 184 | assert( VerifyEip1559Header(parentHeader, &header)!= false) 185 | //verify difficulty 186 | expected := makedifficult(big.NewInt(9700000)(header.Time, &parent.Header) 187 | // 9700000 should be changed to the desired 188 | return verifyCascadingFields(header) 189 | } 190 | ``` 191 | 192 | Fork processing: 193 | 194 | ```go 195 | func (m ClientState) RestrictChain(cdc codec.BinaryCodec, store sdk.KVStore, new Header) error { 196 | si, ti := m.Header.Height, new.Height 197 | var err error 198 | current := m.Header 199 | // si > ti 200 | if si.RevisionHeight > ti.RevisionHeight { 201 | assert(store.Get(host.ConsensusStateKey(ti)) != nil) 202 | 203 | cdc.UnmarshalInterface(ConsensusTmp, &tiConsensus) 204 | tmpConsensus, ok := tiConsensus.(*ConsensusState) 205 | 206 | root := tmpConsensus.Root 207 | currentBytes := store.Get(headerIndexKey) 208 | assert(currentBytes != nil) 209 | cdc.UnmarshalInterface(currentBytes, ¤tHeaderInterface) 210 | current = currentHeaderInterface.(*EthHeader) 211 | si = ti 212 | } 213 | 214 | // use newHashes to cache header hash 215 | newHashes := make([]common.Hash, 0) 216 | 217 | // ti > si 218 | for ti.RevisionHeight > si.RevisionHeight { 219 | newHashes = append(newHashes, new.Hash()) 220 | newTmp := GetParentHeaderFromIndex(store, new) 221 | assert(newTmp != nil) 222 | 223 | cdc.UnmarshalInterface(newTmp, ¤tly) 224 | tmpConsensus:= currently.(*Header) 225 | new = *tmpConsensus 226 | // till ti = si 227 | ti.RevisionHeight-- 228 | } 229 | 230 | // when si.parent != ti.parent run to si.parent = ti.parent 231 | for !bytes.Equal(current.ParentHash, new.ParentHash) { 232 | newHashes = append(newHashes, new.Hash()) 233 | newTmp := GetParentHeaderFromIndex(store, new) 234 | assert(newTmp != nil) 235 | cdc.UnmarshalInterface(newTmp, ¤tly); 236 | tmpConsensus, ok := currently.(*Header) 237 | new = *tmpConsensus 238 | ti.RevisionHeight-- 239 | si.RevisionHeight-- 240 | currentTmp := GetParentHeaderFromIndex(store, current) 241 | cdc.UnmarshalInterface(currentTmp, ¤tly) 242 | tmpConsensus = currently.(*Header) 243 | current = *tmpConsensus 244 | } 245 | // make newHashs cache to main_chain 246 | for i := len(newHashes) - 1; i >= 0; i-- { 247 | // get from HeaderIndex 248 | newTmp := store.Get(EthHeaderIndexKey(newHashes[i], ti.GetRevisionHeight())) 249 | cdc.UnmarshalInterface(newTmp, ¤tly) 250 | tmpHeader, ok := currently.(*Header) 251 | 252 | consensusState := &ConsensusState{ 253 | Timestamp: tmpHeader.Time, 254 | Number: tmpHeader.Height, 255 | Root: tmpHeader.Root[:], 256 | } 257 | consensusStateBytes, err := cdc.MarshalInterface(consensusState) 258 | 259 | // set to main_chain 260 | store.Set(host.ConsensusStateKey(ti), consensusStateBytes) 261 | ti.RevisionHeight++ 262 | } 263 | return err 264 | } 265 | ``` 266 | 267 | ### State verification functions 268 | 269 | `ETH` client state verification functions check a Merkle proof against a previously validated commitment root. 270 | 271 | ```go 272 | func (cs ClientState) VerifyPacketCommitment( 273 | height Height, 274 | prefix Prefix, 275 | proof []byte, 276 | sourceChain string, 277 | destChain string, 278 | sequence uint64, 279 | commitmentBytes bytes) { 280 | 281 | ethProof, consensusState, err := produceVerificationArgs(store, cdc, m, height, proof) 282 | // check delay period has passed 283 | assert(cs.DelayBlock() <= cs.Header.Number - height) 284 | 285 | // verify that the provided commitment has been stored 286 | constructor := NewProofKeyConstructor(sourceChain, destChain, sequence) 287 | trie.VerifyProof(ethProof,consensusState,contractAddr,commitment,proofKey) 288 | } 289 | 290 | func (cs ClientState) VerifyPacketAcknowledgement( 291 | height Height, 292 | prefix Prefix, 293 | proof []byte, 294 | sourceChain string, 295 | destChain string, 296 | sequence uint64, 297 | acknowledgement bytes) { 298 | ethProof, consensusState, err := produceVerificationArgs(store, cdc, m, height, proof) 299 | // check delay period has passed 300 | assert(cs.DelayBlock() <= cs.Header.Number - height) 301 | 302 | // verify that the provided commitment has been stored 303 | constructor := NewProofKeyConstructor(sourceChain, destChain, sequence) 304 | trie.VerifyProof(ethProof,consensusState,contractAddr,commitment,proofKey) 305 | } 306 | 307 | func (cs ClientState) VerifyPacketCleanCommitment( 308 | height Height, 309 | prefix CommitmentPrefix, 310 | proof []byte, 311 | sourceChain string, 312 | destChain string, 313 | sequence uint64) { 314 | ethProof, consensusState, err := produceVerificationArgs(store, cdc, m, height, proof) 315 | // check delay period has passed 316 | assert(cs.DelayBlock() <= cs.Header.Number - height) 317 | 318 | // verify that the provided commitment has been stored 319 | constructor := NewProofKeyConstructor(sourceChain, destChain, sequence) 320 | trie.VerifyProof(ethProof,consensusState,contractAddr,commitment,proofKey) 321 | } 322 | ``` -------------------------------------------------------------------------------- /core/tics-002-client-semantics/README.md: -------------------------------------------------------------------------------- 1 | 2 | | tics | title | stage | category | kind | requires | author | created | modified | 3 | | ---- | ---------------- | ----- | -------- | --------- | -------- | ------------------- | ---------- | ---------- | 4 | | 2 | Client Semantics | draft | TIBC/TAO | interface | 23,24 | zhiqiang@bianjie.ai | 2021-07-23 | 2021-07-26 | 5 | 6 | ## Synopsis 7 | 8 | This specification describes the full life cycle of the light client, including create, update, upgrade and definitions of the entire process of verifying source chain data using the light client state. Implementation details of each light client is beyond the scope of this specification. 9 | 10 | ### Motivation 11 | 12 | In the TIBC protocol, an actor, which may be an end user, an off-chain process, or a machine, needs to be able to verify the state updates which the other machine's consensus algorithm has agreed upon, and reject any possible updates which the other machine's consensus algorithm has not agreed upon. A light client is an algorithm with which a machine can do so. This standard formalizes the light client model and requirements, so that the IBC protocol can easily integrate with new machines which are running new consensus algorithms as long as associated light client algorithms fulfilling the listed requirements are provided. 13 | 14 | ### Definitions 15 | 16 | - `get`, `set`, `Path` and `Identifier` are as defined in TICS 24. 17 | - `CommitmentRoot` is as defined in TICS 23. It must provide an inexpensive way for downstream logic to verify whether key/value pairs are present in state at a particular height. 18 | - `ConsensusState` is an interface type representing the state of a validity predicate. `ConsensusState` must be able to verify state updates agreed upon by the associated consensus algorithm. It must also be serializable in a canonical fashion so that third parties, such as counterparty machines, can check that a particular machine has stored a particular consensus state. It must finally be introspectable by the state machine which it is for, such that the state machine can look up its own consensus state at a past height. 19 | - `ClientState` is an interface type representing the state of a client. A `ClientState` must expose query functions to verify membership or non-membership of key/value pairs in state at particular heights and to retrieve the current `ClientState`. 20 | 21 | ### Desired Properties 22 | 23 | Light clients must provide a secure algorithm to verify other chains' canonical `header`s, using the existing `ConsensusState`. The higher-level abstractions will then be able to verify sub-components of the state with the `CommitmentRoot` stored in the `ConsensusState`, which are guaranteed to have been committed by the other chain's consensus algorithm. 24 | 25 | Validity predicates are expected to reflect the behaviour of the full nodes which are running the corresponding consensus algorithm. Given a `ConsensusState` and a list of messages, if a full node accepts the new `Header` generated with `Commit`, then the light client MUST also accept it, and if a full node rejects it, then the light client MUST also reject it. 26 | 27 | Light clients are not replaying the whole message transcript, so it is possible under cases of consensus misbehaviour that the light clients' behaviour differs from the full nodes'. In this case, a misbehaviour proof that proves the divergence between the validity predicate and the full node can be generated and submitted to the chain so that the chain can safely deactivate the light client, invalidate past state roots, and await higher-level intervention. 28 | 29 | ## Technical Specification 30 | 31 | ### Data Storage 32 | 33 | - ClientState 34 | 35 | ```go 36 | func SetClientState(chainName string, clientState ClientState) { 37 | store := k.ClientStore(ctx, chainName) 38 | store.Set("clientState", clientState) 39 | } 40 | 41 | func ClientStore(chainName string) sdk.KVStore{ 42 | return "clients/{chainName}" 43 | } 44 | ``` 45 | 46 | - ConsensusState 47 | 48 | ```go 49 | func SetConsensusState(chainName string, height Height, consensusState ConsensusState) { 50 | store := k.ClientStore(ctx, chainName) 51 | store.Set("consensusStates/{height}", consensusState) 52 | } 53 | ``` 54 | 55 | - ChainName 56 | 57 | `ChainName` specifies the name of the chain, which must be specified in genesis and be written to the blockchain during initialization (`InitGenesis`) of the chain, also, `ChainName` must be unique to create its own light client on other chains. 58 | 59 | ```go 60 | func SetChainName(chainName string) { 61 | store := ctx.KVStore(k.storeKey) 62 | store.Set("client/chainName", chainName) 63 | } 64 | ``` 65 | 66 | ### Data Structures 67 | 68 | #### Client Type 69 | 70 | The client type specifies the type of consensus algorithm used by the light client. In order to facilitate future expansion, it is defined as a constant as follows: 71 | 72 | ```golang 73 | type ClientType = string 74 | ``` 75 | 76 | #### Header 77 | 78 | A `Header` is an interface data structure defined by a client type that provides information to update a `ConsensusState`. A `Header` can be submitted to an associated client to update the stored `ConsensusState`. They likely contain a height, a proof, a commitment root, and possibly updates to the validity predicate. Different blockchains have different ways of implementation, so the block header is defined as an interface: 79 | 80 | ```golang 81 | type Header interface { 82 | ClientType() string 83 | GetHeight() Height 84 | ValidateBasic() error 85 | } 86 | ``` 87 | 88 | #### Height 89 | 90 | `Height` defines the current block height information of the destination blockchain. Considering the possibility of forks, it should also contain some other information like revision. The definition is as follows: 91 | 92 | ```go 93 | type Height interface { 94 | IsZero() bool 95 | LT(Height) bool 96 | LTE(Height) bool 97 | EQ(Height) bool 98 | GT(Height) bool 99 | GTE(Height) bool 100 | GetRevisionNumber() uint64 101 | GetRevisionHeight() uint64 102 | Increment() Height 103 | Decrement() (Height, bool) 104 | String() string 105 | } 106 | ``` 107 | 108 | #### State 109 | 110 | `State` defines the state of the current light client, such as `Active`, `Expired`, and `Unknown`, which can be defined as follows: 111 | 112 | ```go 113 | type Active = "Active" 114 | type Expired = "Expired" 115 | type Unknown = "Unknown" 116 | ``` 117 | 118 | The specific state judgment logic is implemented by the specific light client. When the light client expires, its state cannot be updated directly, but can only be updated by an upgrade proposal. 119 | 120 | #### ClientState 121 | 122 | `ClientState` is an interface data structure defined by a client type. It may keep an arbitrary internal state to track verified roots and past misbehaviours. `ClientState` consists of a series of interfaces, which work jointly to check the validity of the cross-chain data packet. 123 | 124 | - ClientType 125 | `ClientType` is used to return the definition of the consensus algorithm type used by the current light client. 126 | 127 | ```go 128 | func (cs ClientState) ClientType() string 129 | ``` 130 | 131 | - The unique identifier of the light client 132 | 133 | ```go 134 | func (cs ClientState) ChainName() string 135 | ``` 136 | 137 | - The latest height 138 | 139 | Returns the latest height of the light client. 140 | 141 | ```go 142 | func (cs ClientState) GetLatestHeight() Height 143 | ``` 144 | 145 | - Validity check 146 | 147 | Check the validity of the current light client data. 148 | 149 | ```go 150 | func (cs ClientState) Validate() error 151 | ``` 152 | 153 | - Proof verification rules 154 | 155 | - State of the light client 156 | 157 | Returns the state of the current light client. 158 | 159 | ```go 160 | func (cs ClientState) Status() Status 161 | ``` 162 | 163 | - The time period of delayed acknowledgement 164 | 165 | Returns the time period of delayed acknowledgement of the current light client. 166 | 167 | ```go 168 | func (cs ClientState) DelayTime() uint64 169 | ``` 170 | 171 | - The block period of delayed acknowledgement 172 | 173 | Returns the block period of delayed acknowledgement of the current light client, for example, Bitcoin requires an acknowledgement period of more than 6 blocks. 174 | 175 | ```go 176 | func (cs ClientState) DelayBlock() uint64 177 | ``` 178 | 179 | - MerklePath prefix 180 | 181 | The MerklePath prefix of the current light client, as defined in `tics-023`. 182 | 183 | ```go 184 | func (cs ClientState) Prefix() Prefix 185 | ``` 186 | 187 | - Initialize the light client 188 | 189 | ```go 190 | func (cs ClientState) Initialize(consensusState consensusState) error 191 | ``` 192 | 193 | - Verify and update the light client state 194 | 195 | ```go 196 | func (cs ClientState) CheckHeaderAndUpdateState(header Header) (ClientState, ConsensusState, error) 197 | ``` 198 | 199 | - Verify the cross-chain data packet 200 | 201 | Verify the received cross-chain data packet using the consensus state of the light client and information such as proofs. 202 | 203 | ```go 204 | func (cs ClientState) VerifyPacketCommitment( 205 | height Height, 206 | prefix Prefix, 207 | proof []byte, 208 | sourceChain string, 209 | destChain string, 210 | sequence uint64, 211 | commitmentBytes []byte, 212 | ) error 213 | ``` 214 | 215 | The specific parameters are explained as follows: 216 | 217 | - `height`: The height of proof in the current cross-chain data packet. 218 | - `prefix`: The name of store in the cross-chain data packet (storeKey). 219 | - `proof`: The merkle proof of the cross-chain data packet. 220 | - `sourceChain`: The source chain of the data packet. 221 | - `destChain`: The destination chain of the data packet. 222 | - `sequence`: The sequence of the cross-chain data packet. 223 | - `commitmentBytes`: The sequence of the cross-chain data packet. 224 | 225 | - Verify the cross-chain data packet Ack 226 | 227 | Verify the cross-chain data packet acknowledgement using the consensus state of the light client and information such as proofs. 228 | 229 | ```go 230 | func (cs ClientState) VerifyPacketAcknowledgement( 231 | height Height, 232 | prefix Prefix, 233 | proof []byte, 234 | sourceChain string, 235 | destChain string, 236 | sequence uint64, 237 | acknowledgement []byte 238 | ) error 239 | ``` 240 | 241 | The specific parameters are explained as follows: 242 | 243 | - `height`: The height of proof in the current cross-chain data packet. 244 | - `prefix`: The name of store in the cross-chain data packet (storeKey). 245 | - `proof`: The merkle proof of the cross-chain data packet. 246 | - `sourceChain`: The source chain of the data packet. 247 | - `destChain`: The destination chain of the data packet. 248 | - `sequence`: The sequence of the cross-chain data packet. 249 | - `acknowledgement`: The commitment of the cross-chain data packet acknowledgement (the hash value of the cross-chain data packet acknowledgement is calculated following the same rules). 250 | 251 | - Verify the cross-chain data packet (light client state cleanup) 252 | 253 | If it is needed to clean up the expired state information of the light client, a cleanup light client cross-chain data packet can be sent to clean up the state. 254 | 255 | ```go 256 | func (cs ClientState) verifyPacketCleanCommitment( 257 | height Height, 258 | prefix Prefix, 259 | proof []byte, 260 | sourceChain string, 261 | destChain string, 262 | sequence uint64, 263 | cleanCommitmentBytes []byte 264 | ) error 265 | ``` 266 | 267 | The specific parameters are explained as follows: 268 | 269 | - `height`: The height of proof in the current cross-chain data packet. 270 | - `prefix`: The name of store in the cross-chain data packet (storeKey). 271 | - `proof`: The merkle proof of the cross-chain data packet. 272 | - `sourceChain`: The source chain of the data packet. 273 | - `destChain`: The destination chain of the data packet. 274 | - `sequence`: The sequence of the cross-chain data packet. 275 | - `cleanCommitmentBytes`: The commitment of the cleanup light client data packet (the hash value of the cross-chain data packet acknowledgement is calculated following the same rules). 276 | 277 | - ConsensusState 278 | 279 | `ConsensusState` defines the consensus result of the light client at a given height, which is usually defined in the form of `merkle root`. The `merkle root` here can either be storage root or transaction root, the specific implementation of which is defined by the light client. 280 | 281 | ```go 282 | type ConsensusState interface { 283 | ClientType() string // Consensus kind 284 | 285 | // GetRoot returns the commitment root of the consensus state, 286 | // which is used for key-value pair verification. 287 | GetRoot() []byte 288 | 289 | // GetTimestamp returns the timestamp (in nanoseconds) of the consensus state 290 | GetTimestamp() uint64 291 | 292 | ValidateBasic() error 293 | } 294 | ``` 295 | 296 | ## Transaction 297 | 298 | ### Update the light client state 299 | 300 | Upon submitting a cross-chain data packet, a user must update the light client state first before submitting the transaction. Under the situation where the source chain does not offer instant finality and needs to delay the acknowledgement to confirm the transaction, the user should update the light client state N blocks before submitting the cross-chain data package. For example, if the source chain needs to delay the acknowledgement for 6 blocks, then the proof height of the cross-chain data packet submitted by the user must meet the following requirement: 301 | 302 | ```text 303 | proofHeight - clientState.GetLatestHeight() >= 6 304 | ``` 305 | 306 | The transaction structure of updating the light client is defined as: 307 | 308 | ```go 309 | type MsgUpdateClient struct { 310 | ChainName string 311 | Header Header 312 | } 313 | ``` 314 | 315 | The process of updating the light client is as follows: 316 | 317 | ```go 318 | func UpdateClient(chainName string,header Header) error { 319 | AssertClientExist(chainName) 320 | clientState := k.GetClientState(ctx, chainName) 321 | newClientState, newConsensusState := clientState.CheckHeaderAndUpdateState(header) 322 | SetClientState(chainName, newClientState) 323 | SetConsensusState(chainName,header.GetHeight() newConsensusState) 324 | return nil 325 | } 326 | ``` 327 | 328 | ## Management of the light client 329 | 330 | The TIBC light client is managed through governance on the hub, or other approaches such as multi-signature on other blockchains. 331 | 332 | ### CreateClientProposal 333 | 334 | ```go 335 | type CreateClientProposal struct { 336 | ChainName string 337 | ClientState ClientState 338 | ConsensusState ConsensusState 339 | } 340 | ``` 341 | 342 | On Ethereum, `ChainName` refers to the contract address of a light client deployed on Ethereum, where `CreateClientProposal` is used instead to register and initialize the light client for the management contract. Note that the writing method of the light client can only be called by the management contract to avoid modification of the contract's state and ensure the security of the light client. This process is briefed as follows: 343 | 344 | 345 | ```text 346 | deploy the management contract -> deploy the light client contract -> register light client for the management contract -> Update the light client contract state 347 | ``` 348 | 349 | An example: 350 | 351 | ```go 352 | func CreateClient(chainName string,clientState ClientState,consensusState ConsensusState) (string, error){ 353 | AssertClientNotExist(chainName) 354 | 355 | SetClientState(chainName, clientState) 356 | if err := clientState.Initialize(consensusState); err != nil { 357 | return "", err 358 | } 359 | 360 | SetConsensusState(chainName,clientState.GetLatestHeight() newConsensusState) 361 | return nil 362 | } 363 | ``` 364 | 365 | ### UpgradeClientProposal 366 | 367 | When the light client expires or the state of which is incorrect, an upgrade can be proposed to force the light client state to be updated. 368 | 369 | ```go 370 | type UpgradeClientProposal struct { 371 | ChainName string 372 | ClientState ClientState 373 | ConsensusState ConsensusState 374 | } 375 | ``` 376 | 377 | An example of the process is as follows: 378 | 379 | ```go 380 | func UpgradeClient(ctx sdk.Context, chainName string, upgradedClient ClientState, upgradedConsState ConsensusState){ 381 | AssertClientExist(chainName) 382 | SetClientState(chainName, upgradedClient) 383 | SetConsensusState(chainName,updatedClientState.GetLatestHeight() newConsensusState) 384 | } 385 | ``` 386 | 387 | ### RegisterRelayerProposal 388 | 389 | In order to ensure timely and complete relay of cross-chain data packets to the destination chain, a relayer needs to be deployed to scan the transactions on the source chain and relay them to the destination chain. The relayer needs to complete the following tasks: 390 | 391 | - Update the state of the light client of the source chain on the destination chain. 392 | - Relay cross-chain transactions to the destination chain. 393 | 394 | Considering the security of cross-chain data, relayers are registered through a voting method. The relayer chain needs to authenticate the source of data and accept transactions sent by authenticated relayers only. The RegisterRelayerProposal is designed as follows: 395 | 396 | ```go 397 | type RegisterRelayerProposal struct { 398 | ChainName string 399 | Relayers []string 400 | } 401 | ``` 402 | 403 | ```go 404 | func RegisterRelayer(ctx sdk.Context, chainName string, relayers []sdk.AccAddress){ 405 | AssertClientExist(chainName) 406 | SetRelayers(chainName, relayers) 407 | } 408 | 409 | func AuthRelayer(ctx sdk.Context, chainName string, relayer sdk.AccAddress) bool { 410 | AssertClientExist(chainName) 411 | return GetRelayer(chainName).Contains(relayer) 412 | } 413 | ``` 414 | -------------------------------------------------------------------------------- /core/tics-004-port-and-packet-semantics/README.md: -------------------------------------------------------------------------------- 1 | | tics | title | stage | category | kind | requires | author | created | modified | 2 | | ---- | ----------------------- | ----- | -------- | ------------- | -------- | -------------------------------- | ---------- | ---------- | 3 | | 4 | Port & Packet Semantics | draft | TIBC/TAO | instantiation | 2, 3, 26 | Wenxi Cheng | 2021-07-23 | 2021-07-26 | 4 | 5 | ## Synopsis 6 | 7 | `Port` specifies the port allocation system by which modules can bind to uniquely named ports allocated by the TIBC handler. Ports can then be used to pass `Packet` and can be transferred or later released by the module which originally bound to them. 8 | 9 | `Packet` defines the standard of cross-chain data packets. The modules which send and receive TIBC packets decide how to construct packet data and how to act upon the incoming packet data, and must utilise their own application logic to determine which state to apply in transactions according to what data the packet contains. 10 | 11 | ### Motivation 12 | 13 | The interblockchain communication protocol uses a cross-chain message passing model. IBC packets are relayed from one blockchain to the other by external relayer processes. Chain `A` and chain `B` confirm new blocks independently, and packets from one chain to the other may be delayed, censored, or re-ordered arbitrarily. Packets are visible to relayers and can be read from a blockchain by any relayer process and submitted to any other blockchain. 14 | 15 | The TIBC protocol must provide exactly-once delivery guarantees to allow applications to reason about the combined state of connected modules on two chains. For example, an application may wish to allow a single tokenized asset to be transferred between and held on multiple blockchains while preserving fungibility and conservation of supply. The application can mint asset vouchers on chain `B` when a particular IBC packet is committed to chain `B`, and require outgoing sends of that packet on chain `A` to escrow an equal amount of the asset on chain `A` until the vouchers are later redeemed back to chain `A` with an IBC packet in the reverse direction. This ordering guarantee along with correct application logic can ensure that total supply is preserved across both chains and that any vouchers minted on chain `B` can later be redeemed back to chain `A`. 16 | 17 | ### Definitions 18 | 19 | `ConsensusState` is as defined in [TICS 2 ](../tics-002-client-semantics). 20 | 21 | `Port` is a particular kind of identifier that is used to issue permission to channel opening and usage to modules. 22 | 23 | ```go 24 | enum Port { 25 | FT, 26 | NFT, 27 | SERVICE, 28 | CONTRACT, 29 | } 30 | ``` 31 | 32 | `module` is a sub-component of the host state machine independent of the TIBC handler. Examples include Ethereum smart contracts and Cosmos SDK & Substrate modules. The TIBC specification makes no assumptions of module functionality other than the ability of the host state machine to use object-capability or source authentication to permission ports to modules. 33 | 34 | `hash` is a generic collision-resistant hash function, the specifics of which must be agreed on by the modules utilising the channel. `hash` can be defined differently by different chains. 35 | 36 | `Packet` is a particular interface by which a cross-chain data packet is defined: 37 | 38 | ```go 39 | interface Packet { 40 | sequence: uint64 41 | port: Identifier 42 | sourceChain: Identifier 43 | destChain: Identifier 44 | relayChain: Identifier 45 | data: bytes 46 | } 47 | ``` 48 | 49 | - The `sequence` number corresponds to the order in which the packet is sent. 50 | - `port` identifies the ports on both sending and receiving chain. 51 | - `sourceChain` identifies the sending chain of the data packet. 52 | - `destChain` identifies the receiving chain of the data packet. 53 | - `relaychain` identifies the relay chain of the data packet, which, when showing null, means that relay is not required. 54 | - `data` is an opaque value that can be defined by the application logic of the associated modules. 55 | 56 | > Note that `Packet` is never directly serialised. Rather it is an intermediary structure used in certain function calls that may need to be created or processed by modules calling the TIBC handler. 57 | 58 | > `OpaquePacket` is a data packet, but cloaked in an obscuring data type by the host state machine, such that a module cannot act upon it other than to pass it to the TIBC handler. The TIBC handler can cast a packet to an OpaquePacket and vice versa. 59 | 60 | ```go 61 | type OpaquePacket = object 62 | ``` 63 | 64 | `CleanPacket` defines the cross-chain data packet used to clean up the state. 65 | 66 | ```go 67 | interface CleanPacket { 68 | sequence: uint64 69 | sourceChain: Identifier 70 | destChain: Identifier 71 | relayChain: Identifier 72 | } 73 | ``` 74 | 75 | - `sequence` identifies the maximum sequence number of the data to be cleaned up. 76 | - `sourceChain` identifies the sending chain of the data packet. 77 | - `destChain` identifies the receiving chain of the data packet. 78 | - `relaychain` identifies the relay chain of the data packet, which can be null. 79 | 80 | > Note that the `CleanPacket` execution is idempotent, thus doesn't include the sequence attribute itself to avoid repeated operations. 81 | 82 | ### Desired Properties 83 | 84 | #### Efficiency 85 | 86 | - The speed of packet transmission and confirmation should be limited only by the speed of the underlying chains. Proofs should be batchable where possible. 87 | 88 | #### Exactly-once delivery 89 | 90 | - TIBC packets sent on one end of a channel should be delivered exactly once to the other end. 91 | - If one or both of the chains halt, packets may be delivered no more than once, and once the chains resume packets should be able to flow again. 92 | 93 | #### Permissioning 94 | 95 | ## Technical Specification 96 | 97 | ### Dataflow visualisation 98 | 99 | ### Preliminaries 100 | 101 | #### 存储路径 102 | 103 | `nextSequenceSend` is an unsigned integer counter store path that identifies the sequence number for the next packet to be sent. 104 | 105 | ```go 106 | function nextSequenceSendPath(sourceChainIdentifier: Identifier, destChainIdentifier: Identifier): Path { 107 | return "seqSends/{sourceChainIdentifier}/{destChainIdentifier}/nextSequenceSend" 108 | } 109 | ``` 110 | 111 | Constant-size commitments to packet data fields are stored under the `packetCommitmentPath`: 112 | 113 | ```go 114 | function packetCommitmentPath(sourceChainIdentifier: Identifier, destChainIdentifier Identifier, sequence: uint64): Path { 115 | return "commitments/{sourceChainIdentifier}/{destChainIdentifier}/packets/" + sequence 116 | } 117 | ``` 118 | 119 | Packet receipt data are stored under the `packetReceiptPath`: 120 | 121 | ```go 122 | function packetReceiptPath(sourceChainIdentifier: Identifier, destChainIdentifier Identifier, sequence: uint64): Path { 123 | return "receipts/{sourceChainIdentifier}/{destChainIdentifier}/receipts/" + sequence 124 | } 125 | ``` 126 | 127 | Packet acknowledgement data are stored under the `packetAcknowledgementPath`: 128 | 129 | ```go 130 | function packetAcknowledgementPath(sourceChainIdentifier: Identifier, destChainIdentifier Identifier, sequence: uint64): Path { 131 | return "acks/{sourceChainIdentifier}/{destChainIdentifier}/acknowledgements/" + sequence 132 | } 133 | ``` 134 | 135 | Packet history cleanup data are stored under the `packetCleanPath`: 136 | 137 | ```go 138 | function packetCleanPath(chainIdentifier: Identifier): Path { 139 | return "cleans/{chainIdentifier}/clean" 140 | } 141 | ``` 142 | 143 | ### Sub-protocols 144 | 145 | #### Identifier validation 146 | 147 | #### Packet flow & handling 148 | 149 | ##### Packet lifecycle 150 | 151 | The following sequence of steps must occur for a packet to be sent from module *1* on machine `A` to module *2* on machine `B`, starting from scratch. 152 | 153 | The module can interface with the TIBC handler through [TICS 26 ](../tics-026-routing-module). 154 | 155 | ##### Sending Packets 156 | 157 | The sendPacket function is called by a module in order to send a TIBC packet on a channel end owned by the calling module to the corresponding module on the counterparty chain. 158 | 159 | Calling modules MUST execute application logic atomically in conjunction with calling `sendPacket`. 160 | 161 | The TIBC handler performs the following steps in order: 162 | 163 | - Checks that the client on the receiving chain is available 164 | - Checks that the calling module owns a sending port 165 | - Increments the send sequence counter associated with the channel 166 | - Stores a constant-size commitment to the packet data & packet timeout 167 | 168 | Note that the full packet is not stored in the state of the chain - merely a short hash-commitment to the data & timeout value. The packet data can be calculated from the transaction execution and possibly returned as log output which relayers can index. 169 | 170 | ```go 171 | function sendPacket(packet: Packet) { 172 | nextSequenceSend = provableStore.get(nextSequenceSendPath(packet.sourceChain, packet.destChain)) 173 | abortTransactionUnless(packet.sequence === nextSequenceSend) 174 | 175 | // all assertions passed, we can alter state 176 | nextSequenceSend = nextSequenceSend + 1 177 | provableStore.set(nextSequenceSendPath(packet.sourceChain, packet.destChain), nextSequenceSend) 178 | provableStore.set(packetCommitmentPath(packet.sourceChain, packet.destChain, packet.sequence), hash(packet.data)) 179 | 180 | // log that a packet has been sent 181 | emitLogEntry("sendPacket", {sequence: packet.sequence, data: packet.data, sourceChain: packet.sourceChain, destChain: packet.destChain, relayChain: packet.relayChain}) 182 | } 183 | ``` 184 | 185 | #### Receiving packets 186 | 187 | The `recvPacket` function is called by a module in order to receive a TIBC packet sent on the corresponding channel end on the counterparty chain. 188 | 189 | In conjunction with calling `recvPacket`, calling modules MUST either execute application logic or queue the packet for future execution. 190 | 191 | TIBC handler performs the following steps in order: 192 | 193 | - Checks that the client on the sending chain is available 194 | - Checks that the calling module own a reveiving port 195 | - Checks the inclusion proof of packet data commitment in the sending chain's state 196 | - Sets a store path to indicate that the packet has been received 197 | - Increments the packet receive sequence associated with the channel end 198 | 199 | ```go 200 | function recvPacket( 201 | packet: OpaquePacket, 202 | proof: CommitmentProof, 203 | proofHeight: Height): Packet { 204 | abortTransactionUnless(packet.relaychain === packet.sourceChain) 205 | abortTransactionUnless(packet.relaychain === packet.destChain) 206 | 207 | signChain = packet.sourceChain 208 | if !packet.relayChain.isEmpty() && selfChain == packet.destChain { 209 | signChain = packet.relayChain 210 | } 211 | client = provableStore.get(clientPath(signChain)) 212 | 213 | abortTransactionUnless(client.verifyPacketData( 214 | proofHeight, 215 | proof, 216 | packet.sourceChain, 217 | packet.destChain, 218 | packet.sequence, 219 | concat(packet.data) 220 | )) 221 | 222 | // all assertions passed (except sequence check), we can alter state 223 | if selfChain == packet.relaychain { 224 | // store commitment 225 | provableStore.set(packetCommitmentPath(packet.sourceChain, packet.destChain, packet.sequence), hash(packet.data)) 226 | 227 | // log that a packet has been sent 228 | emitLogEntry("sendPacket", {sequence: packet.sequence, data: packet.data, sourceChain: packet.sourceChain, destChain: packet.destChain, relayChain: packet.relayChain}) 229 | } 230 | 231 | if selfChain == packet.destChain{ 232 | // recive packet 233 | abortTransactionUnless(provableStore.get(packetReceiptPath(packet.sourceChain, packet.destChain, packet.sequence) === null)) 234 | provableStore.set( 235 | packetReceiptPath(packet.sourceChain, packet.destChain, packet.sequence), 236 | "1" 237 | ) 238 | 239 | // log that a packet has been received 240 | emitLogEntry("recvPacket", {sequence: packet.sequence, sourceChain: packet.sourceChain, destChain: packet.destChain, data: packet.data, relayChain: packet.relayChain}) 241 | 242 | // return transparent packet 243 | return packet 244 | } 245 | } 246 | ``` 247 | 248 | #### Writing Acknowledgements 249 | 250 | The `writeAcknowledging` function is called by a module in order to write data which resulted from processing a TIBC packet that the sending chain can then verify, which is a sort of "execution receipt" or "RPC call response". 251 | 252 | Calling modules must execute application logic atomically in conjunction with calling `writeacknowledging`. 253 | 254 | This is an asynchronous acknowledgement, the contents of which do not need to be determined when the packet is received, only when processing is completed. In the synchronous case, `writeAcknowledgement` can be called in the same transaction (atomically) with `recvPacket`. 255 | 256 | Acknowledging packets is not required; however, if an ordered channel uses acknowledgements, either all or no packets must be acknowledged (since the acknowledgements are processed in order). Note that if packets are not acknowledged, packet commitments cannot be deleted on the source chain. Future versions of TIBC may include ways for modules to specify whether or not they will be acknowledging packets in order to allow for cleanup. 257 | 258 | `writeAcknowledging` does not check if the packet being acknowledged was actually received, because this would result in proofs being verified twice for acknowledged packets. This aspect of correctness is the responsibility of the calling module. The calling module must only call `writebackiding` with a packet previously received from recvPacket. 259 | 260 | TIBC handler performs the following steps in order: 261 | 262 | - Checks that an acknowledgement for this packet has not yet been written 263 | - Sets the opaque acknowledgement value at a store path unique to the packet 264 | 265 | ```go 266 | function writeAcknowledgement( 267 | packet: Packet, 268 | acknowledgement: bytes): Packet { 269 | // cannot already have written the acknowledgement 270 | abortTransactionUnless(provableStore.get(packetAcknowledgementPath(packet.sourceChain, packet.destChain, packet.sequence) === null)) 271 | 272 | // write the acknowledgement 273 | provableStore.set( 274 | packetAcknowledgementPath(packet.sourceChain, packet.destChain, packet.sequence), 275 | hash(acknowledgement) 276 | ) 277 | 278 | // log that a packet has been acknowledged 279 | emitLogEntry("writeAcknowledgement", {sequence: packet.sequence, sourceChain: packet.sourceChain, destChain: packet.destChain, relayChain: packet.relayChain, data: packet.data, acknowledgement}) 280 | } 281 | ``` 282 | 283 | #### Processing acnowledgements 284 | 285 | The `acknowledgePacket` function is called by a module to process the acknowledgement of a packet previously sent by the calling module on a channel to a counterparty module on the counterparty chain. `acknowledgePacket` also cleans up the packet commitment, which is no longer necessary since the packet has been received and acted upon. 286 | 287 | Calling modules may atomically execute appropriate application acknowledgement-handling logic in conjunction with calling `acknowledgePacket`. 288 | 289 | ```go 290 | function acknowledgePacket( 291 | packet: OpaquePacket, 292 | acknowledgement: bytes, 293 | proof: CommitmentProof, 294 | proofHeight: Height): Packet { 295 | abortTransactionUnless(packet.relaychain === packet.sourceChain) 296 | abortTransactionUnless(packet.relaychain === packet.destChain) 297 | 298 | signChain = packet.destChain 299 | if !packet.relayChain.isEmpty() && selfChain == packet.sourceChain { 300 | signChain = packet.relayChain 301 | } 302 | client = provableStore.get(clientPath(signChain)) 303 | 304 | // verify we sent the packet and haven't cleared it out yet 305 | abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourceChain, packet.destChain, packet.sequence)) === hash(packet.data)) 306 | 307 | // abort transaction unless correct acknowledgement on counterparty chain 308 | abortTransactionUnless(client.verifyPacketAcknowledgement( 309 | proofHeight, 310 | proof, 311 | packet.sourceChain, 312 | packet.destChain, 313 | packet.sequence, 314 | acknowledgement 315 | )) 316 | 317 | if selfChain == packet.relaychain { 318 | // write the acknowledgement 319 | provableStore.set( 320 | packetAcknowledgementPath(packet.sourceChain, packet.destChain, packet.sequence), 321 | hash(acknowledgement) 322 | ) 323 | 324 | // log that a packet has been acknowledged 325 | emitLogEntry("writeAcknowledgement", {sequence: packet.sequence, sourceChain: packet.sourceChain, destChain: packet.destChain, relayChain: packet.relayChain, data: packet.data, acknowledgement}) 326 | } 327 | 328 | // delete our commitment so we can't "acknowledge" again 329 | provableStore.delete(packetCommitmentPath(packet.sourceChain, packet.destChain, packet.sequence)) 330 | 331 | // return transparent packet 332 | return packet 333 | } 334 | ``` 335 | 336 | ##### Acknowledgement Envelope 337 | 338 | The acknowledgement returned from the remote chain is defined as arbitrary bytes in the TIBC protocol. This data may either encode a successful execution or a failure (anything besides a timeout). There is no generic way to distinguish the two cases, which requires that any client-side packet visualiser understands every app-specific protocol in order to distinguish the case of successful or failed relay. In order to reduce this issue, we offer an additional specification for acknowledgement formats, which should be used by the app-specific protocols. 339 | 340 | ``` 341 | message Acknowledgement { 342 | oneof response { 343 | bytes result = 21; 344 | string error = 22; 345 | } 346 | } 347 | ``` 348 | 349 | #### Cleaning up state 350 | 351 | Packets must be acknowledged in order to be cleaned-up. 352 | 353 | Define a new state cleanup packet to clean up the data storage generated in cross-chain packet lifecycle. The packet is able to clean up storage within itself. 354 | 355 | ##### Sending cleanup packets 356 | 357 | The `sendCleanPacket` function is called by a module in order to send a TIBC data packet on the corresponding channel end owned by the calling module to the counterpart chain. 358 | 359 | ```go 360 | function sendCleanPacket(packet: CleanPacket) Packet{ 361 | provableStore.set(packetCleanPath(packet.sourceChain), packet.sequence) 362 | 363 | // log that a packet has been sent 364 | emitLogEntry("CleanPacket", {sourceChain: packet.sourceChain, destChain: packet.destChain, relayChain: packet.relayChain, sequence: packet.sequence) 365 | return packet 366 | } 367 | ``` 368 | 369 | ##### Receiving cleanup packets 370 | 371 | The `recvCleanPacket` funciton is called by a module in order to receive a TIBC packet sent on the corresponding channel end on the counterparty chain. 372 | 373 | In conjunction with calling `recvCleanPacket`, calling modules MUST either execute application logic or queue the packet for future execution. 374 | 375 | TIBC handler performs the following steps in order: 376 | 377 | - Checks that the client on the sending chain is available 378 | - Sets a store path to indicate that the packet has been received 379 | - Clean up the `Receipt` and `Acknowledgement` whose sequence number is less than the specified value. 380 | 381 | ```go 382 | function recvCleanPacket( 383 | packet: CleanPacket, 384 | proof: CommitmentProof, 385 | proofHeight: Height) { 386 | abortTransactionUnless(packet.relaychain === packet.sourceChain) 387 | abortTransactionUnless(packet.relaychain === packet.destChain) 388 | 389 | signChain = packet.sourceChain 390 | if !packet.relayChain.isEmpty() && selfChain == packet.destChain { 391 | signChain = packet.relayChain 392 | } 393 | 394 | client = provableStore.get(clientPath(signChain)) 395 | 396 | abortTransactionUnless(client.verifyCleanData( 397 | proofHeight, 398 | proof, 399 | packet.sourceChain, 400 | concat(packet.data), 401 | )) 402 | 403 | // Overwrite previous clean packet 404 | provableStore.set(packetCleanPath(packet.sourceChain), packet.sequence) 405 | 406 | // Clean all receipts and acknowledgements whose sequence is less than packet.sequence 407 | provableStore.clean(packetReceiptPath(packet.sourceChain, packet.destChain, packet.sequence)) 408 | provableStore.clean(packetAcknowledgementPath(packet.sourceChain, packet.destChain, packet.sequence)) 409 | 410 | // log that a packet has been received 411 | emitLogEntry("CleanPacket", {sequence: packet.sequence, sourceChain: packet.sourceChain, destChain: packet.destChain, relayChain: packet.relayChain) 412 | } 413 | ``` 414 | -------------------------------------------------------------------------------- /core/tics-024-Host-Environments/README.md: -------------------------------------------------------------------------------- 1 | | ics | title | stage | category | kind | requires | required-by | author | created | modified | 2 | | ---------------- | ---------------------------------------------- | ---------- | ----------------------------------- | -------------- | ------------- | --------------------- | ---------------------------------------- | ------------ | ------------- | 3 | | 24 | Host Environments | draft | TIBC/TAO | interface | 23 | | | 2021-07-21 | 2021-07-21 | 4 | 5 | ## Synopsis 6 | 7 | This specification defines the minimal set of interfaces which must be provided and properties which must be fulfilled by a state machine hosting an implementation of the interblockchain communication protocol. 8 | 9 | ### Motivation 10 | 11 | TIBC is designed to be a common standard which will be hosted by a variety of blockchains & state machines and must clearly define the requirements of the host. 12 | 13 | ### Definitions 14 | 15 | ### Desired Properties 16 | 17 | TIBC should require as simple an interface from the underlying state machine as possible to maximize the ease of correct implementation. 18 | 19 | ## Technical Specification 20 | 21 | ### Module system 22 | 23 | The host state machine must support a module system, whereby self-contained, potentially mutually distrusted packages of code can safely execute on the same ledger, control how and when they allow other modules to communicate with them, and be identified and manipulated by a "master module" or execution environment. 24 | 25 | The TIBC/TAO specifications define the implementations of two modules: the core "TIBC handler" module and the "TIBC relayer" module. TIBC/APP specifications further define other modules for particular packet handling application logic. TIBC requires that the "master module" or execution environment can be used to grant other modules on the host state machine access to the TIBC handler module and/or the TIBC routing module, but otherwise does not impose requirements on the functionality or communication abilities of any other modules which may be co-located on the state machine. 26 | 27 | ### Paths, identifiers, separators 28 | 29 | An `Identifier` is a bytestring used as a key for an object stored in state, such as a connection, channel, or light client. 30 | 31 | Identifiers must be non-empty (of positive integer length). 32 | 33 | Identifiers must consist of characters in one of the following categories only: 34 | - Alphanumeric 35 | - `. `, `_ `, `+ `, `- `, `#` 36 | - `[ `, `] `, `< `, `>` 37 | 38 | A `Path` is a bytestring used as the key for an object stored in state. Paths must contain only identifiers, constant strings, and the separator `"/"`. 39 | 40 | Identifiers are not intended to be valuable resources — to prevent name squatting, minimum length requirements or pseudorandom generation may be implemented, but particular restrictions are not imposed by this specification. 41 | 42 | The separator `"/"` is used to separate and concatenate two identifiers or an identifier and a constant bytestring. Identifiers must not contain the `"/"` character, which prevents ambiguity. 43 | 44 | Variable interpolation, denoted by curly braces, is used throughout this specification as shorthand to define path formats, e.g. `client/{clientIdentifier}/consensusState`. 45 | 46 | All identifiers, and all strings listed in this specification, must be encoded as ASCII unless otherwise specified. 47 | 48 | By default, identifiers have the following minimum and maximum lengths in characters: 49 | 50 | | Port identifier | Client identifier | 51 | | -------------------------- | ---------------------------- | 52 | | 2 - 64 | 9 - 64 | 53 | 54 | ### Key/value Store 55 | 56 | The host state machine must provide a key/value store interface with three functions that behave in the standard way: 57 | 58 | ``` 59 | type get = (path: Path) => Value | void 60 | type set = (path: Path, value: Value) => void 61 | type delete = (path: Path) => void 62 | ``` 63 | 64 | `Path` is as defined as above. `Value` is an arbitrary bytestring encoding of a particular data structure. Encoding details are left to separate ics. 65 | 66 | These functions must be permissioned to the IBC handler module (the implementation of which is described in separate standards) only, so only the IBC handler module can `set` or `delete` the paths that can be read by `get`. This can possibly be implemented as a sub-store (prefixed key-space) of a larger key/value store used by the entire state machine. 67 | 68 | Host state machines must provide two instances of this interface - a `provableStore` for storage read by (i.e. proven to) other chains, and a `privateStore` for storage local to the host, upon which `get` , `set`, and `delete` can be called, e.g. `provableStore.set('some/path', 'value')`. 69 | 70 | `provableStore`: 71 | 72 | - must write to a key/value store whose data can be externally proved with a vector commitment as defined in ICS 23 73 | - must use canonical data structure encodings provided in these specifications as proto3 files 74 | 75 | `privateStore`: 76 | 77 | - may support external proofs, but is not required to - the TIBC handler will never write data to it which needs to be proved 78 | - may use canonical proto3 data structures, but is not required to - it can use whatever format is preferred by the application environment 79 | 80 | > Note: any key/value store interface which provides these methods & properties is sufficient for TIBC. Host state machines may implement "proxy stores" with path & value mappings which do not directly match the path & value pairs set and retrieved through the store interface — paths could be grouped into buckets & values stored in pages which could be proved in a single commitment, path-spaces could be remapped non-contiguously in some bijective manner, etc — as long as `get`, `set`, and `delete` behave as expected and other machines can verify commitment proofs of path & value pairs (or their absence) in the provable store. If applicable, the store must expose this mapping externally so that clients (including relayers) can determine the store layout & how to construct proofs. Clients of a machine using such a proxy store must also understand the mapping, so it will require either a new client type or a parameterised client. 81 | 82 | > Note: this interface does not necessitate any particular storage backend or backend data layout. State machines may elect to use a storage backend configured in accordance with their needs, as long as the store on top fulfils the specified interface and provides commitment proofs. 83 | 84 | ### Path-space 85 | 86 | At present, TIBC/TAO recommends the following path prefixes for the `provableStore` and `privateStore`. 87 | 88 | Future paths may be used in future versions of the protocol, so the entire key-space in the provable store must be reserved for the TIBC handler. 89 | 90 | Keys used in the provable store may safely vary on a per-client-type basis as long as there exists a bipartite mapping between the key formats defined herein and the ones actually used in the machine's implementation. 91 | 92 | Parts of the private store may safely be used for other purposes as long as the TIBC handler has exclusive access to the specific keys required. Keys used in the private store may safely vary as long as there exists a bipartite mapping between the key formats defined herein and the ones actually used in the private store implementation. 93 | 94 | Note that the client-related paths listed below reflect the Tendermint client as defined in ICS 7 and may vary for other client types. 95 | 96 | | Store | Path format | Value type | Defined in | 97 | | -------------- | ------------------------------------------------------------------------------ | ----------------- | ---------------------- | 98 | | provableStore | "clients/{identifier}/clientType" | ClientType | TICS 2 | 99 | | privateStore | "clients/{identifier}/clientState" | ClientState | TICS 2 | 100 | | provableStore | "clients/{identifier}/consensusStates/{height}" | ConsensusState | ICS 7 | 101 | | privateStore | "ports/{identifier}" | CapabilityKey | ICS 5 | 102 | 103 | 104 | 105 | ### Module layout 106 | 107 | Represented spatially, the layout of modules & their included specifications on a host state machine looks like so (Aardvark, Betazoid, and Cephalopod are arbitrary modules): 108 | 109 | ``` 110 | +----------------------------------------------------------------------------------+ 111 | | | 112 | | Host State Machine | 113 | | | 114 | | +-------------------+ +--------------------+ +----------------------+ | 115 | | | Module Aardvark | <--> | TIBC Routing Module| | IBC Handler Module | | 116 | | +-------------------+ | | | | | 117 | | | Implements TICS 26 | | Implements TICS 2, | | 118 | | | | | ICS 5 internally. | | 119 | | +-------------------+ | | | | | 120 | | | Module Betazoid | <--> | | --> | Exposes interface | | 121 | | +-------------------+ | | | defined in ICS 25. | | 122 | | | | | | | 123 | | +-------------------+ | | | | | 124 | | | Module Cephalopod | <--> | | | | | 125 | | +-------------------+ +--------------------+ +----------------------+ | 126 | | | 127 | +----------------------------------------------------------------------------------+ 128 | ``` 129 | 130 | ### Consensus state introspection 131 | 132 | Host state machines must provide the ability to introspect their current height using `getCurrentHeight`: 133 | 134 | ``` 135 | type getCurrentHeight = () => Height 136 | ``` 137 | 138 | Host state machines must define a unique `consensusstate` type fulfilling the requirements of ICS 2, with a canonical binary serialisation. 139 | 140 | Host state machines must provide the ability to introspect their own consensus state using `getConsensusState`: 141 | 142 | ``` 143 | type getConsensusState = (height: Height) => ConsensusState 144 | ``` 145 | 146 | `getConsensusState` must return the consensus state for at least some number `n` of contiguous recent heights, where `n` is constant for the host state machine. Heights older than `n` may be safely pruned (causing future calls to fail for those heights). 147 | 148 | Host state machines must provide the ability to introspect this stored recent consensus state count `n`,using `getStoredRecentConsensusStateCount`: 149 | 150 | ``` 151 | type getStoredRecentConsensusStateCount = () => Height 152 | ``` 153 | 154 | ### Commitment path introspection 155 | 156 | Host chains must provide the ability to inspect their commitment path using `getCommitmentPrefix`: 157 | 158 | ``` 159 | type getCommitmentPrefix = () => CommitmentPrefix 160 | ``` 161 | 162 | The result `CommitmentPrefix` is the prefix used by the host state machine's key-value store. With the `CommitmentRoot` root and `CommitmentState` state of the host state machine, the following property must be preserved: 163 | 164 | ``` 165 | if provableStore.get(path) === value { 166 | prefixedPath = applyPrefix(getCommitmentPrefix(), path) 167 | if value !== nil { 168 | proof = createMembershipProof(state, prefixedPath, value) 169 | assert(verifyMembership(root, proof, prefixedPath, value)) 170 | } else { 171 | proof = createNonMembershipProof(state, prefixedPath) 172 | assert(verifyNonMembership(root, proof, prefixedPath)) 173 | } 174 | } 175 | ``` 176 | 177 | For a host state machine, the return value of `getCommitmentPrefix` must be constant. 178 | 179 | ### Timestamp access 180 | 181 | Host chains must provide a current Unix timestamp, accessible with `currentTimestamp`: 182 | 183 | ``` 184 | type currentTimestamp = () => uint64 185 | ``` 186 | 187 | In order for timestamps to be used safely in timeouts, timestamps in subsequent headers must be non-decreasing. 188 | 189 | ### Port system 190 | 191 | Host state machines must implement a port system, where the TIBC handler can allow different modules in the host state machine to bind to uniquely named ports. Ports are identified by an `Identifier`. 192 | 193 | Host state machines must implement permission interaction with the TIBC handler such that: 194 | 195 | - Once a module is bound to a port, no other modules can use that port until the module releases it 196 | - A single module can bind to multiple ports 197 | - Ports are allocated first-come first-serve and "reserved" ports for known modules can be bound when the state machine is first started 198 | 199 | This permissioning can be implemented with unique references (object capabilities) for each port (a la the Cosmos SDK), with source authentication (Ethereum), or with some other method of access control, in any case enforced by the host state machine. See ICS 5 for details. 200 | 201 | ### Datagram submission 202 | 203 | Host state machines which implement the routing module may define a `submitDatagram` function to submit datagrams, which will be included in transactions, directly to the routing module (defined in ICS 26): 204 | 205 | ``` 206 | type submitDatagram = (datagram: Datagram) => void 207 | ``` 208 | 209 | `submitDatagram` allows relayer processes to submit TIBC datagrams directly to the routing module on the host state machine. Host state machines may require that the relayer process submitting the datagram has an account to pay transaction fees, signs over the datagram in a larger transaction structure, etc — `submitDatagram` must define & construct any such packaging required. 210 | 211 | ### Exception system 212 | 213 | Host state machines must support an exception system, whereby a transaction can abort execution and revert any previously made state changes (including state changes in other modules happening within the same transaction), excluding gas consumed & fee payments as appropriate, and a system invariant violation can halt the state machine. 214 | 215 | This exception system must be exposed through two functions: `abortTransactionUnless` and `abortSystemUnless`, where the former reverts the transaction and the latter halts the state machine. 216 | 217 | This exception system must be exposed through two functions: `abortTransactionUnless` and `abortSystemUnless`, where the former reverts the transaction and the latter halts the state machine. 218 | 219 | ``` 220 | type abortTransactionUnless = (bool) => void 221 | ``` 222 | 223 | If the boolean passed to `abortTransactionUnless` is true, the host state machine need not do anything. If the boolean passed to `abortTransactionUnless` is false, the host state machine must abort the transaction and revert any previously made state changes, excluding gas consumed & fee payments as appropriate. 224 | 225 | ``` 226 | type abortSystemUnless = (bool) => void 227 | ``` 228 | 229 | If the boolean passed to `abortSystemUnless` is true, the host state machine need not do anything. If the boolean passed to `abortSystemUnless` is false, the host state machine must halt. 230 | s 231 | ### Data availability 232 | 233 | For deliver-or-timeout safety, host state machines must have eventual data availability, such that any key/value pairs in the state can be eventually searched by relayers. For consideration of safety, data availability is not required. 234 | 235 | For liveness of packet relay, host state machines must have bounded transactional liveness (and thus necessarily consensus liveness), such that incoming transactions are confirmed within a block height bound (in particular, less than the timeouts assign to the packets). 236 | 237 | IBC packet data, and other data which is not directly stored in the state vector but is relied upon by relayers, must be available to & efficiently computable by relayer processes. 238 | 239 | Light clients of particular consensus algorithms may have different and/or more strict data availability requirements. 240 | 241 | ### Event logging system 242 | 243 | The host state machine must provide an event logging system whereby arbitrary data can be logged in the course of transaction execution which can be stored, indexed, and later queried by processes executing the state machine. These event logs are utilized by relayers to read TIBC packet data & timeouts, which are not stored directly in the chain state (as this storage is presumed to be expensive) but are instead committed to with a succinct cryptographic commitment (only the commitment is stored). 244 | 245 | This system is expected to have at minimum one function for emitting log entries and one function for querying past logs, approximately as follows. 246 | 247 | The function `emitLogEntry` can be called by the state machine during transaction execution to write a log entry: 248 | 249 | ``` 250 | type emitLogEntry = (topic: string, data: []byte) => void 251 | ``` 252 | 253 | The function `queryByTopic` can be called by an external process (such as a relayer) to search all log entries associated with a given topic written by transactions which were executed at a given height. 254 | 255 | ``` 256 | type queryByTopic = (height: Height, topic: string) => []byte[] 257 | ``` 258 | 259 | More complex query functionalities may also be supported, and may allow for more efficient relayer process queries, but is not required. 260 | 261 | ### Handling upgrades 262 | 263 | Host machines may safely upgrade parts of their state machine without disruption to TIBC functionality. In order to do this safely, the TIBC handler logic must remain compliant with the specification, and all TIBC-related states (in both the provable & private stores) must be persisted across the upgrade. If clients exist for an upgrading chain on other chains, and the upgrade will change the light client validation algorithm, these clients must be informed prior to the upgrade so that they can safely switch atomically and preserve continuity of connections & channels. 264 | 265 | ## Backwards Compatibility 266 | 267 | Not applicable. 268 | 269 | ## Forwards Compatibility 270 | 271 | 272 | ## Example Implementation 273 | 274 | 275 | ## Other Implementations 276 | 277 | 278 | ## History 279 | 280 | 281 | ## Copyright 282 | -------------------------------------------------------------------------------- /core/tics-026-packet-routing/README.md: -------------------------------------------------------------------------------- 1 | 2 | | tics | title | stage | category | kind | requires | author | created | modified | 3 | | ---- | -------------- | ----- | -------- | --------- | -------- | ---------------- | ---------- | -------- | 4 | | 26 | Packet Routing | draft | TIBC/TAO | interface | 2,5,20 | dgsbl@bianjie.ai | 2021-07-26 | | 5 | 6 | ## Synopsis 7 | 8 | The routing module is a default implementation of a secondary module which will accept external datagrams and call the TIBC handler to deal with data packet relay. The routing module keeps a lookup table of modules, which it can use to look up and call a module when a packet is received, so that external relayers need only to relay packets to the routing module. The routing module on the relay chain needs to maintain a routing whitelist to control the packet routing. 9 | 10 | ### Motivation 11 | 12 | The default TIBC handler uses a receiver call pattern, where modules must individually call the TIBC handler in order to bind to ports, send and receive packets, etc. This is flexible and simple but is a little bit tricky to understand and may require extra work on the part of relayer processes, which must track the state of many modules. This standard describes an TIBC "routing module" to automate most common functionality, route packets, and simplify the task of relayers. 13 | 14 | In a multi-hop cross-chain environment, the routing module on the relay chain needs to maintain a routing whitelist to control the routing of cross-chain data packets. 15 | 16 | ### Desired Properties 17 | 18 | - Routing modules should call back the specified handler function on the module when packets need to be acted upon. 19 | - The relay chain should be able to control the packet routing through routing modules and to intercept or release corresponding datagram according to the whitelist. 20 | 21 | ### Module callback interface 22 | 23 | Modules must expose the following function signatures to the routing module, which are called upon the receipt of various datagrams: 24 | 25 | ```typescript 26 | function onRecvPacket(packet: Packet): bytes { 27 | // defined by the module, returns acknowledgement 28 | } 29 | 30 | function onAcknowledgePacket(packet: Packet) { 31 | // defined by the module 32 | } 33 | 34 | function onCleanPacket(packet: Packet) { 35 | // defined by the module 36 | } 37 | 38 | function validatePacket(packet: Packet) error { 39 | // defined by the module 40 | } 41 | ``` 42 | 43 | Exceptions must be thrown to indicate failure, reject the incoming packet, etc. 44 | 45 | These are combined in a `TIBCModule` interface: 46 | 47 | ```typescript 48 | interface TIBCModule { 49 | onRecvPacket: onRecvPacket, 50 | onAcknowledgePacket: onAcknowledgePacket, 51 | onCleanPacket: onCleanPacket, 52 | validatePacket: validatePacket 53 | } 54 | ``` 55 | 56 | The module objects which implemented TIBC module are stored in `Router` for unified management, and the router object is written into the memory when the app is initialized. 57 | 58 | ```go 59 | // The router is a map from module name to the TIBCModule 60 | type Router struct { 61 | router: map[string]TIBCModule 62 | sealed: boolean 63 | } 64 | ``` 65 | 66 | `Router` provides the following approaches: 67 | 68 | ```go 69 | // Seal prevents the Router from any subsequent route handlers to be registered. 70 | // Seal will panic if called more than once. 71 | func Seal(){ 72 | if router.sealed { 73 | panic("router already sealed") 74 | } 75 | router.sealed = true 76 | } 77 | 78 | // Sealed returns a boolean signifying if the Router is sealed or not. 79 | func Sealed() boolean { 80 | return router 81 | } 82 | 83 | // AddRoute adds TIBCModule for a given module name. It returns the Router 84 | // so AddRoute calls can be linked. It will panic if the Router is sealed. 85 | func AddRoute(module string, cbs TIBCModule) *Router { 86 | if router.sealed { 87 | panic(fmt.Sprintf("router sealed; cannot register %s route callbacks", module)) 88 | } 89 | if router.HasRoute(module) { 90 | panic(fmt.Sprintf("route %s has already been registered", module)) 91 | } 92 | 93 | router.routes[module] = cbs 94 | return router 95 | } 96 | 97 | // UpdateRoute updates TIBCModule for a given module name. It returns the Router 98 | // so UpdateRoute calls can be linked. It will panic if the Router is sealed. 99 | func UpdateRoute(module string, cbs TIBCModule) *Router { 100 | if router.sealed { 101 | panic(fmt.Sprintf("router sealed; cannot register %s route callbacks", module)) 102 | } 103 | if !router.HasRoute(module) { 104 | panic(fmt.Sprintf("route %s has not been registered", module)) 105 | } 106 | 107 | router.routes[module] = cbs 108 | return router 109 | } 110 | 111 | // HasRoute returns true if the Router has a module registered or false otherwise. 112 | func HasRoute(module string) boolean { 113 | _, ok := router.routes[module] 114 | return ok 115 | } 116 | 117 | // GetRoute returns a TIBCModule for a given module. 118 | func GetRoute(module string) (TIBCModule, boolean) { { 119 | if !router.HasRoute(module) { 120 | return nil, false 121 | } 122 | return router.routes[module], true 123 | } 124 | ``` 125 | 126 | ### Routing Rules 127 | 128 | Routing modules also maintain routing rules as the routing whitelist, the desired properties of which are: 129 | 130 | - Implementation on the Hub. 131 | - Routing rules should be configured by the management module or the Gov module. 132 | - Configuration of whitelist entry formats should be src, dest, port, where the src is the source chain ID, dest is the destination chain ID, and port is the port bonded by a module. 133 | - Support wildcard characters, for example, irishub, wenchangchain, *. 134 | - A union of rules should be adopted where rules overlap. 135 | - Routing rules should only be valid for packet and not intercept ACK. 136 | 137 | All rules are passed through strings to merge into a single persistent string. Rules are stored under `RoutingRuleManager` for management, which is as follows: 138 | 139 | ```go 140 | type RoutingRuleManager struct { 141 | RoutingRules string 142 | } 143 | ``` 144 | 145 | `RoutingRuleManager` should provide the following approaches: 146 | 147 | ```go 148 | func (rrm RoutingRuleManager) SetRules(rules []string) (error) { 149 | for i, rule range rules{ 150 | err := rrm.ValidateRule(rule) 151 | } 152 | rrm.RoutingRules, err := CombineRulesString(rules) 153 | return err 154 | } 155 | 156 | func (rrm RoutingRuleManager) GetRules() (rules []string){ 157 | rules = SplitRulesString(rrm.Routing) 158 | return rules 159 | } 160 | 161 | // ValidateRule performs rule string validation returning an error 162 | func (rrm RoutingRuleManager) ValidateRule(rule string) error { 163 | } 164 | ``` 165 | 166 | ### Properties & Invariants 167 | 168 | - Proxy port binding is first-come-first-serve: once a module binds to a port through the TIBC routing module, only that module can utilize that port until the module releases it. 169 | 170 | ## Backwards Compatibility 171 | 172 | Not applicable. 173 | 174 | ## Forwards Compatibility 175 | 176 | Routing modules are closely tied to the TIBC handler interface. 177 | 178 | ## Example Implementation 179 | 180 | Coming soon. 181 | 182 | -------------------------------------------------------------------------------- /relayer/tics-018-relayer-algorithms/README.md: -------------------------------------------------------------------------------- 1 | | tics | title | stage | category | kind | requires | required-by | author | created | modified | 2 | | ---------------- | ---------------------------------------------- | ---------- | ----------------------------------- | -------------- | ------------- | --------------------- | ---------------------------------------- | ------------ | ------------- | 3 | | 18 | Relayer Algorithms | draft | TIBC/TAO | interface | 23 | | | 2021-07-26 | 2021-07-26 | 4 | 5 | ## Synopsis 6 | 7 | Relayer algorithms are the "physical" connection layer of TIBC — off-chain processes responsible for relaying data between two chains running the TIBC protocol by scanning the state of each chain, constructing appropriate datagrams, and executing them on the opposite chain as allowed by the protocol. 8 | 9 | ### Motivation 10 | 11 | In the TIBC protocol, a blockchain can only record the intention to send particular data to another chain — it does not have direct access to a network transport layer. Physical datagram relay must be performed by off-chain infrastructure with access to a transport layer such as TCP/IP. This standard defines the concept of a relayer algorithm, executable by an off-chain process with the ability to query chain state, to perform this relay. 12 | 13 | ### Definitions 14 | 15 | A *relayer* is an *off-chain* process with the ability to read the state of and submit transactions to some set of ledgers utilizing the TIBC protocol. 16 | 17 | ### Desired Properties 18 | 19 | - No exactly-once or deliver-or-timeout safety properties of TIBC should completely depend on relayer behavior (assume Byzantine relayers). 20 | - Packet relay liveness properties of TIBC should depend only on the existence of at least one correct, live relayer. 21 | - Relaying should be permissionless, all requisite verification should be performed on-chain. 22 | - Requisite communication between the TIBC user and the relayer should be minimized. 23 | - Provision for relayer incentivization should be possible at the application layer. 24 | 25 | ## Technical Specification 26 | 27 | ### Basic relayer algorithm 28 | 29 | The relayer algorithm is defined over a set *C* of chains implementing the TIBC protocol. Each relayer may not necessarily have access to read state from and write datagrams to all chains in the interchain network (especially in the case of permissioned or private chains) — different relayers may relay between different subsets. 30 | 31 | `relay` is called by the relayer every so often — no more frequently than once per block on either chain, and possibly less frequently, according to how often the relayer wishes to relay. 32 | 33 | Different relayers may relay between different chains — as long as each pair of chains has at least one correct & live relayer and the chains remain live, all packets flowing between chains in the network will eventually be relayed. 34 | 35 | Relayer supports implementing multi-client SDK, but only two connected chain configurations can be configured for each instantiation started. 36 | 37 | #### Relayer architecture 38 | 39 | ![relayer architecture](./tibc-relayer-architecture.png) 40 | 41 | For the newly connected SDK client, the following interfaces must be implemented: 42 | 43 | ```go 44 | interface IChain { 45 | func GetBlockAndPackets(height uint64) (interface{}, error); 46 | func GetBlockHeader(height uint64) (interface{}, error); 47 | func GetLightClientState(chainName string) (interface{}, error); 48 | func GetLightClientConsensusState(chainName string, height uint64) (interface{}, error); 49 | func GetStatus() (interface{}, error); 50 | func GetLatestHeight() (uint64, error); 51 | func GetDelay() uint64; 52 | } 53 | ``` 54 | 55 | The following interfaces need to be implemented on the business layer: 56 | 57 | ```go 58 | interface IService { 59 | func UpdateClient(chain: Chain, counterparty: Chain); 60 | func PendingDatagrams(chain: Chain, counterparty: Chain): List>; 61 | } 62 | ``` 63 | 64 | Relayer performs relay with two thread management datagrams. 65 | 66 | Thread 1 is mainly used to regularly update the availability of the client. 67 | 68 | ```go 69 | func UpdateClient(chain: Chain, counterparty: Chain) { 70 | height = chain.GetLatestHeight() 71 | client = counterparty.GetLightClientConsensusState(chain) 72 | if client.height < height { 73 | header = chain.GetBlockHeader(height+1) 74 | counterpartyDatagrams.push(ClientUpdate{chain, header}) 75 | } 76 | submitDatagramHeader = ConversionHeaderMsg(blockData) 77 | submitTx(txs, dest) 78 | } 79 | ``` 80 | 81 | Thread 2 is mainly used to relay `packet` ,the process is as follows: 82 | 83 | 1. The relayer obtains the latest **relayed** height `latestHeight` of the **source chain** from the target chain. 84 | 2. The relayer obtains the header of `latestHeight+1` and the `packet` of `latestHeight-delay` from the **source chain**. 85 | 3. The relayer assembles the obtained `submitDatagramHeader` and `submitDatagramPacket` information, and then submit them 86 | 87 | ```go 88 | func pendingDatagrams(source: Chain, dest: Chain) { 89 | latestHeight = dest.GetLightClientState(chain_name) 90 | blockData = source.getBlockHeader(latestHeight+1) 91 | submitDatagramHeader = ConversionHeaderMsg(blockData) 92 | blockPacketData, proof = source.GetBlockAndPackets(latestHeight-delay) 93 | submitDatagramPacket = ConversionPacketMsg(blockPacketData, proof) 94 | txs = []tx{submitDatagramHeader, submitDatagramPacket} 95 | submitTx(txs, dest) 96 | } 97 | ``` 98 | 99 | ### Packets 100 | 101 | #### Relaying packets 102 | 103 | Packets in an unordered channel can be relayed in an event-based fashion. The relayer should watch the source chain for events emitted whenever packets are sent, then compose the packet using the data in the event log. Subsequently, the relayer should check whether the destination chain has received the packet already by querying for the presence of an acknowledgment at the packet's sequence number, and if the number is not yet present the relayer should relay the packet. 104 | 105 | ### Pending datagrams 106 | 107 | `pendingDatagrams` collates datagrams to be sent from one machine to another. The implementation of this function will depend on the subset of the TIBC protocol supported by both machines and the state layout of the source machine. Particular relayers will likely also want to implement their own filter functions in order to relay only a subset of the datagrams which could possibly be relayed (e.g. the subset for which they have been paid to relay in some off-chain manner). 108 | 109 | An example implementation that performs a unidirectional relay between two chains is outlined below. It can be altered to perform bidirectional relay by switching `chain` and `counterparty`. Which relayer process is responsible for which datagrams is a flexible choice — in this example, the relayer process relays all handshakes which started on `chain` (sending datagrams to both chains), relays all packets sent from `chain` to `counterparty`, and relays all packet acknowledgments sent from `counterparty` to `chain`. 110 | 111 | 1. Users initiate cross-chain transactions 112 | 2. Chain A produces commitments -> packet 113 | 3. Relayer A listens for/pulls the cross-chain request from Chain A, and determines whether the `Dest Chain` in the Packet is a Hub or a Zone registered in a Hub: 114 | - If yes, then relayer A executes the transfer 115 | - If no, then relayer A discards the packet 116 | 4. When a Hub receives a cross-chain request, determines whether the `Dest Chain` is itself: 117 | - If yes, then produces: 118 | - ack 119 | - receipts -> packet 120 | - If no, then produces: 121 | - commitments -> packet 122 | - receipts -> packet 123 | 5. Relayer B listens for/pulls the cross-chain request, and transfers it to the `Dest Chain`. 124 | 6. Chain B receives the request & processes it, and produces: 125 | - receipts -> packet 126 | - ack 127 | 7. Relayer B returns the ack of Chain B to the Hub 128 | 8. The Hub stores the ack and deletes commitments 129 | 9. Relayer A returns the ack of the Hub to Chain A 130 | 10. Chain A receives the ack and deletes commitments 131 | 132 | One-hop sequence diagram 133 | 134 | ![TIBC relayer - one hop](./tibc-relayer-one-hop.png) 135 | 136 | Two-hop sequence diagram 137 | 138 | ![TIBC relayer - two hop](./tibc-relayer-two-hops.png) 139 | 140 | ```go 141 | func pendingDatagrams(chain: Chain, counterparty: Chain): List> { 142 | const localDatagrams = [] 143 | const counterpartyDatagrams = [] 144 | 145 | // ICS2 : Clients 146 | // - Determine if light client needs to be updated (local & counterparty) 147 | height = chain.GetLatestHeight() 148 | client = counterparty.GetLightClientConsensusState(chain) 149 | if client.height < height { 150 | header = chain.GetBlockHeader(height+1) 151 | counterpartyDatagrams.push(ClientUpdate{chain, header}) 152 | } 153 | // get datagram packet 154 | chainDelay = chain.GetDelay() 155 | submitDatagramPacket = ConversionPacketMsg(blockPacketData, proof) 156 | counterpartyDatagrams.push(Packcet{counterparty, submitDatagramPacket}) 157 | 158 | counterpartyHeight = counterparty.latestHeight() 159 | client = chain.GetLightClientConsensusState(counterparty) 160 | if client.height < counterpartyHeight { 161 | header = counterparty.GetBlockHeader(counterpartyHeight+1) 162 | localDatagrams.push(ClientUpdate{counterparty, header}) 163 | } 164 | 165 | // get datagram packet 166 | counterpartyDelay = counterparty.GetDelay() 167 | blockPacketData, proof = counterparty.GetBlockAndPackets(counterpartyHeight-counterpartyDelay) 168 | submitDatagramPacket = ConversionPacketMsg(blockPacketData, proof) 169 | localDatagrams.push(Packcet{counterparty, submitDatagramPacket}) 170 | return [localDatagrams, counterpartyDatagrams] 171 | } 172 | ``` 173 | 174 | Relayers may filter these datagrams in order to relay particular clients and particular kinds of packets, perhaps in accordance with the fee payment model (which this document does not specify, as it may vary). 175 | 176 | ### Error handling 177 | 178 | In order to reduce storage pressure, an additional `ClearPacket` is implemented, which can be executed by on-chain timing or off-chain triggering 179 | 180 | After the packet is `clear`ed, the relayer may maliciously re-upload the historical `packet`, which may cause repeated cross-chain and successful execution. 181 | 182 | Solution: Record the sequence number of the latest cleared packets on chain, and cross-chain transfer only the Packet whose Sequence number is greater than the recorded latest sequence number. 183 | 184 | ```go 185 | // Candidate implementation on chain 186 | func validBasic(packet: Packet, counterparty: Chain) error { 187 | // get latest cleared packet's sequence number on chain 188 | latestSequence = counterparty.GetLatestSequence() 189 | if packet.sequence < latestSequence{ 190 | return error("curSequence < latestSequence") 191 | } 192 | } 193 | 194 | ``` 195 | 196 | ### Ordering constraints 197 | 198 | There are implicit ordering constraints imposed on the relayer process determining which datagrams must be submitted in what order. For example, a header must be submitted to finalize the stored consensus state & commitment root for a particular height in a light client before a packet can be relayed. The relayer process is responsible for frequently querying the state of the chains between which they are relaying in order to determine what must be relayed and when. 199 | 200 | ### Bundling 201 | 202 | If supported by the host state machine, the relayer process can bundle many datagrams into a single transaction, which will cause them to be executed in sequence, and amortize any overhead costs (e.g. signature checks for fee payment). 203 | 204 | ### Race conditions 205 | 206 | Multiple relayers relaying between the same pair of modules & chains may attempt to relay the same packet (or submit the same header) at the same time. If two relayers do so, the first transaction will succeed and the second will fail. Out-of-band coordination between the relayers or between the actors who sent the original packets and the relayers is necessary to mitigate this situation. Further discussion is out of scope of this standard. 207 | 208 | ### Incentivisation 209 | 210 | The relay process must have access to accounts on both chains with sufficient balance to pay for transaction fees. Relayers may employ application-level methods to recoup these fees, such as including a small payment to themselves in the packet data — protocols for relayer fee payment will be described in future versions of this ICS or in separate ICSs. 211 | -------------------------------------------------------------------------------- /relayer/tics-018-relayer-algorithms/tibc-relayer-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bianjieai/tibc/2f2ea6a28a28e14ddfcf787987d40a626e184fcd/relayer/tics-018-relayer-algorithms/tibc-relayer-architecture.png -------------------------------------------------------------------------------- /relayer/tics-018-relayer-algorithms/tibc-relayer-one-hop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bianjieai/tibc/2f2ea6a28a28e14ddfcf787987d40a626e184fcd/relayer/tics-018-relayer-algorithms/tibc-relayer-one-hop.png -------------------------------------------------------------------------------- /relayer/tics-018-relayer-algorithms/tibc-relayer-two-hops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bianjieai/tibc/2f2ea6a28a28e14ddfcf787987d40a626e184fcd/relayer/tics-018-relayer-algorithms/tibc-relayer-two-hops.png -------------------------------------------------------------------------------- /zh/apps/tics-030-non-fungible-token-transfer/README.md: -------------------------------------------------------------------------------- 1 | ## 概要 2 | 3 | 本标准文档定义了包的数据结构,状态机处理逻辑以及编码细节在两个模块之间通过TIBC通道传输NFT的详细信息。 4 | 5 | ## 动机 6 | 7 | 通过 TIBC 协议连接的一组链的用户可能希望在一条链上使用在另一条链上发行的资产,也许是为了利用额外的功能,例如交换或隐私保护,同时保留与发行链上原始资产的唯一性。 该应用层标准描述了一种用于在与 TIBC 连接的链之间传输NFT的协议,该协议保留了资产的唯一性,保留了资产所有权,限制了拜占庭故障的影响,并且不需要额外的许可。 8 | 9 | ## 定义 10 | 11 | TIBC Handler接口和TIBC路由模块接口分别在ICS 25和TICS 26中定义。 12 | 13 | ## 要求的属性 14 | 15 | - 保持唯一性 16 | - 保持总供应量 17 | 18 | ## 技术规范 19 | 20 | ### 数据结构 21 | 22 | 只需要一个数据包数据类型:NonFungibleTokenPacketData,它指定Class, id,uri, 发送者,接收者,源链,目标链以及是否远离源链。 23 | 24 | ```go 25 | interface NonFungibleTokenPacketData { 26 | class: string // nft class 27 | id: string 28 | uri: string // 必须有值 29 | sender: string 30 | receiver: string 31 | awayFromOrigin:bool // 是否远离源链 32 | } 33 | ``` 34 | 35 | 当`nft`使用`tics-30` 协议跨链发送时,它们开始累积已传输的记录。 该信息被编码到`class`字段中。 36 | 37 | `class`字段以`{prefix}/{sourceChain}/class}`形式实现, `prefix = "tibc/nft"`。当没有`prefix`和`sourceChain`的时候,表示发送链就是`NFT`的源头;如果带有`prefix`和`sourceChain`,则表示是从`sourceChain`转过来的。比如`tibc/nft/A/B/nftClass`,假如 C 链上存在此`NFT`,则表示`nftClass`是从 A 转到 B 再转到 C 的。如果 C 想退回此`NFT`,那么必须先退回到B,变成`tibc/nft/A`,再由 B 链退回到 A。 38 | 39 | 同时`awayFromOrigin` 在`NFT`的源头发向目标链和目标链退回到源链指定的,中间的跳转构造的包的`awayFromOrigin`字段都是获取接收包中的`awayFromOrigin`字段数据。 40 | 41 | 42 | 43 | 确认数据类型描述传输是成功还是失败,以及失败的原因(如果有)。 44 | 45 | ```go 46 | type NonFungibleTokenPacketAcknowledgement = NonFungibleTokenPacketSuccess | NonFungibleTokenPacketError; 47 | 48 | interface NonFungibleTokenPacketSuccess { 49 | success: "AQ==" 50 | } 51 | 52 | interface NonFungibleTokenPacketError { 53 | error: string 54 | } 55 | ``` 56 | 57 | ### 子协议 58 | 59 | 此处描述的子协议应在“NFT传输桥”模块中实现,该模块可访问NFT模块和 Packet模块。 60 | 61 | #### 端口设置 62 | 63 | 必须在创建模块时(可能在初始化区块链本身时)调用 setup 函数一次以绑定到适当的端口并创建托管地址(由模块拥有)。 64 | 65 | ```go 66 | function setup() { 67 | routingModule.bindPort("nftTransfer", ModuleCallbacks{ 68 | onRecvPacket, 69 | onAcknowledgePacket, 70 | onCleanPacket, 71 | }) 72 | } 73 | ``` 74 | 75 | 一旦调用了 setup 函数,就可以通过 TIBC 路由模块在不同链上的nftTransfer模块的实例之间创建通道。 76 | 77 | #### 路由模块回调 78 | 79 | 在A链和B链之间: 80 | 81 | - 当A链上的NFT远离A的时候,nftTransfer模块在A链上托管现有的NFT资产,并在接收链B上mintNFT。 82 | - 当B链上的NFT靠近A的时候,nftTransfer模块会在B链上销毁本地凭证,并在接收链A上释放NFT资产托管。 83 | - Acknowledgement 数据用于处理失败,例如无效的目标帐户。 返回失败确认比中止交易更可取,因为它更容易使发送链能够根据失败的性质采取适当的行动。 84 | 85 | `createOutgoingPacket `必须由模块中的交易处理程序调用,该处理程序执行适当的签名检查,特定于主机状态机上的帐户所有者。 86 | 87 | ```go 88 | function createOutgoingPacket( 89 | class:string 90 | id: string 91 | uri: string 92 | sender: string, 93 | receiver: string, 94 | awayFromOrigin: boolean, 95 | destChain:string, 96 | relayChain:string, 97 | { 98 | if awayFromSource { 99 | // 托管NFT(如果余额不足,则假设失败) 100 | nft.lock(sender, escrowAccount, class, id, uri) 101 | } else { 102 | // 接收者靠近源链 则burnNFT 103 | nft.BurnNFT(sender, class, id) 104 | } 105 | NonFungibleTokenPacketData data = NonFungibleTokenPacketData{class, id, uri, awayFromOrigin, sender, receiver} 106 | handler.sendPacket(Packet{sequence, port, relayChain, destChain}) 107 | } 108 | ``` 109 | 110 | 当路由模块收到发往该模块的数据包时,路由模块会调用`onRecvPacket`。 111 | 112 | ```go 113 | function onRecvPacket(packet: Packet) { 114 | NonFungibleTokenPacketData data = packet.data 115 | // 构建默认的成功确认 116 | NonFungibleTokenPacketAcknowledgement ack = NonFungibleTokenPacketAcknowledgement{true, null} 117 | if awayFromSource { 118 | if string.hasPrefix(packet.class){ 119 | // tibc/nft/A/class -> tibc/nft/A/B/class 120 | newClass = packet.sourceChain + packet.class 121 | } else{ 122 | // class -> tibc/nft/A/class 123 | newClass = prefix + packet.sourceChain + packet.class 124 | } 125 | nft.MintNft(newClass, id, nftName, uri, nftData, sender) 126 | } else { 127 | if packet.class.split("/").length > 4{ 128 | // tibc/nft/A/B/class -> tibc/nft/A/class 129 | newClass = packet.class - packet.destChain 130 | }else{ 131 | // tibc/nft/A/class -> class 132 | newClass = packet.class - packet.destChain - prefix 133 | } 134 | nft.unlock(escrowAccount, data.receiver, newClass, id) 135 | } 136 | } 137 | ``` 138 | 139 | 当路由模块发送的数据包被确认时,onAcknowledgePacket 被路由模块调用。 140 | 141 | ```go 142 | function onAcknowledgePacket( 143 | packet: Packet, 144 | acknowledgement: bytes) { 145 | // 如果转账失败,退还nft 146 | if (!ack.success) 147 | refundTokens(packet) 148 | } 149 | ``` 150 | 151 | refundTokens 由 onAcknowledgePacket 调用,以将托管NFT退还给原始发送者。 152 | 153 | ```go 154 | function refundTokens(packet: Packet) { 155 | NonFungibleTokenPacketData data = packet.data 156 | if awayFromSource { 157 | // 将托管的NFT再发回给发送者 158 | nft.unlock(escrowAccount, data.receiver, class,id) 159 | } else { 160 | // 如果是靠近源链,则mintNFT 161 | nft.MintNft(class, id, nftName, uri, nftData, sender) 162 | } 163 | } 164 | ``` 165 | 166 | ## 跨链单跳举例 167 | 168 | ### A链发送NFT到B链 169 | 170 | > 从A链发送NFT到B链,中继链指定为空,A链只需要将NFT托管,B链生成一个对应的NFT 171 | 172 | 173 | 174 | A链需要将NFT托管,然后构造一个跨链数据包发送到B链 175 | 176 | ```go 177 | NFTPacket: 178 | class: hellokitty 179 | id: id 180 | uri: uri 181 | sender: a... 182 | receiver: c... 183 | awayFromOrigin: true 184 | sourceChain: A 185 | relayChain : "" 186 | destChain: B 187 | ``` 188 | 189 | B链会`mint`一个新的`NFT`,**newClass = prefix + "/" + sourceChain + "/" + class = tibc/nft/A/hellokitty** 190 | 191 | 192 | ### B链将NFT退回到A 193 | 194 | > 将nft从B链退回到A链,B链burnNFT ,A 链释放托管的NFT 195 | 196 | 197 | 198 | B链需要将要退回的NFT进行burn操作,然后构造一个跨链数据包 199 | 200 | ```go 201 | NFTPacket: 202 | class: tibc/nft/A/hellokitty 203 | id: id 204 | sender: b... 205 | receiver: a... 206 | awayFromOrigin: false 207 | sourceChain: B 208 | destChain: A 209 | relayChain: "" 210 | ``` 211 | 212 | A链会取消HelloKitty的托管。 213 | 214 | 215 | 216 | ## 跨链两跳举例 217 | 218 | ### A链通过B链将NFT发送到C 219 | 220 | > 首先A托管此class的NFT,然后再B链上mint一个nft,nft的`class`为`tibc/nft/A/hellokitty`,接着B会托管此NFT并生成一个新的packet,目标链指向C,C会再mint对应的nft。 221 | 222 | A链需要将NFT托管,然后构造一个跨链数据包发送到B链 223 | 224 | ``` 225 | NFTPacket: 226 | class: hellokitty 227 | id: id 228 | uri: uri 229 | sender: a... 230 | receiver: b... 231 | awayFromOrigin: true 232 | sourceChain: A 233 | relayChain :"" 234 | destChain: B 235 | ``` 236 | 237 | B链会mint一个新的NFT,`newClass` = prefix + "/" + sourceChain + "/" + class = `tibc/nft/A/hellokitty`,接着B链会托管刚生成的新的NFT,然后重新构造新的数据包: 238 | 239 | ```go 240 | NFTPacket: 241 | class: tibc/nft/A/hellokitty 242 | id: id 243 | uri: uri 244 | sender: b... 245 | receiver: c... 246 | awayFromOrigin: true 247 | sourceChain: B 248 | relayChain :"" 249 | destChain: C 250 | ``` 251 | 252 | C链会mint一个新的NFT,如果已经有prefix的话,则newClass = packet.class + packet.sourceChain = tibc/nft/A/B/hellokitty 253 | 254 | 255 | 256 | ### C链通过B链将NFT退回到A链 257 | 258 | > C上的nft再退回到A链的过程:C会判断自己是不是远离源,如果不是,则burnNFT并构建一个packet,class为tibc/nft/A/B/hellokitty(A-C的完整路径),接着到了B之后会释放掉B托管的NFT,并burnNFT(class=tibc/nft/A /hellokitty),最后会再构造一个packet,让A链去释放最初托管的NFT,从而达到从C-A的退回目的。 259 | 260 | C链判断是否远离源链,然后burn NFT,之后构造packet发到B链 261 | 262 | 263 | ```go 264 | NFTPacket: 265 | class: tibc/nft/A/B/hellokitty 266 | id: id 267 | sender: c... 268 | receiver: b... 269 | awayFromOrigin: false 270 | sourceChain: C 271 | destChain: B 272 | relayChain: "" 273 | ``` 274 | 275 | B链接受到C链发的包后,会根据包去掉packet.descChain,生成一个newClass,并到自己链上查询,如果有的话,直接就取消托管,并且burn掉。之后会构造新的包发给A。 276 | 277 | 278 | ```go 279 | NFTPacket: 280 | class: tibc/nft/A/hellokitty 281 | id: id 282 | sender: b... 283 | receiver: a... 284 | awayFromOrigin: false 285 | sourceChain: B 286 | destChain: A 287 | realy_chain: "" 288 | ``` 289 | 290 | A接收到包后会判断是否远离源链,并去掉前缀,查看自己链上是否存在此NFT,并释放掉。 291 | 292 | ## 向后兼容 293 | 294 | 不适用。 295 | 296 | ## 向前兼容 297 | 298 | ## 示例实现 299 | 300 | 不久到来。 301 | 302 | ## 其他实现 303 | 304 | 不久到来。 305 | 306 | ## 历史 307 | 308 | ## 版权 309 | 310 | 本文中的所有内容均在 Apache 2.0 下获得许可。 311 | 312 | 313 | 314 | -------------------------------------------------------------------------------- /zh/client/tics-007-tendermint-client/README.md: -------------------------------------------------------------------------------- 1 | | tics | title | stage | category | kind | requires | 2 | | ---- | ------------------------------------ | ----- | -------- | ------ | -------- | 3 | | 7 | Tendermint 客户端(Tendermint Client) | 草案 | TIBC/TAO | 实例化 | 2 | 4 | 5 | ## 概要 6 | 7 | 本规范文档描述了使用 Tendermint 共识的区块链客户端(验证算法)。 8 | 9 | ### 动机 10 | 11 | 使用 Tendermint 共识算法复制的各种状态机可能希望通过`TIBC`与其他复制状态机或单机连接。 12 | 13 | ### 定义 14 | 15 | 功能和术语在 [TICS 2](../../core/tics-002-client-semantics) 中定义。 16 | 17 | `currentTimestamp` 在 [TICS 24](../../core/tics-024-host-requirements) 中定义。 18 | 19 | Tendermint 轻客户端使用`ICS 23`中定义的通用 Merkle 证明格式。 20 | 21 | `hash` 是一个通用的抗碰撞散列函数,可以轻松配置。 22 | 23 | ### 所需属性 24 | 25 | 该规范必须满足`TICS-002`中定义的客户端接口。 26 | 27 | ## 技术指标 28 | 29 | 该规范取决于[Tendermint 共识算法](https://github.com/tendermint/spec/blob/master/spec/consensus/consensus.md) 和[轻客户端算法](https://github.com/tendermint/spec/blob/master/spec/light-client/README.md)。 30 | 31 | ### 客户端状态 32 | 33 | Tendermint 客户端状态跟踪当前版本、当前验证器集、信任期、解绑期、最新高度、最新时间戳(区块时间)和可能的冻结高度。 34 | 35 | ```go 36 | type ClientState struct{ 37 | ChainID string 38 | ValidatorSet List> 39 | TrustLevel Rational 40 | TrustingPeriod uint64 41 | UnbondingPeriod uint64 42 | LatestHeight Height 43 | LatestTimestamp uint64 44 | FrozenHeight Height 45 | MaxClockDrift uint64 46 | ProofSpecs: []ProofSpec 47 | } 48 | ``` 49 | 50 | ### 共识状态 51 | 52 | Tendermint 客户端跟踪所有先前验证的共识状态的时间戳(区块时间)、验证者集和承诺根(这些可以在解绑期过后进行修剪,但不应事先修剪)。 53 | 54 | ```go 55 | type ConsensusState struct{ 56 | Timestamp uint64 57 | NextValidatorsHash []byte 58 | Root []byte 59 | } 60 | ``` 61 | 62 | ### 高度 63 | 64 | Tendermint 客户端的高度由两个 `uint64` 组成:修订号和修订中的高度。 65 | 66 | ```go 67 | type Height struct{ 68 | RevisionNumber uint64 69 | RevisionHeight uint64 70 | } 71 | ``` 72 | 73 | 高度之间的比较实现如下: 74 | 75 | ```go 76 | func Compare(a Height, b Height): Ord { 77 | if (a.RevisionNumber < b.RevisionNumber) 78 | return LT 79 | else if (a.RevisionNumber === b.RevisionNumber) 80 | if (a.RevisionHeight < b.RevisionHeight) 81 | return LT 82 | else if (a.RevisionHeight === b.RevisionHeight) 83 | return EQ 84 | return GT 85 | } 86 | ``` 87 | 88 | 这旨在允许高度重置为 `0`,而修订号增加 `1`,以便通过零高度升级保留超时。 89 | 90 | ### 区块头 91 | 92 | Tendermint 客户端标头包括高度、时间戳、提交根、完整的验证器集以及提交块的验证器的签名。 93 | 94 | ```go 95 | type Header struct{ 96 | SignedHeader SignedHeader 97 | ValidatorSet List> 98 | TrustedValidators List> 99 | TrustedHeight Height 100 | } 101 | ``` 102 | 103 | ### 客户端初始化 104 | 105 | `Tendermint`客户端初始化需要(主观选择的)最新共识状态,包括完整的验证器集。为了清理过期的共识状态信息,需要记录每个`ConsensusState`的保存路径,方便客户端清理。 106 | 107 | ```go 108 | func (cs ClientState) Initialize(clientStore sdk.KVStore,consState ConsensusState) { 109 | clientStore.Set("iterateConsensusStates/{cs.LatestHeight}", "consensusStates/{cs.LatestHeight}") 110 | clientStore.Set("consensusStates/{cs.LatestHeight}/processTime", consState.Timestamp) 111 | return nil 112 | } 113 | ``` 114 | 115 | ### 状态 116 | 117 | ```go 118 | func (cs ClientState) Status(clientStore sdk.KVStore) Status { 119 | assert(!cs.FrozenHeight.IsZero()) return Frozen 120 | 121 | onsState, err := GetConsensusState(clientStore, cdc, cs.GetLatestHeight()) 122 | assert(err == nil) return Unknown 123 | assert(consState.Timestamp+cs.TrustingPeriod > now()) return Expired 124 | } 125 | ``` 126 | 127 | ### 有效性断言 128 | 129 | Tendermint 客户端有效性检查使用 [Tendermint 规范](https://github.com/tendermint/spec/tree/master/spec/consensus/light-client) 中描述的二分算法。 130 | 131 | ```go 132 | func (cs ClientState) CheckHeaderAndUpdateState(header Header) { 133 | // assert trusting period has not yet passed 134 | assert(currentTimestamp() - clientState.latestTimestamp < clientState.trustingPeriod) 135 | // assert header timestamp is less than trust period in the future. This should be resolved with an intermediate header. 136 | assert(header.timestamp - clientState.latestTimeStamp < trustingPeriod) 137 | // assert header timestamp is past current timestamp 138 | assert(header.timestamp > clientState.latestTimestamp) 139 | // assert header height is newer than any we know 140 | assert(header.height > clientState.latestHeight) 141 | // call the `verify` function 142 | assert(verify(clientState.validatorSet, clientState.latestHeight, clientState.trustingPeriod, maxClockDrift, header)) 143 | // update validator set 144 | clientState.validatorSet = header.validatorSet 145 | // update latest height 146 | clientState.latestHeight = header.height 147 | // update latest timestamp 148 | clientState.latestTimestamp = header.timestamp 149 | // create recorded consensus state, save it 150 | consensusState = ConsensusState{header.timestamp, header.validatorSet, header.commitmentRoot} 151 | return clientState,consensusState 152 | } 153 | ``` 154 | 155 | ### 状态验证功能 156 | 157 | Tendermint 客户端状态验证功能根据先前验证的承诺根检查 Merkle 证明。 158 | 159 | 这些函数利用初始化客户端的`proofSpecs`。 160 | 161 | ```go 162 | func (cs ClientState) VerifyPacketCommitment( 163 | height Height, 164 | prefix Prefix, 165 | proof []byte, 166 | sourceChain string, 167 | destChain string, 168 | sequence uint64, 169 | commitmentBytes bytes) { 170 | path = applyPrefix(prefix, "commitments/{sourceChain}/{destChain}/packets/{sequence}") 171 | // check that the client is at a sufficient height 172 | assert(clientState.latestHeight >= height) 173 | // check delay period has passed 174 | if err := verifyDelayPeriodPassed(height, cs.DelayTime(), cs.DelayBlock()); err != nil { 175 | return err 176 | } 177 | // fetch the previously verified commitment root & verify membership 178 | root = get("clients/{clientState.ChainName()}/consensusStates/{height}") 179 | // verify that the provided commitment has been stored 180 | assert(root.verifyMembership(clientState.proofSpecs, path, hash(commitmentBytes), proof)) 181 | } 182 | 183 | func (cs ClientState) VerifyPacketAcknowledgement( 184 | height Height, 185 | prefix Prefix, 186 | proof []byte, 187 | sourceChain string, 188 | destChain string, 189 | sequence uint64, 190 | acknowledgement bytes) { 191 | path = applyPrefix(prefix, "acks/{sourceChain}/{destChain}/acknowledgements/{sequence}") 192 | // check that the client is at a sufficient height 193 | assert(clientState.latestHeight >= height) 194 | // check that the client is unfrozen or frozen at a higher height 195 | assert(clientState.frozenHeight === null || clientState.frozenHeight > height) 196 | // fetch the previously verified commitment root & verify membership 197 | root = get("clients/{clientState.ChainName()}/consensusStates/{height}") 198 | // verify that the provided acknowledgement has been stored 199 | assert(root.verifyMembership(clientState.proofSpecs, path, hash(acknowledgement), proof)) 200 | } 201 | 202 | func (cs ClientState) VerifyPacketCleanCommitment( 203 | height Height, 204 | prefix CommitmentPrefix, 205 | proof []byte, 206 | sourceChain string, 207 | destChain string, 208 | sequence uint64) { 209 | path = applyPrefix(prefix, "cleans/{sourceChain}/clean") 210 | // check that the client is at a sufficient height 211 | assert(clientState.latestHeight >= height) 212 | // check that the client is unfrozen or frozen at a higher height 213 | assert(clientState.frozenHeight === null || clientState.frozenHeight > height) 214 | // fetch the previously verified commitment root & verify membership 215 | root = get("clients/{clientState.ChainName()}/consensusStates/{height}") 216 | // verify that the nextSequenceRecv is as claimed 217 | assert(root.verifyMembership(clientState.proofSpecs, path, sequence, proof)) 218 | } 219 | ``` 220 | -------------------------------------------------------------------------------- /zh/client/tics-008-bsc-client/README.md: -------------------------------------------------------------------------------- 1 | | tics | title | stage | category | kind | requires | 2 | | ---- | ------------------------------------ | ----- | -------- | ------ | -------- | 3 | | 8 | BSC 轻客户端 | 草案 | IBC/TAO | 实例化 | 2 | 4 | 5 | ## 概要 6 | 7 | 本规范文档描述了使用BSC `parlia`共识的区块链客户端(验证算法),以下简称`parlia`共识 8 | 9 | ### 动机 10 | 11 | 使用BSC `parlia`共识算法复制的各种状态机可能希望通过`TIBC`与其他复制状态机或单机连接。 12 | 13 | ### 定义 14 | 15 | 功能和术语在 [TICS 2](../../core/tics-002-client-semantics) 中定义。 16 | 17 | `currentTimestamp` 在 [TICS 24](../../core/tics-024-host-requirements) 中定义。 18 | 19 | ### 所需属性 20 | 21 | 该规范必须满足`TICS-002`中定义的客户端接口。 22 | 23 | ## 技术指标 24 | 25 | 该规范取决于[`parlia`共识算法](https://github.com/binance-chain/docs-site/blob/master/docs/smart-chain/guides/concepts/consensus.md)。 26 | 27 | ### 区块头 28 | 29 | `BSC`客户端的`Header`完全采用`Ethereum`的定义,只是对`Extra`字段进行扩展,添加出块人的签名信息等。 30 | 31 | ```go 32 | type Header struct { 33 | ParentHash [32]byte 34 | UncleHash [32]byte 35 | Coinbase [20]byte 36 | Root [32]byte 37 | TxHash [32]byte 38 | ReceiptHash [32]byte 39 | Bloom [256]byte 40 | Difficulty *big.Int 41 | Number *big.Int 42 | GasLimit uint64 43 | GasUsed uint64 44 | Time uint64 45 | Extra []byte 46 | MixDigest [32]byte 47 | Nonce [8]byte 48 | } 49 | ``` 50 | 51 | 在BSC中采用了DPOS+POA的方式进行共识,目前每隔`200`个区块进行一次验证人集合的更新,如果一个区块的高度为200的倍数,则称为纪元区块。纪元块中的header.Extra保存了下一个纪元的验证人集合,数据格式为: 52 | 53 | ```text 54 | extraVanity = [32]byte{} 55 | validateSets = N * [20]byte{} 56 | signature = [65]byte{} 57 | header.Extra = extraVanity + validateSets + signature 58 | ``` 59 | 60 | 非纪元块: 61 | 62 | ```text 63 | extraVanity = [32]byte{} 64 | signature = [65]byte{} 65 | header.Extra = extraVanity + signature 66 | ``` 67 | 68 | 区块头的消息签名算法为: 69 | 70 | ```go 71 | func encodeSigHeader(w io.Writer, header *types.Header, chainId *big.Int) { 72 | err := rlp.Encode(w, []interface{}{ 73 | chainId, 74 | header.ParentHash, 75 | header.UncleHash, 76 | header.Coinbase, 77 | header.Root, 78 | header.TxHash, 79 | header.ReceiptHash, 80 | header.Bloom, 81 | header.Difficulty, 82 | header.Number, 83 | header.GasLimit, 84 | header.GasUsed, 85 | header.Time, 86 | header.Extra[:len(header.Extra)-65], 87 | header.MixDigest, 88 | header.Nonce, 89 | }) 90 | if err != nil { 91 | panic("can't encode: " + err.Error()) 92 | } 93 | } 94 | 95 | func SealHash(header *types.Header, chainId *big.Int) (hash common.Hash) { 96 | hasher := sha3.NewLegacyKeccak256() 97 | encodeSigHeader(hasher, header, chainId) 98 | hasher.Sum(hash[:0]) 99 | return hash 100 | } 101 | 102 | ``` 103 | 104 | ### 客户端状态 105 | 106 | ```go 107 | type ClientState struct{ 108 | Header Header 109 | ChainId *big.Int 110 | Epoch uint64 111 | Period uint64 112 | Validators map[[32]byte]struct{} 113 | RecentSingers map[uint64][32]byte 114 | ContractAddress [20]byte 115 | TrustingPeriod uint64 116 | } 117 | ``` 118 | 119 | - `ChainId`: 区块链的唯一标识符。 120 | - `Period`:要强制执行的块之间的秒数,当前BSC设置为3 121 | - `Epoch`:每个纪元经历的块数,当前BSC设置为200。 122 | - `Validators`:当前纪元的验证人集合 123 | - `RecentSingers`:最近签名的验证人集合(机会均等) 124 | - `ContractAddress`:跨链管理合约地址 125 | - `TrustingPeriod`:当前轻客户端的信任周期(ns) 126 | 127 | ### 共识状态 128 | 129 | `BSC`客户端保存了当前高度的时间戳、高度以及状态根信息。 130 | 131 | ```go 132 | type ConsensusState struct{ 133 | Timestamp uint64 134 | Number *big.Int 135 | Root []byte 136 | } 137 | ``` 138 | 139 | ### 状态 140 | 141 | ```go 142 | func (cs ClientState) Status(clientStore sdk.KVStore) { 143 | onsState, err := GetConsensusState(clientStore, cdc, cs.GetLatestHeight()) 144 | assert(err == nil) return Unknown 145 | assert(consState.Timestamp+cs.TrustingPeriod > now()) return Expired 146 | } 147 | ``` 148 | 149 | ### 默克尔证明 150 | 151 | ```go 152 | type Proof struct { 153 | Address string 154 | Balance string 155 | CodeHash string 156 | Nonce string 157 | StorageHash string 158 | AccountProof []string 159 | StorageProof []StorageResult 160 | } 161 | 162 | // StorageProof ... 163 | type StorageResult struct { 164 | Key string 165 | Value string 166 | Proof []string 167 | } 168 | ``` 169 | 170 | ### 客户端初始化 171 | 172 | `BSC`的共识算法中加入了验证人的概念,而且分为纪元区块和非纪元区块,只有纪元区块才会更新验证人集合,所以需要在初始化时必须上传纪元区块以保存当前的验证人集合。 173 | 174 | ```go 175 | func (cs ClientState) Initialize(clientStore sdk.KVStore,consState ConsensusState) { 176 | assert(cs.Header.Number % cs.Epoch != 0, "must be epoch block") 177 | validatorBytes := cs.Header.Extra[extraVanity : len(cs.Header.Extra)-extraSeal] 178 | for _, v := range ParseValidators(validatorBytes) { 179 | cs.Validators[v.Address] = struct{}{} 180 | } 181 | } 182 | ``` 183 | 184 | ### 延迟确认区块周期 185 | 186 | `BSC`的任何关键应用程序可能必须等待`2/3*N+1`块才能确保相对安全的终结 187 | 188 | ```go 189 | func (cs ClientState) DelayBlock() uint64{ 190 | return (2*len(cs.Validators)/3 + 1) 191 | } 192 | ``` 193 | 194 | ### 延迟确认时间周期 195 | 196 | 返回当前轻客户端的延迟确认时间周期。 197 | 198 | ```go 199 | func (cs ClientState) DelayTime() uint64 { 200 | return (2*len(cs.Validators)/3 + 1) * cs.Period 201 | } 202 | ``` 203 | 204 | ### 有效性断言 205 | 206 | `BSC` 客户端有效性检查大致和以太坊的轻客户端相同,区别在于:BSC的区块头的校验是基于纪元的。 207 | 208 | 验证区块头时,除了验证区块头自身的合法性以外,还需要验证难度系数、验证人变化、机会均等机制等: 209 | 210 | - 验证人集合变更事件发生在`header.Number % cs.Epoch`块 211 | - 验证人集合变更生效发生在`header.Number % cs.Epoch == uint64(len(cs.Validators)/2)` 212 | - 为避免某些恶意节点持续出块,`Parlia`中规定每一个认证节点在连续`limit`个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有`len(cs.Validators) - limit`个认证节点可以参与区块签发。其中`limit = floor(len(cs.Validators) / 2) + 1`。 213 | 214 | ```go 215 | 216 | var ( 217 | diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures 218 | diffNoTurn = big.NewInt(1) // Block difficulty for out-of-turn signatures 219 | ) 220 | 221 | func (cs ClientState) CheckHeaderAndUpdateState(header Header) { 222 | // assert header timestamp is past timestamp 223 | assert(header.Time > currentTime) 224 | assert(length(header.Extra) >= 32+65) 225 | assert(header.MixDigest != []byte{}) 226 | assert(header.Difficulty != nil) 227 | assert(header.GasLimit <= uint64(0x7fffffffffffffff)) 228 | assert(header.GasUsed <= header.GasLimit) 229 | assert(cs.Header.Number + 1 = header.Number) 230 | assert(cs.Header.Hash() = header.ParentHash) 231 | verifyCascadingFields(header, cs.Header) 232 | 233 | signer = ecrecover(header, signature, cs.ChainId) 234 | if signer != header.Coinbase { 235 | return errCoinBaseMisMatch 236 | } 237 | 238 | // The validator set change occurs at `header.Number % cs.Epoch == 0` 239 | if header.Number % cs.Epoch == 0 { 240 | validatorBytes := checkpoint.Extra[extraVanity : len(checkpoint.Extra)-extraSeal] 241 | validators = ParseValidators(validatorBytes) 242 | set("clientState/snapshot/{header.Number}",validators) 243 | } 244 | 245 | // Delete the oldest validator from the recent list to allow it signing again 246 | if limit := uint64(len(cs.Validators)/2 + 1); header.Number >= limit { 247 | delete(cs.RecentSingers, header.Number-limit) 248 | } 249 | 250 | cs.RecentSingers[header.Number] = signer 251 | 252 | // The validator set change takes effect on `header.Number % cs.Epoch == len(cs.Validators)/2` 253 | if header.Number % cs.Epoch == uint64(len(cs.Validators)/2){ 254 | epochNumber := header.Number / cs.Epoch 255 | epochHeight := epochNumber * cs.Epoch 256 | validators := get("clientState/snapshot/{epochHeight}") 257 | 258 | newVals := make(map[common.Address]struct{}, len(validators)) 259 | for _, val := range validators { 260 | newVals[val] = struct{}{} 261 | } 262 | 263 | oldLimit := len(clientState.Validators)/2 + 1 264 | newLimit := len(newVals)/2 + 1 265 | if newLimit < oldLimit { 266 | for i := 0; i < oldLimit-newLimit; i++ { 267 | delete(cs.RecentSingers, header.Number-uint64(newLimit)-uint64(i)) 268 | } 269 | } 270 | clientState.Validators := newVals 271 | } 272 | 273 | if _, ok := cs.Validators[signer]; !ok { 274 | return errUnauthorizedValidator 275 | } 276 | 277 | for seen, recent := range cs.RecentSingers { 278 | if recent == signer { 279 | // Signer is among RecentSingers, only fail if the current block doesn't shift it out 280 | if limit := uint64(len(cs.Validators)/2 + 1); seen > number-limit { 281 | return errRecentlySigned 282 | } 283 | } 284 | } 285 | 286 | inturn := cs.inturn(signer) 287 | if inturn && header.Difficulty.Cmp(diffInTurn) != 0 { 288 | return errWrongDifficulty 289 | } 290 | if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 { 291 | return errWrongDifficulty 292 | } 293 | 294 | clientState.Header = header 295 | consensusState = ConsensusState{header.Time, header.Number, header.Root} 296 | return clientState,consensusState 297 | } 298 | 299 | func (cs ClientState) inturn(validator common.Address) bool { 300 | offset := cs.Header.Number % uint64(len(cs.Validators)) 301 | return validators[offset] == validator 302 | } 303 | ``` 304 | 305 | ### 状态验证功能 306 | 307 | BSC客户端状态验证功能根据先前验证的承诺根检查Merkle证明。 308 | 309 | ```go 310 | func (cs ClientState) VerifyPacketCommitment( 311 | height Height, 312 | prefix Prefix, 313 | proof []byte, 314 | sourceChain string, 315 | destChain string, 316 | sequence uint64, 317 | commitmentBytes bytes) { 318 | 319 | assert(cs.DelayBlock() <= cs.Header.Number - height) 320 | path = applyPrefix(prefix, "commitments/{sourceChain}/{destChain}/packets/{sequence}") 321 | assert(sha256(path) == proof.StorageProof[0].Key) 322 | // verify whether it is the management contract address 323 | assert(proof.Address == cs.ContractAddress) 324 | // fetch the previously verified commitment root & verify membership 325 | root = get("clients/{clientState.ChainName()}/consensusStates/{height}") 326 | // verify that the provided commitment has been stored 327 | trie.VerifyProof(root, account, proof.AccountProof) 328 | v := trie.VerifyProof(root, storageHash, proof.StorageProof) 329 | assert(commitmentBytes == v) 330 | } 331 | 332 | func (cs ClientState) VerifyPacketAcknowledgement( 333 | height Height, 334 | prefix Prefix, 335 | proof []byte, 336 | sourceChain string, 337 | destChain string, 338 | sequence uint64, 339 | acknowledgement bytes) { 340 | assert(cs.DelayBlock() <= cs.Header.Number - height) 341 | path = applyPrefix(prefix, "acks/{sourceChain}/{destChain}/acknowledgements/{sequence}") 342 | assert(sha256(path) >= proof.StorageProof[0].Key) 343 | // verify whether it is the management contract address 344 | assert(proof.Address == cs.ContractAddress) 345 | 346 | // check that the client is at a sufficient height 347 | assert(clientState.Header.Height >= height) 348 | // fetch the previously verified commitment root & verify membership 349 | root = get("clients/{clientState.ChainName()}/consensusStates/{height}") 350 | // verify that the provided commitment has been stored 351 | trie.VerifyProof(root, account, proof.AccountProof) 352 | v:= trie.VerifyProof(root, storageHash, proof.StorageProof) 353 | assert(commitmentBytes == v) 354 | } 355 | 356 | func (cs ClientState) VerifyPacketCleanCommitment( 357 | height Height, 358 | prefix CommitmentPrefix, 359 | proof []byte, 360 | sourceChain string, 361 | destChain string, 362 | sequence uint64) { 363 | assert(cs.DelayBlock() <= cs.Header.Number - height) 364 | path = applyPrefix(prefix, "cleans/{sourceChain}/clean") 365 | assert(sha256(path) >= proof.StorageProof[0].Key) 366 | // verify whether it is the management contract address 367 | assert(proof.Address == cs.ContractAddress) 368 | 369 | // check that the client is at a sufficient height 370 | assert(clientState.Header.Height >= height) 371 | // fetch the previously verified commitment root & verify membership 372 | root = get("clients/{clientState.ClientID()}/consensusStates/{height}") 373 | // verify that the provided commitment has been stored 374 | trie.VerifyProof(root, account, proof.AccountProof) 375 | v := trie.VerifyProof(root, storageHash, proof.StorageProof) 376 | assert(sequence == v) 377 | } 378 | ``` 379 | -------------------------------------------------------------------------------- /zh/client/tics-009-eth-client/README.md: -------------------------------------------------------------------------------- 1 | | tics | title | stage | category | kind | requires | 2 | | ---- | ------------ | ----- | -------- | ------ | -------- | 3 | | 9 | Ethereum 轻客户端 | 草案 | TIBC/TAO | 实例化 | 2 | 4 | 5 | ## 概要 6 | 7 | 本规范文档描述了使用 Ethereum 基于 `PoW` 共识的区块链客户端(验证算法),以下简称 `PoW` 共识。 8 | 9 | ### 动机 10 | 11 | 使用 Ethereum `PoW` 共识算法复制的各种状态机可能希望通过 `TIBC` 与其它复制状态机或单机连接。 12 | 13 | ### 定义 14 | 15 | 功能和术语在 [TICS 2](../../core/tics-002-client-semantics) 中定义。 16 | 17 | ### 所需属性 18 | 19 | 该规范必须满足 `TICS-002` 中定义的客户端接口。 20 | 21 | ## 技术指标 22 | 23 | 该规范取决于 [PoW 共识算法](https://github.com/ethereum/go-ethereum/blob/master/consensus/ethash/consensus.go)。 24 | 25 | ### 区块头 26 | 27 | `ETH`客户端的`Header`完全采用`Ethereum`的定义. 28 | 29 | ```go 30 | type Header struct { 31 | ParentHash [32]byte 32 | UncleHash [32]byte 33 | Coinbase [20]byte 34 | Root [32]byte 35 | TxHash [32]byte 36 | ReceiptHash [32]byte 37 | Bloom [256]byte 38 | Difficulty *big.Int 39 | Number *big.Int 40 | GasLimit uint64 41 | GasUsed uint64 42 | Time uint64 43 | Extra []byte 44 | MixDigest [32]byte 45 | Nonce [8]byte 46 | BaseFee *big.Int 47 | } 48 | ``` 49 | 50 | 51 | 52 | ### 客户端状态 53 | 54 | ```go 55 | type ClientState struct{ 56 | Header Header 57 | ChainId *big.Int 58 | ContractAddress [20]byte 59 | TrustingPeriod uint64 60 | TimeDelay uint64 61 | BlockDelay uint64 62 | } 63 | ``` 64 | 65 | - `Header` : 区块头 66 | - `ChainId`: 区块链的唯一标识符 67 | - `ContractAddress`:跨链 packet 合约地址 68 | - `TrustingPeriod`:当前轻客户端的信任周期(ns) 69 | - `TimeDelay`延迟确认时间周期 70 | - `BlockDelay`延迟确认区块周期 71 | 72 | ### 共识状态 73 | 74 | `ETH` 客户端保存了当前高度的时间戳、高度以及状态根信息。 75 | 76 | ```go 77 | type ConsensusState struct{ 78 | Timestamp uint64 79 | Number *big.Int 80 | Root []byte 81 | } 82 | ``` 83 | 84 | ### 状态 85 | 86 | ```go 87 | func (cs ClientState) Status(clientStore sdk.KVStore) { 88 | onsState, err := GetConsensusState(clientStore, cdc, cs.GetLatestHeight()) 89 | assert(err == nil) return Unknown 90 | assert(consState.Timestamp+cs.GetDelayTime() currentTime) 176 | assert(length(header.Extra) >= 32) 177 | assert(header.MixDigest != []byte{}) 178 | assert(header.Difficulty != nil) 179 | assert(header.GasLimit <= uint64(0x7fffffffffffffff)) 180 | assert(header.GasUsed <= header.GasLimit) 181 | assert(parent.Hash() = header.ParentHash) 182 | 183 | // verify header 1559 184 | assert( VerifyEip1559Header(parentHeader, &header)!= false) 185 | //verify difficulty 186 | expected := makedifficult(big.NewInt(9700000)(header.Time, &parent.Header) 187 | // 9700000 should be changed to the desired 188 | return verifyCascadingFields(header) 189 | } 190 | ``` 191 | 分叉处理: 192 | ```go 193 | func (m ClientState) RestrictChain(cdc codec.BinaryCodec, store sdk.KVStore, new Header) error { 194 | si, ti := m.Header.Height, new.Height 195 | var err error 196 | current := m.Header 197 | // si > ti 198 | if si.RevisionHeight > ti.RevisionHeight { 199 | assert(store.Get(host.ConsensusStateKey(ti)) != nil) 200 | 201 | cdc.UnmarshalInterface(ConsensusTmp, &tiConsensus) 202 | tmpConsensus, ok := tiConsensus.(*ConsensusState) 203 | 204 | root := tmpConsensus.Root 205 | currentBytes := store.Get(headerIndexKey) 206 | assert(currentBytes != nil) 207 | cdc.UnmarshalInterface(currentBytes, ¤tHeaderInterface) 208 | current = currentHeaderInterface.(*EthHeader) 209 | si = ti 210 | } 211 | 212 | // use newHashes to cache header hash 213 | newHashes := make([]common.Hash, 0) 214 | 215 | // ti > si 216 | for ti.RevisionHeight > si.RevisionHeight { 217 | newHashes = append(newHashes, new.Hash()) 218 | newTmp := GetParentHeaderFromIndex(store, new) 219 | assert(newTmp != nil) 220 | 221 | cdc.UnmarshalInterface(newTmp, ¤tly) 222 | tmpConsensus:= currently.(*Header) 223 | new = *tmpConsensus 224 | // till ti = si 225 | ti.RevisionHeight-- 226 | } 227 | 228 | // when si.parent != ti.parent run to si.parent = ti.parent 229 | for !bytes.Equal(current.ParentHash, new.ParentHash) { 230 | newHashes = append(newHashes, new.Hash()) 231 | newTmp := GetParentHeaderFromIndex(store, new) 232 | assert(newTmp != nil) 233 | cdc.UnmarshalInterface(newTmp, ¤tly); 234 | tmpConsensus, ok := currently.(*Header) 235 | new = *tmpConsensus 236 | ti.RevisionHeight-- 237 | si.RevisionHeight-- 238 | currentTmp := GetParentHeaderFromIndex(store, current) 239 | cdc.UnmarshalInterface(currentTmp, ¤tly) 240 | tmpConsensus = currently.(*Header) 241 | current = *tmpConsensus 242 | } 243 | // make newHashs cache to main_chain 244 | for i := len(newHashes) - 1; i >= 0; i-- { 245 | // get from HeaderIndex 246 | newTmp := store.Get(EthHeaderIndexKey(newHashes[i], ti.GetRevisionHeight())) 247 | cdc.UnmarshalInterface(newTmp, ¤tly) 248 | tmpHeader, ok := currently.(*Header) 249 | 250 | consensusState := &ConsensusState{ 251 | Timestamp: tmpHeader.Time, 252 | Number: tmpHeader.Height, 253 | Root: tmpHeader.Root[:], 254 | } 255 | consensusStateBytes, err := cdc.MarshalInterface(consensusState) 256 | 257 | // set to main_chain 258 | store.Set(host.ConsensusStateKey(ti), consensusStateBytes) 259 | ti.RevisionHeight++ 260 | } 261 | return err 262 | } 263 | ``` 264 | 265 | ### 状态验证功能 266 | 267 | `ETH` 客户端状态验证功能根据先前验证的承诺根检查 Merkle 证明。 268 | 269 | ```go 270 | func (cs ClientState) VerifyPacketCommitment( 271 | height Height, 272 | prefix Prefix, 273 | proof []byte, 274 | sourceChain string, 275 | destChain string, 276 | sequence uint64, 277 | commitmentBytes bytes) { 278 | 279 | ethProof, consensusState, err := produceVerificationArgs(store, cdc, m, height, proof) 280 | // check delay period has passed 281 | assert(cs.DelayBlock() <= cs.Header.Number - height) 282 | 283 | // verify that the provided commitment has been stored 284 | constructor := NewProofKeyConstructor(sourceChain, destChain, sequence) 285 | trie.VerifyProof(ethProof,consensusState,contractAddr,commitment,proofKey) 286 | } 287 | 288 | func (cs ClientState) VerifyPacketAcknowledgement( 289 | height Height, 290 | prefix Prefix, 291 | proof []byte, 292 | sourceChain string, 293 | destChain string, 294 | sequence uint64, 295 | acknowledgement bytes) { 296 | ethProof, consensusState, err := produceVerificationArgs(store, cdc, m, height, proof) 297 | // check delay period has passed 298 | assert(cs.DelayBlock() <= cs.Header.Number - height) 299 | 300 | // verify that the provided commitment has been stored 301 | constructor := NewProofKeyConstructor(sourceChain, destChain, sequence) 302 | trie.VerifyProof(ethProof,consensusState,contractAddr,commitment,proofKey) 303 | } 304 | 305 | func (cs ClientState) VerifyPacketCleanCommitment( 306 | height Height, 307 | prefix CommitmentPrefix, 308 | proof []byte, 309 | sourceChain string, 310 | destChain string, 311 | sequence uint64) { 312 | ethProof, consensusState, err := produceVerificationArgs(store, cdc, m, height, proof) 313 | // check delay period has passed 314 | assert(cs.DelayBlock() <= cs.Header.Number - height) 315 | 316 | // verify that the provided commitment has been stored 317 | constructor := NewProofKeyConstructor(sourceChain, destChain, sequence) 318 | trie.VerifyProof(ethProof,consensusState,contractAddr,commitment,proofKey) 319 | } 320 | ``` -------------------------------------------------------------------------------- /zh/core/tics-002-client-semantics/README.md: -------------------------------------------------------------------------------- 1 | 2 | | tics | title | stage | category | kind | requires | author | created | modified | 3 | | ---- | ---------------- | ----- | -------- | --------- | -------- | ------------------- | ---------- | ---------- | 4 | | 2 | Client Semantics | draft | TIBC/TAO | interface | 23,24 | zhiqiang@bianjie.ai | 2021-07-23 | 2021-07-26 | 5 | 6 | ## 大纲 7 | 8 | 本文描述了轻客户端的整个生命周期,主要包括:创建、更新、升级以及利用轻客户端状态验证来源链数据的整个流程的定义,每种轻客户端的详细实现不在本文档的范围之内。 9 | 10 | ### 动机 11 | 12 | 在TIBC协议中,参与者(可能是直接用户,链下进程或一台机器)需要能够验证另一台机器的共识算法已同意的状态更新,并拒绝另一台机器的共识算法尚未达成共识的任何可能的更新。轻客户端是机器可以执行的算法。该标准规范了轻客户端模型和需求,因此只要提供满足列出要求的相关轻客户端算法,TIBC协议就可以轻松地与运行新的共识算法的新机器集成 13 | 14 | ### 定义 15 | 16 | - `get`、`set`、`Path` 和`Identifier` 在 TICS 24 中定义。 17 | - `CommitmentRoot` 在 TICS 23 中定义。 它必须为下游逻辑提供一种廉价的方式来验证键/值对是否处于特定高度的状态。 18 | - `ConsensusState` 是表示有效性断言状态的接口类型。`ConsensusState`必须能够验证相关共识算法同意的状态更新。它也必须以规范的方式可序列化,以便第三方(例如对方机器)可以检查特定机器是否已存储特定机器的共识状态。它最终必须由它所针对的状态机进行自省,以便状态机可以在过去的高度查找自己的共识状态。 19 | - `ClientState`是代表客户端状态的接口类型。`ClientState`必须公开查询函数以验证特定高度状态中键/值对的成员资格或非成员资格,并检索当前的ConsensusState。 20 | 21 | ### 所需属性 22 | 23 | 轻客户端必须提供一种安全算法来验证其他链的规范`header`,使用现有的`ConsensusState`。 然后,更高级别的抽象将能够使用存储在`ConsensusState`中的 `CommitmentRoot`来验证状态的子组件,这些子组件保证已由其他链的共识算法提交。 24 | 25 | 有效性断言应反映运行相应共识算法的全节点的行为。 给定一个`ConsensusState`和一个消息列表,如果一个完整节点接受由`Commit`生成的新的`Header`,那么轻客户端也必须接受它,如果一个完整节点拒绝它,那么轻客户端也必须拒绝它。 26 | 27 | 轻客户端不会重放整个消息副本,因此在共识不当行为的情况下,轻客户端的行为可能与完整节点的行为不同。 在这种情况下,可以生成一个不当行为证明,证明有效性断言和完整节点之间的差异,并提交给链,以便链可以安全地停用轻客户端,使过去的状态根无效,并等待更高级别的干预。 28 | 29 | ## 技术规格 30 | 31 | ### 数据存储 32 | 33 | - ClientState 34 | 35 | ```go 36 | func SetClientState(chainName string, clientState ClientState) { 37 | store := k.ClientStore(ctx, chainName) 38 | store.Set("clientState", clientState) 39 | } 40 | 41 | func ClientStore(chainName string) sdk.KVStore{ 42 | return "clients/{chainName}" 43 | } 44 | ``` 45 | 46 | - ConsensusState 47 | 48 | ```go 49 | func SetConsensusState(chainName string, height Height, consensusState ConsensusState) { 50 | store := k.ClientStore(ctx, chainName) 51 | store.Set("consensusStates/{height}", consensusState) 52 | } 53 | ``` 54 | 55 | - ChainName 56 | 57 | `ChainName` 指定了链的名称,必须在genesis中指定,链初始化(`InitGenesis`)时写入区块链, 并且它必须是唯一的,用于在其他链上创建自己的轻客户端。 58 | 59 | ```go 60 | func SetChainName(chainName string) { 61 | store := ctx.KVStore(k.storeKey) 62 | store.Set("client/chainName", chainName) 63 | } 64 | ``` 65 | 66 | ### 数据结构 67 | 68 | #### 客户端类型 69 | 70 | 客户端类型指明了创建的轻客户端所使用的的共识算法类型,为了方便以后扩展,定义为常量形式,定义如下: 71 | 72 | ```golang 73 | type ClientType = string 74 | ``` 75 | 76 | #### 区块头 77 | 78 | `区块头`是由客户端类型定义的接口数据结构,它提供更新`ConsensusState`的信息。 可以将`区块头`提交给关联的客户端以更新存储的`ConsensusState`。 它们可能包含一个高度、一个证明、一个承诺根,还可能包含对有效性断言的更新。不同的区块链实现方式各有不同,所以区块头被定义为接口形式: 79 | 80 | ```golang 81 | type Header interface { 82 | ClientType() string 83 | GetHeight() Height 84 | ValidateBasic() error 85 | } 86 | ``` 87 | 88 | #### 高度 89 | 90 | `高度`定义了目标链当前的区块高度信息,考虑到分叉的可能性还应该包含一些其他信息,比如`版本信息`等,定义如下: 91 | 92 | ```go 93 | type Height interface { 94 | IsZero() bool 95 | LT(Height) bool 96 | LTE(Height) bool 97 | EQ(Height) bool 98 | GT(Height) bool 99 | GTE(Height) bool 100 | GetRevisionNumber() uint64 101 | GetRevisionHeight() uint64 102 | Increment() Height 103 | Decrement() (Height, bool) 104 | String() string 105 | } 106 | ``` 107 | 108 | #### 状态 109 | 110 | `状态`定义了当前轻客户端的所处的状态,例如`活跃`、`过期`、`未知`,定义如下: 111 | 112 | ```go 113 | type Active = "Active" 114 | type Expired = "Expired" 115 | type Unknown = "Unknown" 116 | ``` 117 | 118 | 具体的状态判断逻辑由具体轻客户端实现,当轻客户端过期后,无法直接更新,只能采用提议升级的方式更新轻客户端状态。 119 | 120 | #### 客户端状态 121 | 122 | `客户端状态`是由客户端类型定义的接口类型的数据结构。它可以保持任意内部状态,以跟踪经过验证的根和过去的不良行为,客户端状态由一系列接口组成,共同完成跨链数据包的合法性校验。 123 | 124 | - 类型 125 | 126 | 客户端类型用于用于返回当前轻客户端使用的共识算法类型定义。 127 | 128 | ```go 129 | func (cs ClientState) ClientType() string 130 | ``` 131 | 132 | - 轻客户端唯一标识 133 | 134 | ```go 135 | func (cs ClientState) ChainName() string 136 | ``` 137 | 138 | - 最新高度 139 | 140 | 返回轻客户端当前的最新高度。 141 | 142 | ```go 143 | func (cs ClientState) GetLatestHeight() Height 144 | ``` 145 | 146 | - 合法性校验 147 | 148 | 对于当前轻客户端数据的合法性校验 149 | 150 | ```go 151 | func (cs ClientState) Validate() error 152 | ``` 153 | 154 | - Proof校验规则 155 | 156 | - 轻客户端状态 157 | 158 | 返回当前轻客户端状态。 159 | 160 | ```go 161 | func (cs ClientState) Status() Status 162 | ``` 163 | 164 | - 延迟确认时间周期 165 | 166 | 返回当前轻客户端的延迟确认时间周期。 167 | 168 | ```go 169 | func (cs ClientState) DelayTime() uint64 170 | ``` 171 | 172 | - 延迟确认区块周期 173 | 174 | 返回当前轻客户端的延迟确认区块周期,例如比特币需要6个区块以上的确认周期。 175 | 176 | ```go 177 | func (cs ClientState) DelayBlock() uint64 178 | ``` 179 | 180 | - MerklePath 前缀 181 | 182 | 当前轻客户端的MerklePath前缀。定义在`tics-023`中。 183 | 184 | ```go 185 | func (cs ClientState) Prefix() Prefix 186 | ``` 187 | 188 | - 初始化轻客户端 189 | 190 | ```go 191 | func (cs ClientState) Initialize(consensusState consensusState) error 192 | ``` 193 | 194 | - 验证并更新轻客户端状态 195 | 196 | ```go 197 | func (cs ClientState) CheckHeaderAndUpdateState(header Header) (ClientState, ConsensusState, error) 198 | ``` 199 | 200 | - 验证跨链数据包 201 | 202 | 利用轻客户端共识状态,以及proof等信息对接收到的跨链数据包进行校验。 203 | 204 | ```go 205 | func (cs ClientState) VerifyPacketCommitment( 206 | height Height, 207 | prefix Prefix, 208 | proof []byte, 209 | sourceChain string, 210 | destChain string, 211 | sequence uint64, 212 | commitmentBytes []byte, 213 | ) error 214 | ``` 215 | 216 | 具体参数解释如下: 217 | 218 | - `height`:当前跨链数据包proof所在的高度。 219 | - `prefix`:跨链数据包存储store的名称(storeKey)。 220 | - `proof`: 跨链数据包的merkle证明。 221 | - `sourceChain`:数据包的发送链。 222 | - `destChain`: 数据包的接受链。 223 | - `sequence`: 跨链数据包的顺序。 224 | - `commitmentBytes`: 跨链数据包的承诺(跨链数据包按照相同规则hash运算)。 225 | 226 | - 验证跨链数据包Ack 227 | 228 | 利用轻客户端共识状态,以及proof等信息对跨链数据包确认消息进行校验。 229 | 230 | ```go 231 | func (cs ClientState) VerifyPacketAcknowledgement( 232 | height Height, 233 | prefix Prefix, 234 | proof []byte, 235 | sourceChain string, 236 | destChain string, 237 | sequence uint64, 238 | acknowledgement []byte 239 | ) error 240 | ``` 241 | 242 | 具体参数解释如下: 243 | 244 | - `height`:当前跨链数据包proof所在的高度。 245 | - `prefix`:跨链数据包存储store的名称(storeKey)。 246 | - `proof`: 跨链数据包的merkle证明。 247 | - `sourceChain`:数据包的发送链。 248 | - `destChain`: 数据包的接受链。 249 | - `sequence`: 跨链数据包的顺序。 250 | - `acknowledgement`: 跨链数据包Ack的承诺(跨链数据包确认消息按照相同规则hash运算)。 251 | 252 | - 验证跨链数据包(轻客户端状态清理) 253 | 254 | 当需要清理轻客户端过期的状态信息时,可以发送清理轻客户端跨链数据包来清理状态。 255 | 256 | ```go 257 | func (cs ClientState) verifyPacketCleanCommitment( 258 | height Height, 259 | prefix Prefix, 260 | proof []byte, 261 | sourceChain string, 262 | destChain string, 263 | sequence uint64, 264 | cleanCommitmentBytes []byte 265 | ) error 266 | ``` 267 | 268 | 具体参数解释如下: 269 | 270 | - `height`:当前跨链数据包proof所在的高度。 271 | - `prefix`:跨链数据包存储store的名称(storeKey)。 272 | - `proof`: 跨链数据包的merkle证明。 273 | - `sourceChain`:数据包的发送链。 274 | - `destChain`: 数据包的接受链。 275 | - `sequence`: 跨链数据包的顺序。 276 | - `cleanCommitmentBytes`: 清理轻客户端跨链数据包的承诺(跨链数据包确认消息按照相同规则hash运算)。 277 | 278 | - 共识状态 279 | 280 | 共识状态定义了轻客户端在某个高度的共识结果,一般以`merkle root`的形式定义,这个里的`merkle root`可能是存储根,也可能是交易根,具体实现由轻客户端定义。 281 | 282 | ```go 283 | type ConsensusState interface { 284 | ClientType() string // Consensus kind 285 | 286 | // GetRoot returns the commitment root of the consensus state, 287 | // which is used for key-value pair verification. 288 | GetRoot() []byte 289 | 290 | // GetTimestamp returns the timestamp (in nanoseconds) of the consensus state 291 | GetTimestamp() uint64 292 | 293 | ValidateBasic() error 294 | } 295 | ``` 296 | 297 | ## 交易 298 | 299 | ### 更新轻客户端状态 300 | 301 | 用户提交跨链数据包的时候,必须先更新轻客户端状态,然后再提交交易。如果源链非即时最终性,需要延后区块确认交易,那么用户在提交跨链数据包前N个区块更新轻客户端状态。例如源链需要延迟6个区块确认,用户提交的跨链数据包proof高度必须满足: 302 | 303 | ```text 304 | proofHeight - clientState.GetLatestHeight() >= 6 305 | ``` 306 | 307 | 更新轻客户端的交易结构定义为: 308 | 309 | ```go 310 | type MsgUpdateClient struct { 311 | ChainName string 312 | Header Header 313 | } 314 | ``` 315 | 316 | 更新过程示例如下: 317 | 318 | ```go 319 | func UpdateClient(chainName string,header Header) error { 320 | AssertClientExist(chainName) 321 | clientState := k.GetClientState(ctx, chainName) 322 | newClientState, newConsensusState := clientState.CheckHeaderAndUpdateState(header) 323 | SetClientState(chainName, newClientState) 324 | SetConsensusState(chainName,header.GetHeight() newConsensusState) 325 | return nil 326 | } 327 | ``` 328 | 329 | ## 轻客户端管理 330 | 331 | TIBC的轻客户端在hub上采用Gov的方式进行管理,在其他链(例如以太坊)可以采用其他方式,比如多签。 332 | 333 | ### 创建轻客户端提议 334 | 335 | ```go 336 | type CreateClientProposal struct { 337 | ChainName string 338 | ClientState ClientState 339 | ConsensusState ConsensusState 340 | } 341 | ``` 342 | 343 | 如果在以太坊上,`ChainName`指以太坊上部署的某个轻客户端的合约地址,利用`CreateClientProposal`实现向管理合约注册并初始化的该轻客户端。注意,轻客户端的写入方法必须由管理合约调用,其他账户不能修改合约状态,以保证轻客户端的安全性。简要流程如下: 344 | 345 | ```text 346 | 部署管理合约 -> 部署轻客户端合约 -> 向管理合约注册轻客户端 -> 轻客户端合约状态更新 347 | ``` 348 | 349 | 示例如下: 350 | 351 | ```go 352 | func CreateClient(chainName string,clientState ClientState,consensusState ConsensusState) (string, error){ 353 | AssertClientNotExist(chainName) 354 | 355 | SetClientState(chainName, clientState) 356 | if err := clientState.Initialize(consensusState); err != nil { 357 | return "", err 358 | } 359 | 360 | SetConsensusState(chainName,clientState.GetLatestHeight() newConsensusState) 361 | return nil 362 | } 363 | ``` 364 | 365 | ### 升级轻客户端提议 366 | 367 | 当轻客户端过期或者轻客户端状态不正确时,可以使用升级的方式强制更新轻客户端状态。 368 | 369 | ```go 370 | type UpgradeClientProposal struct { 371 | ChainName string 372 | ClientState ClientState 373 | ConsensusState ConsensusState 374 | } 375 | ``` 376 | 377 | 过程示例如下: 378 | 379 | ```go 380 | func UpgradeClient(ctx sdk.Context, chainName string, upgradedClient ClientState, upgradedConsState ConsensusState){ 381 | AssertClientExist(chainName) 382 | SetClientState(chainName, upgradedClient) 383 | SetConsensusState(chainName,updatedClientState.GetLatestHeight() newConsensusState) 384 | } 385 | ``` 386 | 387 | ### 中继器注册提议 388 | 389 | 为保证跨链数据包及时、完整的中继到目标链,需要部署中继器程序,扫描源链上的交易,并将其中继到目标链。中继器需要完成的工作: 390 | 391 | - 更新来源链在目标链上轻客户端状态。 392 | - 中继跨链交易到目标链。 393 | 394 | 考虑到跨链数据的安全性,中继器采用投票注册的方式,中继链需要需要对数据的来源进行身份认证,只有认证通过的中继器发送的交易才能被中继链接受。注册提议设计如下: 395 | 396 | ```go 397 | type RegisterRelayerProposal struct { 398 | ChainName string 399 | Relayers []string 400 | } 401 | ``` 402 | 403 | ```go 404 | func RegisterRelayer(ctx sdk.Context, chainName string, relayers []sdk.AccAddress){ 405 | AssertClientExist(chainName) 406 | SetRelayers(chainName, relayers) 407 | } 408 | 409 | func AuthRelayer(ctx sdk.Context, chainName string, relayer sdk.AccAddress) bool { 410 | AssertClientExist(chainName) 411 | return GetRelayer(chainName).Contains(relayer) 412 | } 413 | ``` 414 | -------------------------------------------------------------------------------- /zh/core/tics-004-port-and-packet-semantics/README.md: -------------------------------------------------------------------------------- 1 | | tics | title | stage | category | kind | requires | author | created | modified | 2 | | ---- | ----------------------- | ----- | -------- | ------------- | -------- | -------------------------------- | ---------- | ---------- | 3 | | 4 | Port & Packet Semantics | draft | TIBC/TAO | instantiation | 2, 3, 26 | Wenxi Cheng | 2021-07-23 | 2021-07-26 | 4 | 5 | ## 大纲 6 | 7 | `Port`规定了端口分配系统,通过该系统,模块可以绑定到 TIBC 处理程序分配的唯一命名的端口。然后可以在端口间传递`Packet`,并且可以通过最初绑定到它们的模块进行传输或之后释放。 8 | 9 | `Packet`定义了链间数据包标准。发送和接收 TIBC 数据包的模块决定如何构造数据包数据以及如何处理传入的数据包数据,并且必须使用自己的应用程序逻辑根据数据包包含的数据来决定应用哪种状态交易。 10 | 11 | ### 动机 12 | 13 | 区块链间通信协议使用跨链消息传递模型。IBC 数据包通过外部relayer进程从一个区块链中继到另一个区块链。链 `A` 和链 `B` 独立地确认新的块,从一个链到另一个链的数据包可能被延迟、审查或任意重新排序。数据包对于relayer是可见的,任何relayer进程可以从一个区块链读取和提交到任何其他区块链。 14 | 15 | TIBC 协议必须提供准确一次的传递保证,以允许应用程序推断两个链上连接模块的组合状态。例如,一个应用程序可能希望允许一个标记化的资产之间转移,并在多个区块链上持有,同时保持可替换性和供应保护。当一个特定的 IBC 包被提交给链 `B` 时,应用程序可以在链 `B` 上铸造资产凭证,并要求发出请求的链 `A` 上的托管相等数量的资产,直到这些凭证随后被返回到链 `A`,并且在相反的方向上有一个 IBC 数据包。这种顺序保证以及正确的应用逻辑可以确保两条供应链的总供应量都得到保留,而且在 b 链上铸造的任何凭证日后都可以兑换回 `A` 链。 16 | 17 | ### 定义 18 | 19 | `ConsensusState `符合 [TICS 2 ](../tics-002-client-semantics)中的定义。 20 | 21 | `Port` 是一种特殊的标识符,用于对模块进行权限通道打开和使用。 22 | 23 | ```go 24 | enum Port { 25 | FT, 26 | NFT, 27 | SERVICE, 28 | CONTRACT, 29 | } 30 | ``` 31 | 32 | `module` 是独立于 TIBC 处理程序的主机状态机的子组件。例子包括 Ethereum 智能合约和 Cosmos SDK & Substrate 模块。TIBC 规范除了主机状态机使用对象能力或源身份验证对模块的权限端口的能力之外,没有对模块功能进行任何设想。 33 | 34 | `hash` 是一种通用的抗冲突散列函数,其细节必须由使用该通道的模块商定。`hash`可以由不同的链来定义。 35 | 36 | `Packet` 是一个特定的接口,定义了跨链数据包规范: 37 | 38 | ```go 39 | interface Packet { 40 | sequence: uint64 41 | port: Identifier 42 | sourceChain: Identifier 43 | destChain: Identifier 44 | relayChain: Identifier 45 | data: bytes 46 | } 47 | ``` 48 | 49 | - `sequence` 编号对应于发送的顺序 50 | - `port` 标识对应的数据包发送和接收的端口。 51 | - `sourceChain` 标识数据包的发送链。 52 | - `destChain`标识数据包的接受链。 53 | - `relaychain`标识数据包途经的中继链,为空表示不经由中继链。 54 | - `data` 是一个不透明的值,可以由相关模块的应用程序逻辑定义。 55 | 56 | > 请注意,`Packet` 永远不会直接序列化。 相反,它是在某些函数调用中使用的中间结构,可能需要由调用 TIBC 处理程序的模块创建或处理。 57 | 58 | > `OpaquePacket` 是一个数据包,但由主机状态机隐藏在模糊数据类型中,因此模块只能将其传递给 TIBC 处理程序,而不能对其进行操作。TIBC 处理程序可以将一个 Packet 强制转换为一个 OpaquePacket,反之亦然。 59 | 60 | ```go 61 | type OpaquePacket = object 62 | ``` 63 | 64 | `CleanPacket`定义了对状态清理的跨链数据包: 65 | 66 | ```go 67 | interface CleanPacket { 68 | sequence: uint64 69 | sourceChain: Identifier 70 | destChain: Identifier 71 | relayChain: Identifier 72 | } 73 | ``` 74 | 75 | - `sequence` 标识待清除数据的最大编号 76 | - `sourceChain` 标识数据包的发送链。 77 | - `destChain`标识数据包的终止链。 78 | - `relaychain`标识数据包途经的中继链,可为空。 79 | 80 | > 请注意,`CleanPacket`执行操作是幂等的,因此自身不包括sequence属性用于避免重复执行的操作。 81 | 82 | ### 所需属性 83 | 84 | #### 效率 85 | 86 | - 数据包传输和确认的速度应该仅仅受到底层链路的速度的限制。在可能的情况下,证明应该是成批的。 87 | 88 | #### 一次传输 89 | 90 | - 在通道一端发送的 TIBC 数据包应该准确地传送到另一端一次 91 | - 如果链路中的一个或两个中断,数据包可能不超过一次传递,一旦链路恢复数据包应该能够再次流动 92 | 93 | #### 授权 94 | 95 | ## 技术规格 96 | 97 | ### 数据流可视化 98 | 99 | ### 初步报告 100 | 101 | #### 存储路径 102 | 103 | `nextSequenceSend`无符号整数计数器存储,标识下一个跨链包的序号: 104 | 105 | ```go 106 | function nextSequenceSendPath(sourceChainIdentifier: Identifier, destChainIdentifier: Identifier): Path { 107 | return "seqSends/{sourceChainIdentifier}/{destChainIdentifier}/nextSequenceSend" 108 | } 109 | ``` 110 | 111 | 对分组数据字段的固定大小的承诺存储在`packetCommitmentPath`下: 112 | 113 | ```go 114 | function packetCommitmentPath(sourceChainIdentifier: Identifier, destChainIdentifier Identifier, sequence: uint64): Path { 115 | return "commitments/{sourceChainIdentifier}/{destChainIdentifier}/packets/" + sequence 116 | } 117 | ``` 118 | 119 | 数据包收据数据存储在 `packetReceiptPath` 下: 120 | 121 | ```go 122 | function packetReceiptPath(sourceChainIdentifier: Identifier, destChainIdentifier Identifier, sequence: uint64): Path { 123 | return "receipts/{sourceChainIdentifier}/{destChainIdentifier}/receipts/" + sequence 124 | } 125 | ``` 126 | 127 | 数据包确认数据存储在 `packetAcknowledgementPath` 下: 128 | 129 | ```go 130 | function packetAcknowledgementPath(sourceChainIdentifier: Identifier, destChainIdentifier Identifier, sequence: uint64): Path { 131 | return "acks/{sourceChainIdentifier}/{destChainIdentifier}/acknowledgements/" + sequence 132 | } 133 | ``` 134 | 135 | 对历史数据清理的的存储在`packetCleanPath`下: 136 | 137 | ```go 138 | function packetCleanPath(chainIdentifier: Identifier): Path { 139 | return "cleans/{chainIdentifier}/clean" 140 | } 141 | ``` 142 | 143 | ### 子协议 144 | 145 | #### 标识符验证 146 | 147 | #### 数据包的流动和处理 148 | 149 | ##### 包生命周期 150 | 151 | 要将数据包从机器 `A` 的模块1发送到机器 `B` 的模块 *2* ,必须从头开始执行以下步骤。 152 | 153 | 该模块可以通过 [TICS 26 ](../tics-026-routing-module) 与 TIBC 处理程序连接。 154 | 155 | ##### 发送数据包 156 | 157 | 模块调用 sendPacket 函数,以便将调用模块所拥有的通道端上的 TIBC 数据包发送到对应方链上的相应模块。 158 | 159 | 调用模块必须与调用 `sendPacket` 一起自动地执行应用程序逻辑。 160 | 161 | TIBC 处理程序按顺序执行以下步骤: 162 | 163 | - 检查目标链client是否可用 164 | - 检查调用模块是否拥有发送端口 165 | - 增加与通道关联的发送序列计数器 166 | - 存储对数据包数据和数据包超时的固定大小承诺 167 | 168 | 请注意,完整的数据包并不存储在链的状态中——仅仅是对 data & timeout 值的一个简短的哈希提交。分组数据可以从交易执行中计算出来,并可能作为relayer可以索引的日志输出返回。 169 | 170 | ```go 171 | function sendPacket(packet: Packet) { 172 | nextSequenceSend = provableStore.get(nextSequenceSendPath(packet.sourceChain, packet.destChain)) 173 | abortTransactionUnless(packet.sequence === nextSequenceSend) 174 | 175 | // all assertions passed, we can alter state 176 | nextSequenceSend = nextSequenceSend + 1 177 | provableStore.set(nextSequenceSendPath(packet.sourceChain, packet.destChain), nextSequenceSend) 178 | provableStore.set(packetCommitmentPath(packet.sourceChain, packet.destChain, packet.sequence), hash(packet.data)) 179 | 180 | // log that a packet has been sent 181 | emitLogEntry("sendPacket", {sequence: packet.sequence, data: packet.data, sourceChain: packet.sourceChain, destChain: packet.destChain, relayChain: packet.relayChain}) 182 | } 183 | ``` 184 | 185 | #### 接收数据包 186 | 187 | 一个模块调用 `recvPacket` 函数,以接收在对应方链的相应信道端发送的 TIBC 包。 188 | 189 | 与调用 `recvPacket` 相结合,调用模块必须执行应用程序逻辑或将数据包排队以便将来执行。 190 | 191 | TIBC 处理程序按顺序执行以下步骤: 192 | 193 | - 检查来源链client是否可用 194 | - 检查调用模块是否拥有接收端口 195 | - 检查输出链状态中数据包数据承诺的包含证明 196 | - 设置存储路径以指示已收到数据包 197 | - 增加与通道结束关联的数据包接收序列 198 | 199 | ```go 200 | function recvPacket( 201 | packet: OpaquePacket, 202 | proof: CommitmentProof, 203 | proofHeight: Height): Packet { 204 | abortTransactionUnless(packet.relaychain === packet.sourceChain) 205 | abortTransactionUnless(packet.relaychain === packet.destChain) 206 | 207 | signChain = packet.sourceChain 208 | if !packet.relayChain.isEmpty() && selfChain == packet.destChain { 209 | signChain = packet.relayChain 210 | } 211 | client = provableStore.get(clientPath(signChain)) 212 | 213 | abortTransactionUnless(client.verifyPacketData( 214 | proofHeight, 215 | proof, 216 | packet.sourceChain, 217 | packet.destChain, 218 | packet.sequence, 219 | concat(packet.data) 220 | )) 221 | 222 | // all assertions passed (except sequence check), we can alter state 223 | if selfChain == packet.relaychain { 224 | // store commitment 225 | provableStore.set(packetCommitmentPath(packet.sourceChain, packet.destChain, packet.sequence), hash(packet.data)) 226 | 227 | // log that a packet has been sent 228 | emitLogEntry("sendPacket", {sequence: packet.sequence, data: packet.data, sourceChain: packet.sourceChain, destChain: packet.destChain, relayChain: packet.relayChain}) 229 | } 230 | 231 | if selfChain == packet.destChain{ 232 | // recive packet 233 | abortTransactionUnless(provableStore.get(packetReceiptPath(packet.sourceChain, packet.destChain, packet.sequence) === null)) 234 | provableStore.set( 235 | packetReceiptPath(packet.sourceChain, packet.destChain, packet.sequence), 236 | "1" 237 | ) 238 | 239 | // log that a packet has been received 240 | emitLogEntry("recvPacket", {sequence: packet.sequence, sourceChain: packet.sourceChain, destChain: packet.destChain, data: packet.data, relayChain: packet.relayChain}) 241 | 242 | // return transparent packet 243 | return packet 244 | } 245 | } 246 | ``` 247 | 248 | #### 写确认 249 | 250 | 模块调用 `writeacknowledging` 函数,以便写入处理 TIBC 数据包所产生的数据,然后发送链可以验证这些数据,这是一种“执行收据”或“ RPC 调用响应”。 251 | 252 | 调用模块必须与调用 `writeacknowledging` 一起自动地执行应用程序逻辑。 253 | 254 | 这是一个异步确认,只有在处理完成时,才需要在接收到数据包时确定其内容。在同步情况下,可以在与 `recvPacket` 相同的事务中(原子地)调用 `writeAcknowledgement`。 255 | 256 | 不需要确认数据包; 但是,如果已排序的通道使用确认,则必须确认所有数据包或不确认数据包(因为确认是按顺序处理的)。请注意,如果数据包没有得到确认,则无法在源链上删除数据包提交。TIBC 的未来版本可能包括一些方法,让模块指定它们是否会确认数据包,以便进行清理。 257 | 258 | `Writetacknowledging` 不检查正在被确认的数据包是否真正被接收,因为这将导致对确认的数据包进行两次验证。这方面的正确性是调用模块的责任。调用模块必须只使用以前从 `recvPacket` 接收到的数据包调用 `writebackiding`。 259 | 260 | TIBC 处理程序按顺序执行以下步骤: 261 | 262 | - 检查此数据包的确认是否尚未写入 263 | - 在数据包唯一的存储路径上设置不透明确认值 264 | 265 | ```go 266 | function writeAcknowledgement( 267 | packet: Packet, 268 | acknowledgement: bytes): Packet { 269 | // cannot already have written the acknowledgement 270 | abortTransactionUnless(provableStore.get(packetAcknowledgementPath(packet.sourceChain, packet.destChain, packet.sequence) === null)) 271 | 272 | // write the acknowledgement 273 | provableStore.set( 274 | packetAcknowledgementPath(packet.sourceChain, packet.destChain, packet.sequence), 275 | hash(acknowledgement) 276 | ) 277 | 278 | // log that a packet has been acknowledged 279 | emitLogEntry("writeAcknowledgement", {sequence: packet.sequence, sourceChain: packet.sourceChain, destChain: packet.destChain, relayChain: packet.relayChain, data: packet.data, acknowledgement}) 280 | } 281 | ``` 282 | 283 | #### 处理确认 284 | 285 | `acknowledgePacket ` 函数由一个模块调用,以处理由调用模块先前发送到对应方链上的对应方模块的数据包的确认。`acknowledgePacket `还清理数据包承诺,由于已经收到数据包并对其进行操作,因此不再需要数据包承诺。 286 | 287 | 调用模块可以结合调用 `acknowledgePacket ` 自动执行适当的应用程序确认处理逻辑。 288 | 289 | ```go 290 | function acknowledgePacket( 291 | packet: OpaquePacket, 292 | acknowledgement: bytes, 293 | proof: CommitmentProof, 294 | proofHeight: Height): Packet { 295 | abortTransactionUnless(packet.relaychain === packet.sourceChain) 296 | abortTransactionUnless(packet.relaychain === packet.destChain) 297 | 298 | signChain = packet.destChain 299 | if !packet.relayChain.isEmpty() && selfChain == packet.sourceChain { 300 | signChain = packet.relayChain 301 | } 302 | client = provableStore.get(clientPath(signChain)) 303 | 304 | // verify we sent the packet and haven't cleared it out yet 305 | abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourceChain, packet.destChain, packet.sequence)) === hash(packet.data)) 306 | 307 | // abort transaction unless correct acknowledgement on counterparty chain 308 | abortTransactionUnless(client.verifyPacketAcknowledgement( 309 | proofHeight, 310 | proof, 311 | packet.sourceChain, 312 | packet.destChain, 313 | packet.sequence, 314 | acknowledgement 315 | )) 316 | 317 | if selfChain == packet.relaychain { 318 | // write the acknowledgement 319 | provableStore.set( 320 | packetAcknowledgementPath(packet.sourceChain, packet.destChain, packet.sequence), 321 | hash(acknowledgement) 322 | ) 323 | 324 | // log that a packet has been acknowledged 325 | emitLogEntry("writeAcknowledgement", {sequence: packet.sequence, sourceChain: packet.sourceChain, destChain: packet.destChain, relayChain: packet.relayChain, data: packet.data, acknowledgement}) 326 | } 327 | 328 | // delete our commitment so we can't "acknowledge" again 329 | provableStore.delete(packetCommitmentPath(packet.sourceChain, packet.destChain, packet.sequence)) 330 | 331 | // return transparent packet 332 | return packet 333 | } 334 | ``` 335 | 336 | ##### 确认规范 337 | 338 | 在 TIBC 协议中,从远程链返回的确认被定义为任意字节。此数据可能编码成功执行或失败(除了超时之外的任何事情)。没有通用的方法来区分这两种情况,这要求任何客户端数据包可视化程序理解每个应用程序特定的协议,以区分成功或失败的中继情况。为了减少这个问题,我们提供了一个附加的确认格式规范,应该由应用程序特定的协议使用。 339 | 340 | ``` 341 | message Acknowledgement { 342 | oneof response { 343 | bytes result = 21; 344 | string error = 22; 345 | } 346 | } 347 | ``` 348 | 349 | #### 清理状态 350 | 351 | 必须确认数据包才能进行清理。 352 | 353 | 定义一个新的状态清理packet用于清理跨链数据包生命周期中产生的数据存储。该packet可以清理自身的存储。 354 | 355 | ##### 发送清理 Packet 356 | 357 | 模块调用`sendCleanPacket`函数,以便将调用模块所拥有的通道端上的 TIBC 数据包发送到对应方链上。 358 | 359 | ```go 360 | function sendCleanPacket(packet: CleanPacket) Packet{ 361 | provableStore.set(packetCleanPath(packet.sourceChain), packet.sequence) 362 | 363 | // log that a packet has been sent 364 | emitLogEntry("CleanPacket", {sourceChain: packet.sourceChain, destChain: packet.destChain, relayChain: packet.relayChain, sequence: packet.sequence) 365 | return packet 366 | } 367 | ``` 368 | 369 | ##### 接收清理数据包 370 | 371 | 一个模块调用 `recvCleanPacket` 函数,以接收在对应方链的相应信道端发送的 TIBC 包。 372 | 373 | 与调用 `recvCleanPacket` 相结合,调用模块必须执行应用程序逻辑或将数据包排队以便将来执行。 374 | 375 | TIBC 处理程序按顺序执行以下步骤: 376 | 377 | - 检查来源链client是否可用 378 | - 设置存储路径以指示已收到清理数据包 379 | - 清理sequence小于指定值的`Receipt`和`Acknowledgement` 380 | 381 | ```go 382 | function recvCleanPacket( 383 | packet: CleanPacket, 384 | proof: CommitmentProof, 385 | proofHeight: Height) { 386 | abortTransactionUnless(packet.relaychain === packet.sourceChain) 387 | abortTransactionUnless(packet.relaychain === packet.destChain) 388 | 389 | signChain = packet.sourceChain 390 | if !packet.relayChain.isEmpty() && selfChain == packet.destChain { 391 | signChain = packet.relayChain 392 | } 393 | 394 | client = provableStore.get(clientPath(signChain)) 395 | 396 | abortTransactionUnless(client.verifyCleanData( 397 | proofHeight, 398 | proof, 399 | packet.sourceChain, 400 | concat(packet.data), 401 | )) 402 | 403 | // Overwrite previous clean packet 404 | provableStore.set(packetCleanPath(packet.sourceChain), packet.sequence) 405 | 406 | // Clean all receipts and acknowledgements whose sequence is less than packet.sequence 407 | provableStore.clean(packetReceiptPath(packet.sourceChain, packet.destChain, packet.sequence)) 408 | provableStore.clean(packetAcknowledgementPath(packet.sourceChain, packet.destChain, packet.sequence)) 409 | 410 | // log that a packet has been received 411 | emitLogEntry("CleanPacket", {sequence: packet.sequence, sourceChain: packet.sourceChain, destChain: packet.destChain, relayChain: packet.relayChain) 412 | } 413 | ``` 414 | -------------------------------------------------------------------------------- /zh/core/tics-024-Host-Environments/README.md: -------------------------------------------------------------------------------- 1 | | ics | title | stage | category | kind | requires | required-by | author | created | modified | 2 | | ---------------- | ---------------------------------------------- | ---------- | ----------------------------------- | -------------- | ------------- | --------------------- | ---------------------------------------- | ------------ | ------------- | 3 | | 24 | Host Environments | draft | TIBC/TAO | interface | 23 | | | 2021-07-21 | 2021-07-21 | 4 | 5 | ## Synopsis 大纲 6 | 7 | 本规范定义了必须提供的接口的最小集合,以及承载 链间通信协议 实现的状态机必须满足的属性。 8 | 9 | ### Motivation 动机 10 | 11 | TIBC 被设计成一个通用标准,将由各种区块链和状态机托管,并且必须明确定义主机的要求。 12 | 13 | ### Definitions 定义 14 | 15 | ### Desired Properties 期望的特性 16 | 17 | TIBC 应该要求底层状态机提供尽可能简单的接口,以最大限度地提高正确实现的易用性。 18 | 19 | ## Technical Specification 技术规格 20 | 21 | ### Module system 模块系统 22 | 23 | 24 | 主机状态机必须支持模块系统,通过该系统,独立的、可能相互不信任的代码包可以安全地在同一个账本上执行,控制它们如何以及何时允许其他模块与它们通信,并由“ master 模块”或执行环境识别和操作。 25 | 26 | TIBC/TAO规范定义了两个模块的实现:核心的“TIBC handler”模块和“TIBC relayer”模块。TIBC/APP 规范进一步定义了用于特定分组处理应用程序逻辑的其他模块。TIBC 要求可以使用“ master 模块”或执行环境来授予主机状态机上的其他模块对 TIBC 处理程序模块 和/或 TIBC routing 模块的访问权限,但在其他方面,不会对可能位于状态机上的任何其他模块的功能或通信能力提出要求。 27 | 28 | ### Paths, identifiers, separators 路径,标识符,分隔符 29 | 30 | `Identifier ` 是一个 bytestring,用作存储在状态中的对象(如连接、通道或轻客户端)的键。 31 | 32 | 标识符必须为非空(长度为正整数)。 33 | 34 | 标识符只能由以下类别之一的字符组成: 35 | 36 | - 字母和数字数字 37 | - `. `, `_ `, `+ `, `- `, `#` 38 | - `[ `, `] `, `< `, `>` 39 | 40 | `Path` 是一个bytestring,用作状态中存储的对象的键。path 只能包含标识符、常量字符串和分隔符`/`。 41 | 42 | 标识符并不打算成为有价值的资源 -- 为了防止名称占用,可以实现最小长度要求或伪随机生成,但本规范没有施加特定的限制。 43 | 44 | 分隔符 `/` 用于分隔和连接两个标识符或一个标识符和一个常量bytestring。标识符不能包含 `/` 字符,这样可以防止歧义。 45 | 46 | 变量插值,用大括号表示,在本规范中用作定义路径格式的缩写,例如 `client/{clientIdentifier}/consensusState ` 47 | 48 | 除非另有规定,否则本规范中列出的所有标识符和所有字符串必须编码为ASCII。 49 | 50 | 默认情况下,标识符的最小和最大字符长度如下: 51 | 52 | | Port identifier | Client identifier 客户标识符 | 53 | | -------------------------- | ---------------------------- | 54 | | 2 - 64 | 9 - 64 | 55 | 56 | ### Key/value Store 键/值存储区 57 | 58 | 主机状态机必须提供一个 key/value 存储接口,该接口具有三个以标准方式运行的功能: 59 | 60 | ``` 61 | type get = (path: Path) => Value | void 62 | type set = (path: Path, value: Value) => void 63 | type delete = (path: Path) => void 64 | ``` 65 | 66 | `Path ` 如上所述。 `Value ` 是特定数据结构的任意bytestring编码。编码细节留给单独的ics处理。 67 | 68 | 这些函数必须只允许IBC处理程序模块(其实现在单独的标准中描述)使用,因此只有IBC处理程序模块才能`set` 或 `delete ` 可由 `get` 读取的路径。这可以实现为整个状态机使用的较大 key/value 存储的子存储(前缀键空间)。 69 | 70 | 主机状态机必须提供此接口的两个实例—一个 `provableStore` 用于其他链读取的存储(即证明给其他链读取的存储),另一个 `privateStore` 用于主机本地存储,可以在其上调用 `get` 、 `set` 和 `delete` ,例如 `provableStore.set('some/path', 'value') ` 。 71 | 72 | `provableStore`: 73 | 74 | - 必须写入键/值存储,其数据可通过 [ICS 23] 中定义的向量承诺进行外部证明 75 | - 必须使用这些规范中提供的规范数据结构编码作为proto3文件 76 | 77 | `privateStore`: 78 | 79 | - 可能支持外部证明,但不是必需的 -- TIBC 处理程序永远不会向其写入需要证明的数据。 80 | - 可以使用规范的 proto3 数据结构,但不是必需的 -- 它可以使用应用程序环境首选的任何格式。 81 | 82 | > 注意:任何提供这些方法和属性的键/值存储接口对于 TIBC 来说都是足够的。主机状态机可以使用 path 和 value 映射来实现 “代理存储” ,path 和 value 映射与通过存储接口设置和检索的 path 和 value 对不直接匹配- path 可以分组到存储在页面中的 bucket 和 value 中,这些 bucket 和 value 可以在单个承诺中得到证明,path-spaces 可以以某种双射方式等非连续地重新映射,只要 `get`, `set`, 和 `delete` 的行为符合预期,并且其他机器可以在可证明存储中验证 path 和 value 对(或其不存在)的承诺证明。如果适用, store 必须在外部公开这个映射,以便客户(包括 relayer )可以确定 store 的设计 以及如何构造证明。使用这种代理存储的机器的客户端也必须理解映射,因此它将需要新的客户端类型或参数化的客户端。 83 | 84 | > 注意:此接口不需要任何特定的存储后端或后端数据设计。状态机可以选择使用根据其需要配置的存储后端,只要上面的存储满足指定的接口并提供承诺证明。 85 | 86 | ### Path-space 路径空间 87 | 88 | 目前, TIBC/TAO 为 `provoblestore` 和 `privateStore` 推荐以下路径前缀。 89 | 90 | 未来的 path 可能会在协议的未来版本中使用,因此可证明存储中的整个 key-space 必须为 TIBC 处理程序保留。 91 | 92 | 只要本文定义的 key 格式与机器实现中实际使用的 key 格式之间存在 二分映射(二分图),可证明存储中使用的 key 就可以在每个客户端类型的基础上安全地变化。 93 | 94 | 只要 TIBC handler 对所需的特定 key 具有独占访问权,私有存储的部分内容就可以安全地用于其他目的。只要在本文定义的 key 格式和在私有存储实现中实际使用的密钥格式之间存在 二分映射,在私有存储中使用的 key 就可以安全地变化。 95 | 96 | 请注意,下面列出的与客户机相关的路径反映了[ICS 7] 中定义的 Tendermint 客户端,对于其他客户端类型可能有所不同。 97 | 98 | | Store | Path format | Value type | Defined in | 99 | | -------------- | ------------------------------------------------------------------------------ | ----------------- | ---------------------- | 100 | | provableStore | "clients/{identifier}/clientType" | ClientType | TICS 2 | 101 | | privateStore | "clients/{identifier}/clientState" | ClientState | TICS 2 | 102 | | provableStore | "clients/{identifier}/consensusStates/{height}" | ConsensusState | ICS 7 | 103 | | privateStore | "ports/{identifier}" | CapabilityKey | ICS 5 | 104 | 105 | 106 | 107 | ### Module layout 模块布局 108 | 109 | 在空间上表示,模块的布局及其在主机状态机上包含的规范如下所示(Aardvark、Betazoid和Cephalopod是任意模块): 110 | 111 | ``` 112 | +----------------------------------------------------------------------------------+ 113 | | | 114 | | 主机状态机 | 115 | | | 116 | | +-------------------+ +--------------------+ +----------------------+ | 117 | | | Module Aardvark | <--> | TIBC Routing Module| | IBC Handler Module | | 118 | | +-------------------+ | | | | | 119 | | | Implements TICS 26 | | Implements TICS 2, | | 120 | | | | | ICS 5 internally. | | 121 | | +-------------------+ | | | | | 122 | | | Module Betazoid | <--> | | --> | Exposes interface | | 123 | | +-------------------+ | | | defined in ICS 25. | | 124 | | | | | | | 125 | | +-------------------+ | | | | | 126 | | | Module Cephalopod | <--> | | | | | 127 | | +-------------------+ +--------------------+ +----------------------+ | 128 | | | 129 | +----------------------------------------------------------------------------------+ 130 | ``` 131 | 132 | ### Consensus state introspection 共识状态的反省 133 | 134 | 135 | 主机状态机必须提供使用 `getCurrentHeight` 内省其当前高度的能力: 136 | 137 | ``` 138 | type getCurrentHeight = () => Height 139 | ``` 140 | 141 | 主机状态机必须定义一个满足 ICS 2 要求的唯一 `consenssusstate` 类型,并使用规范的二进制序列化。 142 | 143 | 主机状态机必须提供用 `getConsensusState ` 内省自己一致性状态的能力: 144 | 145 | ``` 146 | type getConsensusState = (height: Height) => ConsensusState 147 | ``` 148 | 149 | `getConsensusState` 必须返回至少一些连续最近高度的 `n` 的 共识状态,其中 `n` 对于主机状态机是常量。早于 `n` 的高度可能会被安全地修剪(会导致未来对这些高度的调用失败)。 150 | 151 | 主机状态机必须提供使用 `getStoredRecentConsensusStateCount` 内省此存储的最近一致性状态计数 `n` 的功能: 152 | 153 | ``` 154 | type getStoredRecentConsensusStateCount = () => Height 155 | ``` 156 | 157 | ### Commitment path introspection 承诺路径内省 158 | 159 | 主机链必须提供使用 `getCommitmentPrefix` 检查其提交 path 的能力: 160 | 161 | ``` 162 | type getCommitmentPrefix = () => CommitmentPrefix 163 | ``` 164 | 165 | 结果 `CommitmentPrefix` 是主机状态机的 key/value 存储使用的前缀。对于主机状态机的 `CommitmentRoot` 根 和 `CommitmentState` 状态 ,必须保留以下属性: 166 | 167 | ``` 168 | if provableStore.get(path) === value { 169 | prefixedPath = applyPrefix(getCommitmentPrefix(), path) 170 | if value !== nil { 171 | proof = createMembershipProof(state, prefixedPath, value) 172 | assert(verifyMembership(root, proof, prefixedPath, value)) 173 | } else { 174 | proof = createNonMembershipProof(state, prefixedPath) 175 | assert(verifyNonMembership(root, proof, prefixedPath)) 176 | } 177 | } 178 | ``` 179 | 180 | 对于主机状态机,`getCommitmentPrefix` 的返回值必须是常量。 181 | 182 | ### Timestamp access 时间戳访问 183 | 184 | 主机链必须提供一个当前 Unix 时间戳,可通过 `currentTimestamp` 访问: 185 | 186 | ``` 187 | type currentTimestamp = () => uint64 188 | ``` 189 | 190 | 为了在超时中安全地使用时间戳,后续报头中的时间戳必须是非递减的。 191 | 192 | ### Port system 端口系统 193 | 194 | 主机状态机必须实现 端口系统,其中 TIBC 处理程序可以允许主机状态机中的不同模块绑定到唯一命名的端口。端口由 `Identifier` 标识。 195 | 196 | 主机状态机必须实现与 TIBC 处理程序的权限交互,以便: 197 | 198 | - 一旦某个模块绑定到某个端口,在该模块释放该端口之前,其他模块都不能使用该端口 199 | - 一个模块可以绑定到多个端口 200 | - 端口分配为先到先服务,当状态机首次启动时,可以绑定已知模块的“保留”端口 201 | 202 | 这种授权可以通过每个端口的唯一引用(对象功能)(Cosmos SDK)、源身份验证(Ethereum)或其他访问控制方法来实现,在任何情况下都是由主机状态机强制实现的。详见 ICS 5 。 203 | 204 | ### Datagram submission 数据报提交 205 | 206 | 实现路由模块的主机状态机可以定义一个 `submitDatagram` 函数,将交易中包含的 数据报 直接提交给路由模块(在 ICS 26 中定义): 207 | 208 | ``` 209 | type submitDatagram = (datagram: Datagram) => void 210 | ``` 211 | `submitDatagram` 允许 relayer 将 TIBC 数据报直接提交到主机状态机上的路由模块。主机状态机可能要求提交数据报的 relayer 有一个支付交易费用的帐户,在更大的交易结构中对数据报进行签名,等等。 `submitDatagram` 必须定义和构造所需的任何此类打包。 212 | 213 | ### Exception system 异常系统 214 | 215 | 主机状态机必须支持异常系统,通过该系统,交易可以中止执行并还原以前所做的任何状态更改(包括同一交易中发生的其他模块中的状态更改),不包括消耗的 gas 和适当的费用支付,并且系统不变违规 可以停止状态机。 216 | 217 | 此异常系统必须通过两个函数公开: `abortTransactionUnless` 和 `abortSystemUnless` ,前者还原交易,后者停止状态机。 218 | 219 | ``` 220 | type abortTransactionUnless = (bool) => void 221 | ``` 222 | 223 | 如果传递给 `abortTransactionUnless` 的布尔值为 `true` ,则主机状态机无需执行任何操作。如果传递给 `abortTransactionUnless` 的布尔值为 `false` ,则主机状态机必须中止交易并还原以前所做的任何状态更改,不包括消耗的 gas 和适当的费用支付。 224 | 225 | ``` 226 | type abortSystemUnless = (bool) => void 227 | ``` 228 | 如果传递给 `abortSystemUnless` 的布尔值为 `true` ,则主机状态机无需执行任何操作。如果传递给 `abortSystemUnless` 的布尔值为 `false` ,则主机状态机必须停止。 229 | 230 | ### Data availability 数据可用性 231 | 232 | 为了交付或超时安全,主机状态机必须具有最终的数据可用性,这样状态中的任何 key/value对 最终都可以由 relayer 检索。为了安全起见,不需要数据可用性。 233 | 234 | 对于数据包中继的活跃度,主机状态机必须具有有限的交易活跃度(因此必须具有共识活跃度),以便传入交易在块高度范围内得到确认(特别是,小于分配给数据包的超时)。 235 | 236 | IBC 数据包,以及其他不直接存储在状态向量中但 relay 依赖的数据,必须可供 relayer 有效地计算。 237 | 238 | 特定共识算法的轻客户端可能有不同 和/或 更严格的数据可用性要求。 239 | 240 | ### Event logging system 事件记录系统 241 | 242 | 主机状态机必须提供一个事件记录系统,在交易执行过程中可以记录任意数据,这些数据可以存储、索引,然后由执行状态机的进程查询。relayer 利用这些事件日志来读取 TIBC 数据包数据和超时,这些数据包数据和超时不是直接以链状态存储的(因为这种存储被认为是昂贵的),而是通过简洁的加密承诺(只存储承诺)。 243 | 244 | 该系统至少应具有一个用于发送日志条目的函数和一个用于查询过去日志的函数,大致如下所示。 245 | 246 | 在交易执行期间,状态机可以调用函数 `emitLogEntry` 来写入日志项: 247 | 248 | ``` 249 | type emitLogEntry = (topic: string, data: []byte) => void 250 | ``` 251 | `queryByTopic` 函数可由外部进程(如 relayer )调用,以检索在给定高度执行的 tx 所编写的与给定主题相关联的所有日志条目。 252 | 253 | ``` 254 | type queryByTopic = (height: Height, topic: string) => []byte[] 255 | ``` 256 | 257 | 还可以支持更复杂的查询功能,并且可以允许更高效的 relayer 查询,但这不是必需的。 258 | 259 | ### Handling upgrades 处理升级 260 | 261 | 主机可以安全地升级其状态机的某些部分,而不会中断 TIBC 功能。为了安全地做到这一点, TIBC 处理程序逻辑必须与规范保持一致,并且所有 TIBC 相关的状态(在 provable 和 private 存储中)必须在整个升级过程中保持不变。如果在其他链上存在用于升级链的客户端,并且升级将更改轻客户端验证算法,则必须在升级之前通知这些客户端,以便它们可以安全地原子切换并保持连接和通道的连续性。 262 | 263 | ## Backwards Compatibility 向后兼容性 264 | 265 | Not applicable. 266 | 267 | 不适用。 268 | 269 | ## Forwards Compatibility 向前兼容性 270 | 271 | 272 | ## Example Implementation 实施范例 273 | 274 | 275 | ## Other Implementations 其他实现 276 | 277 | 278 | ## History 历史 279 | 280 | 281 | 282 | ## Copyright 版权所有 283 | -------------------------------------------------------------------------------- /zh/core/tics-026-packet-routing/README.md: -------------------------------------------------------------------------------- 1 | 2 | | tics | title | stage | category | kind | requires | author | created | modified | 3 | | ---- | -------------- | ----- | -------- | --------- | -------- | ---------------- | ---------- | -------- | 4 | | 26 | Packet Routing | draft | TIBC/TAO | interface | 2,5,20 | dgsbl@bianjie.ai | 2021-07-26 | | 5 | 6 | ## 概要 7 | 8 | 路由模块是辅助模块的默认实现,它将接受外部数据报并调用TIBC协议处理程序来处理数据包中继。路由模块保留一个模块查找表,当接收到数据包时,它可以使用该表查找和调用模块,因此外部relayer只需要将数据包中继到路由模块。中继链上的路由模块需要维护一个路由白名单,对packet做路由控制。 9 | 10 | ### 动机 11 | 12 | 默认的TIBC处理程序使用接收器调用模式,其中模块必须单独调用TIBC处理程序,以便绑定到端口、发送和接收数据包,这是灵活和简单的,但理解起来有点棘手,可能需要中继进程的额外工作,relayer进程必须跟踪许多模块的状态。本标准描述了一个TIBC“路由模块”,用于自动化最常见的功能、路由数据包并简化relayer的任务。 13 | 14 | 在多跳跨链环境中,relay chain中的路由模块需要维护一个路由白名单,对跨链数据包进行路由控制。 15 | 16 | ### 所需属性 17 | 18 | - 当模块需要对数据包执行操作时,路由模块应该回调模块上指定的处理程序函数。 19 | - relay chain通过路由模块对packet进行路由控制。通过白名单对相应数据报拦截或放行。 20 | 21 | ### 模块回调接口 22 | 23 | 模块必须向路由模块公开以下功能签名,这些签名在收到各种数据报后即被调用: 24 | 25 | ```typescript 26 | function onRecvPacket(packet: Packet): bytes { 27 | // defined by the module, returns acknowledgement 28 | } 29 | 30 | function onAcknowledgePacket(packet: Packet) { 31 | // defined by the module 32 | } 33 | 34 | function onCleanPacket(packet: Packet) { 35 | // defined by the module 36 | } 37 | 38 | function validatePacket(packet: Packet) error { 39 | // defined by the module 40 | } 41 | ``` 42 | 43 | 必须抛出异常以指示失败,拒绝传入的数据包等。 44 | 45 | 它们组合在一个`TIBCModule`接口中: 46 | 47 | ```typescript 48 | interface TIBCModule { 49 | onRecvPacket: onRecvPacket, 50 | onAcknowledgePacket: onAcknowledgePacket, 51 | onCleanPacket: onCleanPacket, 52 | validatePacket: validatePacket 53 | } 54 | ``` 55 | 56 | 实现了TIBCModule的模块对象存储在`Router`中统一管理,router对象在初始化app时就写入内存中 57 | 58 | ```go 59 | // The router is a map from module name to the TIBCModule 60 | type Router struct { 61 | router: map[string]TIBCModule 62 | sealed: boolean 63 | } 64 | ``` 65 | 66 | `Router`提供以下方法: 67 | 68 | ```go 69 | // Seal prevents the Router from any subsequent route handlers to be registered. 70 | // Seal will panic if called more than once. 71 | func Seal(){ 72 | if router.sealed { 73 | panic("router already sealed") 74 | } 75 | router.sealed = true 76 | } 77 | 78 | // Sealed returns a boolean signifying if the Router is sealed or not. 79 | func Sealed() boolean { 80 | return router 81 | } 82 | 83 | // AddRoute adds TIBCModule for a given module name. It returns the Router 84 | // so AddRoute calls can be linked. It will panic if the Router is sealed. 85 | func AddRoute(module string, cbs TIBCModule) *Router { 86 | if router.sealed { 87 | panic(fmt.Sprintf("router sealed; cannot register %s route callbacks", module)) 88 | } 89 | if router.HasRoute(module) { 90 | panic(fmt.Sprintf("route %s has already been registered", module)) 91 | } 92 | 93 | router.routes[module] = cbs 94 | return router 95 | } 96 | 97 | // UpdateRoute updates TIBCModule for a given module name. It returns the Router 98 | // so UpdateRoute calls can be linked. It will panic if the Router is sealed. 99 | func UpdateRoute(module string, cbs TIBCModule) *Router { 100 | if router.sealed { 101 | panic(fmt.Sprintf("router sealed; cannot register %s route callbacks", module)) 102 | } 103 | if !router.HasRoute(module) { 104 | panic(fmt.Sprintf("route %s has not been registered", module)) 105 | } 106 | 107 | router.routes[module] = cbs 108 | return router 109 | } 110 | 111 | // HasRoute returns true if the Router has a module registered or false otherwise. 112 | func HasRoute(module string) boolean { 113 | _, ok := router.routes[module] 114 | return ok 115 | } 116 | 117 | // GetRoute returns a TIBCModule for a given module. 118 | func GetRoute(module string) (TIBCModule, boolean) { { 119 | if !router.HasRoute(module) { 120 | return nil, false 121 | } 122 | return router.routes[module], true 123 | } 124 | ``` 125 | 126 | 127 | 128 | ### Routing Rules 129 | 130 | 路由模块还维护一个Routing Rules作为路由白名单,routing Rules所需属性为: 131 | 132 | - Hub 上实现。 133 | - 由管理模块或 Gov 模块配置。 134 | - 配置白名单条目格式为src,dest,port,src为起始链chain ID,dest为目标链chain ID,port为模块绑定端口。例:irishub, wenchangchain, nft。 135 | - 支持通配符,例:irishub, wenchangchain, *。 136 | - 规则有重叠时取并集。 137 | - 仅对 Packet 有效,不拦截 ACK。 138 | 139 | 140 | 141 | 所有的rule以string数组传入,拼成一条字符串进行持久化。rules放在RoutingRuleManager中进行管理。RoutingRuleManager结构如下。 142 | 143 | ```go 144 | type RoutingRuleManager struct { 145 | RoutingRules string 146 | } 147 | ``` 148 | 149 | RoutingRuleManager需提供以下方法: 150 | 151 | ```go 152 | func (rrm RoutingRuleManager) SetRules(rules []string) (error) { 153 | for i, rule range rules{ 154 | err := rrm.ValidateRule(rule) 155 | } 156 | rrm.RoutingRules, err := CombineRulesString(rules) 157 | return err 158 | } 159 | 160 | func (rrm RoutingRuleManager) GetRules() (rules []string){ 161 | rules = SplitRulesString(rrm.Routing) 162 | return rules 163 | } 164 | 165 | // ValidateRule performs rule string validation returning an error 166 | func (rrm RoutingRuleManager) ValidateRule(rule string) error { 167 | } 168 | ``` 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | ### 属性和不变式 178 | 179 | - 代理端口绑定是先到先得服务:一旦模块通过TIBC路由模块绑定到端口,只有该模块才能使用该端口,直到模块释放它。 180 | 181 | ## 向后兼容 182 | 183 | 不适用。 184 | 185 | 186 | ## 向前兼容 187 | 188 | 路由模块与TIBC处理程序接口紧密相连。 189 | 190 | ## 示例实现 191 | 192 | 即将推出。 193 | 194 | -------------------------------------------------------------------------------- /zh/relayer/tics-018-relayer-algorithms/README.md: -------------------------------------------------------------------------------- 1 | | tics | title | stage | category | kind | requires | required-by | author | created | modified | 2 | | ---------------- | ---------------------------------------------- | ---------- | ----------------------------------- | -------------- | ------------- | --------------------- | ---------------------------------------- | ------------ | ------------- | 3 | | 18 | Relayer Algorithms | draft | TIBC/TAO | interface | 23 | | | 2021-07-26 | 2021-07-26 | 4 | 5 | ## Synopsis 大纲 6 | 7 | relayer 算法是 TIBC 的“物理”连接层 -- off-chain(链外) 进程负责通过扫描每个链的状态,构建适当的数据报并在协议允许的异构链(the opposite chain)上执行它们,在运行 TIBC 协议的两个链之间中继数据。 8 | 9 | ### Motivation 动机 10 | 11 | 在 TIBC 协议中,区块链只能记录将特定数据发送到另一条链的意图,而不能直接访问网络传输层。 物理数据报中继必须由可访问传输层(例如TCP/IP)的链外基础设施执行。 该标准定义了 relayer 算法的概念,该算法可由具有查询链状态能力的链外进程执行,以执行此中继。 12 | 13 | 14 | ### Definitions 定义 15 | 16 | *relayer* 是一个 *off-chain(链外)* 进程,具有使用 TIBC 协议读取交易状态并将交易提交到某些分类账集的能力。 17 | 18 | ### 所需属性 Desired Properties 19 | 20 | - TIBC 的 一次性 或 交付超时 安全属性都不应完全取决于relayer行为(假设拜占庭relayer) 21 | - TIBC 的数据包中继活性属性应仅依赖于至少一个正确的活跃 relayer 的存在。 22 | - 中继应该是未经许可的,所有必要的验证都应在链上执行。 23 | - 应尽量减少 TIBC 用户和 relayer 之间的必要通信。 24 | - 应在应用层提供 relayer 激励措施。 25 | 26 | ## 技术指标 Technical Specification 27 | 28 | ### 基本relayer算法 Basic relayer algorithm 29 | 30 | relayer 算法是在实现 TIBC 协议的一组 *C* 链上定义的。 每个 relayer 不一定可以访问链间网络中所有链的状态读取数据报或向链间网络中的所有链写入数据报(特别是在许可链或专用链的情况下)-不同的 relayer 可以在不同的子集之间中继。 31 | 32 | relayer 每隔一段时间就调用一次 `relay` —- 在任何一条链上,每个块的 relay 调用频率都不超过一次,根据 relayer 希望中继的频率,调用的频率可能更低。 33 | 34 | 不同的 relayer 可能会在不同的链之间进行中继 -- 只要每对链中至少有一个正确且有效的 relayer ,并且链保持有效,网络中 链之间流动的所有数据包最终都将被中继。 35 | 36 | relayer 支持实现多客户端sdk,但每次启动的实例只能配置两个相连的链配置。 37 | 38 | #### Relayer 架构 39 | 40 | ![relayer架构](./relayer架构.png) 41 | 42 | 新接入sdk客户端,必须完成以下接口 43 | 44 | ```go 45 | interface IChain { 46 | func GetBlockAndPackets(height uint64) (interface{}, error); 47 | func GetBlockHeader(height uint64) (interface{}, error); 48 | func GetLightClientState(chainName string) (interface{}, error); 49 | func GetLightClientConsensusState(chainName string, height uint64) (interface{}, error); 50 | func GetStatus() (interface{}, error); 51 | func GetLatestHeight() (uint64, error); 52 | func GetDelay() uint64; 53 | } 54 | ``` 55 | 56 | 业务层需要实现以下接口 57 | 58 | ```go 59 | interface IService { 60 | func UpdateClient(chain: Chain, counterparty: Chain); 61 | func PendingDatagrams(chain: Chain, counterparty: Chain): List>; 62 | } 63 | ``` 64 | 65 | relayer 用两个线程管理包中继 66 | 67 | 线程1主要用来定时更新客户端的可用性 68 | 69 | ```go 70 | func UpdateClient(chain: Chain, counterparty: Chain) { 71 | height = chain.GetLatestHeight() 72 | client = counterparty.GetLightClientConsensusState(chain) 73 | if client.height < height { 74 | header = chain.GetBlockHeader(height+1) 75 | counterpartyDatagrams.push(ClientUpdate{chain, header}) 76 | } 77 | submitDatagramHeader = ConversionHeaderMsg(blockData) 78 | submitTx(txs, dest) 79 | } 80 | ``` 81 | 82 | 线程2主要用来中继`packet`,处理步骤如下: 83 | 84 | 1. relayer 从 目标链获取 **源链** 的已 *中继* 的最新高度 `latestHeight` 。 85 | 2. relayer 去 **源链** 获取 `latestHeight+1`的 区块头,获取`latestHeight-delay`的 `packet` 86 | 3. relayer 组装获取到的`submitDatagramHeader`和`submitDatagramPacket`信息,并提交 87 | 88 | ```go 89 | func pendingDatagrams(source: Chain, dest: Chain) { 90 | latestHeight = dest.GetLightClientState(chain_name) 91 | blockData = source.getBlockHeader(latestHeight+1) 92 | submitDatagramHeader = ConversionHeaderMsg(blockData) 93 | blockPacketData, proof = source.GetBlockAndPackets(latestHeight-delay) 94 | submitDatagramPacket = ConversionPacketMsg(blockPacketData, proof) 95 | txs = []tx{submitDatagramHeader, submitDatagramPacket} 96 | submitTx(txs, dest) 97 | } 98 | ``` 99 | 100 | ### 数据包 Packets 101 | 102 | #### 中继数据包 Relaying packets 103 | 104 | 可以以基于事件的方式中继无序信道中的数据包。 relayer 应在源链(source chain)中监视每当发送数据包时发出的事件,然后使用事件(event)日志中的数据来组成数据包。 随后, relayer 应通过在数据包的序列号查询是否存在确认来检查目的链是否已接收到该数据包,如果尚未出现, relayer 应中继该数据包。 105 | 106 | ### 待处理的数据报 Pending datagrams 107 | 108 | `pendingDatagrams` 整理从一台机器发送到另一台机器的数据报。 此功能的实现将取决于两台机器都支持的 TIBC 协议的子集以及源机器的状态布局。 特定的 relayer 可能还希望实现其自己的过滤器功能,以便仅中继可能被中继的数据报的子集(例如,已支付报文的子集以某种链下方式中继)。 109 | 110 | 下面概述了在两条链之间执行单向中继的示例实现。 可以通过切换 `chain` 和 `counterparty` 来更改为执行双向中继。 哪个 relayer 进程负责哪个数据报是一个灵活的选择-在此示例中, relayer 进程中继在 `chain` 上开始的所有握手(将数据报发送到两条链),将所有从 `chain` 发送的数据包中继到 `counterparty` , 并将从 `counterparty` 发送的所有数据包确认转发到`chain`。 111 | 112 | 1. 用户发起跨链交易 113 | 2. 链 A 产生 commitments → packet 114 | 3. Relayer A Listen/Pull 链 A 的跨链请求,判断 Packet 中的 Dest Chain: 115 | - 若是 Hub 或 Hub 中注册的 Zone,则进行转发 116 | - 若都不是,则丢弃 117 | 4. Hub 收到跨链请求 118 | - 若 Dest Chain 是自己,则产生: 119 | - ack 120 | - receipts → packet 121 | - 若 Destin Chain 不是自己,则产生: 122 | - commitments → packet 123 | - receipts → packet 124 | 5. Relayer B Listen/Pull Hub 的跨链请求,并向 Dest Chain 进行转发 125 | 6. Chain B 接收到请求并进行处理,产生: 126 | - receipts → packet 127 | - ack 128 | 7. Relayer B 将 Chain B 的 ack 返回 Hub 129 | 8. Hub 存储 ack 并删除 commitments 130 | 9. Relayer A 将 Hub 的 ack 返回链 A 131 | 10. 链 A 接收到 ack 后删除 commitments 132 | 133 | 一跳时序图 134 | 135 | ![tibc-relayer-一跳跨链时序图](./tibc-relayer-一跳跨链时序图.png) 136 | 137 | 两跳时序图 138 | 139 | ![tibc-relayer-两跳跨链时序图](./tibc-relayer-两跳跨链时序图.png) 140 | 141 | 142 | 143 | ```go 144 | func pendingDatagrams(chain: Chain, counterparty: Chain): List> { 145 | const localDatagrams = [] 146 | const counterpartyDatagrams = [] 147 | 148 | // ICS2 : Clients 149 | // - Determine if light client needs to be updated (local & counterparty) 150 | height = chain.GetLatestHeight() 151 | client = counterparty.GetLightClientConsensusState(chain) 152 | if client.height < height { 153 | header = chain.GetBlockHeader(height+1) 154 | counterpartyDatagrams.push(ClientUpdate{chain, header}) 155 | } 156 | // 获取packet 数据报 157 | chainDelay = chain.GetDelay() 158 | submitDatagramPacket = ConversionPacketMsg(blockPacketData, proof) 159 | counterpartyDatagrams.push(Packcet{counterparty, submitDatagramPacket}) 160 | 161 | counterpartyHeight = counterparty.latestHeight() 162 | client = chain.GetLightClientConsensusState(counterparty) 163 | if client.height < counterpartyHeight { 164 | header = counterparty.GetBlockHeader(counterpartyHeight+1) 165 | localDatagrams.push(ClientUpdate{counterparty, header}) 166 | } 167 | 168 | // 获取packet 数据报 169 | counterpartyDelay = counterparty.GetDelay() 170 | blockPacketData, proof = counterparty.GetBlockAndPackets(counterpartyHeight-counterpartyDelay) 171 | submitDatagramPacket = ConversionPacketMsg(blockPacketData, proof) 172 | localDatagrams.push(Packcet{counterparty, submitDatagramPacket}) 173 | return [localDatagrams, counterpartyDatagrams] 174 | } 175 | ``` 176 | 177 | relayer 可以选择过滤这些数据报,以中继特定的客户端,特定种类的数据包,也许是根据费用支付模型(本文档未指定,因为它可能会有所不同)。 178 | 179 | ### 差错处理 Error handling 180 | 181 | 为了降低存储压力,额外实现 ClearPacket,可由链上定时或链外触发执行 182 | 183 | Packet 被 Clear 之后,Relayer 可能作恶将历史的 Packet 重新上传,这样可能会导致重复跨链并成功执行 184 | 185 | 解决办法:链上存储最后清理的 Sequence,Packet 跨链时,需要判断该 Packet 的 Sequence 必须大于清理的最新 Sequence 186 | 187 | ```go 188 | // 链上可能如下处理 189 | func validBasic(packet: Packet, counterparty: Chain) error { 190 | // 获取链上存储的清理的sequence 191 | latestSequence = counterparty.GetLatestSequence() 192 | if packet.sequence < latestSequence{ 193 | return error("curSequence < latestSequence") 194 | } 195 | } 196 | 197 | ``` 198 | 199 | ### 排序约束 Ordering constraints 200 | 201 | 在 relayer 进程上存在隐式排序约束,以确定必须以什么顺序提交哪些数据报。 例如,必须先提交标头才能最终确定存储在轻客户端中特定高度的共识状态和承诺根,然后才能转发数据包。 relayer 进程负责频繁查询它们在其间中继的链的状态,以确定何时必须中继什么。 202 | 203 | ### 打包 Bundling 204 | 205 | 如果主机状态机支持它,则 relayer 进程可以将许多数据报打包到一个交易中,这将导致它们按顺序执行,并摊销所有间接费用(例如,签名检查以支付费用)。 206 | 207 | ### 竞争条件 Race conditions 208 | 209 | 在同一对模块和链之间进行中继的多个 relayer 可能会尝试同时中继相同的数据包(或提交相同的标头)。 如果两个 relayer 这样做,则第一个交易将成功,而第二个交易将失败。 为减轻这种情况, relayer 之间或发送原始数据包的参与者与 relayer 之间的带外协调是必要的。 进一步的讨论超出了本标准的范围。 210 | 211 | ### 激励 Incentivisation 212 | 213 | relayer 进程必须能够访问两个链上的帐户,并有足够的余额支付交易费用。 relayer 可以采用应用程序级别的方法来补偿这些费用,例如通过在数据包数据中包括对自己的少量支付 -— relayer 费用支付的协议将在此 ICS 的未来版本中或在单独的 ICS 中进行描述。 214 | 215 | -------------------------------------------------------------------------------- /zh/relayer/tics-018-relayer-algorithms/relayer架构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bianjieai/tibc/2f2ea6a28a28e14ddfcf787987d40a626e184fcd/zh/relayer/tics-018-relayer-algorithms/relayer架构.png -------------------------------------------------------------------------------- /zh/relayer/tics-018-relayer-algorithms/tibc-relayer-一跳跨链时序图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bianjieai/tibc/2f2ea6a28a28e14ddfcf787987d40a626e184fcd/zh/relayer/tics-018-relayer-algorithms/tibc-relayer-一跳跨链时序图.png -------------------------------------------------------------------------------- /zh/relayer/tics-018-relayer-algorithms/tibc-relayer-两跳跨链时序图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bianjieai/tibc/2f2ea6a28a28e14ddfcf787987d40a626e184fcd/zh/relayer/tics-018-relayer-algorithms/tibc-relayer-两跳跨链时序图.png --------------------------------------------------------------------------------