diff --git a/code/account/calc-account-ret-fee/Cargo.toml b/code/account/calc-account-ret-fee/Cargo.toml deleted file mode 100644 index 0a77b8940..000000000 --- a/code/account/calc-account-ret-fee/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "calc-account-ret-fee" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -solana-client = "1.16.6" -solana-sdk = "1.16.6" diff --git a/code/account/calc-account-ret-fee/src/main.rs b/code/account/calc-account-ret-fee/src/main.rs deleted file mode 100644 index d2727cf53..000000000 --- a/code/account/calc-account-ret-fee/src/main.rs +++ /dev/null @@ -1,18 +0,0 @@ -// 计算账户费用 -// 在Solana上保持账户活跃会产生一项存储费用,称为 租金/rent。 -// 通过存入至少两年租金的金额,你可以使账户完全免除租金收取。 -// 对于费用的计算,你需要考虑你打算在账户中存储的数据量。 -use solana_client::rpc_client::RpcClient; -use solana_sdk::commitment_config::CommitmentConfig; - -fn main() { - let rpc_url = String::from("http://127.0.0.1:8899"); - let connection = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed()); - let data_length = 1500; - - let rent_exemption_amount = connection - .get_minimum_balance_for_rent_exemption(data_length) - .unwrap(); - - println!("rent exemption amount: {}", rent_exemption_amount); -} diff --git a/code/account/create-system-account-rs/Cargo.toml b/code/account/create-system-account-rs/Cargo.toml deleted file mode 100644 index 04089bdc2..000000000 --- a/code/account/create-system-account-rs/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "create-system-account-rs" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -solana-client = "1.16.6" -solana-sdk = "1.16.6" diff --git a/code/account/create-system-account-rs/src/main.rs b/code/account/create-system-account-rs/src/main.rs deleted file mode 100644 index 90077ee91..000000000 --- a/code/account/create-system-account-rs/src/main.rs +++ /dev/null @@ -1,60 +0,0 @@ -use solana_client::rpc_client::RpcClient; -use solana_sdk::commitment_config::CommitmentConfig; -use solana_sdk::signature::Signer; -use solana_sdk::{signature::Keypair, system_instruction, transaction::Transaction}; - -fn main() { - let from_keypair = Keypair::new(); - - let from_pubkey = from_keypair.pubkey(); - let new_account_keypair = Keypair::new(); - let new_account_pubkey = new_account_keypair.pubkey(); - - let space = 80; // 10个u64字段,每个字段8字节 - let connection = - RpcClient::new_with_commitment("http://127.0.0.1:8899", CommitmentConfig::finalized()); - - let airdrop_sol_for_from_keypair = connection - .request_airdrop(&from_pubkey, 10_000_000_000) - .unwrap(); - dbg!(airdrop_sol_for_from_keypair); - - let airdrop_sol_for_new_account_pubkey = connection - .request_airdrop(&new_account_pubkey, 10_000_000_000) - .unwrap(); - dbg!(airdrop_sol_for_new_account_pubkey); - - let rent_exemption_amount = connection - .get_minimum_balance_for_rent_exemption(space) - .expect("break rent exemption amount"); - dbg!(rent_exemption_amount); - - let create_account_ix = system_instruction::create_account( - &from_pubkey, - &new_account_pubkey, - rent_exemption_amount, - space as u64, - &from_pubkey, - ); - - let (recent_blockhash, _) = connection.get_recent_blockhash().unwrap(); - - let create_account_tx = Transaction::new_signed_with_payer( - &[create_account_ix], - Some(&from_pubkey), - &[&from_keypair, &new_account_keypair], - recent_blockhash, - ); - - match connection.send_and_confirm_transaction(&create_account_tx) { - Ok(sig) => loop { - if let Ok(confirmed) = connection.confirm_transaction(&sig) { - if confirmed { - println!("Transaction: {} Status: {}", sig, confirmed); - break; - } - } - }, - Err(e) => println!("Error creating system account: {e:?}"), - }; -} diff --git a/code/account/create-system-account/.env b/code/account/create-system-account/.env deleted file mode 100644 index 8a51e386f..000000000 --- a/code/account/create-system-account/.env +++ /dev/null @@ -1 +0,0 @@ -PRIVATE_KEY=[45,94,60,30,60,120,252,85,121,246,243,12,133,114,27,181,99,191,101,123,212,232,57,250,119,79,138,103,88,138,124,52,162,41,183,76,199,134,69,100,40,235,244,66,118,96,47,109,3,205,26,112,27,135,214,245,93,87,254,4,128,139,76,70] \ No newline at end of file diff --git a/code/account/create-system-account/.gitignore b/code/account/create-system-account/.gitignore deleted file mode 100644 index b94707787..000000000 --- a/code/account/create-system-account/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -dist/ diff --git a/code/account/create-system-account/LICENSE b/code/account/create-system-account/LICENSE deleted file mode 100644 index a612ad981..000000000 --- a/code/account/create-system-account/LICENSE +++ /dev/null @@ -1,373 +0,0 @@ -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. diff --git a/code/account/create-system-account/README.md b/code/account/create-system-account/README.md deleted file mode 100644 index 3703ffd15..000000000 --- a/code/account/create-system-account/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Solana Scripting Template - -This template is a starting point for writing scripts to interact with the Solana blockchain. Simply add your code to `index.ts` and run `npm start`. - -You can create a local repository with this starter code using the `npx create-solana-client [PROJECT_NAME] --initialize-keypair` command in the terminal. \ No newline at end of file diff --git a/code/account/create-system-account/package-lock.json b/code/account/create-system-account/package-lock.json deleted file mode 100644 index 2ccdf1277..000000000 --- a/code/account/create-system-account/package-lock.json +++ /dev/null @@ -1,1487 +0,0 @@ -{ - "name": "solana-course-client", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "solana-course-client", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@solana/spl-token": "^0.3.6", - "@solana/web3.js": "^1.73.0", - "dotenv": "^16.0.1" - }, - "devDependencies": { - "ts-node": "^10.8.0", - "typescript": "^4.6.4" - } - }, - "node_modules/@babel/runtime": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", - "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", - "dependencies": { - "regenerator-runtime": "^0.13.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", - "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.13", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", - "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@noble/ed25519": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.1.tgz", - "integrity": "sha512-Rk4SkJFaXZiznFyC/t77Q0NKS4FL7TLJJsVG2V2oiEq3kJVeTdxysEe/yRWSpnWMe808XRDJ+VFh5pt/FN5plw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, - "node_modules/@noble/hashes": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.5.tgz", - "integrity": "sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, - "node_modules/@noble/secp256k1": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", - "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, - "node_modules/@solana/buffer-layout": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.0.tgz", - "integrity": "sha512-lR0EMP2HC3+Mxwd4YcnZb0smnaDw7Bl2IQWZiTevRH5ZZBZn6VRWn3/92E3qdU4SSImJkA6IDHawOHAnx/qUvQ==", - "dependencies": { - "buffer": "~6.0.3" - }, - "engines": { - "node": ">=5.10" - } - }, - "node_modules/@solana/buffer-layout-utils": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz", - "integrity": "sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g==", - "dependencies": { - "@solana/buffer-layout": "^4.0.0", - "@solana/web3.js": "^1.32.0", - "bigint-buffer": "^1.1.5", - "bignumber.js": "^9.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@solana/buffer-layout/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/@solana/spl-token": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.3.7.tgz", - "integrity": "sha512-bKGxWTtIw6VDdCBngjtsGlKGLSmiu/8ghSt/IOYJV24BsymRbgq7r12GToeetpxmPaZYLddKwAz7+EwprLfkfg==", - "dependencies": { - "@solana/buffer-layout": "^4.0.0", - "@solana/buffer-layout-utils": "^0.2.0", - "buffer": "^6.0.3" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@solana/web3.js": "^1.47.4" - } - }, - "node_modules/@solana/spl-token/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/@solana/web3.js": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.73.0.tgz", - "integrity": "sha512-YrgX3Py7ylh8NYkbanoINUPCj//bWUjYZ5/WPy9nQ9SK3Cl7QWCR+NmbDjmC/fTspZGR+VO9LTQslM++jr5PRw==", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@noble/ed25519": "^1.7.0", - "@noble/hashes": "^1.1.2", - "@noble/secp256k1": "^1.6.3", - "@solana/buffer-layout": "^4.0.0", - "agentkeepalive": "^4.2.1", - "bigint-buffer": "^1.1.5", - "bn.js": "^5.0.0", - "borsh": "^0.7.0", - "bs58": "^4.0.1", - "buffer": "6.0.1", - "fast-stable-stringify": "^1.0.0", - "jayson": "^3.4.4", - "node-fetch": "2", - "rpc-websockets": "^7.5.0", - "superstruct": "^0.14.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", - "dev": true - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.28", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", - "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "node_modules/@types/lodash": { - "version": "4.14.182", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", - "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==" - }, - "node_modules/@types/node": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.35.tgz", - "integrity": "sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg==" - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, - "node_modules/@types/ws": { - "version": "7.4.7", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", - "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agentkeepalive": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", - "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", - "dependencies": { - "debug": "^4.1.0", - "depd": "^1.1.2", - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bigint-buffer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz", - "integrity": "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==", - "hasInstallScript": true, - "dependencies": { - "bindings": "^1.3.0" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/bignumber.js": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", - "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==", - "engines": { - "node": "*" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" - }, - "node_modules/borsh": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", - "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", - "dependencies": { - "bn.js": "^5.2.0", - "bs58": "^4.0.0", - "text-encoding-utf-8": "^1.0.2" - } - }, - "node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "dependencies": { - "base-x": "^3.0.2" - } - }, - "node_modules/buffer": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.1.tgz", - "integrity": "sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/bufferutil": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.6.tgz", - "integrity": "sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/delay": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", - "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dotenv": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", - "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dependencies": { - "es6-promise": "^4.0.3" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "node_modules/eyes": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", - "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", - "engines": { - "node": "> 0.1.90" - } - }, - "node_modules/fast-stable-stringify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", - "integrity": "sha1-XFVDRisiru79NtBbNOUceMuG0xM=" - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/isomorphic-ws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/jayson": { - "version": "3.6.6", - "resolved": "https://registry.npmjs.org/jayson/-/jayson-3.6.6.tgz", - "integrity": "sha512-f71uvrAWTtrwoww6MKcl9phQTC+56AopLyEenWvKVAIMz+q0oVGj6tenLZ7Z6UiPBkJtKLj4kt0tACllFQruGQ==", - "dependencies": { - "@types/connect": "^3.4.33", - "@types/express-serve-static-core": "^4.17.9", - "@types/lodash": "^4.14.159", - "@types/node": "^12.12.54", - "@types/ws": "^7.4.4", - "commander": "^2.20.3", - "delay": "^5.0.0", - "es6-promisify": "^5.0.0", - "eyes": "^0.1.8", - "isomorphic-ws": "^4.0.1", - "json-stringify-safe": "^5.0.1", - "JSONStream": "^1.3.5", - "lodash": "^4.17.20", - "uuid": "^8.3.2", - "ws": "^7.4.5" - }, - "bin": { - "jayson": "bin/jayson.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jayson/node_modules/@types/node": { - "version": "12.20.52", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.52.tgz", - "integrity": "sha512-cfkwWw72849SNYp3Zx0IcIs25vABmFh73xicxhCkTcvtZQeIez15PpwQN8fY3RD7gv1Wrxlc9MEtfMORZDEsGw==" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "engines": [ - "node >= 0.2.0" - ] - }, - "node_modules/JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-gyp-build": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", - "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==", - "optional": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "node_modules/rpc-websockets": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.5.0.tgz", - "integrity": "sha512-9tIRi1uZGy7YmDjErf1Ax3wtqdSSLIlnmL5OtOzgd5eqPKbsPpwDP5whUDO2LQay3Xp0CcHlcNSGzacNRluBaQ==", - "dependencies": { - "@babel/runtime": "^7.17.2", - "eventemitter3": "^4.0.7", - "uuid": "^8.3.2", - "ws": "^8.5.0" - }, - "funding": { - "type": "paypal", - "url": "https://paypal.me/kozjak" - }, - "optionalDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - } - }, - "node_modules/rpc-websockets/node_modules/ws": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", - "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/superstruct": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz", - "integrity": "sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==" - }, - "node_modules/text-encoding-utf-8": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", - "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/ts-node": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.0.tgz", - "integrity": "sha512-/fNd5Qh+zTt8Vt1KbYZjRHCE9sI5i7nqfD/dzBBRDeVXZXS6kToW6R7tTU6Nd4XavFs0mAVCg29Q//ML7WsZYA==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/typescript": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", - "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/utf-8-validate": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.9.tgz", - "integrity": "sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/ws": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", - "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - } - }, - "dependencies": { - "@babel/runtime": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", - "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", - "requires": { - "regenerator-runtime": "^0.13.11" - } - }, - "@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "0.3.9" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", - "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.13", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", - "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@noble/ed25519": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.1.tgz", - "integrity": "sha512-Rk4SkJFaXZiznFyC/t77Q0NKS4FL7TLJJsVG2V2oiEq3kJVeTdxysEe/yRWSpnWMe808XRDJ+VFh5pt/FN5plw==" - }, - "@noble/hashes": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.5.tgz", - "integrity": "sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==" - }, - "@noble/secp256k1": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", - "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==" - }, - "@solana/buffer-layout": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.0.tgz", - "integrity": "sha512-lR0EMP2HC3+Mxwd4YcnZb0smnaDw7Bl2IQWZiTevRH5ZZBZn6VRWn3/92E3qdU4SSImJkA6IDHawOHAnx/qUvQ==", - "requires": { - "buffer": "~6.0.3" - }, - "dependencies": { - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - } - } - }, - "@solana/buffer-layout-utils": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz", - "integrity": "sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g==", - "requires": { - "@solana/buffer-layout": "^4.0.0", - "@solana/web3.js": "^1.32.0", - "bigint-buffer": "^1.1.5", - "bignumber.js": "^9.0.1" - } - }, - "@solana/spl-token": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.3.7.tgz", - "integrity": "sha512-bKGxWTtIw6VDdCBngjtsGlKGLSmiu/8ghSt/IOYJV24BsymRbgq7r12GToeetpxmPaZYLddKwAz7+EwprLfkfg==", - "requires": { - "@solana/buffer-layout": "^4.0.0", - "@solana/buffer-layout-utils": "^0.2.0", - "buffer": "^6.0.3" - }, - "dependencies": { - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - } - } - }, - "@solana/web3.js": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.73.0.tgz", - "integrity": "sha512-YrgX3Py7ylh8NYkbanoINUPCj//bWUjYZ5/WPy9nQ9SK3Cl7QWCR+NmbDjmC/fTspZGR+VO9LTQslM++jr5PRw==", - "requires": { - "@babel/runtime": "^7.12.5", - "@noble/ed25519": "^1.7.0", - "@noble/hashes": "^1.1.2", - "@noble/secp256k1": "^1.6.3", - "@solana/buffer-layout": "^4.0.0", - "agentkeepalive": "^4.2.1", - "bigint-buffer": "^1.1.5", - "bn.js": "^5.0.0", - "borsh": "^0.7.0", - "bs58": "^4.0.1", - "buffer": "6.0.1", - "fast-stable-stringify": "^1.0.0", - "jayson": "^3.4.4", - "node-fetch": "2", - "rpc-websockets": "^7.5.0", - "superstruct": "^0.14.2" - } - }, - "@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true - }, - "@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true - }, - "@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true - }, - "@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", - "dev": true - }, - "@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "requires": { - "@types/node": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.28", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", - "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "@types/lodash": { - "version": "4.14.182", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", - "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==" - }, - "@types/node": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.35.tgz", - "integrity": "sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg==" - }, - "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, - "@types/ws": { - "version": "7.4.7", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", - "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", - "requires": { - "@types/node": "*" - } - }, - "acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", - "dev": true - }, - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - }, - "agentkeepalive": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", - "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", - "requires": { - "debug": "^4.1.0", - "depd": "^1.1.2", - "humanize-ms": "^1.2.1" - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "bigint-buffer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz", - "integrity": "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==", - "requires": { - "bindings": "^1.3.0" - } - }, - "bignumber.js": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", - "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==" - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" - }, - "borsh": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", - "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", - "requires": { - "bn.js": "^5.2.0", - "bs58": "^4.0.0", - "text-encoding-utf-8": "^1.0.2" - } - }, - "bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "requires": { - "base-x": "^3.0.2" - } - }, - "buffer": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.1.tgz", - "integrity": "sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "bufferutil": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.6.tgz", - "integrity": "sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==", - "optional": true, - "requires": { - "node-gyp-build": "^4.3.0" - } - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "delay": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", - "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==" - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "dotenv": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", - "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==" - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "requires": { - "es6-promise": "^4.0.3" - } - }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "eyes": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", - "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" - }, - "fast-stable-stringify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", - "integrity": "sha1-XFVDRisiru79NtBbNOUceMuG0xM=" - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" - }, - "humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "requires": { - "ms": "^2.0.0" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "isomorphic-ws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", - "requires": {} - }, - "jayson": { - "version": "3.6.6", - "resolved": "https://registry.npmjs.org/jayson/-/jayson-3.6.6.tgz", - "integrity": "sha512-f71uvrAWTtrwoww6MKcl9phQTC+56AopLyEenWvKVAIMz+q0oVGj6tenLZ7Z6UiPBkJtKLj4kt0tACllFQruGQ==", - "requires": { - "@types/connect": "^3.4.33", - "@types/express-serve-static-core": "^4.17.9", - "@types/lodash": "^4.14.159", - "@types/node": "^12.12.54", - "@types/ws": "^7.4.4", - "commander": "^2.20.3", - "delay": "^5.0.0", - "es6-promisify": "^5.0.0", - "eyes": "^0.1.8", - "isomorphic-ws": "^4.0.1", - "json-stringify-safe": "^5.0.1", - "JSONStream": "^1.3.5", - "lodash": "^4.17.20", - "uuid": "^8.3.2", - "ws": "^7.4.5" - }, - "dependencies": { - "@types/node": { - "version": "12.20.52", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.52.tgz", - "integrity": "sha512-cfkwWw72849SNYp3Zx0IcIs25vABmFh73xicxhCkTcvtZQeIez15PpwQN8fY3RD7gv1Wrxlc9MEtfMORZDEsGw==" - } - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" - }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "node-gyp-build": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", - "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==", - "optional": true - }, - "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "rpc-websockets": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.5.0.tgz", - "integrity": "sha512-9tIRi1uZGy7YmDjErf1Ax3wtqdSSLIlnmL5OtOzgd5eqPKbsPpwDP5whUDO2LQay3Xp0CcHlcNSGzacNRluBaQ==", - "requires": { - "@babel/runtime": "^7.17.2", - "bufferutil": "^4.0.1", - "eventemitter3": "^4.0.7", - "utf-8-validate": "^5.0.2", - "uuid": "^8.3.2", - "ws": "^8.5.0" - }, - "dependencies": { - "ws": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", - "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", - "requires": {} - } - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "superstruct": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz", - "integrity": "sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==" - }, - "text-encoding-utf-8": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", - "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "ts-node": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.0.tgz", - "integrity": "sha512-/fNd5Qh+zTt8Vt1KbYZjRHCE9sI5i7nqfD/dzBBRDeVXZXS6kToW6R7tTU6Nd4XavFs0mAVCg29Q//ML7WsZYA==", - "dev": true, - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - } - }, - "typescript": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", - "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", - "dev": true - }, - "utf-8-validate": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.9.tgz", - "integrity": "sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==", - "optional": true, - "requires": { - "node-gyp-build": "^4.3.0" - } - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - }, - "v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "ws": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", - "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", - "requires": {} - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - } - } -} diff --git a/code/account/create-system-account/package.json b/code/account/create-system-account/package.json deleted file mode 100644 index 4331d07db..000000000 --- a/code/account/create-system-account/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "solana-course-client", - "version": "1.0.0", - "description": "Starter template for a Solana client script", - "main": "index.ts", - "scripts": { - "start": "ts-node src/index.ts" - }, - "keywords": [], - "author": "test", - "license": "ISC", - "devDependencies": { - "ts-node": "^10.8.0", - "typescript": "^4.6.4" - }, - "dependencies": { - "@solana/spl-token": "^0.3.6", - "@solana/web3.js": "^1.73.0", - "dotenv": "^16.0.1" - } -} diff --git a/code/account/create-system-account/src/index.ts b/code/account/create-system-account/src/index.ts deleted file mode 100644 index c06051570..000000000 --- a/code/account/create-system-account/src/index.ts +++ /dev/null @@ -1,122 +0,0 @@ -// We're adding these -import * as Web3 from '@solana/web3.js'; -import * as fs from 'fs'; -import dotenv from 'dotenv'; -dotenv.config(); - -async function main() { - const connection = new Web3.Connection("http://localhost:8899", "confirmed"); - const signer = await initializeKeypair(connection); - - console.log("Public key:", signer.publicKey.toBase58()); - - // When generating a keypair - await airdropSolIfNeeded(signer, connection); - - await SystemCall(connection, signer); -} - -main() - .then(() => { - console.log("Finished successfully") - process.exit(0) - }) - .catch((error) => { - console.log(error) - process.exit(1) - }) - - -async function initializeKeypair(_connection: Web3.Connection): Promise { - if (!process.env.PRIVATE_KEY) { - console.log('Generating new keypair... 🗝️'); - const signer = Web3.Keypair.generate(); - - console.log('Creating .env file'); - fs.writeFileSync('.env', `PRIVATE_KEY=[${signer.secretKey.toString()}]`); - - return signer; - } - - const secret = JSON.parse(process.env.PRIVATE_KEY ?? '') as number[]; - const secretKey = Uint8Array.from(secret); - const keypairFromSecret = Web3.Keypair.fromSecretKey(secretKey); - return keypairFromSecret; -} - -async function airdropSolIfNeeded( - signer: Web3.Keypair, - connection: Web3.Connection -) { - const balance = await connection.getBalance(signer.publicKey); - console.log('Current balance is', balance / Web3.LAMPORTS_PER_SOL, 'SOL'); - - // 1 SOL should be enough for almost anything you wanna do - if (balance / Web3.LAMPORTS_PER_SOL < 1) { - // You can only get up to 2 SOL per request - console.log('Airdropping 1 SOL'); - const airdropSignature = await connection.requestAirdrop( - signer.publicKey, - Web3.LAMPORTS_PER_SOL - ); - - const latestBlockhash = await connection.getLatestBlockhash(); - - await connection.confirmTransaction({ - blockhash: latestBlockhash.blockhash, - lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, - signature: airdropSignature, - }); - - const newBalance = await connection.getBalance(signer.publicKey); - console.log('New balance is', newBalance / Web3.LAMPORTS_PER_SOL, 'SOL'); - } -} - -async function SystemCall(connection: Web3.Connection, payer: Web3.Keypair) { - // 在Solana中,space变量是用来定义新创建账户中所需存储空间的大小。 - // 这个值应该根据您打算存储在账户中的数据量来设置。如果您正在与特定的智能合约或程序进行交互, - // 您可能需要根据该程序的文档或代码来确定合适的空间大小。 - // - // 设置存储空间:space变量定义了新创建的Solana账户所需的存储空间大小。在此例中,它被设置为80字节(10个u64字段,每个字段8字节)。 - const space = 10 * 8; // 10个u64字段,每个字段8字节 - // - // 如何计算账户费用 - // 在Solana上保持账户活跃会产生一项存储费用,称为 租金/rent。 - // 通过存入至少两年租金的金额,你可以使账户完全免除租金收取。对于费用的计算,你需要考虑你打算在账户中存储的数据量。 - // - // 获取租金豁免金额:通过调用connection.getMinimumBalanceForRentExemption(space), - // 计算了新账户所需的租金豁免金额。如果账户持有的余额至少为此金额,该账户将被视为租金豁免,从而免除了存储费用。 - const rentExemptionAmount = await connection.getMinimumBalanceForRentExemption(space); - // 生成新的密钥对:创建一个新的密钥对newAccountPubkey,代表新的账户的公钥和私钥。 - const newAccountPubkey = Web3.Keypair.generate(); - const fromPubkey = payer; - - // 创建和添加事务:创建一个新的事务对象,并通过add方法添加一个创建新账户的指令。 - const createAccountTransaction = new Web3.Transaction().add( - // 置创建账户的参数:定义了一个名为createAccountParams的对象,其中包含创建新账户所需的所有参数, - // 例如发件人公钥、新账户公钥、豁免租金的lamports数量、存储空间大小以及关联的系统程序ID。 - // - // create account instruction - Web3.SystemProgram.createAccount({ - fromPubkey: fromPubkey.publicKey, - newAccountPubkey: newAccountPubkey.publicKey, - lamports: rentExemptionAmount, - space, - programId: Web3.SystemProgram.programId, - }) - ); - - // 发送并确认事务:使用Web3.sendAndConfirmTransaction函数发送并确认事务。 - // 该函数接受连接对象、事务对象以及包括付款者和新账户密钥对在内的签名者数组。 - let tx_hash = await Web3.sendAndConfirmTransaction(connection, createAccountTransaction, [ - fromPubkey, - newAccountPubkey, - ]); - - // 日志输出:最后,该函数使用console.log打印一条消息,其中包含新创建的账户的链接,可以在Solana的区块链浏览器上查看。 - console.log( - `Account created: https://explorer.solana.com/address/${newAccountPubkey.publicKey.toString()}` - ); - console.log(`Txhash: ${tx_hash}`); -} diff --git a/code/account/create-system-account/tsconfig.json b/code/account/create-system-account/tsconfig.json deleted file mode 100644 index 56794816b..000000000 --- a/code/account/create-system-account/tsconfig.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig.json to read more about this file */ - - /* Projects */ - // "incremental": true, /* Enable incremental compilation */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ - // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - - /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ - // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "resolveJsonModule": true, /* Enable importing .json files */ - // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ - - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ - // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ - // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } -} diff --git a/other_docs/Solana-Bootcamp-zh/README.md b/other_docs/Solana-Bootcamp-zh/README.md deleted file mode 100644 index 1fcfed9dd..000000000 --- a/other_docs/Solana-Bootcamp-zh/README.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -sidebar_position: 110 -sidebar_label: Solana 中文 BootCamp 💾 -sidebar_class_name: green ---- - -# Solana 中文 BootCamp 💾 - -video: [https://www.youtube.com/channel/UC55hLTz7EuR97neBW2trFMA](https://www.youtube.com/channel/UC55hLTz7EuR97neBW2trFMA) - -## Week1: Solana 基础知识 - -- [Solana 介绍](./week1/solana-intro.md) -- [Solana核心概念](./week1/solana-core-concerpt.md) -- [SPL 代币](./week1/spl-token.md) -- [Solana命令行](./week1/tenermail_tools.md) -- [钱包使用](./week1/wallet-useage.md) -- [课后练习](./week1/homework.md) - -## Week2: 通过RPC与Solana交互 - -- [Solana的RPC介绍](./week2/solana-rpc-intro.md) -- [接口RPC](./week2/interface-rpc.md) -- [推送RPC](./week2/push-rpc.md) -- [课后练习](./week2/homework.md) - -## Week3: 与Solana合约交互 - -- [Solana的Web3.js](./week3/web3-js.md) -- [与钱包交互](./week3/call-wallet.md) -- [`Program`调用](./week3/call-program.md) -- [课后练习](./week3/homework.md) - - -## Week4: Rust基本知识 - -- [Hello World](./week4/hello-world.md) -- [Rust基本语法](./week4/rust-basic.md) -- [通过Cargo管理工程](./week4/cargo.md) -- [Rustaceans的理解](./week4/rustaceans-understand.md) -- [课后练习](./week4/homework.md) - -## Week5: Solana合约开发 Part.1 - -## Week6: Solana合约开发 Part.2 - -## Week7: Solana合约开发进阶 - -## Week8: Solana DApp开发实践 DeFi & NFT - -## Week9: Solana合约安全 diff --git a/other_docs/Solana-Bootcamp-zh/img/week1/a_program.png b/other_docs/Solana-Bootcamp-zh/img/week1/a_program.png deleted file mode 100644 index 3b2638887..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week1/a_program.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week1/a_program_curl.png b/other_docs/Solana-Bootcamp-zh/img/week1/a_program_curl.png deleted file mode 100644 index d2b3d3e82..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week1/a_program_curl.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week1/mint_wallet_account.png b/other_docs/Solana-Bootcamp-zh/img/week1/mint_wallet_account.png deleted file mode 100644 index 0672590ee..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week1/mint_wallet_account.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week1/phantom.png b/other_docs/Solana-Bootcamp-zh/img/week1/phantom.png deleted file mode 100644 index 3f3c71435..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week1/phantom.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week1/phantom_install.png b/other_docs/Solana-Bootcamp-zh/img/week1/phantom_install.png deleted file mode 100644 index 87a4a63aa..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week1/phantom_install.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week1/phantom_new.png b/other_docs/Solana-Bootcamp-zh/img/week1/phantom_new.png deleted file mode 100644 index bab1e01b1..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week1/phantom_new.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week1/phantom_testnet.png b/other_docs/Solana-Bootcamp-zh/img/week1/phantom_testnet.png deleted file mode 100644 index 9332ae9de..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week1/phantom_testnet.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week1/phantom_transfer.png b/other_docs/Solana-Bootcamp-zh/img/week1/phantom_transfer.png deleted file mode 100644 index e5e8c68d1..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week1/phantom_transfer.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week1/phantom_ui.png b/other_docs/Solana-Bootcamp-zh/img/week1/phantom_ui.png deleted file mode 100644 index c91da5011..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week1/phantom_ui.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week1/sol_kline.png b/other_docs/Solana-Bootcamp-zh/img/week1/sol_kline.png deleted file mode 100644 index dcde76973..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week1/sol_kline.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week1/solana-overview-client-program.png b/other_docs/Solana-Bootcamp-zh/img/week1/solana-overview-client-program.png deleted file mode 100644 index 42b80cee5..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week1/solana-overview-client-program.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week1/spl_account.png b/other_docs/Solana-Bootcamp-zh/img/week1/spl_account.png deleted file mode 100644 index c7771ba18..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week1/spl_account.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week1/spl_account_1.png b/other_docs/Solana-Bootcamp-zh/img/week1/spl_account_1.png deleted file mode 100644 index c7771ba18..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week1/spl_account_1.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week1/spl_pda_account.png b/other_docs/Solana-Bootcamp-zh/img/week1/spl_pda_account.png deleted file mode 100644 index a367f60ef..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week1/spl_pda_account.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week1/transfer.png b/other_docs/Solana-Bootcamp-zh/img/week1/transfer.png deleted file mode 100644 index 4648f08f1..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week1/transfer.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week1/wallet_list.png b/other_docs/Solana-Bootcamp-zh/img/week1/wallet_list.png deleted file mode 100644 index cc773a9e8..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week1/wallet_list.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week2/dot_arch.png b/other_docs/Solana-Bootcamp-zh/img/week2/dot_arch.png deleted file mode 100644 index 1840e8473..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week2/dot_arch.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week3/cons.png b/other_docs/Solana-Bootcamp-zh/img/week3/cons.png deleted file mode 100644 index 016ce3333..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week3/cons.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week3/data_bin.png b/other_docs/Solana-Bootcamp-zh/img/week3/data_bin.png deleted file mode 100644 index af815f1a1..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week3/data_bin.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week3/rpc_method.png b/other_docs/Solana-Bootcamp-zh/img/week3/rpc_method.png deleted file mode 100644 index 790d593a3..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week3/rpc_method.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week3/wallet_connect.png b/other_docs/Solana-Bootcamp-zh/img/week3/wallet_connect.png deleted file mode 100644 index 8ac374fb9..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week3/wallet_connect.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week3/wallets_select_ui.png b/other_docs/Solana-Bootcamp-zh/img/week3/wallets_select_ui.png deleted file mode 100644 index e2a002342..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week3/wallets_select_ui.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week3/web3uidemo.png b/other_docs/Solana-Bootcamp-zh/img/week3/web3uidemo.png deleted file mode 100644 index 80a2d9fbd..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week3/web3uidemo.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week5/playground_build_deplay.png b/other_docs/Solana-Bootcamp-zh/img/week5/playground_build_deplay.png deleted file mode 100644 index 3dcaa5267..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week5/playground_build_deplay.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week5/playground_create_hellowrld.png b/other_docs/Solana-Bootcamp-zh/img/week5/playground_create_hellowrld.png deleted file mode 100644 index 485fa88c7..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week5/playground_create_hellowrld.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week5/playground_explorer.png b/other_docs/Solana-Bootcamp-zh/img/week5/playground_explorer.png deleted file mode 100644 index b7a8eee38..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week5/playground_explorer.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week5/playground_helloworld_explorer.png b/other_docs/Solana-Bootcamp-zh/img/week5/playground_helloworld_explorer.png deleted file mode 100644 index 5cb05349b..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week5/playground_helloworld_explorer.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week5/playground_import_wallet.png b/other_docs/Solana-Bootcamp-zh/img/week5/playground_import_wallet.png deleted file mode 100644 index 11a830de0..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week5/playground_import_wallet.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/img/week5/playground_wallet.png b/other_docs/Solana-Bootcamp-zh/img/week5/playground_wallet.png deleted file mode 100644 index 1f99a25e2..000000000 Binary files a/other_docs/Solana-Bootcamp-zh/img/week5/playground_wallet.png and /dev/null differ diff --git a/other_docs/Solana-Bootcamp-zh/week1/README.md b/other_docs/Solana-Bootcamp-zh/week1/README.md deleted file mode 100644 index 2175d06e0..000000000 --- a/other_docs/Solana-Bootcamp-zh/week1/README.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -sidebar_position: 111 -sidebar_label: Week1 Solana 基础知识 -sidebar_class_name: green ---- - -# Week1: Solana 基础知识 - -- [Solana 介绍](./solana-intro.md) -- [Solana核心概念](./solana-core-concerpt.md) -- [SPL 代币](./spl-token.md) -- [Solana命令行](./tenermail_tools.md) -- [钱包使用](./wallet-useage.md) -- [课后练习](./homework.md) diff --git a/other_docs/Solana-Bootcamp-zh/week1/homework.md b/other_docs/Solana-Bootcamp-zh/week1/homework.md deleted file mode 100644 index 88c06d05d..000000000 --- a/other_docs/Solana-Bootcamp-zh/week1/homework.md +++ /dev/null @@ -1,127 +0,0 @@ ---- -sidebar_position: 116 -sidebar_label: 课后练习 -sidebar_class_name: green ---- - -# 课后练习 - -通过命令行,发行一个代币。并给自己账号`mint`一定数量的代币。 并通过插件钱包或者命令行的方式给其他同学空投该代币 - -提示: - -- 命令行操作,查看`spl-token`命令的帮助文档 -- `solana config set --url https://api.devnet.solana.com` - ---- - -## 示例 - -设置环境为开发环境: - -创建账号: - -```bash -$ solana-keygen new --force -Generating a new keypair - -For added security, enter a BIP39 passphrase - -NOTE! This passphrase improves security of the recovery seed phrase NOT the -keypair file itself, which is stored as insecure plain text - -BIP39 Passphrase (empty for none): - -Wrote new keypair to /Users/you/.config/solana/id.json -=========================================================================== -pubkey: Czorr4y9oFvE3VdfCLVFuKDYxaNUG1iyQomR7kMZUuzi -=========================================================================== -Save this seed phrase and your BIP39 passphrase to recover your new keypair: -tail ... despair -=========================================================================== -``` - -申请水龙头: - -```bash -$ solana airdrop 1 -Requesting airdrop of 1 SOL - -Signature: 3pDfybgjsP8oS4pX32f24SmTE4sTjPAsuJd43jqz6qAXu7vXBwaxmoAZQL3QquxXYxXChtiWuQWv79odj9XndG4A - -1 SOL -``` - -对应浏览器 [3pDfybgjsP8oS4pX32f24SmTE4sTjPAsuJd43jqz6qAXu7vXBwaxmoAZQL3QquxXYxXChtiWuQWv79odj9XndG4A](https://solscan.io/tx/3pDfybgjsP8oS4pX32f24SmTE4sTjPAsuJd43jqz6qAXu7vXBwaxmoAZQL3QquxXYxXChtiWuQWv79odj9XndG4A?cluster=devnet) - -创建`Token`: - -```bash -$ spl-token create-token -Creating token 7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9 under program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA - -Address: 7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9 -Decimals: 9 - -Signature: 5QRdzn59ig3j3qjEazteDR2zoCLUWoCWdbFc7iQTd68esfdV9je3fE2We3Ms7NUGfBt6kapCj7oBAr1kbiTskSmz -``` - -`Token`地址:`7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9` 精度: 9 - -交易浏览器[5QRdzn59ig3j3qjEazteDR2zoCLUWoCWdbFc7iQTd68esfdV9je3fE2We3Ms7NUGfBt6kapCj7oBAr1kbiTskSmz](https://solscan.io/tx/5QRdzn59ig3j3qjEazteDR2zoCLUWoCWdbFc7iQTd68esfdV9je3fE2We3Ms7NUGfBt6kapCj7oBAr1kbiTskSmz?cluster=devnet) - -创建`Token Account`: - -```bash -$ spl-token create-account 7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9 -Creating account EZhhUANUMKsRhRMArczio1kLc9axefTUAh5xofGX35AK - -Signature: 59yBhJzC2HDkF61AhgaXcvVGiw5CjdnNpFyxvCzbqQrCjGCVKotNvCMaRQooJkxmu6ypJ9P7AZDiKxYex7pvBZKq -``` - -这里实际上调用了`ATA`合约,并创建了ATA账号:`EZhhUANUMKsRhRMArczio1kLc9axefTUAh5xofGX35AK` - -交易浏览器[59yBhJzC2HDkF61AhgaXcvVGiw5CjdnNpFyxvCzbqQrCjGCVKotNvCMaRQooJkxmu6ypJ9P7AZDiKxYex7pvBZKq](https://solscan.io/tx/59yBhJzC2HDkF61AhgaXcvVGiw5CjdnNpFyxvCzbqQrCjGCVKotNvCMaRQooJkxmu6ypJ9P7AZDiKxYex7pvBZKq?cluster=devnet) - -给自己的这个`Token Account`发送(`mint`) - -```bash -$ spl-token mint 7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9 100 EZhhUANUMKsRhRMArczio1kLc9axefTUAh5xofGX35AK -Minting 100 tokens - Token: 7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9 - Recipient: EZhhUANUMKsRhRMArczio1kLc9axefTUAh5xofGX35AK - -Signature: 5eE21U9ukZLP7Uvck5mzBbKRcXjxEYZYxCTnJX6qoS9kdXzfhPuN8k2Ko6BBekBdP2mhLmPMHAWNJW6bqyo6mqQe -``` - -交易记录 [5eE21U9ukZLP7Uvck5mzBbKRcXjxEYZYxCTnJX6qoS9kdXzfhPuN8k2Ko6BBekBdP2mhLmPMHAWNJW6bqyo6mqQe](https://solscan.io/tx/5eE21U9ukZLP7Uvck5mzBbKRcXjxEYZYxCTnJX6qoS9kdXzfhPuN8k2Ko6BBekBdP2mhLmPMHAWNJW6bqyo6mqQe?cluster=devnet) - -查询余额: - -```bash -$ spl-token balance 7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9 -100 -``` - -因为这里是求取`ATA`账号,所以只需要提供`Token Mint`地址即刻。 - -去浏览器找一个其他地址,如`BBy1K96Y3bohNeiZTHuQyB53LcfZv6NWCSWqQp89TiVu`,这个是个SOL地址 - -```bash -$ spl-token transfer --fund-recipient 7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9 1 BBy1K96Y3bohNeiZTHuQyB53LcfZv6NWCSWqQp89TiVu -Transfer 1 tokens - Sender: EZhhUANUMKsRhRMArczio1kLc9axefTUAh5xofGX35AK - Recipient: BBy1K96Y3bohNeiZTHuQyB53LcfZv6NWCSWqQp89TiVu - Recipient associated token account: H1jfKknnnyfFGYPVRd4ZHwUbXLF4PbFSWSH6wMJq6EK9 - Funding recipient: H1jfKknnnyfFGYPVRd4ZHwUbXLF4PbFSWSH6wMJq6EK9 - -Signature: 5VqeT7ctVtGdcJDvTmLzL4Pbti8PzM3mSrRpdE8GNG4ghF3svSJMkTn4AfNRQDSeYqCotEQuzDY9KLgdSJbKEjXt -``` - -这里帮这个用户创建了`ATA`账号 `H1jfKknnnyfFGYPVRd4ZHwUbXLF4PbFSWSH6wMJq6EK9` - -交易为[5VqeT7ctVtGdcJDvTmLzL4Pbti8PzM3mSrRpdE8GNG4ghF3svSJMkTn4AfNRQDSeYqCotEQuzDY9KLgdSJbKEjXt](https://solscan.io/tx/5VqeT7ctVtGdcJDvTmLzL4Pbti8PzM3mSrRpdE8GNG4ghF3svSJMkTn4AfNRQDSeYqCotEQuzDY9KLgdSJbKEjXt?cluster=devnet) - -查询下这个[账号](https://solscan.io/account/H1jfKknnnyfFGYPVRd4ZHwUbXLF4PbFSWSH6wMJq6EK9?cluster=devnet) - -余额为1. diff --git a/other_docs/Solana-Bootcamp-zh/week1/solana-core-concerpt.md b/other_docs/Solana-Bootcamp-zh/week1/solana-core-concerpt.md deleted file mode 100644 index 836734a61..000000000 --- a/other_docs/Solana-Bootcamp-zh/week1/solana-core-concerpt.md +++ /dev/null @@ -1,185 +0,0 @@ ---- -sidebar_position: 113 -sidebar_label: Solana核心概念 -sidebar_class_name: green ---- - -# Solana核心概念 - -## Account - -在Solana中,"Everythin is an Account" 类似Linux世界里面把所有的资源都抽象成"文件"一样。 - -Solana作为一个分布式区块链系统,所有的信息都存储在`Account`对象中,如合约(Solana叫Onchain Program), 账号信息,合约中存储的内容等都是存储在一个个`Account`对象中。 - -`Account`的定义如下: - -```rust -pub struct Account { - /// lamports in the account - pub lamports: u64, - /// data held in this account - #[serde(with = "serde_bytes")] - pub data: Vec, - /// the program that owns this account. If executable, the program that loads this account. - pub owner: Pubkey, - /// this account's data contains a loaded program (and is now read-only) - pub executable: bool, - /// the epoch at which this account will next owe rent - pub rent_epoch: Epoch, -} -``` - -其中的`lamports`表示账号余额,`data`表示存储的内容,`owner`表示这个`Account`可以被谁来操作,类似文件所有者。 如果是合约账号,这里`data`的内容就是合约编译后的代码,同时`executable`为`true`。 - -## 账号和签名 - -Solana的签名系统使用的是 [`Ed25519`](https://en.wikipedia.org/wiki/EdDSA#Ed25519) ,说人话就是: `Ed25519`是一种计算快,安全性高,且生成的签名内容小的一种不对称加密算法。新一代公链几乎都支持这个算法。 - -所以Solana的,我们用户理解的账号,就是一串`Ed25519`的私钥,各种钱包里面的助记词,会被转换成随机数种子, 再用随机数种子来生成一个私钥,所以助记词最终也是换算成私钥。所以用户账号的本质就是私钥,而用户账号的地址 则是这私钥对应的公钥,优于公钥是二进制的,为了可读性,将其进行`Base58`编码后的值,就是这个账号的地址。 如:`HawRVHh7t4d3H3bitWHFt25WhhoDmbJMCfWdESQQoYEy` - -把这里的公钥和私钥放一起,就是所谓的`Keypair`,或者叫公私钥对。假设这里把私钥进行加密,并由用户来设置密码, 公钥作为这个私钥的索引。就实现了一个简单的钱包系统了。 - -通过用户选择的公钥,加上密码,得到对应的私钥,再用私钥去操作的他的账号 - -## 交易 - -交易就是链外数据和链上数据产生的一次交互。比如发起一笔转账,在StepN里面发起一次Claim动作。 交易是对多个交易指令的打包,所以起内容主要就是各个交易指令,以及相应指令对应的发起人和签名。 - -`Transaction`的定义为: - -```rust -pub struct Message { - /// The message header, identifying signed and read-only `account_keys`. - /// Header values only describe static `account_keys`, they do not describe - /// any additional account keys loaded via address table lookups. - pub header: MessageHeader, - - /// List of accounts loaded by this transaction. - #[serde(with = "short_vec")] - pub account_keys: Vec, - - /// The blockhash of a recent block. - pub recent_blockhash: Hash, - - /// Instructions that invoke a designated program, are executed in sequence, - /// and committed in one atomic transaction if all succeed. - /// - /// # Notes - /// - /// Program indexes must index into the list of message `account_keys` because - /// program id's cannot be dynamically loaded from a lookup table. - /// - /// Account indexes must index into the list of addresses - /// constructed from the concatenation of three key lists: - /// 1) message `account_keys` - /// 2) ordered list of keys loaded from `writable` lookup table indexes - /// 3) ordered list of keys loaded from `readable` lookup table indexes - #[serde(with = "short_vec")] - pub instructions: Vec, - - /// List of address table lookups used to load additional accounts - /// for this transaction. - #[serde(with = "short_vec")] - pub address_table_lookups: Vec, -} - -pub enum VersionedMessage { - Legacy(LegacyMessage), - V0(v0::Message), -} - -pub struct VersionedTransaction { - /// List of signatures - #[serde(with = "short_vec")] - pub signatures: Vec, - /// Message to sign. - pub message: VersionedMessage, -} -``` - -从中可以简单理解为,交易就是一连串的交易指令,以及需要签名的指令的签名内容。 - -## 交易指令 - -上面说到的交易指令又是什么呢?先来看下定义: - -```rust -pub struct CompiledInstruction { - /// Index into the transaction keys array indicating the program account that executes this instruction. - pub program_id_index: u8, - /// Ordered indices into the transaction keys array indicating which accounts to pass to the program. - #[serde(with = "short_vec")] - pub accounts: Vec, - /// The program input data. - #[serde(with = "short_vec")] - pub data: Vec, -} -``` -从这些成员变量名就可以猜到。交易指令就是 执行哪个合约(`program_id_index`),输入为数据`data`,执行过程 中需要用到哪些`Account: accounts` - -类似函数调用一样,`program_id_index`是函数名,因为合约都是用地址标识的,所以这里指在`accounts`数组中 的第几个地址。传入的参数包含两部分,二进制数据`data`和需要使用到的`Account`资源:`accounts`。 - - -## 合约 - -合约分为两类,一类是普通合约一类是系统合约,前者在Solana中称为"On Chain Program" 后者称为"Native Program" 其实本质都是类似其他公链上所说的合约。 - -### 系统合约 - -系统合约是由节点在部署的时候生成的,普通用户无法更新,他们像普通合约一样,可以被其他合约或者RPC进行调用 - -系统合约有 - -- `System Program`: 创建账号,转账等作用 -- `BPF Loader Program`: 部署和更新合约 -- `Vote program`: 创建并管理用户POS代理投票的状态和奖励 -- ... - -### 普通合约 - -一般我们说的合约都是普通合约,或者叫 "On Chain Program"。普通合约是由用户开发并部署,Solana官方也有 一些官方开发的合约,如Token、ATA账号等合约。 - -当用户通过"BPF Loader Program"部署一个新合约的时候,新合约`Account`中的被标记为`true`,表示他是一个可以 被执行的合约账号。不同于有些公链,Solana上的合约是可以被更新的,也可以被销毁。并且当销毁的时候,用于存储 代码的账号所消耗的资源也会归还给部署者。 - -### 合约与Account - -在上面的`Account`介绍中,我们有个`owner`的成员,这个就表示这个`Account`是被哪个合约管理的,或者说哪个 合约可以对这个`Account`进行读写,类似Linux操作系统中,文件属于哪个用户。 - -比如一般合约,他的`Owner`都是BPF Loader: - -![](../img/week1/a_program.png) - -而存放我们代币余额的内容的ower都是Token合约: - -![](../img/week1/a_program_curl.png) - -对应的代币为: - -![](../img/week1/spl_account.png) - -## 租约 - -Solana的资金模型中,每个 Solana 账户在区块链上存储数据的费用称为“租金”。 这种基于时间和空间的费用来保持账户及其数据在区块链上的活动为节点提供相应的收入。 - -所有 Solana 账户(以及计划)都需要保持足够高的 `LAMPORT` 余额,才能免除租金并保留在 Solana 区块链上。 - -当帐户不再有足够的 `LAMPORTS` 来支付租金时,它将通过称为垃圾收集的过程从网络中删除。 - -注意:租金与交易费用不同。 支付租金(或保存在账户中)以将数据存储在 Solana 区块链上。 而交易费用是为了处理网络上的指令而支付的。 - -### 租金率 - -Solana 租金率是在网络范围内设置的,主要基于每年每字节设置的 `LAMPORTS`。 目前,租金率为静态金额并存储在 `Rent` 系统变量中。 - -### 免租 - -保持最低 `LAMPORT` 余额超过 2 年租金的账户被视为“免租金”,不会产生租金。 - -每次账户余额减少时,都会检查该账户是否仍免租金。 导致账户余额低于租金豁免阈值的交易将会失败。 - -### 垃圾收集 - -未保持租金豁免状态或余额不足以支付租金的帐户将通过称为垃圾收集的过程从网络中删除。 完成此过程是为了帮助减少不再使用/维护的数据的网络范围存储。 - -关于租约的[提案](https://docs.solana.com/implemented-proposals/rent) diff --git a/other_docs/Solana-Bootcamp-zh/week1/solana-intro.md b/other_docs/Solana-Bootcamp-zh/week1/solana-intro.md deleted file mode 100644 index 136bd2a04..000000000 --- a/other_docs/Solana-Bootcamp-zh/week1/solana-intro.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -sidebar_position: 112 -sidebar_label: Solana介绍 -sidebar_class_name: green ---- - -# Solana介绍 - -一句话概括 - -> Solana是一条高性能的L1公链 - -一张图表示 - -![](../img/week1/sol_kline.png) - -曾经也是辉煌夺目 - -## Solana历史 - -2017年11月,Anatoly Yakovenko发表了一篇白皮书,介绍了“Proof of History”这一技术,用于在不信任彼此的计算机之间进行时间同步。根据Anatoly在高通、Mesosphere和Dropbox设计分布式系统的经验,他知道可靠的时钟可以使网络同步变得非常简单。当同步变得简单时,结果的网络可以非常快速,仅受网络带宽的限制。 - -Anatoly注意到,没有时钟的区块链系统(如比特币和以太坊)在全球范围内的交易速度在15次每秒时遇到困难,而世界中心化支付系统(如Visa)则需要峰值65000次每秒。 没有时钟,很明显他们永远无法成为全球支付系统或全球超级计算机。当Anatoly解决了计算机之间不信任时间一致性的问题时,他知道他拥有将40年分布式系统研究带给区块链世界的关键。由此产生的集群不仅仅是10倍、100倍或1000倍,而是立即实现了出厂时是一万倍的速度! - -Anatoly的实施开始是在一个私人代码库中,使用C编程语言进行实现。曾在Qualcomm Incorporated与Anatoly一起合作的Greg Fitzgerald鼓励他以Rust编程语言重新实现该项目。Greg曾在LLVM编译器基础设施上工作,该基础设施是Clang C/C++编译器和Rust编译器的基础。Greg声称该语言的安全保证将提高软件的生产率,并且其无垃圾回收器将使程序能够像使用C编写的程序一样运行。Anatoly尝试了一下,仅用了两个星期,就将他的整个代码库迁移到了Rust上。成功了。计划将全球的交易编织在一个可伸缩的区块链上,Anatoly将该项目命名为Loom。 - -2018年2月13日,Greg开始为Anatoly的白皮书创建开源实现的原型。该项目在Loom协议组织的GitHub上发布,命名为Silk。2月28日,Greg发布了首个版本,演示了超过10,000个签名交易可以在半秒内验证和处理。不久之后,另一位曾在高通工作的同事Stephen Akridge演示了通过将签名验证转移至图形处理器可以大大提高吞吐量。Anatoly邀请Greg、Stephen和其他三个人共同创办了一家名为Loom的公司。 - -与此同时,基于以太坊的项目Loom Network涌现出来,许多人对它们是否是同一个项目感到困惑。Loom团队决定进行重新品牌推广。他们选择名为Solana的名字,以致敬他们在工作于高通期间住和冲浪了三年的圣地亚哥北部的一个小海滩城镇Solana Beach。 2018年3月28日,团队创建了Solana GitHub组织,并将Greg的原型命名为Solana。 - -2018年6月,团队将技术扩展到云网络上运行,并于7月19日发布了一个50个节点的许可,且公开的测试网络,能够始终支持每秒25万个交易的突发。在稍后于12月的v0.10 Pillbox版本中,团队发布了一个以千兆位网络运行150个节点的许可测试网络,演示了平均每秒处理20万个交易和突发500万个交易的吸收测试。该项目还扩展到支持使用C编程语言编写的链上程序,并在称为SBF的安全执行环境中并行运行。 - -## Solana开发流程 - -Solana网络是一个庞大的全球计算机,任何人都可以支付费用来存储和执行代码。部署的代码被称为程序,在其他区块链上通常被称为智能合约。要与程序交互,您需要从客户端在区块链上发送一笔交易。这是一个高层次的表示。需要注意的是,这只是为了简单易懂而对Solana网络的过度简化。 - -Solana开发者工作流程是程序-客户(program-client)模型。程序开发的第一个工作流程允许您直接创建和部署自定义的Rust、C和C++程序到区块链。一旦这些程序部署完成,任何知道如何与它们通信的人都可以使用它们。您可以使用任何可用的客户端SDK(或CLI)编写dApps来与这些程序通信,所有这些SDK都在底层使用JSON RPC API。 - -客户端开发是第二个工作流,您可以在这里编写与部署的程序通信的dApp。您的应用程序可以通过客户端SDK向这些程序提交交易指令,以创建各种应用程序,如钱包、交易所等。最常用的应用程序是浏览器扩展钱包和Web应用程序,但您也可以构建移动、桌面应用程序或任何能够与JSON RPC API通信的应用程序。 - -这两个部分共同工作,创建了一个由dApp和程序组成的网络,它们可以相互通信以更新状态并查询区块链。 - -![](../img/week1/solana-overview-client-program.png) - -也就是所谓的前端开发和合约开发。后续我们的课程也是围绕着两个方向来展开。 diff --git a/other_docs/Solana-Bootcamp-zh/week1/spl-token.md b/other_docs/Solana-Bootcamp-zh/week1/spl-token.md deleted file mode 100644 index b258a6469..000000000 --- a/other_docs/Solana-Bootcamp-zh/week1/spl-token.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -sidebar_position: 114 -sidebar_label: SPL 代币 -sidebar_class_name: green ---- - -# SPL 代币 - -在以太坊中,普通代币被一个叫ERC20的提案定了规范,可以认为普通代币合约统一叫做ERC20代币。 - -那么Solana世界里的ERC20代币是什么呢?答案就是SPL代币。 - -> The Solana Program Library (SPL) is a collection of on-chain programs targeting the Sealevel parallel runtime. - -SPL Token是 " Solana Program Library"中的一个组成部分,叫做"Token Program",简称为SPL Token。 - -所有的代币都有这个合约来管理,该合约代码在 https://github.com/solana-labs/solana-program-library/tree/master/token - -## 代币信息 - -不同于以太坊中,一个代币就是一个合约。 - -SPL Token中,一个代币,仅仅是一个归`Token`合约管理的普通的`Account`对象,这个对象里面的二进制数据定义了 这个代币的基本属性。其结构为: - -```rust -// ref: https://github.com/solana-labs/solana-program-library/blob/30d0e1b654414b5a2aca6c6d3a90cd66c820c31e/token/program/src/state.rs#L16 -pub struct Mint { - /// Optional authority used to mint new tokens. The mint authority may only be provided during - /// mint creation. If no mint authority is present then the mint has a fixed supply and no - /// further tokens may be minted. - pub mint_authority: COption, - /// Total supply of tokens. - pub supply: u64, - /// Number of base 10 digits to the right of the decimal place. - pub decimals: u8, - /// Is `true` if this structure has been initialized - pub is_initialized: bool, - /// Optional authority to freeze token accounts. - pub freeze_authority: COption, -} -``` - -相对有意义的就是`supply`表示总共的供应量,`decimals`表示代币的精度信息。 - -![](../img/week1/mint_wallet_account.png) - -## SPL Token Account - -那么每个用户的拥有的代币数量信息存在哪里呢? - -这个合约又定义了一个账号结构,来表示某个地址含有某个代币的数量。 - -```rust -pub struct Account { - /// The mint associated with this account - pub mint: Pubkey, - /// The owner of this account. - pub owner: Pubkey, - /// The amount of tokens this account holds. - pub amount: u64, - /// If `delegate` is `Some` then `delegated_amount` represents - /// the amount authorized by the delegate - pub delegate: COption, - /// The account's state - pub state: AccountState, - /// If is_native.is_some, this is a native token, and the value logs the rent-exempt reserve. An - /// Account is required to be rent-exempt, so the value is used by the Processor to ensure that - /// wrapped SOL accounts do not drop below this threshold. - pub is_native: COption, - /// The amount delegated - pub delegated_amount: u64, - /// Optional authority to close the account. - pub close_authority: COption, -} -``` - -这里`owner`表示谁的代币,`amount`表示代币的数量。 - -![](../img/week1/spl_account_1.png) - -## Account关系 - -所以整体结构是这样的: - -![](../img/week1/spl_pda_account.png) - -这两个结构体都是SPL Token Program管理的`Account`对象,其自身所携带的数据,分别为代币信息,和 存储哪个代币的信息。 - -这样当需要进行代币的交易时,只需要相应用户的相应代币账号里面的`amount`即可。 diff --git a/other_docs/Solana-Bootcamp-zh/week1/tenermail_tools.md b/other_docs/Solana-Bootcamp-zh/week1/tenermail_tools.md deleted file mode 100644 index e6ee463b4..000000000 --- a/other_docs/Solana-Bootcamp-zh/week1/tenermail_tools.md +++ /dev/null @@ -1,173 +0,0 @@ ---- -sidebar_position: 115 -sidebar_label: Solana命令行 -sidebar_class_name: green ---- - -# Solana命令行 - -接下来我们来开始体验Solana,Solana为我们提供了一套命令行工具来实现对Solana的操作。 这里注意,这个命令行工具,是除了节点外,官方提供的唯一工具。什么钱包,scan浏览器等还 都是第三方的,所以我们从这里开始。 - -这里建议开发工具平台使用Mac/Linux(Ubuntu),Windows不建议折腾,虽然Solana也是支持 的,下面我们以Mac作为演示平台进行讲演。 - -## 安装 - -打开命令行,输入: - -```bash -$ sh -c "$(curl -sSfL https://release.solana.com/stable/install)" -``` - -输出: - -```bash -downloading stable installer -✨ stable commit cd1c6d0 initialized -Adding -export PATH="/Users/you/.local/share/solana/install/active_release/bin:$PATH" to /Users/you/.profile - -Close and reopen your terminal to apply the PATH changes or run the following in your existing shell: - -export PATH="/Users/you/.local/share/solana/install/active_release/bin:$PATH" -``` - -这里需要科学上网,大家自行处理。 - -按照提示设置好`Path`,就可以验证是否安装成功了: - -```bash -$ solana --version -solana-cli 1.14.20 (src:cd1c6d0d; feat:1879391783) -``` - -这里打印出来cli的版本号。 - -更新到1.16.x版本 - -```bash -$ solana-install init 1.16.8 -``` - -## 设置网络环境 - -Solana的网络环境分成开发网、测试网、主网三类,开发网为Solana节点开发使用,更新频繁,测试网主要 给到DApp开发者使用,相对稳定。主网则是正式环境,里面的是真金白银。 - -官方RPC地址分别是: - -- DevNet: https://api.devnet.solana.com -- TestNet: https://api.testnet.solana.com -- MainNet: https://api.mainnet-beta.solana.com - -这里我们使用开发网,开发网可以申请空投测试币。 - -```bash -$ solana config set --url https://api.devnet.solana.com -``` - -`solana config get` 输出: - -```bash -Config File: /Users/you/.config/solana/cli/config.yml -RPC URL: https://api.devnet.solana.com -WebSocket URL: wss://api.devnet.solana.com/ (computed) -Keypair Path: /Users/you/.config/solana/id.json -Commitment: confirmed -``` - -## 创建账号 - -执行: - -```bash -$ solana-keygen new --force -``` - -输出 - -```bash -Generating a new keypair - -For added security, enter a BIP39 passphrase - -NOTE! This passphrase improves security of the recovery seed phrase NOT the -keypair file itself, which is stored as insecure plain text - -BIP39 Passphrase (empty for none): - -Wrote new keypair to /Users/you/.config/solana/id.json -======================================================================== -pubkey: 5pWae6RxD3zrYzBmPTMYo1LZ5vef3vfWH6iV3s8n6ZRG -======================================================================== -Save this seed phrase and your BIP39 passphrase to recover your new keypair: -pistol note gym mesh public endless salt maximum ... -======================================================================== -``` - -这里设置好密码后,提示`keypair`被加密存在存在"`/Users/you/.config/solana/id.json`"。 同时其对应的`BIP39`的助记词为: - -```bash -pistol note gym mesh public endless salt maximum ... -``` - -对应的地址:`5pWae6RxD3zrYzBmPTMYo1LZ5vef3vfWH6iV3s8n6ZRG` - -这里助记词要记住,后续使用钱包的时候,可以通过助记词来恢复账号。 - -通过如下命令可以查看当前账号的地址,也就是上面的`Keypair`文件的中的公钥: - -```bash -$ solana-keygen pubkey -``` - -输出: -```bash -5pWae6RxD3zrYzBmPTMYo1LZ5vef3vfWH6iV3s8n6ZRG -``` - -## 申请水龙头 - -只有开发网和测试网可以申请水龙头币,这里可以通过命令行: - -```bash -$ solana airdrop 1 -``` - -输出: - -```bash -Requesting airdrop of 1 SOL - -Signature: 4xYKfGjWcLir8F6puSzVWafbqYhjSyESNKygPygia6RgomSJACy5MhoKXhiePtz6VQ5W8DxYF5baeB4Cf9oKnkqy - -1 SOL -``` - -提示申请1个SOL成功。通过命令 - -```bash -$ solana balance -``` - -输出`1 SOL`的打印。 - -可以查看当前账号的余额。当前账号也就是"`/Users/you/.config/solana/id.json`"中存储的`keypair`对应的账号。 - -## 转账 - -这里通过命令行给另一个账号转账: - -```bash -$ solana transfer --allow-unfunded-recipient CZmVK1DymrSVWHiQCGXx6VG5zgHVrh5J1P514jHKRDxA 0.01 -``` - -输出: - -```bash -Signature: 3wDKwR1GFiKoUzmNJSdTYaoKp5n5fYxNCD712V9Vpj15M6UyK2A2Gtvb8GaiaGHoA8GJki8rqTuCuHnsWiGej7rV -``` - -如果这个账号之前不存在,需要使用`--allow-unfunded-recipient`来进行创建。这里输出的交易`hash`,我们可以 在浏览器中看到结果。 - -![](../img/week1/transfer.png) - -需要注意的是,这里要把环境选择为`Testnet`环境,才能看到我们的这个执行结果。 diff --git a/other_docs/Solana-Bootcamp-zh/week1/wallet-useage.md b/other_docs/Solana-Bootcamp-zh/week1/wallet-useage.md deleted file mode 100644 index 3e8d07545..000000000 --- a/other_docs/Solana-Bootcamp-zh/week1/wallet-useage.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -sidebar_position: 115 -sidebar_label: 钱包使用 -sidebar_class_name: green ---- - -# 钱包使用 - -Solana的钱包目前以Phantom、Solfare等Chrome插件为主, 类似TokenPocket、MathWallet等手机钱包为辅。甚至还有Solana Saga手机设备作为钱包。 - -比如打开raydium的链接钱包界面,我们可以看到这么多钱包: - - - -![](../img/week1/wallet_list.png) - -五花八门,实在是太多了。这里我们抽选Phantom来举例使用。 - -安装 -这里我们以Phantom的Chrome插件为例,来演示其基本操作。当然Phantom还有Firefox、手机 等版本。其操作类似。 - -首先在Chrome中打开: - -[https://chrome.google.com/webstore/detail/phantom/bfnaelmomeimhlpmgjnjophhpkkoljpa](https://chrome.google.com/webstore/detail/phantom/bfnaelmomeimhlpmgjnjophhpkkoljpa) - -点击"添加至Chrome" - -![](../img/week1/phantom_install.png) - -## 创建/导入账号 - -如果没有账号可以在扩展打开的时候,选择创建账号,创建账号的时候,需要记住这里的助记词,并设置一个 密码,就可以创建新账号了。 - -![](../img/week1/phantom.png) - -![](../img/week1/phantom_new.png) - -这里的助记词和我们的命令行工具的助记词是一样的。因此我们还可以选择导入账号,输入我们的已有的助记词, 并设置好密码。 - -![](../img/week1/phantom_ui.png) - -同时我们还可以选择不同的网络环境,这里我们选择开发网,就可以看到我们前面领的水龙头代币了。 - -![](../img/week1/phantom_testnet.png) - -## 转账 - -转账分为转移SOL代币和普通的SPL-Token代币,在很久以前,这里需要注意对方账户地址, 普通用户的SOL账号地址,和SPL-Token地址不一样,现在因为有了ATA账号的存在,可以直接通过 SOL账号地址进行推导,所以只需要知道对方的钱包地址也就是公钥就可以了。 - -![](../img/week1/phantom_transfer.png) - -## 在应用中打开钱包 - -在DApp应用中,当我们点击"Connect"的时候,会弹出钱包选择界面。如前文中列出来的一样。 - -在这里我们选择Phantom,然后看到钱包提示我们链接应用,选择后,就可以看到账号信息了。这些 步骤和Metamask基本类似。 diff --git a/other_docs/Solana-Bootcamp-zh/week2/README.md b/other_docs/Solana-Bootcamp-zh/week2/README.md deleted file mode 100644 index 6d0104bf2..000000000 --- a/other_docs/Solana-Bootcamp-zh/week2/README.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -sidebar_position: 117 -sidebar_label: week2 通过RPC与Solana交互 -sidebar_class_name: green ---- - -# week2 通过RPC与Solana交互 - -- [Solana的RPC介绍](./solana-rpc-intro.md) -- [接口RPC](./interface-rpc.md) -- [推送RPC](./push-rpc.md) -- [课后练习](./homework.md) diff --git a/other_docs/Solana-Bootcamp-zh/week2/homework.md b/other_docs/Solana-Bootcamp-zh/week2/homework.md deleted file mode 100644 index e9d0e2a36..000000000 --- a/other_docs/Solana-Bootcamp-zh/week2/homework.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -sidebar_position: 121 -sidebar_label: 课后练习 -sidebar_class_name: green ---- - -# 课后练习 - -通过curl和wscat命令行来模拟一个监视钱包动作 - -提示: - -- 创建一个新账号 -- 实时展示余额变化 -- 列出已知SPL-Token的余额 -- 实时展示SPL-Token余额变化 - ---- - -## 创建账号 - -我们复用前面的账号 - -- SOL账号: Czorr4y9oFvE3VdfCLVFuKDYxaNUG1iyQomR7kMZUuzi -- SPL-Token(Mint Account): 7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9 -- Token Account: EZhhUANUMKsRhRMArczio1kLc9axefTUAh5xofGX35AK - -## 订阅SOL余额变化 - -这个其实已经在课上演示过了。详情可以查看视频录像以及教程中的"订阅`Account`变化"章节 - -## 展示SPL-Token变化 - -早期的钱包是通过官方的 [token-list](https://github.com/solana-labs/token-list) 来获得 已知的SPL-Token,现在则通过Metaplex的FT标准查询。除此之外还可以通过订阅Token合约管理的账户变化 来判断是否有`Owner`为自己的`Token Account`被创建。 - -这里我们假设第一种情况,钱包只维护知名`token`或者用户自己添加的`Token`,比如上面的 "`7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9`" - -我们首先获取这个SPL-Token下我们有多少 `Token Account`: - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - { - "jsonrpc": "2.0", - "id": 1, - "method": "getTokenAccountsByOwner", - "params": [ - "Czorr4y9oFvE3VdfCLVFuKDYxaNUG1iyQomR7kMZUuzi", - { - "mint": "7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9" - }, - { - "encoding": "jsonParsed" - } - ] - } - ' -``` - -这里根据结果发现只有一个 "`EZhhUANUMKsRhRMArczio1kLc9axefTUAh5xofGX35AK`" - -那么我们只需要按照教程里面,订阅这个`Account`的变化就可以了。如果有多个,那么就订阅多个。 在重复对其他 SPL-Token做同样操作,既可以完成SPL-Token钱包的功能。 - -首先建立websocket链接,并发起对这个`Account`的订阅: - -```bash -$ wscat -c wscat -c wss://api.devnet.solana.com --proxy=http://127.0.0.1:1087 -Connected (press CTRL+C to quit) -> {"jsonrpc":"2.0","id":1,"method":"accountSubscribe","params":["EZhhUANUMKsRhRMArczio1kLc9axefTUAh5xofGX35AK",{"encoding":"jsonParsed","commitment":"finalized"}]} -``` - -然后再另一个终端,用"spl-token"命令行来进行转账: - -```bash -$ spl-token transfer --fund-recipient 7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9 1 BBy1K96Y3bohNeiZTHuQyB53LcfZv6NWCSWqQp89TiVu -Transfer 1 tokens - Sender: EZhhUANUMKsRhRMArczio1kLc9axefTUAh5xofGX35AK - Recipient: BBy1K96Y3bohNeiZTHuQyB53LcfZv6NWCSWqQp89TiVu - Recipient associated token account: H1jfKknnnyfFGYPVRd4ZHwUbXLF4PbFSWSH6wMJq6EK9 - -Signature: 3paamDSKFk5depKufcDjmJ8wc3eXte3qcgtitFu4TyDi8z9GTXMrLGEgPHgQMnAzFBXYoWxyF5JFzA54Fjvi2ZUK -``` -接着我们就可以在前面的监听中收到: - -```bash -< {"jsonrpc":"2.0","method":"accountNotification","params":{"result":{"context":{"slot":236334118},"value":{"lamports":2039280,"data":{"program":"spl-token","parsed":{"info":{"isNative":false,"mint":"7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9","owner":"Czorr4y9oFvE3VdfCLVFuKDYxaNUG1iyQomR7kMZUuzi","state":"initialized","tokenAmount":{"amount":"92000000000","decimals":9,"uiAmount":92.0,"uiAmountString":"92"}},"type":"account"},"space":165},"owner":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","executable":false,"rentEpoch":0,"space":165}},"subscription":18067841}} -``` - -可以看到当前余额为92了。我们在用"balance"确认下 - -```bash -$ spl-token balance 7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9 -92 -``` diff --git a/other_docs/Solana-Bootcamp-zh/week2/interface-rpc.md b/other_docs/Solana-Bootcamp-zh/week2/interface-rpc.md deleted file mode 100644 index b808c213e..000000000 --- a/other_docs/Solana-Bootcamp-zh/week2/interface-rpc.md +++ /dev/null @@ -1,788 +0,0 @@ ---- -sidebar_position: 119 -sidebar_label: 接口RPC -sidebar_class_name: green ---- - -# 接口RPC - -## 节点相关接口 - -### 获取集群节点信息 - -通过`getClusterNodes`方法可以获得当前网络内,集群节点的相关信息,比如验证者的`key`,节点IP,节点版本等。 - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - { - "jsonrpc": "2.0", "id": 1, - "method": "getClusterNodes" - }' -``` - -得到的输出类似这样: - -```json -{ - "jsonrpc": "2.0", - "result": [ - { - "featureSet": 2891131721, - "gossip": "67.209.54.46:8001", - "pubkey": "8pgVP32abaxodvpJx3iXo4o9FUWzarudQ7RHZAkkqEKi", - "pubsub": null, - "rpc": null, - "shredVersion": 28353, - "tpu": "67.209.54.46:8004", - "tpuQuic": "67.209.54.46:8010", - "version": "1.16.2" - } - ... - ] -} -``` - -从结果字段名,也可以比较直观的推出这些字段的意义 - -## 区块相关接口 - -### 获取当前区块高度 - -通过`getBlockHeight`可以获取当前的区块高度 - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - { - "jsonrpc":"2.0","id":1, - "method":"getBlockHeight" - } - ' -``` - -得到输出: - -```json -{ - "jsonrpc": "2.0", - "result": 174302040, - "id": 1 -} -``` - -可以看到,当前测试网的高度到了`174302040`。 - -### 获取最近的`Block Hash` - -通过`getLatestBlockhash`可以获得连上最近的一个`Block`的`Hash`值和高度 - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - { - "id":1, - "jsonrpc":"2.0", - "method":"getLatestBlockhash", - "params":[ - { - "commitment":"processed" - } - ] - } - ' -``` - -得到结果: - -```json -{ - "jsonrpc": "2.0", - "result": { - "context": { - "apiVersion": "1.16.2", - "slot": 207172864 - }, - "value": { - "blockhash": "2rSgjtXjKDcMYZTdSErwSz9bPXota73uecdJXUxEz2a5", - "lastValidBlockHeight": 174481567 - } - }, - "id": 1 -} -``` - -这里根据字面意思,可以看到最近的一个区块的`slot`,`hash`以及`block height`。 - -### 获取指定高度`block`的信息 - -获取指定高度`block`的信息,通过`getBlock`方法。如 - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - { - "jsonrpc": "2.0","id":1, - "method":"getBlock", - "params": [ - 174302734, - { - "encoding": "jsonParsed", - "maxSupportedTransactionVersion":0, - "transactionDetails":"full", - "rewards":false - } - ] - } - ' -``` - -这里结果太多,不再罗列。在请求中,我们加入了 `"encoding": "jsonParsed"`,将结果按照json的格式 进行展示。`transactionDetails` 设置返回的交易信息的内容复杂等级,设置有`"full","accounts","signatures","none",` 默认是`"full"`。`maxSupportedTransactionVersion`这个参数和后面介绍的带版本号的交易有关,表示返回最大的版本号,当前 可以传`0`即可,默认也是`0`。布尔值`rewards`表示是否携带`rewards`信息。 - -### 获取指定`block`的确认状态 - -有时候在上面获得当前最高区块,但是查询区块信息的时候却又查询不到,这里可以通过`getBlockCommitment`查看下对应区块的状态。 - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - { - "jsonrpc": "2.0", "id": 1, - "method": "getBlockCommitment", - "params":[174302734] - } - ' -``` - -得到结果: - -```json -{ - "jsonrpc": "2.0", - "result": { - "commitment": null, - "totalStake": 144333782793465543 - }, - "id": 1 -} -``` - -这里`totalStake`表示提交确认的节点总共`Stake`的SOL数目,也就是POS的权重。如果`commitment`不为`null`的时候,将是一个数组 表示各个集群中Stake的数量分布。 - -### 一次性获取多个`Block`的信息 - -前面的`getBlock`获得了单个`Block`的信息,还可以通过`getBlocks`一次性获得多个`Block`的信息。 - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - { - "jsonrpc": "2.0", "id": 1, - "method": "getBlocks", - "params": [ - 174302734, 174302735 - ] - } - ' -``` - -其余参数都是一样的,这里参数中,前面的部分是`block number`的数组 - -### 分页获取`Block` - -前面两个获取`Block`信息的方法,分别可以获得单个`Block`和多个指定`Block`号的信息。因为`Block Number`是递增且不一定连续的,因此 还Solana还提供了一个分页查询的方式`getBlocksWithLimit`,从起始号查询多少个。 - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - { - "jsonrpc": "2.0", - "id":1, - "method":"getBlocksWithLimit", - "params":[174302734, 3] - } -' -``` - -得到: - -```json -{ - "jsonrpc": "2.0", - "result": [ - 174302734, - 174302735, - 174302736 - ], - "id": 1 -} -``` -三个`BlockNumber`,接着我们可以前面的`GetBlocks`来获得这三个`Block`的详细信息。 - - -## `Slot`和`Epoch`相关接口 - -### 获取当前`Epoch`信息 - -首先`Epoch`是什么,在前面也有介绍到,`epoch`在一般POS中比较常见,表示这个周期内,一些参与验证的节点信息是固定的,如果有新节点或者节点权重变更,将在下一个`epoch`中生效。 - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - {"jsonrpc":"2.0","id":1, "method":"getEpochInfo"} - ' -``` - -输出类似: - -```json -{ - "jsonrpc": "2.0", - "result": { - "absoluteSlot": 207170348, - "blockHeight": 174478875, - "epoch": 492, - "slotIndex": 150092, - "slotsInEpoch": 432000, - "transactionCount": 258177341740 - }, - "id": 1 -} -``` - -里面有当前周期的区块高度,`slot`数目,以及`transaction`的数目。 - -而`getEpochSchedule`方法则是获取`Epoch`的调度信息, - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - { - "jsonrpc":"2.0","id":1, - "method":"getEpochSchedule" - } - ' -``` - -可以看到输出中: - -```json -{ - "jsonrpc": "2.0", - "result": { - "firstNormalEpoch": 14, - "firstNormalSlot": 524256, - "leaderScheduleSlotOffset": 432000, - "slotsPerEpoch": 432000, - "warmup": true - }, - "id": 1 -} -``` - -从字面意思也能看到,这里有`Epoch`中`slot`的数目,起始值等信息。 - -### 获取最新`Slot` - -和`Epoch`类似,可以获得当前的`Slot`: - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - {"jsonrpc":"2.0","id":1, "method":"getSlot"} - ' -``` - -直接得到`slot`值: - - -```json -{ - "jsonrpc":"2.0", - "result":209119756, - "id":1 -} -``` - -## 账号相关接口 - -### 获取`Account`信息 - -第一章有介绍,`Solana`上存储的内容,都是一个`Account`对象,有基础的元数据信息: - -```rust -pub struct Account { - /// lamports in the account - pub lamports: u64, - /// data held in this account - #[serde(with = "serde_bytes")] - pub data: Vec, - /// the program that owns this account. If executable, the program that loads this account. - pub owner: Pubkey, - /// this account's data contains a loaded program (and is now read-only) - pub executable: bool, - /// the epoch at which this account will next owe rent - pub rent_epoch: Epoch, -} -``` - -我们可以通过`getAccountInfo` RPC请求来查看,比如查看我们前面的测试账号: - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - { - "jsonrpc": "2.0", - "id": 1, - "method": "getAccountInfo", - "params": [ - "5pWae6RxD3zrYzBmPTMYo1LZ5vef3vfWH6iV3s8n6ZRG", - { - "encoding": "base58", - "commitment": "finalized" - } - ] - } - ' -``` - -这里我们通过curl来直接发起HTTP请求,最直观的看发生什么。请求中我们指定了测试网的RPC地址。 `https://api.devnet.solana.com` 得到 - -```json -{ - "jsonrpc": "2.0", - "result": { - "context": { - "apiVersion": "1.16.1", - "slot": 206885329 - }, - "value": { - "data": [ - "", - "base58" - ], - "executable": false, - "lamports": 59597675320, - "owner": "11111111111111111111111111111111", - "rentEpoch": 349, - "space": 0 - } - }, - "id": 1 -} -``` - -在`result`里面可以看到`value`里面的值项目,和Rust的结构体是一一对应的,其中`data`表示数据内容, 这里我们的普通账号不是合约账号,因此其为空,后面的`"base58"`表示如果这里有值,那么他将是二进制 内容的`base58`格式编码。这个编码格式是我们在请求里面的`"encoding"`来指定的。`"executable"`表示 是否为可执行合约,`"lamports"`表示余额,这里精度`*10^9`。所有普通账号的`Owner`都是系统根账号: `"11111111111111111111111111111111"`。 - -### 获取账号余额 - -在上面的`Account`信息里面,我们已经可以知道账号余额`lamports`了,同时RPC还提供了`getBalance`可以更 简洁的得到余额信息: - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - { - "jsonrpc": "2.0", "id": 1, - "method": "getBalance", - "params": [ - "5pWae6RxD3zrYzBmPTMYo1LZ5vef3vfWH6iV3s8n6ZRG" - ] - } - ' -``` - -得到: - -```json -{ - "jsonrpc": "2.0", - "result": { - "context": { - "apiVersion": "1.16.1", - "slot": 206886725 - }, - "value": 989995000 - }, - "id": 1 -} -``` -可以看到是989995000,因为SOL的精度是`10^9`.所以也就是0.989995个SOL。 - -### 获取某个合约管理的所有`Account` - -类似Linux查询某个用户所有的文件。Solana提供了一个查询`owener`为某个合约的RPC方法。该方法的作用就是罗列出 某个合约管理的`Account`,比如SPL Token合约记录的所有用户的余额信息。 - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - { - "jsonrpc": "2.0", - "id": 1, - "method": "getProgramAccounts", - "params": [ - "namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX", - { - "encoding": "jsonParsed", - "filters": [ - { - "dataSize": 128 - } - ] - } - ] - } - ' -``` -获取所有`NameService`服务管理的名字且记录空间大小为128字节的记录: - -```bash -{"jsonrpc":"2.0","result":[{"account":{"data":["AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHgMUi7LJb6+YQzBNlYJYu4QoAPOPzOY6F9NasCG9howAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaGVsbG8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","base64"],"executable":false,"lamports":1781761,"owner":"namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX","rentEpoch":349,"space":128},"pubkey":"5mBDoMGJvQTQhgAK2LtjKmG3TGV8J1m3LoEHRMXqits9"},{"account":{"data": - ... -{"account":{"data":["AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHgMUi7LJb6+YQzBNlYJYu4QoAPOPzOY6F9NasCG9howAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaGVsbG8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","base64"],"executable":false,"lamports":1781761,"owner":"namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX","rentEpoch":349,"space":128},"pubkey":"8worhyBqrHu1MYYQdQ3zpg5ByuhUge4rYHHhN8E8Vc3j"}],"id":1} -``` - -这里的`data`还需要用相应的序列化方法进行解析才能知道具体的记录是什么。 - -## `SPL-Token`相关接口 - -### 按照需求查询账号 - -我们知道`SPL Token`的结构为: - -```rust -pub struct Account { - /// The mint associated with this account - pub mint: Pubkey, - /// The owner of this account. - pub owner: Pubkey, - /// The amount of tokens this account holds. - pub amount: u64, - /// If `delegate` is `Some` then `delegated_amount` represents - /// the amount authorized by the delegate - pub delegate: COption, - /// The account's state - pub state: AccountState, - /// If is_native.is_some, this is a native token, and the value logs the rent-exempt reserve. An - /// Account is required to be rent-exempt, so the value is used by the Processor to ensure that - /// wrapped SOL accounts do not drop below this threshold. - pub is_native: COption, - /// The amount delegated - pub delegated_amount: u64, - /// Optional authority to close the account. - pub close_authority: COption, -} -``` - -我们可以查询某个`Token`下,所有`owner`为某人的`Token`账号,或者`delegate`为某人的所有账号。 - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - { - "jsonrpc": "2.0", - "id": 1, - "method": "getTokenAccountsByOwner", - "params": [ - "Czorr4y9oFvE3VdfCLVFuKDYxaNUG1iyQomR7kMZUuzi", - { - "mint": "7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9" - }, - { - "encoding": "jsonParsed" - } - ] - } - ' -``` - -这里查询到这个`token:7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9 ower为CnjrCefFBHmWnKcwH5T8DFUQuVEmUJwfBL3Goqj6YhKw`所有账号。 - -```json -{ - "jsonrpc": "2.0", - "result": { - "context": { - "apiVersion": "1.16.5", - "slot": 234689258 - }, - "value": [ - { - "account": { - "data": { - "parsed": { - "info": { - "isNative": false, - "mint": "7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9", - "owner": "Czorr4y9oFvE3VdfCLVFuKDYxaNUG1iyQomR7kMZUuzi", - "state": "initialized", - "tokenAmount": { - "amount": "99000000000", - "decimals": 9, - "uiAmount": 99.0, - "uiAmountString": "99" - } - }, - "type": "account" - }, - "program": "spl-token", - "space": 165 - }, - "executable": false, - "lamports": 2039280, - "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", - "rentEpoch": 0, - "space": 165 - }, - "pubkey": "EZhhUANUMKsRhRMArczio1kLc9axefTUAh5xofGX35AK" - } - ] - }, - "id": 1 -} -``` - -而通过: - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - { - "jsonrpc": "2.0", - "id": 1, - "method": "getTokenAccountsByDelegate", - "params": [ - "Czorr4y9oFvE3VdfCLVFuKDYxaNUG1iyQomR7kMZUuzi", - { - "mint": "7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9" - }, - { - "encoding": "jsonParsed" - } - ] - } - ' -``` - -因为我们没有设置代理操作。所以这里得到的结果为空。 - -### 获取某个`Token Account`账号的余额 - -查询`SPL Token`的余额,有个`ATA`账号需要了解。本质上就是对应`Token`的账号: - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - { - "jsonrpc": "2.0", "id": 1, - "method": "getTokenAccountBalance", - "params": [ - "EZhhUANUMKsRhRMArczio1kLc9axefTUAh5xofGX35AK" - ] - } - ' -``` - -返回的值,会列出数量: - -```json -{ - "jsonrpc": "2.0", - "result": { - "context": { - "apiVersion": "1.16.3", - "slot": 209132550 - }, - "value": { - "amount": "99000000000", - "decimals": 9, - "uiAmount": 99.0, - "uiAmountString": "99" - } - }, - "id": 1 -} -``` - -这里可以看到,`uiAmount`是可以显示的数量,做了精度转换的。精度和真实`amount`都有列出来。 - -## 交易相关接口 - -### 获取交易手续费 - -针对某个交易,需要预估其手续费时,可以借助节点的预计算: - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - { - "id":1, - "jsonrpc":"2.0", - "method":"getFeeForMessage", - "params":[ - "AQABAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAA", - { - "commitment":"processed" - } - ] - } - ' -``` - -得到: - -```json -{ - "jsonrpc": "2.0", - "result": { - "context": { - "apiVersion": "1.16.3", - "slot": 209111155 - }, - "value": null - }, - "id": 1 -} -``` - -这里参数中的字符串,是`Transaction`打包后的结果。也就是`RawTransaction`的序列化结果。 - -### 获取交易详细信息 - -查询某个交易的详细信息: - -```bash -$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' - { - "jsonrpc": "2.0", - "id": 1, - "method": "getTransaction", - "params": [ - "2o9qCEhwKi8w7hTFQJTLwMBZPFH8qM3iNd9rprtdY6XShyrpsqkWt4Df3Zgsxv6y4nbRe4SDgU8KMvuMfs7HxVhp", - "jsonParsed" - ] - } - ' -``` - -可以看到,结果跟浏览器中的结果基本是对应的: - -```json -{ - "jsonrpc": "2.0", - "result": { - "blockTime": 1674954447, - "meta": { - "computeUnitsConsumed": 12481, - "err": null, - "fee": 5000, - "innerInstructions": [], - "logMessages": [ - "Program gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s invoke [1]", - "Program gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s consumed 2633 of 600000 compute units", - "Program gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s success", - "Program gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s invoke [1]", - "Program gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s consumed 5562 of 597367 compute units", - "Program gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s success", - "Program gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s invoke [1]", - "Program gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s consumed 4286 of 591805 compute units", - "Program gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s success" - ], - "postBalances": [ - 420164575000, - 23942400, - 23942400, - 23942400, - 1169280, - 1141440 - ], - "postTokenBalances": [], - "preBalances": [ - 420164580000, - 23942400, - 23942400, - 23942400, - 1169280, - 1141440 - ], - "preTokenBalances": [], - "rewards": [], - "status": { - "Ok": null - } - }, - "slot": 192074782, - "transaction": { - "message": { - "accountKeys": [ - { - "pubkey": "vir55LvSEGcY55ny876GycLmFCxMXTkoRg7RxDvKiw5", - "signer": true, - "source": "transaction", - "writable": true - }, - { - "pubkey": "8PugCXTAHLM9kfLSQWe2njE5pzAgUdpPk3Nx5zSm7BD3", - "signer": false, - "source": "transaction", - "writable": true - }, - { - "pubkey": "EfnLcrwxCgwALc5vXr4cwPZMVcmotZAuqmHa8afG8zJe", - "signer": false, - "source": "transaction", - "writable": true - }, - { - "pubkey": "6Ukmvns6Uyf3nRVj3ErDBgx7BiZRNJrLyXe1nGQ7CUHA", - "signer": false, - "source": "transaction", - "writable": true - }, - { - "pubkey": "SysvarC1ock11111111111111111111111111111111", - "signer": false, - "source": "transaction", - "writable": false - }, - { - "pubkey": "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s", - "signer": false, - "source": "transaction", - "writable": false - } - ], - "instructions": [ - { - "accounts": [ - "vir55LvSEGcY55ny876GycLmFCxMXTkoRg7RxDvKiw5", - "8PugCXTAHLM9kfLSQWe2njE5pzAgUdpPk3Nx5zSm7BD3", - "SysvarC1ock11111111111111111111111111111111" - ], - "data": "6mJFQCt94hG4CKNYKgVcwiF4v7AWo54Dz3XinKUG6Qm1DDhhmspAST", - "programId": "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s", - "stackHeight": null - }, - { - "accounts": [ - "vir55LvSEGcY55ny876GycLmFCxMXTkoRg7RxDvKiw5", - "EfnLcrwxCgwALc5vXr4cwPZMVcmotZAuqmHa8afG8zJe", - "SysvarC1ock11111111111111111111111111111111" - ], - "data": "6mJFQCt94hG4CKNYKgVcwigC7XswHjekyj7J1dQmjsHsoqHjydqXoV", - "programId": "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s", - "stackHeight": null - }, - { - "accounts": [ - "vir55LvSEGcY55ny876GycLmFCxMXTkoRg7RxDvKiw5", - "6Ukmvns6Uyf3nRVj3ErDBgx7BiZRNJrLyXe1nGQ7CUHA", - "SysvarC1ock11111111111111111111111111111111" - ], - "data": "6mJFQCt94hG4CKNYKgVcwcojsn834cy7vrPD6ksi4ri42uvkGeVMkb", - "programId": "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s", - "stackHeight": null - } - ], - "recentBlockhash": "sS5jHAvxaXfowtGCvg4dWc9QjzeZ1dJS5GonshyDsEq" - }, - "signatures": [ - "2o9qCEhwKi8w7hTFQJTLwMBZPFH8qM3iNd9rprtdY6XShyrpsqkWt4Df3Zgsxv6y4nbRe4SDgU8KMvuMfs7HxVhp" - ] - } - }, - "id": 1 -} -``` - -我们可以通过这个代替去查看浏览器。 - -### 发送交易 - -发送交易通过 `sendTransaction` 接口。这个接口里面需要对`Transaction`对象做编码,所以不做演示。在Javascript/rust的SDK中操作会比较直观。 - -除了发送请求外,还可以通过模拟请求来判断是否可能执行成功,接口为`simulateTransaction`。 - -在发送交易的时候,还可以通过`getFeeForMessage`来预估手续费 diff --git a/other_docs/Solana-Bootcamp-zh/week2/push-rpc.md b/other_docs/Solana-Bootcamp-zh/week2/push-rpc.md deleted file mode 100644 index cda433324..000000000 --- a/other_docs/Solana-Bootcamp-zh/week2/push-rpc.md +++ /dev/null @@ -1,221 +0,0 @@ ---- -sidebar_position: 120 -sidebar_label: 推送RPC -sidebar_class_name: green ---- - -# 推送RPC - -推送RPC,其实是RPC节点允许连接的一个WebSocket长连接。通过在该长连接上发送订阅请求, RPC节点会将相关事件在长连接上推送过来。当前订阅主要分为: - -- `accountSubscribe` : 订阅`Account`的变化,比如`lamports` -- `logsSubscribe` : 订阅交易的日志 -- `programSubscribe` : 订阅合约`Account`的变化 -- `signatureSubscribe` : 订阅签名状态变化 -- `slotSubscribe` : 订阅`slot`的变化 - -每个事件,还有对应的`Unsubscribe`动作,取消订阅。将上面的`Subscribe`替换成`Unsubscribe`即可。 - -这里我们通过`wscat`命令行工具来模拟`wss`客户端。首先安装工具: - -```bash -npm install -g ws wscat -``` - -然后建立连接: - -```bash -wscat -c wss://api.devnet.solana.com -``` - -下面举例说明: - -## 订阅Account变化 - -这里的`Account`就是每个地址的`Account`元数据。主要变化的就是`data`部分和`lamports`部分。 比如我们要订阅我们的账号余额的变化。 - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "method": "accountSubscribe", - "params": [ - "CnjrCefFBHmWnKcwH5T8DFUQuVEmUJwfBL3Goqj6YhKw", - { - "encoding": "jsonParsed", - "commitment": "finalized" - } - ] -} -``` - -这里订阅对账号的变化的事件,我们通过`wscat`来模拟: - -```bash -$ wscat -c wss://api.devnet.solana.com - Connected (press CTRL+C to quit) - > {"jsonrpc":"2.0","id":1,"method":"accountSubscribe","params":["EZhhUANUMKsRhRMArczio1kLc9axefTUAh5xofGX35AK",{"encoding":"jsonParsed","commitment":"finalized"}]} - < {"jsonrpc":"2.0","result":3283925,"id":1} -``` - -然后我们在另外一个终端里面进行转账: - -```bash -$ solana transfer --allow-unfunded-recipient CZmVK1DymrSVWHiQCGXx6VG5zgHVrh5J1P514jHKRDxA 0.01 -``` - -接着我们注意观察上面的`wscat`: - -```bash -Connected (press CTRL+C to quit) - > {"jsonrpc":"2.0","id":1,"method":"accountSubscribe","params":["CnjrCefFBHmWnKcwH5T8DFUQuVEmUJwfBL3Goqj6YhKw",{"encoding":"jsonParsed","commitment":"finalized"}]} - < {"jsonrpc":"2.0","result":3283925,"id":1} - < {"jsonrpc":"2.0","method":"accountNotification","params":{"result":{"context":{"slot":209127027},"value":{"lamports":989995000,"data":["","base64"],"owner":"11111111111111111111111111111111","executable":false,"rentEpoch":0,"space":0}},"subscription":3283925}} -``` - -会发现,一段时间后,也就是到达了 “`finalized`”状态后,就会将修改过后的`Account`信息推送过来: - -```json -{ - "lamports": 989995000, - "data": [ - "", - "base64" - ], - "owner": "11111111111111111111111111111111", - "executable": false, - "rentEpoch": 0, - "space": 0 -} -``` - -可以看到这里余额发生了变化 - -## 订阅日志 - -订阅日志可能是做应用最常见到的,任何在`log`里面打印了相关事件的交易都会被通知 - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "method": "logsSubscribe", - "params": [ - { - "mentions": [ "CdJp6W7S8muM85UXq7u2P42ryytDacqEo8JgoHENSiUi" ] - }, - { - "commitment": "finalized" - } - ] -} -``` - -这里`mentions`来指定,通知了哪个程序或者账号的地址。 - -比如这里我们订阅我们的一个`ATA`的账号: - -```bash -$ wscat -c wss://api.devnet.solana.com - Connected (press CTRL+C to quit) - > {"jsonrpc":"2.0","id":1,"method":"logsSubscribe","params":[{"mentions":["CdJp6W7S8muM85UXq7u2P42ryytDacqEo8JgoHENSiUi"]},{"commitment":"finalized"}]} - < {"jsonrpc":"2.0","result":610540,"id":1} -``` - -然后我们给这个地址做`mint`增加他的余额: - -```bash -$ spl-token mint 7dyTPp6Jd1nWWyz3y7CXqdSG86yFpVF7u45ARKnqDhRF 1000000000 - Minting 1000000000 tokens - Token: 7dyTPp6Jd1nWWyz3y7CXqdSG86yFpVF7u45ARKnqDhRF - Recipient: CdJp6W7S8muM85UXq7u2P42ryytDacqEo8JgoHENSiUi - - Signature: 5NVHNccPo4ADxnHZjVSYZzxk3fuZfZvuLP6MwkhSNBbQRNcGfC2gwScz24XYictZuqaMKFEcmsXuHV4WZDiFUD3r -``` - -可以在事件通知中看到: - -```bash -$ wscat -c wss://api.devnet.solana.com - Connected (press CTRL+C to quit) - > {"jsonrpc":"2.0","id":1,"method":"logsSubscribe","params":[{"mentions":["CdJp6W7S8muM85UXq7u2P42ryytDacqEo8JgoHENSiUi"]},{"commitment":"finalized"}]} - < {"jsonrpc":"2.0","result":610540,"id":1} - < {"jsonrpc":"2.0","method":"logsNotification","params":{"result":{"context":{"slot":209131722},"value":{"signature":"5NVHNccPo4ADxnHZjVSYZzxk3fuZfZvuLP6MwkhSNBbQRNcGfC2gwScz24XYictZuqaMKFEcmsXuHV4WZDiFUD3r","err":null,"logs":["Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [1]","Program log: Instruction: MintToChecked","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4498 of 200000 compute units","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success"]}},"subscription":610540}} -``` - -这里有个"`MintToChecked`"指令。 - -## 订阅合约所属于`Account`事件 - - -比如我们希望知道所有`Token`合约管理的账号的余额变化是,我们可以通过订阅合约管理的账号事件来发现: - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "method": "programSubscribe", - "params": [ - "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", - { - "encoding": "jsonParsed" - } - ] -} -``` - -对应的命令 - -```bash -$ wscat -c wss://api.devnet.solana.com - Connected (press CTRL+C to quit) - > {"jsonrpc":"2.0","id":1,"method":"programSubscribe","params":["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",{"encoding":"jsonParsed"}]} - < {"jsonrpc":"2.0","result":142408,"id":1} - < {"jsonrpc":"2.0","method":"programNotification","params":{"result":{"context":{"slot":209131042},"value":{"pubkey":"GGUY45VyYy9j7vFdHRP3ecyMYhFCfrCBpVQaUxoEtHfv","account":{"lamports":2039280,"data":{"program":"spl-token","parsed":{"info":{"isNative":false,"mint":"GCBnu9k28isstJjCcYoZZcyTkMh5cXTsk7abpgWJesQT","owner":"AV1JYHgShqNdbza84sLi7Hgbtfgd1hn9mNMgez4twBuG","state":"initialized","tokenAmount":{"amount":"0","decimals":9,"uiAmount":0.0,"uiAmountString":"0"}},"type":"account"},"space":165},"owner":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","executable":false,"rentEpoch":0,"space":165}}},"subscription":142408}} - < {"jsonrpc":"2.0","method":"programNotification","params":{"result":{"context":{"slot":209131042},"value":{"pubkey":"GGUXUncym8riA1izYZnBWspYL1k4rVBnLuZ3KbUnc6WG","account":{"lamports":2039280,"data":{"program":"spl-token","parsed":{"info":{"isNative":false,"mint":"GCBnu9k28isstJjCcYoZZcyTkMh5cXTsk7abpgWJesQT","owner":"2E7BD9ibbHinwohM4pLFsjdFYq1S2o4wqKmfaQXXg8Dr","state":"initialized","tokenAmount":{"amount":"0","decimals":9,"uiAmount":0.0,"uiAmountString":"0"}},"type":"account"},"space":165},"owner":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","executable":false,"rentEpoch":0,"space":165}}},"subscription":142408}} - < {"jsonrpc":"2.0","method":"programNotification","params":{"result":{"context":{"slot":209131042},"value":{"pubkey":"GGUZyCzhCKEFZMdf8mDfUU4L1tr4q2xh3FHRWpRM8cPB","account":{"lamports":2039280,"data":{"program":"spl-token","parsed":{"info":{"isNative":false,"mint":"GCBnu9k28isstJjCcYoZZcyTkMh5cXTsk7abpgWJesQT","owner":"7PydWu5QtMcbdj7qgdgn42Rwp247GFf3e2pQ5fQ8LRGY","state":"initialized","tokenAmount":{"amount":"0","decimals":9,"uiAmount":0.0,"uiAmountString":"0"}},"type":"account"},"space":165},"owner":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","executable":false,"rentEpoch":0,"space":165}}},"subscription":142408}} - < {"jsonrpc":"2.0","method":"programNotification","params":{"result":{"context":{"slot":209131042},"value":{"pubkey":"GGUZzsex1ybU4V1duGetLHqFc7zz74jPbLp6rvNoScrR","account":{"lamports":2039280,"data":{"program":"spl-token","parsed":{"info":{"isNative":false,"mint":"GCBnu9k28isstJjCcYoZZcyTkMh5cXTsk7abpgWJesQT","owner":"2E7BD9ibbHinwohM4pLFsjdFYq1S2o4wqKmfaQXXg8Dr","state":"initialized","tokenAmount":{"amount":"0","decimals":9,"uiAmount":0.0,"uiAmountString":"0"}},"type":"account"},"space":165},"owner":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","executable":false,"rentEpoch":0,"space":165}}},"subscription":142408}} - < {"jsonrpc":"2.0","method":"programNotification","params":{"result":{"context":{"slot":209131042},"value":{"pubkey":"GGUV4mWenyQGyVVCNV3xPjmioJoMCCYSPFxFzFB3AmBt","account":{"lamports":2039280,"data":{"program":"spl-token","parsed":{"info":{"isNative":false,"mint":"GCBnu9k28isstJjCcYoZZcyTkMh5cXTsk7abpgWJesQT","owner":"2E7BD9ibbHinwohM4pLFsjdFYq1S2o4wqKmfaQXXg8Dr","state":"initialized","tokenAmount":{"amount":"0","decimals":9,"uiAmount":0.0,"uiAmountString":"0"}},"type":"account"},"space":165},"owner":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","executable":false,"rentEpoch":0,"space":165}}},"subscription":142408}} -``` - -这里里面就可以看到。有很多的SPL Token账号都在变化。并且因为我们加了"`jsonParsed`",所以这里SPL Token的内容也展示出来了。 - -## 订阅交易状态 - -比如我们希望在我们发起交易后,第一时间知道交易的确定状态,我们可以通过订阅该事件来实现: - - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "method": "signatureSubscribe", - "params": [ - "BfQAbgqQZMfsxFHwh6Hve8yGb843QfZcYtD2j2nN3K1hLHZrQjzdwG9uWgNkGXs4tBNVLE3JAzvNLtwJBt3zDsN", - { - "commitment": "finalized", - "enableReceivedNotification": false - } - ] -} -``` - -这里。我们再次发起一笔转账交易: - -```bash -$ solana transfer --allow-unfunded-recipient CZmVK1DymrSVWHiQCGXx6VG5zgHVrh5J1P514jHKRDxA 0.01 - - Signature: BfQAbgqQZMfsxFHwh6Hve8yGb843QfZcYtD2j2nN3K1hLHZrQjzdwG9uWgNkGXs4tBNVLE3JAzvNLtwJBt3zDsN -``` - -然后在另外一个终端,迅速建立`wscat`连接,并订阅该事件: - -```bash -$ wscat -c wss://api.devnet.solana.com - Connected (press CTRL+C to quit) - > {"jsonrpc":"2.0","id":1,"method":"signatureSubscribe","params":["BfQAbgqQZMfsxFHwh6Hve8yGb843QfZcYtD2j2nN3K1hLHZrQjzdwG9uWgNkGXs4tBNVLE3JAzvNLtwJBt3zDsN",{"commitment":"finalized","enableReceivedNotification":false}]} - < {"jsonrpc":"2.0","result":3285176,"id":1} - < {"jsonrpc":"2.0","method":"signatureNotification","params":{"result":{"context":{"slot":209127740},"value":{"err":null}},"subscription":3285176}} -``` - -可以看到。当到达"`finalized`"状态时,通知我们,该交易已经成功,没有出错。 diff --git a/other_docs/Solana-Bootcamp-zh/week2/solana-rpc-intro.md b/other_docs/Solana-Bootcamp-zh/week2/solana-rpc-intro.md deleted file mode 100644 index 1a73d30f4..000000000 --- a/other_docs/Solana-Bootcamp-zh/week2/solana-rpc-intro.md +++ /dev/null @@ -1,138 +0,0 @@ ---- -sidebar_position: 118 -sidebar_label: Solana的RPC介绍 -sidebar_class_name: green ---- - -# Solana的RPC介绍 - -## RPC 介绍 - -RPC 是什么? - ->In distributed computing, a remote procedure call (RPC) is when a computer program causes a procedure (subroutine) to execute in a different address space (commonly on another computer on a shared network) -> ->-- wikipedia - -写代码的应该都知道 RPC 是啥,但是 RPC 跟区块链是什么关系呢? - -引用 Polkadot 的一个架构图: - -![](../img/week2/dot_arch.png) - -RPC 作为区块链系统与外界交互的一层接口调用。被普通用户直接使用。 - -但是为什么普通用户又感知不到 RPC 的存在呢?普通用户只知道钱包,拉起、确定 -> 币没了。 - -这里是因为我们这帮程序员,帮忙将中间的过程都通过代码来串联起来了。所以 RPC 又是用户界面和区块链之间的桥梁。 - -Solana 提供的 RPC 分为主动请求的 HTTP 接口和消息推送的 Websocket 接口。只是单次查询一般使用 HTTP 接口,如发送交易,查询用户余额。而对于链上数据的监控则通过 Websocket 接口,如监控合约执行的日志。 - -## HTTP 接口 - -HTTP 接口是通过 [JSON RPC](https://www.jsonrpc.org/) 的格式对外提供服务,JSON RPC 是一种以 JSSON 作为序列化工具,HTTP 作为传输协议的 RPC 模式,其有多个版本,当前使用的是 v2 版本。 - -其请求格式为: - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "method": "getBalance", - "params": [ - "83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri" - ] -} -``` - -这里最外层是一个字典,其中各个 `Key` 是固定的,其中 `method` 表示 RPC 的函数方法名。`params` 表示该函数的参数。 - -对应的请求结果为: - -```json -{ - "jsonrpc": "2.0", - "result": {}, - "id": 1 -} -``` - -同样的,这里的几个字段也是固定的,`result` 表示请求的结果。`id` 和请求里面的 `id` 对应,表示的是哪个请求的结果。 - -在请求查询的时候,对查询的结果有三种状态选择: - -- '`finalized`' - 节点将查询由超过集群中超多数确认为达到最大封锁期的最新区块,表示集群已将此区块确认为已完成。 -- '`confirmed`' - 节点将查询由集群的超多数投票的最新区块。 -- '`processed`' - 节点将查询最新的区块。注意,该区块可能被集群跳过。 -状态参数可以在"`params`"数组的最后,以字典的形式带入进去。 - -同时 Solana 也对常用的结果做了人为可读的优化。当传递"`encoding`":"`jsonParsed`"会讲结果尽量以 JSON 的方式返回。`encoding` 和上面的状态放在同一个位置。如: - -```json -{ - "commitment":"processed", - "encoding":"jsonParsed" -} -``` - -## Websocket 接口 - -Websocket 是 HTTP 为了补充长链接,而增加一个特性,概括来说就可以认为这个是一条 TCP 长链接。Solana 通过这条长连接来给客户端推送消息。 - -只是这里的消息的内容也是采用了 JSONRPC 的格式,如: - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "method": "accountSubscribe", - "params": [ - "CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12", - { - "encoding": "jsonParsed", - "commitment": "finalized" - } - ] -} -``` - -这样的消息订阅了 `Account("CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12")`的变化消息。 - -当有变化时,也是将结果打包成一个 JSONRPC 的格式推送给客户端: - -```json -{ - "jsonrpc": "2.0", - "method": "accountNotification", - "params": { - "result": { - "context": { - "slot": 5199307 - }, - "value": { - "data": { - "program": "nonce", - "parsed": { - "type": "initialized", - "info": { - "authority": "Bbqg1M4YVVfbhEzwA9SpC9FhsaG83YMTYoR4a8oTDLX", - "blockhash": "LUaQTmM7WbMRiATdMMHaRGakPtCkc2GHtH57STKXs6k", - "feeCalculator": { - "lamportsPerSignature": 5000 - } - } - } - }, - "executable": false, - "lamports": 33594, - "owner": "11111111111111111111111111111111", - "rentEpoch": 635, - "space": 80 - } - }, - "subscription": 23784 - } -} -``` - -每个 `Subscribe` 方法,都对应的有一个 `Unsubscribe` 方法,当发送改方法时,服务器后续不再推送消息。 diff --git a/other_docs/Solana-Bootcamp-zh/week3/README.md b/other_docs/Solana-Bootcamp-zh/week3/README.md deleted file mode 100644 index a21d5328f..000000000 --- a/other_docs/Solana-Bootcamp-zh/week3/README.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -sidebar_position: 122 -sidebar_label: week3 与Solana合约交互 -sidebar_class_name: green ---- - -## Week3: 与Solana合约交互 - -- [Solana的Web3.js](./web3-js.md) -- [与钱包交互](./call-wallet.md) -- [`Program`调用](./call-program.md) -- [课后练习](./homework.md) diff --git a/other_docs/Solana-Bootcamp-zh/week3/call-program.md b/other_docs/Solana-Bootcamp-zh/week3/call-program.md deleted file mode 100644 index 04378b03e..000000000 --- a/other_docs/Solana-Bootcamp-zh/week3/call-program.md +++ /dev/null @@ -1,339 +0,0 @@ ---- -sidebar_position: 125 -sidebar_label: Program 调用 -sidebar_class_name: green ---- - -# `Program` 调用 - -在前面的例子中,我们通过 `web3.js` 提供的 `SystemProgram` 来帮助我们实现了转账的功能。 - -但是对于一个陌生的`Program`,我们要怎么来发起调用请求呢? - -## Program的入口 - -这里我们以 `SPL Token Program`来举例。`SPL Token Program`类似 web3.js 一样,其实已经封装好了一套 JS 库给我们来直接使用。这里我们不使用库,而以一个前端的身份,来看这样的一个`Program`,我们要怎么来交互。 - -我们以 `transfer` 函数作为例子。 - -首先要理解`Program`的作用和参数,这个可以跟`Program`开发去沟通。比如我们从注释了解到 `transfer` 为 - -```rs -/// Transfers tokens from one account to another either directly or via a -/// delegate. If this account is associated with the native mint then equal -/// amounts of SOL and Tokens will be transferred to the destination -/// account. -/// -/// Accounts expected by this instruction: -/// -/// * Single owner/delegate -/// 0. `[writable]` The source account. -/// 1. `[writable]` The destination account. -/// 2. `[signer]` The source account's owner/delegate. -/// -/// * Multisignature owner/delegate -/// 0. `[writable]` The source account. -/// 1. `[writable]` The destination account. -/// 2. `[]` The source account's multisignature owner/delegate. -/// 3. ..3+M `[signer]` M signer accounts. -Transfer { - /// The amount of tokens to transfer. - amount: u64, -}, -``` - -总共需要 3 个`key`,分别是,发送方,接收方以及发送方的 `ower/delegate`。然后有一个类型 `u64`的参数。 - -知道了这些我们才可以构造我们的 `Instruction`。`Instruction` 的定义为: - - -```ts -/** -* Transaction Instruction class -*/ -export class TransactionInstruction { - /** - * Public keys to include in this transaction - * Boolean represents whether this pubkey needs to sign the transaction - */ - keys: Array; - /** - * Program Id to execute - */ - programId: PublicKey; - /** - * Program input - */ - data: Buffer; - constructor(opts: TransactionInstructionCtorFields); -} -``` - -所以我们主要就是要从`Program`的定义中知道这里的 `keys` 是什么, `data` 是什么,`programId` 自然就是`Program`的地址。 - -## 构造 `Instruction` - -在上面,我们知道了 `Instruction` 的定义。那么要如何来构造呢? - -如果你是用 TypeScript,那么比较醒目。`keys` 是 `AccountMeta` 的数组,`AccountMeta` 的定义为: - -```ts -/** -* Account metadata used to define instructions -*/ -type AccountMeta = { - /** An account's public key */ - pubkey: PublicKey; - /** True if an instruction requires a transaction signature matching `pubkey` */ - isSigner: boolean; - /** True if the `pubkey` can be loaded as a read-write account. */ - isWritable: boolean; -}; -``` - -总共就三个成员,一个 `PublicKey` 表示 `Account` 的地址, 一个 `isSigner` 表示是否为签名者,说白了就是是不是你自己。以及 `isWritable`,表示这个 `Account` 的 `Data` 部分是否可以修改。 - -这里 `PublicKey` 的定义为: - -```ts -export class PublicKey extends Struct { - /** - * Create a new PublicKey object - * @param value ed25519 public key as buffer or base-58 encoded string - */ - constructor(value: PublicKeyInitData); - - ... -} - -/** -* Value to be converted into public key -*/ -type PublicKeyInitData = number | string | Uint8Array | Array | PublicKeyData; -``` - -其实就是用公钥的字符串就可以进行构造了。 - -所以如果是用 TypeScript。就严格按照类型来定义就好了。 - -如果是 Javascript,可以用字典来进行显式初始化: - -而 `data` 部分是一个 `Buffer`,其实本质是一段二进制,其格式是根据`Program`来定义的,也可以参考标准,比如"Anchor"。而 `SPL Token` 的二进制定义为: - -![](../img/week3/data_bin.png) - -这里我们可以借助 `web.js` 提供的"`encodeData`"方法来进行序列化。而 `web3.js` 的指令定义依赖了 solana 提供的 `buffer-layout`,因此需要这样来定义: - -这样实际上就是定义了上面的这个序列化的图。当调用`encodeData`方法时,就可以按照这里定义的格式进行序列化了。 - -## 构造 `Transaction` - -有了 `TransactionInstruction` 之后,就可以构造 `Transaction` 了。前面已经说过,现在用的是 `VersionedTransaction`。他的定义为: - -```ts -export class VersionedTransaction { - signatures: Array; - message: VersionedMessage; - get version(): TransactionVersion; - constructor(message: VersionedMessage, signatures?: Array); - serialize(): Uint8Array; - static deserialize(serializedTransaction: Uint8Array): VersionedTransaction; - sign(signers: Array): void; - addSignature(publicKey: PublicKey, signature: Uint8Array): void; -} -``` - -可以通过一个 `VesionedMessage` 来构建,定义为: - -```ts -type VersionedMessage = Message | MessageV0; -export const VersionedMessage: { - deserializeMessageVersion(serializedMessage: Uint8Array): 'legacy' | number; - deserialize: (serializedMessage: Uint8Array) => VersionedMessage; -}; -``` - -`Message` 是为了兼容以前的 `Message`,现在的都是用 `MessageV0`: - -```ts -export class MessageV0 { - header: MessageHeader; - staticAccountKeys: Array; - recentBlockhash: Blockhash; - compiledInstructions: Array; - addressTableLookups: Array; - constructor(args: MessageV0Args); - get version(): 0; - get numAccountKeysFromLookups(): number; - getAccountKeys(args?: GetAccountKeysArgs): MessageAccountKeys; - isAccountSigner(index: number): boolean; - isAccountWritable(index: number): boolean; - resolveAddressTableLookups(addressLookupTableAccounts: AddressLookupTableAccount[]): AccountKeysFromLookups; - static compile(args: CompileV0Args): MessageV0; - serialize(): Uint8Array; - private serializeInstructions; - private serializeAddressTableLookups; - static deserialize(serializedMessage: Uint8Array): MessageV0; -} -``` - -看上去超级复杂。因此 `web3.js` 给我们提供了一个简单的方法,通过`TransactionMessage`来构造: - -```ts -export class TransactionMessage { - payerKey: PublicKey; - instructions: Array; - recentBlockhash: Blockhash; - constructor(args: TransactionMessageArgs); - static decompile(message: VersionedMessage, args?: DecompileArgs): TransactionMessage; - compileToLegacyMessage(): Message; - compileToV0Message(addressLookupTableAccounts?: AddressLookupTableAccount[]): MessageV0; -} -``` - -其`compileToV0Message`可以转换道得到对应的 `MessageV0`。 - -因此只需要提供 `TransactionMessageArgs` 即可,其定义为: - -```ts -type TransactionMessageArgs = { - payerKey: PublicKey; - instructions: Array; - recentBlockhash: Blockhash; -}; - -/** -* Blockhash as Base58 string. -*/ -type Blockhash = string; -``` -终于到正主了,这里我们看到 `payerKey` 是付 `gas` 人的地址。`instructions` 是我们前面介绍的 `Instruction`。 `recentBlockhash` 是最近的 `Blockhash` 这个不能太久远。可以通过 RPC 进行请求。 - -这样我们连起来就是: - -```ts -const txInstructions = - -const message = new TransactionMessage({ - payerKey: this.keypair.publicKey, - recentBlockhash: latestBlockhash.blockhash, - instructions: txInstructions -}).compileToV0Message(); - -const trx = new VersionedTransaction(messageV0); -``` - -## 构造 `SPL Token` 的 转账交易 - -前面我们已经搞清楚了 `SPL Token` `Program`转账指令的结构,3 个账号一个数目。账号比较容易。我们自己账号对应的 `SPL Token` 的 `ATA` 账号,对方接收的账号。这两个都是不需要前面的,并且需要修改的。还有个我们自己的 SOL 账号,这个需要签名。 - -先看下 `Token Program`的 `Transfer` 定义: - -```rs -/// Transfers tokens from one account to another either directly or via a -/// delegate. If this account is associated with the native mint then equal -/// amounts of SOL and Tokens will be transferred to the destination -/// account. -/// -/// Accounts expected by this instruction: -/// -/// * Single owner/delegate -/// 0. `[writable]` The source account. -/// 1. `[writable]` The destination account. -/// 2. `[signer]` The source account's owner/delegate. -/// -/// * Multisignature owner/delegate -/// 0. `[writable]` The source account. -/// 1. `[writable]` The destination account. -/// 2. `[]` The source account's multisignature owner/delegate. -/// 3. ..3+M `[signer]` M signer accounts. -Transfer { - /// The amount of tokens to transfer. - amount: u64, -}, -``` - -按照上面说的,我们依靠 `web3.js` 提供的 `buffer-layout` 我们来定义这个 `transfer` 的指令。 - -```ts -export interface TransferInstructionData { - instruction: TokenInstruction.Transfer; - amount: bigint; -} - -/** TODO: docs */ -export const transferInstructionData = struct([u8('instruction'), u64('amount')]); -``` - -这里比 Rust 的定义,多了个"`instruction`",这个是因为 `Token` 的序列化规则,使用一个 `u8` 来表示是那个指令。 - -定义好指令,我们就可以开始构建了。 - -按照上面说先构建指令: - -```ts -function createTransferInstruction( - source, - destination, - owner, - amount, - programId -) { - const keys = [ - { pubkey: source, isSigner: false, isWritable: true }, - { pubkey: destination, isSigner: false, isWritable: true }, - { pubkey: owner, isSigner:true, isWritable: false} - ]; - - const data = Buffer.alloc(9); - data.writeUInt8(3); - const bigAmount = BigInt(amount); - data.writeBigInt64LE(bigAmount,1) - - - return new TransactionInstruction({ keys, programId, data }); -} -``` - -这里的第一个 `byte` 为 3 表示 `transfer` 指令。 - -然后构建交易: - -```ts -const txInstructions = [ - createTransferInstruction( - ATA_PUBKEY_KEY, - TO_PUBLIC_KEY, - publicKey, - toCount, - TOKEN_PROGRAM_ID - ), -]; - -const { - context: { slot: minContextSlot }, - value: { blockhash, lastValidBlockHeight }, -} = await connection.getLatestBlockhashAndContext(); -const messageV0 = new TransactionMessage({ - payerKey: publicKey, - recentBlockhash: blockhash, - instructions: txInstructions, -}).compileToV0Message(); - -const trx = new VersionedTransaction(messageV0); -``` - -最后利用前面学的通过钱包来发送交易: - -```ts -const signature = await sendTransaction(trx, connection, { - minContextSlot, -}); -console.log("signature:", signature); -``` - -这样我们就完成了通过前端来和特定的`Program`进行交互。 - -## Demo - -[spl token demo](https://www.solanazh.com/assets/files/spl-token-demo.zip) diff --git a/other_docs/Solana-Bootcamp-zh/week3/call-wallet.md b/other_docs/Solana-Bootcamp-zh/week3/call-wallet.md deleted file mode 100644 index 72b8fb37c..000000000 --- a/other_docs/Solana-Bootcamp-zh/week3/call-wallet.md +++ /dev/null @@ -1,145 +0,0 @@ ---- -sidebar_position: 124 -sidebar_label: 与钱包交互 -sidebar_class_name: green ---- - -# 与钱包交互 - -## 通过 `WalletAdatper` 与钱包交互 - -为了给 DApp 提供一套统一的兼容钱包的接口。Solana 设计了一套 `Wallet Adapter`。 Solana 要求,钱包方需要按照该套接口设计,提供实现。这样 DApp 使用方,只需要按照一套接口,就可以轻松支持多个钱包。接口包含了 - -- 网络选择 -- 账号选择 -- 账号签名 -- 等 - -除了统一的接口,Adapter 还设计了一套基础 UI,其包括了弹出钱包的选择列表,以及链接钱包后的的账号地址显示。 - -![](../img/week3/wallets_select_ui.png) - -## 安装 - -在你的工程总安装 `Wallet_Adapter` 依赖 - -```bash -npm install --save \ - @solana/wallet-adapter-base \ - @solana/wallet-adapter-react \ - @solana/wallet-adapter-react-ui \ - @solana/wallet-adapter-wallets \ - @solana/web3.js -``` - -这里我们还会用到一些 `web3.js` 里面的变量,所以也将其 `install` 上。 - -在使用地方先 `import` 相关 SDK - -```ts -import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react'; -import { WalletAdapterNetwork } from '@solana/wallet-adapter-base'; - -import { - WalletModalProvider, - WalletDisconnectButton, - WalletMultiButton -} from '@solana/wallet-adapter-react-ui'; -import { clusterApiUrl } from '@solana/web3.js'; -``` - -这里因为我们的示例 demo 是 react 的,所以使用了 react-ui,Wallet-adapter 同时也提供了 Material UI 和 Ant Design 的组件。 - -已经实现了 Adapter 的钱包参见[列表](https://github.com/solana-labs/wallet-adapter/tree/master/packages/wallets)。 - -这里我们使用: - -```ts -import {SolongWalletAdapter} from '@solana/wallet-adapter-solong' -import {PhantomWalletAdapter} from '@solana/wallet-adapter-phantom'; -``` - -## 链接钱包 - -链接钱包的步骤,是在用户界面设置一个"`Connect`"的按钮,当点击时,弹出一个钱包选择 list 界面。可使用钱包,通过数组参数参数。 - -```ts -this.network = WalletAdapterNetwork.Testnet; - -// You can also provide a custom RPC endpoint. -this.endpoint = clusterApiUrl(this.network); - -this.wallets =[ - new SolongWalletAdapter(), - new PhantomWalletAdapter(), -]; -``` - -然后再弹出 UI 将钱包罗列出来 - -```ts - - - - - - - - -``` - -这里主要使用了 `ConnectionProvider` 来指定相应的网络。`endpoint` 参数为使用的 RPC 环境。通过 `WalletProvider` 来选择实现了 `Adapter` 的插件钱包,示例中我们设置了 `Phantom`。 - -最后在 `WalletModalProvider` 通过相应的按钮触发对钱包的选择。也就是我们上面传递的 `Solong` 和 `Phantom`。 - -![](../img/week3/wallet_connect.png) - -当用户点击 `WalletMultiButton` 的时候,会自动弹出钱包选择界面。选择钱包后,会弹出钱包的链接界面。当用户点击链接后,这里的 `ModalProvider` 会得到选择的账号的信息,并将地址显示在按钮上。 - -当点击 `WalletDisconnectButton` 后,会断开链接。 - -## 发送请求 - -前面介绍了 web3.js 的使用,在发送请求的时候,我们需要用账号的私钥对交易内容做签名。那么在使用钱包的情况下该如何操作呢? - -首先 import 相关库 - -```ts -import { WalletNotConnectedError } from '@solana/wallet-adapter-base'; -import { useConnection, useWallet } from '@solana/wallet-adapter-react'; -import { Keypair, SystemProgram, Transaction } from '@solana/web3.js'; -import React, { FC, useCallback } from 'react'; -``` - -然后先取出链接和公钥: - -```ts -const { connection } = useConnection(); -const { publicKey, sendTransaction } = useWallet(); -``` - -这里通过 `useConnection` 可以得到我们前面钱包里面选择的 RPC 链接,`useWallet` 返回的结果为选中的钱包的地址,以及使用该钱包发送交易的方法。 - -```ts -const { - context: { slot: minContextSlot }, - value: { blockhash, lastValidBlockHeight } -} = await connection.getLatestBlockhashAndContext(); - -const signature = await sendTransaction(transaction, connection, { minContextSlot }); -``` - -通过 `connection.getLatestBlockhashAndContext` 可以得到 `minContextSlot` 信息,然后再调用 `sendTransaction` 方法,就可以出发钱包弹出 UI,并提示用户确认,当用户点击确认后,既完成请求的发送。 - -## 切换账号 - - -如果用户需要切换账号,那么通过 UI 提供的 `Disconnect` 入口,先取消当前账号的链接。然后再通过链接界面,选择其他的钱包账号。所以切换账号就是先断开,再重新链接的过程。 - -取消链接,只需要删除当前记录的用户即刻。 - -而切换账号则可以直接在此使用"连接" 的流程。 - -## Demo - -[wallet adapter demo](https://www.solanazh.com/assets/files/wallet-adapter-demo.zip) diff --git a/other_docs/Solana-Bootcamp-zh/week3/homework.md b/other_docs/Solana-Bootcamp-zh/week3/homework.md deleted file mode 100644 index 7106d0c6b..000000000 --- a/other_docs/Solana-Bootcamp-zh/week3/homework.md +++ /dev/null @@ -1,132 +0,0 @@ ---- -sidebar_position: 126 -sidebar_label: 课后练习 -sidebar_class_name: green ---- - -# 课后练习 - - -实现一个 DApp 页面,实现代币创建,并按白名单发送空投 - -提示: - -- 通过与 `Token program`交互,创建代币 -- 通过与 `Token program` 交互,给白名单中的地址,发送 `SPL Token` 代币 -- 建议使用 `SPL-Token` 提供的库来构建 `instruction` - ---- - -## 思路 - -### 创建代币 - -首先要构造一个 `MintAccount`: - -```ts -mintKeypair = Keypair.generate(); -``` - -然后创建一个创建这个 `MintAccount` 的指令,我们借助系统指令库 - -```ts -SystemProgram.createAccount({ - fromPubkey: publicKey, - newAccountPubkey: mintKeypair.publicKey, - space: MINT_SIZE, - lamports:lamports, - programId: TOKEN_PROGRAM_ID, -}), -``` - -这里 `from` 是付费人,`new` 是要创建的地址,`space` 是 `data` 的大小,因为我们要放 `MintAccount` 其为 `spl-token` 库里的定义 - -`lamports` 我们通过 库提供的`const lamports = await getMinimumBalanceForRentExemptMint(connection);`来获得一个 `Account` 对应这个大小的存储空间的最小的 `rent` 花费。 - -接着创建 创建 `token` 的指令: - -```ts -createInitializeMint2Instruction( - mintKeypair.publicKey, - 9, - publicKey, - publicKey, - TOKEN_PROGRAM_ID -) -``` - -这里依次是创建的 `MintAccount`,精度,`mintAuthority`,`freezeAuthority` 以及 `Token Program`地址。 - -最后就是按照我们前面课程中的方式构造交易并发送。 - -这里要注意,因为我们创建了新的 `MintAccount`,其内容修改需要签名,因此在发送交易的时候,带上: - -```ts -const signature = await sendTransaction(trx, connection, { - minContextSlot, - signers:[mintKeypair], - }); -``` - -### Mint Token - -要 `mint token`,先要生成 `ata` 账号地址,`spl token` 的库里面有函数。 - -```ts -ataAccount = await getAssociatedTokenAddressSync( - mintKeypair.publicKey, - owner, - false, - TOKEN_PROGRAM_ID, - ASSOCIATED_TOKEN_PROGRAM_ID - ); -``` - -依次传入 `MintAccount`,为谁创建,后三个参数固定这样传即可。 - -然后判断这个 `account` 是否存在,可以用我们前面 rpc 的 `getaccount`,这里库也封装了函数 - -```ts -await getAccount(connection, ataAccount); -``` - -没有的时候,会抛异常,我们要创建这个 `ATA` 账号 - -```ts -txInstructions.push( - createAssociatedTokenAccountInstruction( - publicKey, - ataAccount, - owner, - mintKeypair.publicKey, - TOKEN_PROGRAM_ID, - ASSOCIATED_TOKEN_PROGRAM_ID - ) -); -``` - - -调用库里的 `createAssociatedTokenAccountInstruction` 创建指令,依次传入付费的人,上面的 `ata` 账号,为谁创建以及 `MintAccount`。 - -如果存在则跳过 - -最后执行 `Mint`: - -```ts -txInstructions.push( - createMintToInstruction( - mintKeypair.publicKey, - ataAccount, - publicKey, - BigInt(toCount) - ) -); -``` - -调用库函数 `createMintToInstruction` 传入 `MintAccount`,`ata` 账号以及 `MintAuthority`,因为只有他才有权限 `mint`,和数量。 - -然后就是跟前面一样,构造交易并发送。注意这次没有额外的 `singer` 要求了。 - -## 参考实现 - -[spl token exercise](https://www.solanazh.com/assets/files/spl-token-exercise.zip) diff --git a/other_docs/Solana-Bootcamp-zh/week3/web3-js.md b/other_docs/Solana-Bootcamp-zh/week3/web3-js.md deleted file mode 100644 index ef545ce39..000000000 --- a/other_docs/Solana-Bootcamp-zh/week3/web3-js.md +++ /dev/null @@ -1,248 +0,0 @@ ---- -sidebar_position: 123 -sidebar_label: Solana的Web3.js -sidebar_class_name: green ---- - -# Solana的Web3.js - -如果之前接触过 ETH,一定知道 ETH 的[web3.js](https://github.com/web3/web3.js) ,主要提供了通过 JavaScript 与 ETH 上合约进行交互。而 Solana 也提供了与 Solana 的 JSON RPC 接口交互的[solana-web3.js](https://github.com/solana-labs/solana-web3.js) 。通过这个库,可以实现在 dapp 中用 JavaScritp 和 Solana 上的智能合约进行交互。 - -Web3.js 库主要分为三部分 - -- RPC 访问 -- keypair 管理 -- 交易发送 - -## Demo - -首先来看一个 Demo,在 Demo 中,首先导入私钥,然后可以查询该账号的余额。然后执行转账,转账后再可以查询余额来进行结果判断。 - -![](../img/week3/web3uidemo.png) - -这里我们是以开发网为开发环境,所以相应的数据都在开发网上。 - -## 安装 - - -solana/web3.js 提供了 `npm` 包和 `yarn` 包的选择,使用 `yarn` 的话,可以在工程中用 - -```bash -yarn add @solana/web3.js -``` - -进行引用,如果使用的 `npm` 的话,使用: - -```bash -npm install --save @solana/web3.js -``` - -如果想在自己已有的没有使用包管理的项目中使用,可以先 `checkout` 出 `solana/web3.js` 的代码,然后 `checkout` 到最新的分支上,执行: - -```bash -cd your_solana_web3js_directory - git clone https://github.com/solana-labs/solana-web3.js.git - git checkout v1.78.0 - yarn install - yarn build -``` - -从 lib 目录取出 `index.iife.js` 既为浏览器使用的版本,然后使用``进行引用。 - -或者用已经编译好的: - -``` - - -``` - -## `Connection` - -Web3.js 通过 `Connection` 来抽象一个 RPC 链接。通过 - -```ts -let url = 'https://api.devnet.solana.com'; -let rpcConnection = new Connection(url); -``` - -通过指定 RPC 的地址这样来创建。这个对象包含了所有的 RPC 方法: - -![](../img/week3/rpc_method.png) - -可以查看每个方法的文档,来查看使用方法。这里举几个简单的例子。比如获取当前区块高度。 - - -```ts -let latestBlockhash = await this.connection.getLatestBlockhash('finalized'); -console.log(" ✅ - Fetched latest blockhash. Last Valid Height:", latestBlockhash.lastValidBlockHeight); -``` - -这里指定了区块状态为 finalized,在 console 中可以看到: - -```bash -✅ - Fetched latest blockhash. Last Valid Height: 175332530 -``` - -## 账号 - -早年版本(20 年),Web3.js 提供了一个 `Account` 对象,但是后来将其抽象为一个 `Keypair` 对象。 - -`keypair` 在前面的章节中有介绍到。其本质就是一个私钥和公钥的组合。因此 `keypair` 可以用一段私钥来进行初始化: - -```ts -constructor(keypair?: Ed25519Keypair) -``` - -可以通过构造函数,直接创建。或者通过 - -```ts -static fromSecretKey(secretKey: Uint8Array, options?: { skipValidation?: boolean; }) -``` - -来创建。 - -在前面的章节中,通过命令行创建了私钥,在文件"~/.config/solana/id.json"中,没有加密的情况下可以直接取出来 - -```ts -let secretKey = Uint8Array.from(JSON.parse('[24,xxx,119]')); -const keypair = Keypair.fromSecretKey(secretKey); -console.log("address:", keypair.publicKey.toString()) -``` - -可以看到: - -```bash -address: 5pWae6RxD3zrYzBmPTMYo1LZ5vef3vfWH6iV3s8n6ZRG -``` - -和我们命令行中的地址是一样的。 - -这里 `publicKey` 就是对应的账号地址,`keypair` 就是 `Signer`。 - -## 发送交易 - - -在介绍 Solana 核心概念的时候,我们有介绍到 `Instruction` 和 `Transaction` 以及 `Message`。所以发送交易,就是构建 `Instructions` 数组,然后构造 `Message`,再放到 `Transaction` 里面,做签名并进行发送。 - -如果是普通应用合约,需要自己封装 `Instruction`。 - -```ts -/** - * Transaction Instruction class - */ - export class TransactionInstruction { - /** - * Public keys to include in this transaction - * Boolean represents whether this pubkey needs to sign the transaction - */ - keys: Array; - /** - * Program Id to execute - */ - programId: PublicKey; - /** - * Program input - */ - data: Buffer; - constructor(opts: TransactionInstructionCtorFields); - } -``` - - -其中 `programId`表示调用合约的地址。`key` 是合约中需要使用到的 `Account`, `data` 则是所有的输入序列化后的二进制。 - -因为合约的入口是: - -```rs -declare_process_instruction!( - process_instruction, - DEFAULT_COMPUTE_UNITS, - |invoke_context| { - let transaction_context = &invoke_context.transaction_context; - let instruction_context = transaction_context.get_current_instruction_context()?; - let instruction_data = instruction_context.get_instruction_data(); - let instruction = limited_deserialize(instruction_data)?; -``` - -可以简化为: - -```rust -fn process_instruction( - program_id: &Pubkey, - accounts: &[AccountInfo], - instruction_data: &[u8], - ) -> ProgramResult { - -``` - -具体的方法是从 `data` 里面解析出来,然后再解析出来参数。 - -而 Solana 的系统合约,或者说 `Native Program`。 Web3.js 已经为我们封装好了一些 `Instruction`。比如转账: - -```ts -SystemProgram.transfer({ - fromPubkey:new PublicKey(this.state.publicKey), //this.publicKey, - toPubkey: new PublicKey(this.state.toPublicKey),//destination, - lamports: this.state.toCount,//amount, -}) -``` - -这里表示从 `fromPubkey` 地址转 `lamports` 的 SOL 到 `toPubkey` 的地址。他实际上会调用"`11111111111111111111111111111111`"合约的 `transfer`方法。该方法接受三个参数,其中 `fromPubkey` 需要是签名对象。 - -```rs -fn transfer( - from_account_index: IndexOfAccount, - to_account_index: IndexOfAccount, - lamports: u64, - invoke_context: &InvokeContext, - transaction_context: &TransactionContext, - instruction_context: &InstructionContext, -) -> Result<(), InstructionError> { -``` - -这里 `instructions` 是 `Array`一个数组。 - -`payerKey` 则是发送这个消息的 gas 付费者,其也需要提供签名。 `recentBlockhash` 通过我们前面的 RPC 可以获取到。这里 `recentBlockhash` 不能隔的太远。这样就限制了消息的签名时间。最后调用 `compileToV0Message` 构造 `Message` 对象。 - -有了 `Message`,还有构造 `VersionedTransaction`, 早期的 `Transaction` 已经废弃。 - -```ts -export class VersionedTransaction { - signatures: Array; - message: VersionedMessage; - get version(): TransactionVersion; - constructor(message: VersionedMessage, signatures?: Array); - serialize(): Uint8Array; - static deserialize(serializedTransaction: Uint8Array): VersionedTransaction; - sign(signers: Array): void; - addSignature(publicKey: PublicKey, signature: Uint8Array): void; -} -``` - -新的 `VersionedTransaction` 对象,通过传入 `VersionedMessage` 来构造: - -```ts -constructor(message: VersionedMessage, signatures?: Array); -``` - -这里我们上面构造的 `V0` 就是 `VersionedMessage` 的对象。 - -这里可以传入 `signatures`,比如通过硬件钱包签名的内容。或者不传入也可以,调用: - -```ts -sign(signers: Array): void; -``` - -传入我们上面的 `keypair`。也可以对 `VersionedTransaction` 进行签名。 - -构造结束后,通过 `connection` 的 `sendTransaction` 方法发送即可: - -```ts -sendTransaction(transaction: VersionedTransaction, options?: SendOptions): Promise; -``` - -这里返回的 `TransactionSignature` 即为,交易的 `hash`,可以通过浏览器进行查询。 - -## Demo - -[web3 ui demo](https://www.solanazh.com/assets/files/web3-ui-demo.zip) diff --git a/other_docs/Solana-Bootcamp-zh/week4/README.md b/other_docs/Solana-Bootcamp-zh/week4/README.md deleted file mode 100644 index 89e7b0f1a..000000000 --- a/other_docs/Solana-Bootcamp-zh/week4/README.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -sidebar_position: 127 -sidebar_label: Week4 Rust基本知识 -sidebar_class_name: green ---- - -# Week4: Rust基本知识 - -- [Hello World](./hello-world.md) -- [Rust基本语法](./rust-basic.md) -- [通过Cargo管理工程](./cargo.md) -- [Rustaceans的理解](./rustaceans-understand.md) -- [课后练习](./homework.md) diff --git a/other_docs/Solana-Bootcamp-zh/week4/cargo.md b/other_docs/Solana-Bootcamp-zh/week4/cargo.md deleted file mode 100644 index a49eba8fa..000000000 --- a/other_docs/Solana-Bootcamp-zh/week4/cargo.md +++ /dev/null @@ -1,366 +0,0 @@ ---- -sidebar_position: 130 -sidebar_label: 通过Cargo管理工程 -sidebar_class_name: green ---- - -# 通过Cargo管理工程 - -cargo作为rust的工程管理工具。类似go语言的gomod。其主要通过 `Cargo.toml`作为配置文件, 配合cargo 二进制工具来管理依赖。 - -cargo在通过`rustup`安装rust的过程中已经安装好了。`Cargo.toml`在通过`cargo new` 创建工程的时候,自动生成。 - -在构建的时候,cargo 会根据`Cargo.toml`中的依赖,自动拉取依赖的代码,并将相应的版本信息,资源签名,记录在 `Cargo.lock`文件中,该文件类似`go.sum`文件,记录了各个依赖的`meta`信息。 - -## cargo命令 - -### 创建工程 - -首先通过cargo命令,可以创建工程。创建的工程分成两类,一类是库,一类是二进制可执行程序。 - -通过`cargo new project_name` 命令可以创建`project_name`的工程。默认工程是一个可执行程序。 - -通过指定`--lib`可以指定其为库项目。一个工程,只能包含一个库目标,但是可以包含多个二进制程序。 - -### 添加依赖 - -当需要依赖外部库的时候,首先要将其加入到工程中: - -```bash -$ cargo add [options] crate… -$ cargo add [options] --path path -$ cargo add [options] --git url [crate…] -``` - -三种不同的参数,可以针对三种情况的依赖。 - -- 直接跟库名,会去`cargo.io`上索引,找到最新的版本 -- `--path`指定库在本地的路径,可以对本地目录进行依赖 -- `--git` 则指定的git仓库的路径,比如是私有的git仓库 - -通过 `cargo remove` 可以移除相关的依赖。 - -### 构建 & 执行 - -前面已经接触了构建。直接用`build`就可以了: - -```bash -$ cargo build [options] -``` - -这里有几个参数 - -- `--workspace`: 构建整个`workspace`里面的目标 -- `--lib`: 构建库目标 -- `--bin name…`: 只构建指定的可执行文件 -- `--example name…`: 只构建指定的`example` -- `--test name…`: 构建指定的`test` -- `--release`: 采用`relase`构建 - -而通过: - -```bash -$ cargo clean [options] -``` - -则可以清除构建结果 - -执行通过`run`命令来发起: - -```bash -$ cargo run [options] [-- args] -``` - -其中如果是传递给cargo的`flag`直接传入。如果要传递给被执行的程序。则需要使用 "`--`" 做分割。其后的 `flag`才是传递给要运行的程序的。 - -- `--bin name…`: 只执行指定的可执行文件 -- `--example name…`: 只执行指定的`example` - -```bash -$ cargo run --bin helloworld -Finished dev [unoptimized + debuginfo] target(s) in 0.04s -Running `target/debug/helloworld` -Please use ./hellowolrd name. -``` - -没有携带参数。 - -如果是这样: - -```bash -$ cargo run --bin helloworld -l -error: unexpected argument '-l' found - -tip: to pass '-l' as a value, use '-- -l' - -Usage: cargo run [OPTIONS] [args]... - -For more information, try '--help'. -``` - -这里实际上是把`-l`传递给了`cargo run` ,但是`cargo run`本身是不接受"`-l`"的`flag`的。所以这里报错了。 - -```bash -cargo run --bin helloworld -- -l - Finished dev [unoptimized + debuginfo] target(s) in 0.00s - Running `target/debug/helloworld -l` -Hello -l -``` - -通过"`--`"的分割,我们将`flag`参数跳过`cargo run`传递给可执行程序。 - -更多其他参数可以参考 [The Cargo Book](https://doc.rust-lang.org/cargo/) - -## Cargo.toml结构 - -每个`Cargo.toml`包含如下内容: - -- `[cargo-features]` --- Unstable, nightly-only features. -- `[[package]]` --- Defines a package. - - `[name]` --- The name of the package. - - `[version]` --- The version of the package. - - `[authors]` --- The authors of the package. - - `[edition]` --- The Rust edition. - - `[rust-version]` --- The minimal supported Rust version. - - `[description]` --- A description of the package. - - `[documentation]` --- URL of the package documentation. - - `[readme]` --- Path to the package's README file. - - `[homepage]` --- URL of the package homepage. - - `[repository]` --- URL of the package source repository. - - `[license]` --- The package license. - - `[license-file]` --- Path to the text of the license. - - `[keywords]` --- Keywords for the package. - - `[categories]` --- Categories of the package. - - `[workspace]` --- Path to the workspace for the package. - - `[build]` --- Path to the package build script. - - `[links]` --- Name of the native library the package links with. - - `[exclude]` --- Files to exclude when publishing. - - `[include]` --- Files to include when publishing. - - `[publish]` --- Can be used to prevent publishing the package. - - `[metadata]` --- Extra settings for external tools. - - `[default-run]` --- The default binary to run by [cargo run]. - - `[autobins]` --- Disables binary auto discovery. - - `[autoexamples]` --- Disables example auto discovery. - - `[autotests]` --- Disables test auto discovery. - - `[autobenches]` --- Disables bench auto discovery. - - `[resolver]` --- Sets the dependency resolver to use. -- Target tables: - - `[[lib]]` --- Library target settings. - - `[[[bin]]]` --- Binary target settings. - - `[[[example]]]` --- Example target settings. - - `[[[test]]]` --- Test target settings. - - `[[[bench]]]` --- Benchmark target settings. -- Dependency tables: - - `[[dependencies]]` --- Package library dependencies. - - `[[dev-dependencies]]` --- Dependencies for examples, tests, and benchmarks. -- `[[build-dependencies]]` --- Dependencies for build scripts. -- `[[target]]` --- Platform-specific dependencies. -- `[[badges]]` --- Badges to display on a registry. -- `[[features]]` --- Conditional compilation features. -- `[[patch]]` --- Override dependencies. -- `[[replace]]` --- Override dependencies . -- `[[profile]]` --- Compiler settings and optimizations. -- `[[workspace]]` --- The workspace definition. - -整个的完整的内容会比较多,普通情况下只需要使用默认生成的文件,然后在里面填充dependence即可。 - -作为实践,一般将`Cargo.toml`分成两类。对于一个大repo,会将所有的代码放在一个目录下面,通过一个包含`workspace` 的`Cargo.toml`来管理其他自`Cargo.toml`。类似Makefile的嵌套管理。 - -比如solana工程的: - -```toml -[workspace] - members = [ - "account-decoder", - "accounts-bench", - ... - ] - - - exclude = [ - "programs/sbf", - ] - - # This prevents a Travis CI error when building for Windows. - resolver = "2" - - [workspace.package] - version = "1.17.0" - authors = ["Solana Labs Maintainers "] - repository = "https://github.com/solana-labs/solana" - homepage = "https://solanalabs.com/" - license = "Apache-2.0" - edition = "2021" - - [workspace.dependencies] - aes-gcm-siv = "0.10.3" - ahash = "0.8.3" - - ... -``` -这里能看到,主要结构就是通过`workspace.members`来指定了子目录。 `exclude`指定不要的目录。`workspace.dependencies`指定了整个工作 区要依赖的库。 - -另外一种就是具体的执行程序或者库的目录,也就是`workspace`管理的具体子目录,这里比如solana cli的目录: - -```toml -[package] - name = "solana-cli" - description = "Blockchain, Rebuilt for Scale" - documentation = "https://docs.rs/solana-cli" - version = { workspace = true } - authors = { workspace = true } - repository = { workspace = true } - homepage = { workspace = true } - license = { workspace = true } - edition = { workspace = true } - - [dependencies] - bincode = { workspace = true } - bs58 = { workspace = true } - ... - - [dev-dependencies] - solana-streamer = { workspace = true } - solana-test-validator = { workspace = true } - tempfile = { workspace = true } - - [[bin]] - name = "solana" - path = "src/main.rs" - - [package.metadata.docs.rs] - targets = ["x86_64-unknown-linux-gnu"] -``` - -这里`package`下面的键指定了库的属性,比如名字,描述。而`dependencies`指定了依赖, 其中 `{ workspace = true } `表示其继承`workspace`父目录中的 相关位置版本的信息。 - -最后这通过`[[bin]]`定义了这里有个可执行程序叫: "solana" - -## 工程目录结构 - -我们来看一个相对复杂的cargo工程目录: - -```bash -├── Cargo.lock -├── Cargo.toml -├── examples -│ ├── example01.rs -│ └── example_files -│ ├── func.rs -│ └── main.rs -├── src -│ ├── bin -│ │ ├── bin1.rs -│ │ └── bin2.rs -│ └── lib.rs -└── tests - ├── test01.rs - └── test_files - ├── func.rs - └── main.rs -``` - -在这个`demo`里面,我们主要包含了 三个目录: - -- `src`: 库和二进制文件 -- `example`: 例子 -- `tests`: 集成测试 - -## 可执行程序 - -可执行程序,可以将其放入`src/bin`目录下。每个文件可以有自己单独的`main`函数。比如这里: - -```rust -// bin1.rs: -use cargodir::lib_func; - - -fn main() { - lib_func(); - println!("it is bin1"); -} -``` - -使用到的库函数在`lib.rs`中定义: - -```rust -pub fn lib_func() { - println!("lib_func"); -} -``` - -但是在可执行程序文件中,通过`use`来包含,然后在`main`函数中调用。 - -如果不是按照`src/bin`目录来组织代码的,需要在`Cargo.toml`中进行指定,比如用cli目录: - -```toml -[[bin]] - name = "bin1" - path = "src/cli/bin1.rs" - - [[bin]] - name = "bin2" - path = "src/cli/bin2.rs" -``` - -这样就可以通过--bin来指定要执行哪个name的可执行程序了: - -```bash -cargo run --bin bin1 - Finished dev [unoptimized + debuginfo] target(s) in 0.00s - Running `target/debug/bin1` -lib_func -it is bin1 -``` - -## 例子程序 -示例程序可以通`examples`目录来管理。其中可以是单个文件,也可以用一个目录来组织多个文件。单个文件和目录中都可以实现 `main`函数作为示例程序的入口: - -比如`example01.rs`: - -```rust -fn main() { - println!("it is example 01 "); -} -``` - -这样只要执行: - -```bash -cargo run --example example01 -Compiling cargodir v0.1.0 (Solana-Asia-Summer-2023/s101/Solana-Rust/demo/cargodir) - Finished dev [unoptimized + debuginfo] target(s) in 0.20s - Running `target/debug/examples/example01` -it is example 01 -``` - -## 集成测试程序 - -单元测试是放在实现文件中的,如果有集成测试,则可以类似例子一样,组织在`tests`目录中。一样可以单个文件或者多个文件放在一个目录中。 - -比如:`test01.rs`: - -```rust -#[test] - fn test_main() { - println!("it is test 01"); - } -``` - -运行: - -```bash -cargo test --test test01 - Compiling cargodir v0.1.0 (Solana-Asia-Summer-2023/s101/Solana-Rust/demo/cargodir) - Finished test [unoptimized + debuginfo] target(s) in 0.22s - Running tests/test01.rs (target/debug/deps/test01-de791c18df3f4346) - - running 1 test - test test_main ... ok - - test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -``` - -## 参考 - -[The Cargo Book](https://doc.rust-lang.org/cargo/) diff --git a/other_docs/Solana-Bootcamp-zh/week4/hello-world.md b/other_docs/Solana-Bootcamp-zh/week4/hello-world.md deleted file mode 100644 index 1b1a34bcf..000000000 --- a/other_docs/Solana-Bootcamp-zh/week4/hello-world.md +++ /dev/null @@ -1,185 +0,0 @@ ---- -sidebar_position: 128 -sidebar_label: Hello World -sidebar_class_name: green ---- - -# HelloWorld - -Rust没隔6个星期,就会发布一个小版本。在Rust历史中又有三个大版本的差别。这里不是传统的x.y.z里面的版本号。 - -Rust以3年为一个界限,发布一个大版本,新特性可能不兼容之前版本。每个版本叫做一个edition。有点类似C98/C11/C14/C17 的感觉。目前Rust主要有2015/2018/2021三个Edition。 - -## Rust环境安装 - -通过`rustup`工具,可以帮助我们一键安装rust开发环境: - -```bash -$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - -Current installation options: - - -default host triple: x86_64-apple-darwin - default toolchain: stable (default) - profile: default -modify PATH variable: yes - -1) Proceed with installation (default) -2) Customize installation -3) Cancel installation - -直接Enter采用默认方式 - -Rust is installed now. Great! - -To get started you may need to restart your current shell. -This would reload your PATH environment variable to include -Cargo's bin directory ($HOME/.cargo/bin). - -To configure your current shell, run: -source "$HOME/.cargo/env" -``` - -这里执行下环境导入 - -```bash -$ source "$HOME/.cargo/env" -``` - -然后查看rust版本: - -```bash -$ rustc --version -rustc 1.70.0 (90c541806 2023-05-31) -``` - -为当前最新的1.70.0。 - -Rust官方针对不同的IDE都有开发相关插件。比如Vim/Emacs,这里比较推荐实用VS Code。在VS Code界面安装 rust-analyzer插件。 - -这样即可舒服的写rust代码了。 - -这里对于使用mac的用户来说我推荐使用zed编辑器,这里打开zed编辑器,他会自动下载rust-analyzer。 - -## 创建工程 - -创建一个demo目录,用于放工程文件,然后在这个目录中: - -```bash -$ cargo new --bin helloworld - Created binary (application) `helloworld` package -``` - -即可创建好项目。项目目录为: - -```bash -. -└── helloworld - ├── Cargo.toml - └── src - └── main.rs -``` - -然后用VScode打开工程,并打开这里的`main.rs`。 - -```rust -fn main() { - println!("Hello, world!"); -} -``` - -创建工程到时候,已经默认生成了打印"Hello, world!"。我们修改成: - -```rust -use std::env; - -fn main() { - let name = env::args().skip(1).next(); - match name { - Some(n) => println!("Hello {}", n), - None => println!("Please use ./hellowolrd name.") - } -} -``` - -通过`cargo`可以构建: - -```bash -$ cargo build -Compiling helloworld v0.1.0 (Solana-Asia-Summer-2023/s101/Solana-Rust/demo/helloworld) - Finished dev [unoptimized + debuginfo] target(s) in 0.99s -``` - -构建完成后,可执行二进制在: - -```bash -$ ./target/debug/helloworld abc -Hello abc -``` - -输入后可以直接执行,得到结果。 - -也可以直接通过cargo 来执行: - -```bash -$ cargo run abc -Finished dev [unoptimized + debuginfo] target(s) in 0.00s -Running `target/debug/helloworld` -Hello abc -``` - -到这里就完成了HelloWorld过程了。 - -## Rust代码结构 - -上面我们对模板代码做了简单修改。模板代码过于简单。所以我们加入了命令行参数并增加了一个其他语言中没有的关键字 `match`。 - -来看代码开头: - -```rust -use std::env; -``` - -当要使用其他库的时候,首先用`use`来进行导入。这个有点类似其他语言的`import/include`等。 - -然后是main函数: - -```rust -fn main() { - ... -} -``` - -函数的定义是`fn` 开头,然后跟函数名,以及用`()`括起来的参数列表,最后是返回值类型,这里因为没有返回值,所以是空。再来看个函数定义: - -```rust -// Function that returns a boolean value -fn is_divisible_by(lhs: u32, rhs: u32) -> bool { - // Corner case, early return - if rhs == 0 { - return false; - } - - // This is an expression, the `return` keyword is not necessary here - lhs % rhs == 0 -} -``` - -首先用fn表示函数定义。然后函数名为"`is_divisible_by`",参数为:`lhs: u32`, `rhs: u32` 这里又和一些语言不一样了。 格式为 参数名 : 类型 。最后通过 -> 分割返回值类型。 - -这里在函数定义逻辑为: - -```rust -// Corner case, early return -if rhs == 0 { - return false; -} - -// This is an expression, the `return` keyword is not necessary here -lhs % rhs == 0 -``` - -这里和普通语言又有点不一样。 对于返回值,可以显示的调用`reutrn : return false;` - -也可以通过表达式来实现,表达式不要以"`;`"结尾: `lhs % rhs == 0` 表达式的结果,作为返回值,不需要加"`return`"。 diff --git a/other_docs/Solana-Bootcamp-zh/week4/homework.md b/other_docs/Solana-Bootcamp-zh/week4/homework.md deleted file mode 100644 index 529a07840..000000000 --- a/other_docs/Solana-Bootcamp-zh/week4/homework.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -sidebar_position: 132 -sidebar_label: 课后练习 -sidebar_class_name: green ---- - -# 课后练习 - -课后练习 -附件中的工程是一个"Tic-Tac-Toe"游戏。 - -``` -X, select a space - 1 | 2 | 3 --------------- - 4 | 5 | 6 --------------- - 7 | 8 | 9 - ``` - -运行后是这样的一个棋盘。两个玩家依次落子。先排成“横”,“竖”,“斜”一条线的赢。 - -代码中其他文件忽略,只关注`game.rs/board.rs` 这两个文件。里面有“TODO”提示。 在提示的地方填充函数内容。 - -最后运行`cargo test` 提示测试通过: - -```bash -running 27 tests -test board::tests::a_space_can_only_be_taken_once ... ok -test board::tests::finds_available_spaces_in_full_board ... ok -test board::tests::o_plays_next ... ok -test board::tests::finds_available_spaces_in_empty_board ... ok -test board::tests::a_space_above_the_board_cant_be_chosen ... ok -test board::tests::a_negative_space_cant_be_chosen ... ok -test board::tests::finds_available_spaces_in_an_in_progress_board ... ok -test board::tests::starts_with_no_moves ... ok -test board::tests::x_plays_first ... ok -test board::tests::takes_a_number_of_rows ... ok -test game::tests::a_tied_game_is_tied ... ok -test game::tests::a_won_game_is_not_tied ... ok -test game::tests::a_won_game_with_a_full_board_is_not_tied ... ok -test game::tests::an_empty_game_is_not_tied ... ok -test game::tests::an_empty_game_is_not_won ... ok -test game::tests::check_if_game_won_by_x_is_won ... ok -test game::tests::check_if_game_won_by_o_is_won ... ok -test game::tests::check_line_won_by_x ... ok -test game::tests::check_if_tied_game_is_won ... ok -test game::tests::check_row_not_won_by_o ... ok -test game::tests::find_winner_when_nobody_has_won ... ok -test game::tests::find_winner_when_o_has_won ... ok -test game::tests::find_winner_when_x_has_won ... ok -test game::tests::game_is_over_when_board_is_full ... ok -test game::tests::o_is_current_player_after_one_move ... ok -test game::tests::game_not_over_when_board_is_empty ... ok -test game::tests::x_is_current_player_at_start_of_game ... ok - -test result: ok. 27 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -``` - -`cargo run`可以正常游戏 - -[练习工程](https://www.solanazh.com/assets/files/tic-tac-toe.zip) diff --git a/other_docs/Solana-Bootcamp-zh/week4/rust-basic.md b/other_docs/Solana-Bootcamp-zh/week4/rust-basic.md deleted file mode 100644 index a6f96f2a0..000000000 --- a/other_docs/Solana-Bootcamp-zh/week4/rust-basic.md +++ /dev/null @@ -1,946 +0,0 @@ ---- -sidebar_position: 129 -sidebar_label: Rust基本语法 -sidebar_class_name: green ---- - -# Rust基本语法 - -## 变量和类型 - -### 基本数据类型 - -基本数据类型主要有整形、浮点、布尔以及字符类型。 - -整形按照所占空间大小被分为1、2、4、8、16 字节大小的整数。每个大小又有有符号和无符号的差别。 具体的定义如下: - -| Length | Signed | Unsigned | -| --- | --- | ---- | -| 8-bit | i8 | u8 | -| 16-bit | i16 | u16 | -| 32-bit | i32 | u32 | -| 64-bit | i64 | u64 | -| 128-bit | i128 | u128 | -| arch | size | usize | - -而浮点型包括`f32`和`f64`两个分别使用4字节和8字节的IEEE-754 浮点格式的浮点数。 - -布尔类型和其他语言的布尔类型类似,用`true`和`false`来表示。 - -字符类型是用''单引号括起来的字符。rust天生支持utf-8,所以任何单引号括起来的utf-8字符都是合法的字符类型变量。 - -### 复合类型 - - -复合类型是基本类型和复合类型的组合,典型的有 - -元组: - -```rust -let tup: (i32, f64, u8) = (500, 6.4, 1); -``` - -单纯的把几个类型放在一起。访问的时候通过下标索引来访问 比如 这里的`500:tup.0` - -数组: - -```rust -let arr = [1, 2, 3, 4, 5]; -``` - -和元组不通的是,这里每个元素的类型必须是同样的。访问的时候,下标用中括号表示: `arr[0]` - -```rust -struct User { - active: bool, - username: String, - email: String, - sign_in_count: u64, -} - -let user = User { - email: String::from("someone@example.com"), - username: String::from("someusername123"), - active: true, - sign_in_count: 1, -}; -``` - -`struct`类似C语言里的`struct`,将多个类型组合在一起,通过成员名进行访问:`user.email` - -### 变量 - -变量定义为: - -```rust -let x = 5; -``` - -这里x的类型是由系统推到而来的。也可以显示指定类型 - -```rust -let x:u32 = 5; -``` - -这里`x`被赋值为5后,区别与其他语言的点。变量默认是不可以修改的,也就是: - -```rust -let x = 5; -x=6; -``` - -会导致报错: - -```bash -error[E0384]: cannot assign twice to immutable variable `x` - --> src/main.rs:4:5 - | - 2 | let x = 5; - | - - | | - | first assignment to `x` - | help: consider making this binding mutable: `mut x` - 3 | println!("The value of x is: {x}"); - 4 | x = 6; - | ^^^^^ cannot assign twice to immutable variable -``` - -但是可以用如下形式: - -```rust -let x = 5; -let x=6; -``` - -甚至如下形式: - -```rust -let x = 5; -let x="6"; -``` - -这里,第二个`let`相当于重新定义一个变量,可以重新定义其类型。 - -如果要修改变量,可以这样定义: - -```rust -let mut x = 5; -x=5; -``` - - -## 语句和表达式 - -语句是指执行一段逻辑动作的代码,比如`if`语句,`while`语句。而表达式,是可以得到结果值的代码。比如1+1。 虽然表达式也可以执行逻辑,但是区别是表达式可以作为返回值,或者别的变量的赋值。而语句不行。 - -### let表达式 - -let 主要用于变量的定义: - -```rust -let condition = true; -``` - -let还可以和if组合: - -```rust -let number = if condition { 5 } else { "six" }; -``` - -这里if语句里面的值类似返回值。 - -### match表达式 - -match可以做类型匹配和解包: - -```rust -let config_max = Some(3u8); -match config_max { - Some(max) => println!("The maximum is configured to be {}", max), - _ => (), -} -``` - -### 条件 语句 - -`if`和Go语言的`if`比较类似,都是去除了传统语言里面的括号 - -```rust -let number = 3; - -if number < 5 { - println!("only if"); -} - -if number < 5 { - println!("if else: condition was true"); -} else { - println!("if else: condition was false"); -} - -if number < 5 { - println!("if else if : number < 5 "); -} else if number == 5 { - println!("if else if : number == 5 "); -} else{ - println!("if else if : number > 5 "); -} -``` - -### 循环语句 - -rust给无限循环增加了一个`loop`,约等于 `while 1`: - -```rust -loop { - println!("again and again!"); -} -``` - -但是`loop`可以通过`break`来返回一个结果: - -```rust -let mut counter = 0; - -let result = loop { - counter += 1; - - if counter == 10 { - break counter * 2; - } -}; - -println!("The result is {result}"); -``` - -在循环中,可以通过类似`goto`的label定义,`break`到对应层级: - -```rust -let mut count = 0; -'counting_up: loop { - println!("count = {count}"); - let mut remaining = 10; - - loop { - println!("remaining = {remaining}"); - if remaining == 9 { - break; - } - if count == 2 { - break 'counting_up; - } - remaining -= 1; - } - - count += 1; -} -println!("End count = {count}"); -``` - -除了`loop`,常见的`while`也是有的: - -```rust -while true { - println!("again and again!"); -} -``` - -以及`for`语句,不同于c-like语言,`for`语句是迭代器风格的,而不是两个";"三语句模式。 - - -```rust -let a = [10, 20, 30, 40, 50]; - -for element in a { - println!("the value is: {element}"); -} - -// 模拟 int i=1; i<4; i++ - for number in (1..4).rev() { - println!("{number}!"); -} -``` - -## 函数 - -函数分为`main`函数和普通函数。`main`函数是可执行程序的入口函数。对于库是不需要的。`main`函数本身也是个普通函数 只是函数名为`main`。 - -```rust -fn main() { - println!("Hello, world!"); - - another_function(); -} - -fn another_function() { - println!("Another function."); -} -``` - -上面这个是不带参数,没有返回值的函数的最基本定义结构。首先用 fn开始,然后跟函数名以及`()`。最后用`{}`,括起来 的函数逻辑。 - -```rust -fn main() { - print_labeled_measurement(5, 'h'); -} - -fn print_labeled_measurement(value: i32, unit_label: char) { - println!("The measurement is: {value}{unit_label}"); -} -``` - -带参数的函数,在`()`中定义参数,参数为参数名:类型这样的格式。 - -带返回的函数如: - -```rust -fn main() { - let x = plus_one(5); - - println!("The value of x is: {x}"); -} - -fn plus_one(x: i32) -> i32 { - x + 1 -} -``` - -在参数`()`和函数体`{}`中间用-> 分割,罗列返回值类型。 - -这里体现了语句和表达式的区别。语句是执行一个动作,不具有可以作为直的结果。而表达式是可以作为值的结果的。 因此这里表达式`x+1`,作为值,直接进行范围。注意,此时后面不可以加;。 - -## `struct`和`enum` - -### `struct` - -定义结构体如下: - -```rust -struct User { - active: bool, - username: String, - email: String, - sign_in_count: u64, -} -``` - -结构为`struct`关键字加上大写开头的类型名,后面跟`{}`包裹的成员变量,每个成员变量为成员名:类型,。 - -`struct`的初始化: - -```rust -let user1 = User { - email: String::from("someone@example.com"), - username: String::from("someusername123"), - active: true, - sign_in_count: 1, -}; -``` - -如果用其他`struct`来初始化同类型时,可以用: - -```rust -let user2 = User { - email: String::from("another@example.com"), - ..user1 -}; -``` - -有一种特殊的`struct,元组`struct`: - -```rust -struct Color(i32, i32, i32); -struct Point(i32, i32, i32); - -fn main() { - let black = Color(0, 0, 0); - let origin = Point(0, 0, 0); - - println!("black 0:{}", black.0) -} -``` - -还有种更特殊的`struct`: - -```rust -struct AlwaysEqual; - -fn main() { - let subject = AlwaysEqual; -} -``` - -他的值可以是`{}`,所以在代码中看到`{}`就可以认为是一个不占空间的struct的值,比如: - -```rust -Ok({}) -``` - -### `enum` - -rust中的`enum`比任何其他语言的都强大。 - -简单版本: - -```rust -enum IpAddrKind { - V4, - V6, -} -``` - -这个容易理解,使用的时候就是 `let four = IpAddrKind::V4;``````````````````````。 - -指定类型版本: - -```rust -enum IpAddr { - V4(String), - V6(String), -} -``` - -使用的时候:`let home = IpAddr::V4(String::from("127.0.0.1"));` 这就有点不像传统`enum`了。更像一个`struct`定义。 这里"V4","V6"同样的类型,还不直观。 - -```rust -struct Ipv4Addr { - // --snip-- -} - -struct Ipv6Addr { - // --snip-- -} - -enum IpAddr { - V4(Ipv4Addr), - V6(Ipv6Addr), -} -``` - -`V4`是一个类型,`V6是一个类型。这个时候`enum`更像`union`。也还好理解。 - -来个不容易看的: - -```rust -enum IpAddr { - V4(u8, u8, u8, u8), - V6(String), -} - -let home = IpAddr::V4(127, 0, 0, 1); -``` - -这里`V4`变成了一个元组类型。虽然可读性差一点,但是写法方便。 - -最复杂的: - -```rust -enum Message { - Quit, - Move { x: i32, y: i32 }, - Write(String), - ChangeColor(i32, i32, i32), -} -``` - -`Message`定义了一个游戏的消息指令,这里在传统语言如C++中,可能要定义个`Message`基类,然后每个命令消息再去定义子类。 - -而这里一个`enum`搞定。这个时候,这个`enum`真不好说他是什么功能。 - -### 给`struct/enum`定义方法 - - -给struct定义方法: - -```rust -#[derive(Debug)] -struct Rectangle { - width: u32, - height: u32, -} - -impl Rectangle { - fn area(&self) -> u32 { - self.width * self.height - } -} - -fn main() { - let rect1 = Rectangle { - width: 30, - height: 50, - }; - - println!( - "The area of the rectangle is {} square pixels.", - rect1.area() - ); -} -``` - -使用`impl Xxx {}` 语法,在`{}`的方法,就是成员方法。 - -其中不带(`&self`)参数的是类方法,第一个参数为(`&self`)的为类方法。这个类似python2。 - -给`enum`定义也是一样,用`impl Xxx {}` - -```rust -impl Message { - fn call(&self) { - // method body would be defined here - } -} - -let m = Message::Write(String::from("hello")); -m.call(); -``` - -枚举的强大还体现在他的使用上,再不需要上面的类继承来做反射: - -```rust -pub enum NameRegistryInstruction { -Create { - hashed_name: Vec, - lamports: u64, - space: u32, -}, - -Update { offset: u32, data: Vec }, - -Transfer { new_owner: Pubkey }, - -Delete, - -Realloc { - space: u32, -}, -} - -match instruction { - NameRegistryInstruction::Create { - hashed_name, - lamports, - space, - } => { - msg!("Instruction: Create"); - Processor::process_create(program_id, accounts, hashed_name, lamports, space)?; - } - NameRegistryInstruction::Update { offset, data } => { - msg!("Instruction: Update Data"); - Processor::process_update(accounts, offset, data)?; - } - NameRegistryInstruction::Transfer { new_owner } => { - msg!("Instruction: Transfer Ownership"); - Processor::process_transfer(accounts, new_owner)?; - } - NameRegistryInstruction::Delete => { - msg!("Instruction: Delete Name"); - Processor::process_delete(accounts)?; - } - NameRegistryInstruction::Realloc { space } => { - msg!("Instruction: Realloc Name Record"); - Processor::process_realloc(accounts, space)?; - } -} -``` - -通过`match`语句,首先能对`enum`做类型匹配,匹配的同时,还可以对类型做解包, 如`NameRegistryInstruction::Create` - -```rust -{ - hashed_name, - lamports, - space, -} -``` - -这里其实是一个没有给名字的`struct`。 - -## 容器 - -### list/vector - -```rust -Vec -``` - -`vec`是`std`提供的链表类型。可以用来存放相同类型的数组。 - -创建指定类型的`vector`: - -```rust -let v: Vec = Vec::new(); -``` - -也可以通过`vec!`宏直接赋值初始化: - -```rust -let v = vec![1, 2, 3]; -``` - -增加元素: - -```rust -let mut v = Vec::new(); - -v.push(5); -v.push(6); -v.push(7); -v.push(8); -``` - - -这里`mut`表示`vector`是可以做增删修改的。 - -通过`remove`进行删除元素: - -```rust -let mut v = vec![1, 2, 3]; -assert_eq!(v.remove(1), 2); -assert_eq!(v, [1, 3]); -``` - -可以通过`get`给定下标,获取`vector`的元素,通过`Option`来做判断是否存在: - -```rust -let v = vec![1, 2, 3, 4, 5]; - -let third: &i32 = &v[2]; -println!("The third element is {third}"); - -let third: Option<&i32> = v.get(2); -match third { - Some(third) => println!("The third element is {third}"), - None => println!("There is no third element."), -} -``` - -通过迭代器,遍历: - -```rust -let v = vec![100, 32, 57]; -for n_ref in &v { - // n_ref has type &i32 - let n_plus_one: i32 = *n_ref + 1; - println!("{n_plus_one}"); -} -``` - -上面这个是只读的,如果需要修改,使用: - -```rust -let mut v = vec![100, 32, 57]; -for n_ref in &mut v { - // n_ref has type &mut i32 - *n_ref += 50; -} -``` - -### `String` - -rust的`String`不是基础类型,是由`std`提供的类型。创建字符串可以用: - -```rust -let mut s = String::new(); - -let data = "initial contents"; -let s = data.to_string(); - - -let s = String::from("initial contents"); -``` - -三种方法。 - -修改字符串: - -```rust -let mut s = String::from("foo"); -s.push_str("bar"); - -let mut s1 = String::from("foo"); -let s2 = "bar"; -s1.push_str(s2); -println!("s2 is {s2}"); - -let mut s = String::from("lo"); -s.push('l'); - -let s1 = String::from("Hello, "); -let s2 = String::from("world!"); -let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used -``` - -可以使用`push_str`、`push`甚至是"`+`"来修改字符串。注意"`+`"会`borrow`变量。当然要修改字符串前提是字符串是"`mut`"的。 - -可以通过下标操作。或者字符串中的字符: - -```rust -let s1 = String::from("hello"); - let h = s1[0]; -``` - -### Map - - -rust中的`map`也不是基础类型,而是`std`提供的,并且也和一般语言中不一样,类似Java里面明确的命名为"`HashMap`" - -创建: - -```rust -use std::collections::HashMap; - -let mut scores = HashMap::new(); - -scores.insert(String::from("Blue"), 10); -scores.insert(String::from("Yellow"), 50); -``` - -访问: - -```rust -use std::collections::HashMap; - -let mut scores = HashMap::new(); - -scores.insert(String::from("Blue"), 10); -scores.insert(String::from("Yellow"), 50); - -let team_name = String::from("Blue"); -let score = scores.get(&team_name).copied().unwrap_or(0); -``` - -遍历: - -```rust -use std::collections::HashMap; - -let mut scores = HashMap::new(); - -scores.insert(String::from("Blue"), 10); -scores.insert(String::from("Yellow"), 50); - -for (key, value) in &scores { - println!("{key}: {value}"); -} -``` - -插入修改: - -```rust -use std::collections::HashMap; - -let mut scores = HashMap::new(); - -scores.insert(String::from("Blue"), 10); -scores.insert(String::from("Blue"), 25); - -println!("{:?}", scores); -``` - -判断是否存在,存在才插入: - -```rust -use std::collections::HashMap; - -let mut scores = HashMap::new(); -scores.insert(String::from("Blue"), 10); - -scores.entry(String::from("Yellow")).or_insert(50); -scores.entry(String::from("Blue")).or_insert(50); - -println!("{:?}", scores); -``` - -## `trait` - -`trait` 类似其他语言中的接口,但是就好比`enum`一样,他不仅类似其他语言的接口,他更强大。 更复杂。很多继承的特性都是通过 `trait` 来实现。 - -`trait` 的定义类似`struct`: - -```rust -pub trait Summary { - fn summarize(&self) -> String; -} -``` - -`trait`主要是定义方法。这里方法就是个普通的函数定义。但是去掉了参数名。 - -`trait`只是定义了接口方法,而具体实现需要再`struct`中实现: - -```rust -pub struct NewsArticle { - pub headline: String, - pub location: String, - pub author: String, - pub content: String, -} - -impl Summary for NewsArticle { - fn summarize(&self) -> String { - format!("{}, by {} ({})", self.headline, self.author, self.location) - } -} - -pub struct Tweet { - pub username: String, - pub content: String, - pub reply: bool, - pub retweet: bool, -} - -impl Summary for Tweet { - fn summarize(&self) -> String { - format!("{}: {}", self.username, self.content) - } -} -``` - - -使用`impl`关键字,跟要实现的`trait`名加上"`for Xxx`"给具体的`struct`视线。 - -这里还可以再`trait`中给出默认实现: - -```rust -pub trait Summary { - fn summarize(&self) -> String { - String::from("(Read more...)") - } -} -``` - -这样,在"`impl`"里面没有"`summarize`"方法实现时,就默认用`trait`里面的定义。 - -`trait`的作用主要要结合泛型限制,和参数专递才能体现。比如类似其他语言中基本的OOP的动态: - -```rust -pub fn notify(item: &impl Summary) { - println!("Breaking news! {}", item.summarize()); -} -``` - -这里将参数声明为`trait`,任何实现了这个`trait`的类型都可以传递进来。具体的`summarize`是 传入进来的类型的实现。 - -除了可以限制参数,还可以限制返回值。这个就有点类似其他语言的基类、接口。 - -```rust -fn returns_summarizable() -> impl Summary { - Tweet { - username: String::from("horse_ebooks"), - content: String::from( - "of course, as you probably already know, people", - ), - reply: false, - retweet: false, - } -} -``` - -和泛型的结合,见下一章。 - -## 泛型 - -泛型是rust最强的地方,也是rust最难的地方,更是学习rust的拦路虎。 - -来看个定义: - -```rust -pub struct Iter<'a, K, V> { - db_iter: rocksdb::DBIterator<'a>, - _phantom: PhantomData<(K, V)>, -} - -impl<'a, K: DeserializeOwned, V: DeserializeOwned> Iter<'a, K, V> { - pub(super) fn new(db_iter: rocksdb::DBIterator<'a>) -> Self { - Self { - db_iter, - _phantom: PhantomData, - } - } -} - -impl<'a, K: DeserializeOwned, V: DeserializeOwned> Iterator for Iter<'a, K, V> { - type Item = (K, V); - - fn next(&mut self) -> Option { - let (key, value) = self.db_iter.next()?; - let key = bincode::deserialize(&key[PREFIX_LEN..]).ok()?; - let value = bincode::deserialize(&value).ok()?; - - Some((key, value)) - } -} -``` - -感觉正常人都看不懂这个是什么。 - -回到泛型基础语法上来。先来看定义函数: - -```rust -fn largest(list: &[T]) -> &T { - let mut largest = &list[0]; - - for item in list { - if item > largest { - largest = item; - } - } - - largest -} - -fn main() { - let number_list = vec![34, 50, 25, 100, 65]; - - let result = largest(&number_list); - println!("The largest number is {}", result); - - let char_list = vec!['y', 'm', 'a', 'q']; - - let result = largest(&char_list); - println!("The largest char is {}", result); -} -``` - -这里定义的`largest`函数,后面接了"``"这个还算正常,学过C++都知道这里是一个模板定义,或者说类型定义。后续T 代表着一种类型。 - -还可以出现在结构体中: - -```rust -struct Point { - x: T, - y: T, -} - -impl Point { - fn x(&self) -> &T { - &self.x - } -} - -fn main() { - let p = Point { x: 5, y: 10 }; - - println!("p.x = {}", p.x()); -} -``` - -这里"`impl`"也要加上"``" - -以及枚举中,如典型的`Result`: - -```rust -enum Result { - Ok(T), - Err(E), -} -``` - -在后面有介绍生命周期,生命周期也类似类型,也需要放在这里'`<>`' 如: - -```rust -pub struct Iter<'a, K, V> { - db_iter: rocksdb::DBIterator<'a>, - _phantom: PhantomData<(K, V)>, -} -``` diff --git a/other_docs/Solana-Bootcamp-zh/week4/rustaceans-understand.md b/other_docs/Solana-Bootcamp-zh/week4/rustaceans-understand.md deleted file mode 100644 index 84be8d97f..000000000 --- a/other_docs/Solana-Bootcamp-zh/week4/rustaceans-understand.md +++ /dev/null @@ -1,468 +0,0 @@ ---- -sidebar_position: 131 -sidebar_label: Rustaceans的理解 -sidebar_class_name: green ---- - -# Rustaceans的理解 - -如果没有其他语言为基础。那么不建议首先学习 Rust。但是有了其他语言的基础,又会对 Rust 的语言中的一些不那么常见的语法所难倒。 - -这里 Rustaceans 是对 Rust 程序员的一种昵称,我们在写代码的时候,也需要尽量的用 Rust 的思维来写,而不是对其他语言的翻译。比如将已有的 Solidity 翻译成 Rust。 - -这里标题为 Rustaceans,其实内容是 Rust 的陷阱与缺陷。主要讲一些 Rust 里面比较难理解的语法。 - -## 内存管理 - -rust 不是说不需要像 C++一样 new/delete,自己开辟/释放内存么?怎么还需要说内存管理。 - -所有权和借用 - -智能指针 - -rust 的智能指针,主要提供了 - -- `Box` 在堆上分配空间 -- `Rc` 引用计数,可以使得一个对象有多个 `owner` -- `Ref` and `RefMut`, `RefCell` 强制要求在运行时检查借用关系,而不是编译期间,就有点动态检查的意思 - -### `Box` - -box 顾名思义,就是装箱,在 Objective-C 中有相关概念。本质就类似 C 里面 alloc 一段内存,然后将值 copy 过去。 - -```rust -fn main() { - let b = Box::new(5); - println!("b = {}", b); - } -``` - -这个时候,`b` 实际上存储的是一个指针,指向一段放了数字 5 的内存,这段内存在堆上面。 - -类似这样的定义: - -```rust -enum List { - Cons(i32, Box), - Nil, -} - -use crate::List::{Cons, Nil}; - -fn main() { - let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); -} -``` - -就好比是 C++里面的前向类声明,然后存一个该类的指针。如果这里不用 `Box`,就会导致,这里在推测使用了多少空间的时候,陷入了循环。而 `Box` 只需要放一个指针大小就可以了。具体的内容在里面指向。 - -![](../img/week3/cons.png) - -### `Rc` - -Rc:Reference Count,也就是 C++里面智能指针最常见的方式,当某个空间需要使用时,就对其计数加一。当不需要的时候,就减一。当引用技术的值为 0 的时候,就对其进行销毁。 - -比如这样的代码: - -```rust -enum List { - Cons(i32, Box), - Nil, -} - -use crate::List::{Cons, Nil}; - -fn main() { - let a = Cons(5, Box::new(Cons(10, Box::new(Nil)))); - let b = Cons(3, Box::new(a)); - let c = Cons(4, Box::new(a)); -} -``` - -会出错: - -```bash -error[E0382]: use of moved value: `a` - --> src/main.rs:11:30 - | - 9 | let a = Cons(5, Box::new(Cons(10, Box::new(Nil)))); - | - move occurs because `a` has type `List`, which does not implement the `Copy` trait - 10 | let b = Cons(3, Box::new(a)); - | - value moved here - 11 | let c = Cons(4, Box::new(a)); - | ^ value used here after move -``` - -因为这里在用 `Box` 创建 `b` 的时候,已经将 `a` 借用了。接着又在创建 `c` 的时候,借用了 `a`,此时 `a` 所表达的空间的 `owner` 已经不再是 `a`。因此报错。 - -这里可以修改成: - -```rust -enum List { - Cons(i32, Rc), - Nil, -} - -use crate::List::{Cons, Nil}; -use std::rc::Rc; - -fn main() { - let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); - let b = Cons(3, Rc::clone(&a)); - let c = Cons(4, Rc::clone(&a)); -} -``` - -首先将 `a` 定义为 `Rc`,是一个引用计数智能指针,它包含了空间内容,和空间计数。 每次 `Rc::clone` 的时候,都会将计数器`+1`,同时返回一个 `Rc`,其中内容指向的是同一个地方,但是引用计数`+1`。 - -因此就可以同时创建 `b` 和 `c` 了。 - -### `RefCell` - -前面借用有介绍到,不可以在 `mut` 借用后,继续可读借用。 - -比如代码: - -```rust -fn main() { - let x =String::from("hello, world"); - - let y = x.borrow_mut(); - let z = x.borrow(); - - print!("y:{}, z:{}", y,z); -} -``` -报错: - -```bash ---> src/main.rs:1:5 - | - 1 | use std::cell::RefCell; - | ^^^^^^^^^^^^^^^^^^ - | - = note: `#[warn(unused_imports)]` on by default - - error[E0599]: no method named `borrow_mut` found for struct `String` in the current scope - --> src/main.rs:6:15 - | - 6 | let y = x.borrow_mut(); - | ^^^^^^^^^^ method not found in `String` - | - ::: /Users/changzeng/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/borrow.rs:206:8 - | - 206 | fn borrow_mut(&mut self) -> &mut Borrowed; - | ---------- the method is available for `String` here - | - = help: items from traits can only be used if the trait is in scope - help: the following trait is implemented but not in scope; perhaps add a `use` for it: - | - 1 + use std::borrow::BorrowMut; - | - - error[E0599]: no method named `borrow` found for struct `String` in the current scope - --> src/main.rs:7:15 - | - 7 | let z = x.borrow(); - | ^^^^^^ method not found in `String` - | - ::: /Users/changzeng/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/borrow.rs:179:8 - | - 179 | fn borrow(&self) -> &Borrowed; - | ------ the method is available for `String` here - | - = help: items from traits can only be used if the trait is in scope - help: the following trait is implemented but not in scope; perhaps add a `use` for it: - | - 1 + use std::borrow::Borrow; - | -``` - -现在修改成 `RefCell`: - -```rust -use std::cell::RefCell; - - fn main() { - let x = RefCell::new(String::from("hello, world")); - - let y = x.borrow_mut(); - let z = x.borrow(); - - print!("y:{}, z:{}", y,z); - } -``` - -虽然不可以运行,但是却可以通过编译。因为在运行的时候,还是会检查借用关系: - -```bash -Running `target/debug/helloworld` - thread 'main' panicked at 'already mutably borrowed: BorrowError', src/main.rs:7:15 - note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace -``` - -那这个有啥用呢?还不如在编译期间检查。那比如如下代码呢? - -```rust -let x = RefCell::new(String::from("hello, world")); - -if your_x_switch { - let y = x.borrow_mut(); -} -if (your_z_switch) { - let z = x.borrow(); -} -``` - -仅通过编译是没法区分 `if` 分支的,但是再运行时,可以保证只走一个分支。 - -### 生命周期 - -首先生命周期修饰是一个泛型修饰,也就是意味他是针对类型的。生命周期主要用来解决悬垂指针问题。也就是引用了一个已经被释放的空间。 - -那么如何保证被引用的空间一定没有被释放呢?就需要通过生命周期修饰,使得 rust 知道某个空间还在被引用中,不可以自动释放。 - -比如: - -```rust -fn main() { - let r; // ---------+-- 'a - // | - { // | - let x = 5; // -+-- 'b | - r = &x; // | | - } // -+ | - // | - println!("r: {}", r); // | - } // ---------+ -``` - -这里,编译会报错: - -```bash ---> src/main.rs:6:13 - | - 6 | r = &x; - | ^^ borrowed value does not live long enough - 7 | } - | - `x` dropped here while still borrowed - 8 | - 9 | println!("r: {}", r); - | - borrow later used here -``` - -在上面的代码部分,已经用注释吧生命周期范围罗列出来了,因为 `r` 借用超过了`'b` 的空间,所以报错,因为超过`'b` 后,`x` 不再存在。 - -所以一般我们在描述生命周期的是,也采用`'a` `'b` 的形式。 - -再来看一个编译错误的例子: - -```rust -fn longest(x: &str, y: &str) -> &str { - if x.len() > y.len() { - x - } else { - y - } - } - - fn main() { - let string1 = String::from("abcd"); - let string2 = "xyz"; - - let result = longest(string1.as_str(), string2); - println!("The longest string is {}", result); - } -``` - -报错为: - -```bash ---> src/main.rs:9:33 - | - 9 | fn longest(x: &str, y: &str) -> &str { - | ---- ---- ^ expected named lifetime parameter - | - = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y` - help: consider introducing a named lifetime parameter - | - 9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { - | ++++ ++ ++ ++ -``` - -修改方式,编译器也给出来了: - -```rust -fn longest<'a>(x: &'a str, y: &'a str) -> &'a str -``` - -这里`<'a>`是一个泛型修饰符,在原有的`&str` 中间增加 `'a` 来修饰这个参数的生命周期。 - -上面的修改,要求传入的参数生命周期,`x`、`y`、`z `三者的最小范围要一致。 - -## 错误处理 - -在类似 java/c++中,对错误处理,有抛异常的方式, 而类似 go 这样的,一般会通过多返回值的方式,返回一个错误。而在 rust 中,一般通过 `Enum` 的形式,返回一直结果返回值。 - -首先来看这里说的结果返回值,`Result` 的定义。这个定义是标准库中,可以在不用做特别 `use` 的情况下直接使用: - -```rust -enum Result { - Ok(T), - Err(E), -} -``` - -本质上他就是个枚举。包含两种可能,要么是一个 `T`,要么是一个 `E`。`T` 就是成功时应该正确返回的值类型,而 `E` 就是错误时,返回的错误类型。 - -来看一段使用案例: - -```rust -use std::fs::File; - -fn main() { - let greeting_file_result = File::open("hello.txt"); - - let greeting_file = match greeting_file_result { - Ok(file) => file, - Err(error) => panic!("Problem opening the file: {:?}", error), - }; -} -``` - -因为 `Result` 是个 `enum`,因此,这里可以通过 `match` 语法,来对其进行类型匹配。 - -`open` 的原型为: - -```rust -pub fn open>(path: P) -> io::Result -``` - -这里 `io::Result` 定义为: - -```rust -pub type Result = result::Result; -``` - -而 `Error` 是 `std::io::error` 定义的: - -```rust -pub struct Error { - repr: Repr, -} -``` - -所以 Rust 可以推导出如果文件打开失败,这里 `greeting_file_result`,返回的是 `std::io::error::Error`,并放在枚举 `Err(error)`中包裹。通过 `match` 可以解出来进行打印。 - -## 测试代码 - -测试代码主要分为单元测试和集成测试。类似 go 语言特性,go 语言在标准库和工具层面提供了单元测试的方法。 rust 也在工具和标准库层面提供了类似地方方法。除此之外,rust 还提供了集成测试框架模板。 - -### 单元测试 - -在代码所在文件中,添加一个 `test` 的 `mod` 并用`#[cfg(test)]` 特性控制,然后在要执行 `test` 的函数上增加修饰 `[test]` 即可: - -```rust -#[cfg(test)] - mod tests { - #[test] - fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); - } - } -``` - -这样当我们执行 - -```bash -cargo test - Compiling testcase v0.1.0 (Solana-Asia-Summer-2023/s101/Solana-Rust/demo/testcase) - Finished test [unoptimized + debuginfo] target(s) in 0.23s - Running unittests src/lib.rs (target/debug/deps/testcase-4146fa835bb26be8) - - running 1 test - test tests::test_fun ... ok - - test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - - Doc-tests testcase - - running 0 tests - - test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -``` - -就会执行 `test mod` 里面的 `it_works` 函数了。 - -在上面的测试代码中,我们用 `assert_eq!` 宏判断了是否相等,如果失败的话,测试结果就是失败。我们还可以用 `panic`强行失败,这里我们增加测试代码: - -```rust -#[test] -fn test_panic() { - panic!("got panic"); -} -``` - -失败 的时候表现为: - -```bash -cargo test - Compiling testcase v0.1.0 (Solana-Asia-Summer-2023/s101/Solana-Rust/demo/testcase) - Finished test [unoptimized + debuginfo] target(s) in 0.30s - Running unittests src/lib.rs (target/debug/deps/testcase-4146fa835bb26be8) - - running 2 tests - test tests::test_fun ... ok - test tests::test_panic ... FAILED - - failures: - - ---- tests::test_panic stdout ---- - thread 'tests::test_panic' panicked at 'got panic', src/lib.rs:12:9 - note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace - - - failures: - tests::test_panic - - test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - - error: test failed, to rerun pass `--lib` -``` - -## 集成测试 - -如果某个测试,需要涉及到多个模块的代码,并且还有一些初始化或者条件的设置。 - -我们可以在工程目录下新增一个 "`tests`" 目录,然后在在 `tests` 目录下增加文件或者目录。 - -在 `tests` 目录下的单个文件中,此时可以不用使用 `test` 模块。直接写要测试的逻辑。只需要在要测试的逻辑函数的上面用 `#[tset]`进行修饰即可。 - -比如: - -```bash -. - ├── Cargo.lock - ├── Cargo.toml - ├── src - │ └── lib.rs - └── tests - ├── test01.rs - └── test_files - ├── func.rs - └── main.rs -``` - -指定执行测试: - -```bash -cargo test --test test01 - Finished test [unoptimized + debuginfo] target(s) in 0.00s - Running tests/test01.rs (target/debug/deps/test01-0c980e86b9bfdada) - -running 1 test -test test_main ... ok - -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -``` diff --git a/other_docs/Solana-Bootcamp-zh/week5/README.md b/other_docs/Solana-Bootcamp-zh/week5/README.md deleted file mode 100644 index 3ceeaa63f..000000000 --- a/other_docs/Solana-Bootcamp-zh/week5/README.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -sidebar_position: 133 -sidebar_label: Week5 Solana合约开发 part1 -sidebar_class_name: green ---- - -# Week5: Solana合约开发 Part.1 - -- Hello World -- Solana合约基础概念 -- Solana合约处理逻辑 -- Solana合约错误定义 -- 课后练习 diff --git a/other_docs/Solana-Bootcamp-zh/week5/hello-world.md b/other_docs/Solana-Bootcamp-zh/week5/hello-world.md deleted file mode 100644 index 0db6ac792..000000000 --- a/other_docs/Solana-Bootcamp-zh/week5/hello-world.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -sidebar_position: 134 -sidebar_label: hello world -sidebar_class_name: green ---- - -# hello world - -`Solana`为了初学者可以快速入门,提供了一个 [`Solane Playground`](https://beta.solpg.io/) 服务。 可以在不需要本地环境的情况下,进行`Dapp`的开发。 - -## 创建项目 - -在界面中,我们点击 "`Create a New Project`" - -![create a new project](../img/week5/playground_create_hellowrld.png) - -创建完项目后。在`Explorer`里可以看到文件列表,主要有: - -```bash -src/ - lib.rs - -client/ - clinet.ts - -test/ - native.test.ts -``` - -![](../img/week5/playground_explorer.png) - -对应的位置有 - -- "`Build`":构建合约 -- "`Run`" : 运行客户端端,调试合约。 -- "`Test`": 执行测试代码 - -## 链接钱包 - -在界面的左下角有个"`unconnect`"的提示,点击后,弹出: - -![](../img/week5/playground_import_wallet.png) - -在这里选择我们之前生成的"`~/.config/solana/id.json`"秘钥文件。导入后,可以看到钱包详情 - -![](../img/week5/playground_wallet.png) - -## 开发合约 - -打开这里的`lib.rs`,在里面贴上: - -```rust -use solana_program::{ - account_info::AccountInfo, - entrypoint, - entrypoint::ProgramResult, - pubkey::Pubkey, - msg, -}; - - -// Declare and export the program's entrypoint -entrypoint!(process_instruction); - -// Program entrypoint's implementation -pub fn process_instruction( - _program_id: &Pubkey, // Public key of the account the hello world program was loaded into - _accounts: &[AccountInfo], // The account to say hello to - _instruction_data: &[u8], // Ignored, all helloworld instructions are hellos -) -> ProgramResult { - msg!("Hello World Rust program entrypoint"); - - Ok(()) -} -``` - -然后点击"`Build`",在下面的界面可以看到build的结果: - -```bash -Building... -Build successful. Completed in 0.60s. -``` - -然后点击左侧的 锤子+扳手的图标,点击"`Deploy`": - -![](../img/week5/playground_build_deplay.png) - -发布成功,可以在界面上看到发布后的合约地址。在发布过一次后,这里的"`Deploy`"就会变成"`Update`" - -如果我们的合约有修改,只要在这里构建后点击"`Update`"就可以进行更新了。 - -## 客户端开发 - -回到`Explorer`界面,打开`client.ts`,在里面贴上: - -```typescript -// Client -console.log("My address:", pg.wallet.publicKey.toString()); -const balance = await pg.connection.getBalance(pg.wallet.publicKey); -console.log(`My balance: ${balance / web3.LAMPORTS_PER_SOL} SOL`); - -// create an empty transaction -const transaction = new web3.Transaction(); - -// add a hello world program instruction to the transaction -transaction.add( -new web3.TransactionInstruction({ - keys: [], - programId: new web3.PublicKey(pg.PROGRAM_ID), -}), -); - -console.log("Sending transaction..."); -const txHash = await web3.sendAndConfirmTransaction( - pg.connection, - transaction, - [pg.wallet.keypair], -); -console.log("Transaction sent with hash:", txHash); -``` - -这里,不需要过多的`import`,`IDE`已经帮忙们做了`import`。可以直接使用`web3`。其中"`pg.wallet`" 就是我们的钱包,其`publicKey`属性就是钱包的地址。而`pg.connection`就如同我们用`web3`创建 的`connection`对象,这里共用`playground`的设置里面的`RPC`地址。 - -`pb.PROGRAM_ID` 就是我们要刚刚构建并发布的合约的地址。 - -点击"`Run`"我们可以在日志里面看到: - -```bash -Running client... -client.ts: -My address: 5pWae6RxD3zrYzBmPTMYo1LZ5vef3vfWH6iV3s8n6ZRG -My balance: 4.27388232 SOL -Sending transaction... -Transaction sent with hash: 3XcaF6zpXthBQ2mih7DdVBuvwXi7L6Wy4rCHzgwHEaNsqMDNRGC5yppG9xKP9g9hYfT6wPEw127mxgYWBTouS5gz -``` - -打开[`solana`浏览器](https://explorer.solana.com/),贴上这里的`hash`地址。可以看到。在日志部分,执行了我们前面合约代码里面的"`HelloWorld`"。 - -![](../img/week5/playground_helloworld_explorer.png) diff --git a/other_docs/Solana-Bootcamp-zh/week5/solana-program-logic.md b/other_docs/Solana-Bootcamp-zh/week5/solana-program-logic.md deleted file mode 100644 index 99a68a719..000000000 --- a/other_docs/Solana-Bootcamp-zh/week5/solana-program-logic.md +++ /dev/null @@ -1,340 +0,0 @@ ---- -sidebar_position: 136 -sidebar_label: Solana Program处理逻辑 -sidebar_class_name: green ---- - -# Solana Program 处理逻辑 - -我们这里说的处理逻辑,并不是Runtime是如何去处理逻辑的。而是在我们的合约里面,要 怎么安排逻辑。 - -在前面的文章中,我们已经规划了一个合约的代码结构,其中在"process.rs"文件中来放我们的 处理逻辑,每个处理逻辑处理对应的Instruction。并且Instruction的data部分是通过Borsh 来进行序列化的。 - -那么我们一个合约中怎么根据用户的输入来执行不同的逻辑呢?在前面我们知道一个合约只有通过 "entrypoint"宏定义的一个入口。那么我们是怎么区分不同的Instruction的呢? - -本质上来说,只有一个Instruction,在客户端的不同的Instruction其实只是data部分存放的 数据不同而已。这里如果有应用开发经验的同学,立马就会联想到Protobuf里面定义的各种消息。 其实这里我们也是用类似的方法,在data部分的最开始,我们用来放cmd,cmd来表示后面的内容要按照 什么结构来解析。 - -## 结构化工程 - -上面我们在同一个文件中,安排了一个合约的各个部分。当合约逻辑复杂的时候,我们可以将其一一拆分, 在书写的时候更清晰。来看token 合约的结构: - -```bash -├── src -│ ├── entrypoint.rs -│ ├── error.rs -│ ├── instruction.rs -│ ├── lib.rs -│ ├── processor.rs -│ └── state.rs -``` - -其中"entrypoint" 专门用来定义合约入口函数。 - -在entrypoint中,最终会调用"processor"里面定义的具体逻辑。对不同的命令进行处理。 - -在链上要存储的结构数据,如Token账号pub struct Account, Token信息 pub struct Mint放在 "state"中,有点类似MVC结构里面的model。 - -相关错误,定义在"error"里面,在"processor"中处理出错的是,直接进行返回。 - -"lib"作为rust工程的基本结构而存在,里面也可以定义一些脚手架工具函数。 - -## 定义指令 - -在Rust中,我们天然的可以用enum来模拟Protobuf中的Message,或者Oneof。 - -这里我们专门用一个"instruction.rs"文件来定义各个指令,类似pb文件定义消息: - -```rust -/// Instructions supported by the generic Name Registry program -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)] -pub enum HelloWorldInstruction { - /// Greeting to a account - /// - /// Accounts expected by this instruction: - /// 0. `[writeable]` the account to greet - /// - Greeting { - /// greet count - counter: u32, - }, -} -``` - -比如这里定义了"HelloWorldInstruction"指令集,其中有一个 "Greeting" 指令。 这个指令的data部分为包含“counter”成员的struct。 - -因为这里通过derive来默认实现了Borsh的序列化和反序列化逻辑。当我们调用 "HelloWorldInstruction"的 try_to_vec的方法的时候,就可以得到序列化后的结果。 - -同时对于我们这个指令,还有配套使用的Account,要将他们放入到accounts数组中。 - -因此如果要在Rust里面构造这样的一个指令(通常在用Rust写合约调用的时候): - -```rust -#[allow(clippy::too_many_arguments)] -pub fn greeting( - helloworld_program_id: Pubkey, - instruction_data: HelloWorldInstruction, - name_greeting: Pubkey, -) -> Result { - let data = instruction_data.try_to_vec().unwrap(); - let mut accounts = vec![ - AccountMeta::new(name_greeting, false), - ]; - - - Ok(Instruction { - program_id: helloworld_program_id, - accounts, - data, - }) -} -``` - -可以这样来实现,将相关的AccountMeta push到accounts部分。然后就构建了一个Instruction对象。 - -这个一般用于在Rust调用合约,或者写单元测试的时候。 - -## 指令解析 - - -上面我们构建了指令,当前端调用合约的时候,RPC会讲相应的值转换成这里的Instruction,那么我们要怎么去 理解他呢?按照上面的逻辑,我们执行反逻辑就可以了: - -```rust -pub fn process_instruction( - program_id: &Pubkey, - accounts: &[AccountInfo], - instruction_data: &[u8], -) -> ProgramResult { - msg!("Beginning processing"); - let instruction = HelloWorldInstruction::try_from_slice(instruction_data) - .map_err(|_| ProgramError::InvalidInstructionData)?; - msg!("Instruction unpacked"); - - match instruction { - HelloWorldInstruction::Greeting { - counter, - } => { - msg!("Instruction: Greeting"); - Processor::process_greeting(program_id, accounts, counter)?; - } - } - Ok(()) -} -``` - -这里在合约入口里面,首先通过Borsh的 try_from_slice 既可以将其转换成对应的指令枚举结构。对应到这里就是 我们上面定义的HelloWorldInstruction。 - -然后通过match语法,将其一一匹配。 - -```rust -match instruction { - HelloWorldInstruction::Greeting { - counter, - } => todo!() -} -``` -是一个解包语法糖。意思就是将enum中定义的无名struct的成员一一借用到这里的和成员同名的变量。方便后面使用。 - -## 执行指令 - -在上面的解析中,我们已经得到了accounts的AccountMeta数组,以及要处理的Create的指令的三个成员变量。 - -这样我们就可以把他们传递给我们要处理的逻辑,所以我们定义处理函数: - -```rust -pub fn process_greeting( - program_id: &Pubkey, - accounts: &[AccountInfo], - counter: u32, -) -> ProgramResult -``` - -这里传入即为上面解析出来的内容。意义相对明确。并且该函数返回了 ProgramResult类型。 - -在实现中,我们通过: - -```rust -let accounts_iter = &mut accounts.iter(); - -let greeting_account = next_account_info(accounts_iter)?; -``` - -用next_account_info 来依次取出AccountInfo对象。 - -```rust -pub struct AccountInfo<'a> { - /// Public key of the account - pub key: &'a Pubkey, - /// The lamports in the account. Modifiable by programs. - pub lamports: Rc>, - /// The data held in this account. Modifiable by programs. - pub data: Rc>, - /// Program that owns this account - pub owner: &'a Pubkey, - /// The epoch at which this account will next owe rent - pub rent_epoch: Epoch, - /// Was the transaction signed by this account's public key? - pub is_signer: bool, - /// Is the account writable? - pub is_writable: bool, - /// This account's data contains a loaded program (and is now read-only) - pub executable: bool, -} -``` - -可以对这个对象的is_signer/is_writeable/owner等做一些校验。 - -这里我们的逻辑中要修改data部分的内容。 - -## Model层 - -因为要修改data部分,而data部分本质上是一段二进制内容。因此我们这里类同指令部分。借助Borsh做序列化。 将结构化数据序列化成一段二进制数据,在存入这里的data。 - -```rust -/// Define the type of state stored in accounts -#[derive(BorshSerialize, BorshDeserialize, Debug)] -pub struct GreetingInfo{ - /// number of greetings - pub counter: u32, -} -``` - -定义的struct加上BorshSerialize, BorshDeserialize 的derive即可。 - -这样便可以跟指令部分一样,调用try_from_slice 做反序列化,得到结构体的内容。 在通过 serialize将其序列化到&mut [u8]这样的buffer中去。通过对data部分做borrow_mut 可以得到这样的类型。因此,我们在process里面增加: - -```rust -// Increment and store the number of times the account has been greeted -let mut greeting_info= GreetingInfo::try_from_slice(&greeting_account.data.borrow())?; -greeting_info.counter += 1; -greeting_info.serialize(&mut *greeting_account.data.borrow_mut())?; -``` -先将data部分反序列化出来,修改后,在序列化存回去。 - -## 客户端访问 - -这里贴上客户端示例代码: - -```ts -// No imports needed: web3, borsh, pg and more are globally available -import { serialize, deserialize, deserializeUnchecked } from "borsh"; -import { Buffer } from "buffer"; -/** - * The state of a greeting account managed by the hello world program - */ -class GreetingAccount { - counter = 0; - constructor(fields: { counter: number } | undefined = undefined) { - if (fields) { - this.counter = fields.counter; - } - } -} - -/** - * Borsh schema definition for greeting accounts - */ -const GreetingSchema = new Map([ - [GreetingAccount, { kind: "struct", fields: [["counter", "u32"]] }], -]); - -class Assignable { - constructor(properties) { - Object.keys(properties).map((key) => { - return (this[key] = properties[key]); - }); - } -} - -// Our instruction payload vocabulary -class HelloWorldInstruction extends Assignable {} - -// Borsh needs a schema describing the payload -const helloWorldInstructionSchema = new Map([ - [ - HelloWorldInstruction, - { - kind: "struct", - fields: [ - ["id", "u8"], - ["counter", "u32"], - ], - }, - ], -]); - -// Instruction variant indexes -enum InstructionVariant { - Greeting = 0, -} - -/** - * The expected size of each greeting account. - */ -const GREETING_SIZE = borsh.serialize( - GreetingSchema, - new GreetingAccount() -).length; - -// Create greetings account instruction -const greetingAccountKp = new web3.Keypair(); -const lamports = await pg.connection.getMinimumBalanceForRentExemption( - GREETING_SIZE -); -const createGreetingAccountIx = web3.SystemProgram.createAccount({ - fromPubkey: pg.wallet.publicKey, - lamports, - newAccountPubkey: greetingAccountKp.publicKey, - programId: pg.PROGRAM_ID, - space: GREETING_SIZE, -}); - -const helloIx = new HelloWorldInstruction({ - id: InstructionVariant.Greeting, - counter: 2, -}); - -// Serialize the payload -const helloSerBuf = Buffer.from( - serialize(helloWorldInstructionSchema, helloIx) -); - -// Create greet instruction -const greetIx = new web3.TransactionInstruction({ - data: helloSerBuf, - keys: [ - { - pubkey: greetingAccountKp.publicKey, - isSigner: false, - isWritable: true, - }, - ], - programId: pg.PROGRAM_ID, -}); - -// Create transaction and add the instructions -const tx = new web3.Transaction(); -tx.add(createGreetingAccountIx, greetIx); - -// Send and confirm the transaction -const txHash = await web3.sendAndConfirmTransaction(pg.connection, tx, [ - pg.wallet.keypair, - greetingAccountKp, -]); -console.log(`Use 'solana confirm -v ${txHash}' to see the logs`); - -// Fetch the greetings account -const greetingAccount = await pg.connection.getAccountInfo( - greetingAccountKp.publicKey -); - -// Deserialize the account data -const deserializedAccountData = borsh.deserialize( - GreetingSchema, - GreetingAccount, - greetingAccount.data -); - -console.log( - `deserializedAccountData.counter ${deserializedAccountData.counter}` -); -``` diff --git a/other_docs/Solana-Bootcamp-zh/week5/solana-program-struct.md b/other_docs/Solana-Bootcamp-zh/week5/solana-program-struct.md deleted file mode 100644 index cccc6e058..000000000 --- a/other_docs/Solana-Bootcamp-zh/week5/solana-program-struct.md +++ /dev/null @@ -1,316 +0,0 @@ ---- -sidebar_position: 135 -sidebar_label: Solana Program 基础概念 -sidebar_class_name: green ---- - -# Solana Program 基础概念 - - -## Solana Program结构 - -回到我们之前在`Playground`,这次我们直接用其给的模版创建项目。 - -里面看到`Program`代码: - -```rust -use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{ - account_info::{next_account_info, AccountInfo}, - entrypoint, - entrypoint::ProgramResult, - msg, - program_error::ProgramError, - pubkey::Pubkey, -}; - -/// Define the type of state stored in accounts -#[derive(BorshSerialize, BorshDeserialize, Debug)] -pub struct GreetingAccount { - /// number of greetings - pub counter: u32, -} - -// Declare and export the program's entrypoint -entrypoint!(process_instruction); - -// Program entrypoint's implementation -pub fn process_instruction( - program_id: &Pubkey, // Public key of the account the hello world program was loaded into - accounts: &[AccountInfo], // The account to say hello to - _instruction_data: &[u8], // Ignored, all helloworld instructions are hellos -) -> ProgramResult { - msg!("Hello World Rust program entrypoint"); - - // Iterating accounts is safer than indexing - let accounts_iter = &mut accounts.iter(); - - // Get the account to say hello to - let account = next_account_info(accounts_iter)?; - - // The account must be owned by the program in order to modify its data - if account.owner != program_id { - msg!("Greeted account does not have the correct program id"); - return Err(ProgramError::IncorrectProgramId); - } - - // Increment and store the number of times the account has been greeted - let mut greeting_account = GreetingAccount::try_from_slice(&account.data.borrow())?; - greeting_account.counter += 1; - greeting_account.serialize(&mut *account.data.borrow_mut())?; - - msg!("Greeted {} time(s)!", greeting_account.counter); - - Ok(()) -} -``` - -`Program`的整体结构是: - -```rust -use xxx; - -entrypoint!(process_instruction); - -pub fn process_instruction( - program_id: &Pubkey, - accounts: &[AccountInfo], - instruction_data: &[u8], -) -> ProgramResult { - ... - Ok(()) -} -``` - -最前面的 `use` 是`rust`的基本语法,导入相关定义。 - -这里通过 `entrypoint` 宏声明了一个函数"`process_instruction`"为整个`Program`的入口函数。 在前面的调用我们知道,调用的基本单元是`instruction`,其定义为: - -```ts -/** -* Transaction Instruction class -*/ -export class TransactionInstruction { - /** - * Public keys to include in this transaction - * Boolean represents whether this pubkey needs to sign the transaction - */ - keys: Array; - /** - * Program Id to execute - */ - programId: PublicKey; - /** - * Program input - */ - data: Buffer; - constructor(opts: TransactionInstructionCtorFields); -} -``` - -这里的 `programId` 指定了和哪个`Program`交互。而具体执行这个`Program`的哪个方法呢?就是这里的 `entrypoint` 来指定的。其原型必须为: - -```rust -pub fn process_instruction( - program_id: &Pubkey, - accounts: &[AccountInfo], - instruction_data: &[u8], -) -> ProgramResult { - ... - Ok(()) -} -``` - -`program_id`对应了调用`Instruction`里面的 `programId`, `accounts`则对应调用里面的 `keys`。 `instruction_data`则为调用指令里面的 `data`。这样`solana`在处理的时候,就可以将调用与 `Program`逻辑一一对上了。 - -因为函数返回的是一个`Result`: - -```rust -use std::result::Result as ResultGeneric; - -pub type ProgramResult = ResultGeneric<(), ProgramError>; -``` - -所以最后返回结果,成功的时候返回 `Ok(())`.如果错误,需要返回"`solana::program_error::ProgramError`" - -获取`Account`对象 -客户端通过`RPC`调用传递过来的`Account`对象,在`Program`里面要怎么去获取呢? - -```rust -// Iterating accounts is safer than indexing -let accounts_iter = &mut accounts.iter(); - -// Get the account to say hello to -let account = next_account_info(accounts_iter)?; -``` -因为这里,`accounts` 是一个 `&[AccountInfo] AccountInfo`的数组,因此我们可以通过 `iter()`来得到其迭代器,并通过 `solana_program::account_info::next_account_info` 解析出 `solana_program::AccountInfo`对象。 - -```rust -pub fn next_account_info<'a, 'b, I: Iterator>>( - iter: &mut I, -) -> Result { - iter.next().ok_or(ProgramError::NotEnoughAccountKeys) -} - - -pub struct AccountInfo<'a> { - /// Public key of the account - pub key: &'a Pubkey, - /// The lamports in the account. Modifiable by programs. - pub lamports: Rc>, - /// The data held in this account. Modifiable by programs. - pub data: Rc>, - /// Program that owns this account - pub owner: &'a Pubkey, - /// The epoch at which this account will next owe rent - pub rent_epoch: Epoch, - /// Was the transaction signed by this account's public key? - pub is_signer: bool, - /// Is the account writable? - pub is_writable: bool, - /// This account's data contains a loaded program (and is now read-only) - pub executable: bool, -} -``` - -这样就可以得到传递过来的最原始的`Account`对象了。 - -比如这里我们传递了一个`owner`为这个`Program`的`account`对象,并在其`data`部分存储了: - -```rust -pub struct GreetingAccount { - /// number of greetings - pub counter: u32, -} -``` - -这个结构体的数据,作为计数器来使用。 - -所以在获得该对象后,可以进行`Account`信息的相关检查: - -```rust -if account.owner != program_id { - msg!("Greeted account does not have the correct program id"); - return Err(ProgramError::IncorrectProgramId); -} -``` - -如果`Account`里面的`owner`不是我们的`Program`,直接返回出错。并且指定了错误码。 - -`Account`数据存储 -上面我们读取了传递的存储单元`Account`的基础信息,那么具体存储的值是如何操作的呢? - -```rust -let mut greeting_account = GreetingAccount::try_from_slice(&account.data.borrow())?; -``` - -这里因为我们在定义`GreetingAccount`的使用了 - -```rust -#[derive(BorshSerialize, BorshDeserialize, Debug)] -pub struct GreetingAccount { -``` - -`Borsh`的默认实现,所以可以直接通过`try_from_slice`方法,将`Account`中的`data`借用出来做解析。 - -[Borsh](https://borsh.io/) 是一个序列化标准,其有多个语言的实现,比如我们这里的`Rust`和客户端可以用 的`Javascript`。具体逻辑类似我们在前端`web3.js`访问时候的`buffer`的定义。我们可以忽略其具体实现,直接进行使用。 - -当然这里我们还可以使用其他序列化工具,比如`Anchor`。 - -反序列化这里的`data`部分后,就可以得到 `GreetingAccount`对象了。 - -```rust -greeting_account.counter += 1; -greeting_account.serialize(&mut *account.data.borrow_mut())?; -``` - -这里先修改 `GreetingAccount`对象,然后再将其序列化回`Account`的`data`部分中。实际就是将 "`greeting_account`" 序列化成二进制数据,然后再填入`account.data`部分的。 - -因为我们在前端传递的时候,给这个`Account`的`isWritable`是`true`,所以我们`Program`中修改了`Account.data` 部分在`Program`执行结束时,就会修改链上的相关数据。 - -## 客户端访问 - -这里贴上课上客户端访问代码: - -```ts -// No imports needed: web3, borsh, pg and more are globally available - -/** - * The state of a greeting account managed by the hello world program - */ -class GreetingAccount { - counter = 0; - constructor(fields: { counter: number } | undefined = undefined) { - if (fields) { - this.counter = fields.counter; - } - } -} - -/** - * Borsh schema definition for greeting accounts - */ -const GreetingSchema = new Map([ - [GreetingAccount, { kind: "struct", fields: [["counter", "u32"]] }], -]); - -/** - * The expected size of each greeting account. - */ -const GREETING_SIZE = borsh.serialize( - GreetingSchema, - new GreetingAccount() -).length; - -// Create greetings account instruction -const greetingAccountKp = new web3.Keypair(); -const lamports = await pg.connection.getMinimumBalanceForRentExemption( - GREETING_SIZE -); -const createGreetingAccountIx = web3.SystemProgram.createAccount({ - fromPubkey: pg.wallet.publicKey, - lamports, - newAccountPubkey: greetingAccountKp.publicKey, - programId: pg.PROGRAM_ID, - space: GREETING_SIZE, -}); - -// Create greet instruction -const greetIx = new web3.TransactionInstruction({ - keys: [ - { - pubkey: greetingAccountKp.publicKey, - isSigner: false, - isWritable: true, - }, - ], - programId: pg.PROGRAM_ID, -}); - -// Create transaction and add the instructions -const tx = new web3.Transaction(); -tx.add(createGreetingAccountIx, greetIx); - -// Send and confirm the transaction -const txHash = await web3.sendAndConfirmTransaction(pg.connection, tx, [ - pg.wallet.keypair, - greetingAccountKp, -]); -console.log(`Use 'solana confirm -v ${txHash}' to see the logs`); - -// Fetch the greetings account -const greetingAccount = await pg.connection.getAccountInfo( - greetingAccountKp.publicKey -); - -// Deserialize the account data -const deserializedAccountData = borsh.deserialize( - GreetingSchema, - GreetingAccount, - greetingAccount.data -); - -console.log( - `deserializedAccountData.counter :${deserializedAccountData.counter}` -); -``` diff --git a/other_docs/Solana-docs/README.md b/other_docs/Solana-docs/README.md deleted file mode 100644 index 7180b0578..000000000 --- a/other_docs/Solana-docs/README.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -sidebar_position: 200 -sidebar_label: Solana 官方中文文档📄 -sidebar_class_name: green ---- - -# Solana 官方中文文档📄 - -## 开始 - -- 🔜 hello world 快速入门指南 -- 🔜 本地开发快速入门指南 -- 🔜 Rust 程序快速入门指南 - -## 核心概念 - -- 账户 -- 交易 -- 程序 -- 租金 -- 跨程序调用 -- 运行时 - -## 高级概念 - -- 状态压缩 - -## 客户端 - -- JSON RPC API -- Web3 javascript API -- Web3 Api interface -- Rust API - -## 指南 - -- 压缩NFTs - -## 编写程序 - -- Overview -- 使用Rust开发 -- 使用C/C++开发 -- 部署 -- debug -- 程序例子 -- 限制 -- FAQ - -## 原生程序 - -- Overview -- Sysvar Cluster Data - - -## 本地开发 - -- Solana test Validator - -## 向后兼容性政策 diff --git a/other_docs/Solana-docs/get-started/README.md b/other_docs/Solana-docs/get-started/README.md deleted file mode 100644 index 33dd7b95f..000000000 --- a/other_docs/Solana-docs/get-started/README.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -sidebar_position: 201 -sidebar_label: 开始 -sidebar_class_name: green ---- - -# 开始 - -- 🔜 hello world 快速入门指南 -- 🔜 本地开发快速入门指南 -- 🔜 Rust 程序快速入门指南 diff --git a/other_docs/Solana-docs/get-started/hello_world.md b/other_docs/Solana-docs/get-started/hello_world.md deleted file mode 100644 index e72620646..000000000 --- a/other_docs/Solana-docs/get-started/hello_world.md +++ /dev/null @@ -1,239 +0,0 @@ ---- -sidebar_position: 202 -sidebar_label: 🔜 hello world 快速入门指南 -sidebar_class_name: green ---- - -# 🔜 hello world 快速入门指南 - -对于这个“`Hello World`”快速入门指南,我们将使用[`Solana Playground`](https://beta.solpg.io/),一个基于浏览器的`IDE`来开发和部署我们的`Solana`程序。要使用它,您无需在计算机上安装任何软件。只需在您选择的浏览器中打开`Solana Playground`,您就可以开始编写和部署`Solana`程序了。 - -## 你将学到什么 - -- 如何开始使用`Solana Playground` -- 如何在`Playground`上创建`Solana`钱包 -- 如何使用`Rust`编写基本的`Solana`程序 -- 如何构建和部署`Solana Rust`程序 -- 如何使用`JavaScript`与您的链上程序进行交互 - -## 使用Solana Playground - -[`Solana Playground`](https://beta.solpg.io/)是一个基于浏览器的应用程序,可以让您在链上编写、构建和部署`Solana`程序,而无需安装任何软件。 - -这是一个非常好的开发者资源,特别适合在`Windows`上开始`Solana`开发。 - -### 导入我们的示例项目 - -在浏览器的新标签页中,打开我们在`Solana Playground`上的示例项目“`Hello World`”:https://beta.solpg.io/6314a69688a7fca897ad7d1d - -接下来,通过点击“导入”图标并为您的项目命名 `hello_world` ,将项目导入到您的本地工作空间中。 - -![](./img/solana-get-started-import-on-playground.png) - -> 如果您不将程序导入到`Solana Playground`中,那么您将无法对代码进行更改。但您仍然可以构建并部署代码到`Solana`集群。 - -### 创建一个Playground钱包 - -通常在本地开发中,您需要创建一个文件系统钱包,以便与`Solana CLI`一起使用。但是在`Solana Playground`中,您只需要点击几个按钮就可以创建一个基于浏览器的钱包。 - -:::caution -您的 `Playground` 钱包将保存在浏览器的本地存储中。清除浏览器缓存将删除您保存的钱包。创建新钱包时,您可以选择保存钱包的密钥对文件的本地副本。 -::: - -点击屏幕左下角的红色状态指示按钮,(可选)将您的钱包密钥文件保存到计算机备份,然后点击“继续”。 - -创建了`Playground`钱包后,您会注意到窗口底部显示了您的钱包地址、`SOL`余额以及您连接的`Solana`集群(通常默认/推荐为`Devnet`,但也可以接受“`localhost`”测试验证器)。 - - - -## 创建一个Solana程序 - -您的基于`Rust`的`Solana`程序的代码将存放在您的 `src/lib.rs` 文件中。在 `src/lib.rs` 中,您可以导入`Rust`的`crate`并定义您的逻辑。在`Solana Playground`中打开您的 `src/lib.rs` 文件。 - -### 导入 solana_program crate - -在 `lib.rs` 的顶部,我们导入 `solana-program` 包并将所需的项目引入本地命名空间: - -```rust -use solana_program::{ - account_info::AccountInfo, - entrypoint, - entrypoint::ProgramResult, - pubkey::Pubkey, - msg, -}; -``` - -### 编写你的程序逻辑 - -每个 `Solana` 程序都必须定义一个 `entrypoint` ,告诉 `Solana` 运行时从哪里开始执行你的链上代码。你的**程序入口**应该提供一个名为 `process_instruction` 的公共函数: - -```rust -// declare and export the program's entrypoint -entrypoint!(process_instruction); - -// program entrypoint's implementation -pub fn process_instruction( - program_id: &Pubkey, - accounts: &[AccountInfo], - instruction_data: &[u8] -) -> ProgramResult { - // log a message to the blockchain - msg!("Hello, world!"); - - // gracefully exit the program - Ok(()) -} -``` - -每个链上程序都应该返回一个值为 `()` 的[结果枚举](https://doc.rust-lang.org/std/result/)。这告诉`Solana`运行时,你的程序成功执行且没有错误。 - -我们上面的程序将简单地将一条消息“`Hello, world!`”记录到区块链集群中,然后以 `Ok(())` 优雅地退出。 - -### 构建你的程序 - -在左侧边栏中,选择“构建和部署”选项卡。接下来,点击“构建”按钮。 - -如果你查看`Playground`的终端,你应该能看到你的`Solana`程序开始编译。一旦完成,你将会看到一个成功的消息。 - -![](./img/solana-get-started-successful-build.png) - -:::caution -当您的程序编译时,可能会收到有关未使用变量的警告。不用担心,这些警告不会影响您的构建。这是因为我们的非常简单的程序没有使用在 `process_instruction` 函数中声明的所有变量。 -::: - - -### 部署你的程序 - -您可以点击“部署”按钮将您的第一个程序部署到`Solana`区块链上。具体来说,是部署到您选择的集群(例如`Devnet`、`Testnet`等)。 - -每次部署后,您会看到您的Playground钱包余额发生变化。默认情况下,`Solana Playground`会自动代表您请求`SOL`空投,以确保您的钱包有足够的`SOL`来支付部署的费用。 - -:::info -注意:如果你需要更多的`SOL`,你可以在游乐场终端中输入`airdrop`命令来进行空投 -::: - -```bash -solana airdrop 2 -``` - -![](./img/solana-get-started-import-on-playground.png) - -### 找到你的程序ID - -当使用[`web3.js`](https://docs.solana.com/developing/clients/javascript-reference)或从另一个`Solana`程序执行程序时,您需要提供 `program id` (也称为您的程序的公共地址)。 - -在`Solana Playground`的`Build & Deploy`侧边栏中,您可以在`Program Credentials`下拉菜单中找到您的 `program id` 。 - -#### 恭喜! - -您已成功在浏览器中使用Rust语言设置、构建和部署了一个`Solana`程序。接下来,我们将演示如何与您的链上程序进行交互。 - -## 与您的链上程序互动 - -一旦您成功将`Solana`程序部署到区块链上,您将希望能够与该程序进行交互。 - -和大多数开发者一样,我们在创建`dApps`和网站时会使用`JavaScript`与我们的链上程序进行交互。具体来说,我们将使用开源的`NPM`包 [`@solana/web3.js`](https://www.npmjs.com/package/@solana/web3.js) 来帮助我们的客户端应用程序。 - -:::info -这个`web3.js`包是在[`JSON RPC API`](https://docs.solana.com/api)之上的一个抽象层,它减少了重写常见样板代码的需求,有助于简化客户端应用程序代码。 -::: - -### 初始化客户端 - -我们将使用`Solana Playground`进行客户端生成。在`playground`终端中运行 `run` 命令来创建一个客户端文件夹: - -```bash -run -``` - -我们已经创建了一个 `client` 文件夹和一个默认的 `client.ts` 。这是我们接下来的 `hello world` 程序的工作目录。 - -### 游乐场全球 - -在游乐场上,有许多全球可用的实用工具,我们可以直接使用,无需安装或设置任何东西。对于我们的 `hello world` 程序来说,最重要的是 `web3` 用于 `@solana/web3.js` 和 `pg` 用于`Solana`游乐场实用工具。 - - -:::info -您可以在编辑器中按下 `CTRL+SPACE` (或在 `macOS` 上按下 `CMD+SPACE` )来查看所有可用的全局变量。 -::: - -### 调用该程序 - -要执行您的链上程序,您必须向其发送一笔交易。提交到`Solana`区块链的每笔交易都包含一系列指令(以及该指令将与之交互的程序)。 - -在这里我们创建一个新的交易并向其添加一个单独的 `instruction`。 - -```typescript -// create an empty transaction -const transaction = new web3.Transaction(); - -// add a hello world program instruction to the transaction -transaction.add( - new web3.TransactionInstruction({ - keys: [], - programId: new web3.PublicKey(pg.PROGRAM_ID), - }), -); -``` - -每个 ·instruction· 必须包含操作所涉及的所有键和我们要执行的程序`ID`。在这个例子中, `keys` 是空的,因为我们的程序只记录 `hello world` ,不需要任何账户。 - -完成我们的交易后,我们可以将其提交到集群中 - -```typescript -// send the transaction to the Solana cluster -console.log("Sending transaction..."); -const txHash = await web3.sendAndConfirmTransaction( - pg.connection, - transaction, - [pg.wallet.keypair], -); -console.log("Transaction sent with hash:", txHash); -``` - -:::info -签名者数组中的第一个签名者默认为交易手续费支付者。我们将使用我们的密钥对 `pg.wallet.keypair` 进行签名。 -::: - -### 运行应用程序 - -客户端应用程序编写完成后,您可以通过相同的 `run` 命令来运行代码。 - -一旦您的申请完成,您将看到类似于以下的输出: - -```bash -Running client... - client.ts: - My address: GkxZRRNPfaUfL9XdYVfKF3rWjMcj5md6b6mpRoWpURwP - My balance: 5.7254472 SOL - Sending transaction... - Transaction sent with hash: 2Ra7D9JoqeNsax9HmNq6MB4qWtKPGcLwoqQ27mPYsPFh3h8wignvKB2mWZVvdzCyTnp7CEZhfg2cEpbavib9mCcq -``` - -### 获取交易日志 - -我们将直接在游乐场中使用 `solana-cli` 来获取有关任何交易的信息: - -```bash -solana confirm -v -``` - -用从调用 `hello world` 程序获得的哈希值替换 `` 。 - -你应该在输出的日志消息部分看到 `Hello, world!` 。🎉 - -#### 恭喜!!! - -你现在已经为你的链上程序编写了一个客户端应用程序。你现在是一个`Solana`开发者了! - -PS:尝试更新您的程序消息,然后重新构建、重新部署和重新执行您的程序。 - -## 下一步 - -请查看下面的链接,了解有关编写`Solana`程序的更多信息: - -- 设置本地开发环境 -- `Solana`程序编写概述 -- 了解如何使用`Rust`开发`Solana`程序 -- 链上程序调试 diff --git a/other_docs/Solana-docs/get-started/img/solana-get-started-build-and-deploy.png b/other_docs/Solana-docs/get-started/img/solana-get-started-build-and-deploy.png deleted file mode 100644 index 59bb3ef52..000000000 Binary files a/other_docs/Solana-docs/get-started/img/solana-get-started-build-and-deploy.png and /dev/null differ diff --git a/other_docs/Solana-docs/get-started/img/solana-get-started-import-on-playground.png b/other_docs/Solana-docs/get-started/img/solana-get-started-import-on-playground.png deleted file mode 100644 index cd90b00cb..000000000 Binary files a/other_docs/Solana-docs/get-started/img/solana-get-started-import-on-playground.png and /dev/null differ diff --git a/other_docs/Solana-docs/get-started/img/solana-get-started-successful-build.png b/other_docs/Solana-docs/get-started/img/solana-get-started-successful-build.png deleted file mode 100644 index 82b0a5df0..000000000 Binary files a/other_docs/Solana-docs/get-started/img/solana-get-started-successful-build.png and /dev/null differ diff --git a/other_docs/Solana-docs/writing-programs/README.md b/other_docs/Solana-docs/writing-programs/README.md deleted file mode 100644 index 95f370614..000000000 --- a/other_docs/Solana-docs/writing-programs/README.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -sidebar_position: 300 -sidebar_label: Writing Program -sidebar_class_name: green ---- - -# Overview of Writing Programs - -开发人员可以编写和部署自己的程序到Solana区块链上。虽然开发这些“链上”程序可能看起来很繁琐,但整个过程可以概括为几个关键步骤。 - -## Solana 开发生命周期 - -1. 设置您的开发环境 -2. 编写你的程序 -3. 编译程序 -4. 生成程序的公共地址 -5. 部署程序 - -### 1.设置您的开发环境 - -开始Solana开发的最可靠方法是在本地计算机上[安装Solana CLI](https://docs.solana.com/cli/install-solana-cli-tools)工具。这将为您提供最强大的开发环境。 - -一些开发者可能也会选择使用[Solana Playground](https://beta.solpg.io/),这是一个基于浏览器的集成开发环境。它可以让你在浏览器中编写、构建和部署链上程序,而无需进行任何安装。 - - -### 2. 编写你的程序 - -编写Solana程序最常用的方法是使用Rust语言。这些Rust程序实际上与创建传统的[Rust库](https://doc.rust-lang.org/rust-by-example/crates/lib.html)相同。 - -> 您可以在下面了解更多关于其他支持的语言的信息。 - -### 3. 编译程序 - -一旦程序编写完成,必须将其编译成Berkley Packet Filter字节码,然后部署到区块链上。 - -### 4. 生成程序的公共地址 - -使用[Solana CLI](https://docs.solana.com/cli/install-solana-cli-tools),开发者将为新程序生成一个新的唯一[Keypair](https://docs.solana.com/terminology#keypair)。来自该Keypair的公共地址(也称为[Pubkey](https://docs.solana.com/terminology#public-key-pubkey))将在链上用作程序的公共地址(也称为 [programId](https://docs.solana.com/terminology#program-id) )。 - -### 5. 部署程序 - -然后再次使用CLI,编译后的程序可以通过创建包含程序字节码的多个交易来部署到所选的区块链集群中。由于交易内存大小的限制,每个交易实际上以快速连续的方式将程序的小块发送到区块链。 - -一旦整个程序被发送到区块链,最后一笔交易将被发送以将所有缓冲的字节码写入程序的数据账户。这要么标记新程序为 [executable](https://docs.solana.com/developing/programming-model/accounts#executable) ,要么完成升级现有程序的过程(如果它已经存在)。 - - -## 支持的语言 - -Solana的程序通常使用Rust语言编写,但也支持C/C++。 - -还有各种社区驱动的努力,以使用其他编程语言来实现链上编程,包括: - -- 通过[Seahorse](https://seahorse-lang.org/)使用Python(它作为基于Rust的Anchor框架的包装器) - -## 示例程序 - -您还可以探索链上程序的示例,以了解更多实例。 - -## 限制 - -随着你深入进行程序开发,了解与链上程序相关的一些重要限制是很重要的。 - -请在“限制”页面上阅读更多详细信息 - -## 常见问题 - -了解其他开发者在编写/理解Solana程序方面经常遇到的问题。 diff --git a/other_docs/Solana-docs/writing-programs/developing-with-rust.md b/other_docs/Solana-docs/writing-programs/developing-with-rust.md deleted file mode 100644 index 49f762651..000000000 --- a/other_docs/Solana-docs/writing-programs/developing-with-rust.md +++ /dev/null @@ -1,271 +0,0 @@ ---- -sidebar_position: 301 -sidebar_label: 使用Rust开发 -sidebar_class_name: green ---- - -# 使用Rust开发 - -`Solana`支持使用`Rust`编程语言编写链上程序。 - -## 项目布局 - -`Solana`的`Rust`程序遵循典型的`Rust`项目布局: - -```bash -/inc/ -/src/ -/Cargo.toml -``` - -`Solana`的`Rust`程序可以直接依赖彼此,以便在进行跨程序调用时获得指令助手的访问权限。在这样做时,重要的是不要引入依赖程序的入口符号,因为它们可能与程序自身的符号冲突。为了避免这种情况,程序应该在 `Cargo.toml` 中定义一个 `no-entrypoint` 功能,并使用它来排除入口点。 - -- 定义特征 -- 排除入口点 - -然后,当其他程序将此程序作为依赖项时,应使用 `no-entrypoint` 功能进行操作。 - -- 包含无入口点 - -## 项目依赖 - -至少,`Solana Rust`程序必须引入`solana-program crate`。 - -`Solana SBF`程序有一些限制,可能会阻止某些`crate`作为依赖项的包含,或者需要特殊处理。 - -例如: - -- 需要的架构是官方工具链支持的子集。除非对该 `crate` 进行分叉并添加 `SBF` 到那些架构检查中,否则没有其他解决办法。 -- 创建箱子可能依赖于 `rand` ,而`Solana`的确定性程序环境不支持它。要包含一个依赖于 `rand` 的箱子,请参考依赖于`Rand`。 -- 即使程序本身没有包含堆栈溢出的代码,堆栈仍可能溢出。有关更多信息,请参考堆栈。 - -## 如何建造 - -首先设置环境: -- 从` https://rustup.rs/ 安装最新的稳定版`Rust`。 -- 安装最新的`Solana`命令行工具 - -可以使用正常的`Cargo`构建来构建针对您的主机机器的程序,这些程序可用于单元测试 - -```bash -$ cargo build -``` - -为`Solana SBF`目标构建一个特定的程序,比如`SPL Token`,可以部署到集群中 - -```bash -$ cd -$ cargo build-bpf -``` - -## 如何进行测试 - -`Solana`程序可以通过直接调用程序函数来进行传统的单元测试。 - -为了帮助开发人员在更接近实际集群的环境中进行测试,他们可以使用 `program-test crate`。该 `program-test crate`会启动一个本地运行时实例,并允许测试发送多个事务,同时在测试期间保持状态。 - -有关更多信息,请参阅`sysvar`示例中的测试,该测试显示了包含`sysvar`账户的指令如何被程序发送和处理。 - -## 程序入口点 - -程序导出一个已知的入口符号,`Solana`运行时在调用程序时查找并调用该符号。`Solana`支持多个版本的`BPF`加载器,入口点可能会因此而有所不同。程序必须为相同的加载器编写并部署。有关更多详细信息,请参阅加载器的常见问题解答部分。 - -目前有两个支持的加载器`BPF Loader`和`BPF`加载器已弃用 - -它们两个都有相同的原始入口点定义,下面是运行时查找并调用的原始符号: - -```rust -#[no_mangle] -pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64; -``` - -这个入口点接受一个通用的字节数组,其中包含序列化的程序参数(程序`ID`、账户、指令数据等)。为了反序列化参数,每个加载器都包含自己的包装宏,导出原始入口点,反序列化参数,调用用户定义的指令处理函数,并返回结果。 - -您可以在这里找到入口点宏: -- `BPF`加载器的入口宏 -- `BPF Loader`废弃的入口宏 - -程序定义的指令处理函数,入口点宏调用的函数必须符合以下形式: - -```rust -pub type ProcessInstruction = - fn(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult; -``` - -### 参数反序列化 - -每个加载器都提供了一个辅助函数,用于将程序的输入参数反序列化为`Rust`类型。入口点宏会自动调用反序列化辅助函数: - -- `BPF`加载器反序列化 -- `BPF`加载器已弃用反序列化 - -一些程序可能希望自行执行反序列化操作,可以通过提供自己的原始入口点实现。请注意,提供的反序列化函数会保留对序列化字节数组的引用,以便程序可以修改(`lamports`、账户数据)。这样做的原因是,加载器在返回时会读取这些修改,以便进行提交。如果程序实现了自己的反序列化函数,需要确保将程序希望提交的任何修改写回输入字节数组中。 - -有关加载程序如何序列化程序输入的详细信息,请参阅输入参数序列化文档。 - -### 数据类型 - -加载器的入口点宏使用以下参数调用程序定义的指令处理函数: - -```rust -program_id: &Pubkey, -accounts: &[AccountInfo], -instruction_data: &[u8] -``` - -程序`ID`是当前正在执行的程序的公钥。 - -账户是指令引用的账户的有序切片,并以`AccountInfo`结构表示。数组中账户的位置表示其含义,例如,在转移`lamports`时,指令可以将第一个账户定义为源账户,第二个账户定义为目标账户。 - -`AccountInfo` 结构的成员是只读的,除了 `lamports` 和 `data` 。根据运行时执行策略,程序可以修改这两个成员。这两个成员都受到 `Rust` 的 `RefCell` 构造的保护,因此在读取或写入它们时必须进行借用。原因是它们都指向原始输入字节数组,但是在 `accounts` 切片中可能有多个条目指向同一个账户。使用 RefCell 可以确保程序不会意外地通过多个 `AccountInfo` 结构对同一底层数据执行重叠的读取/写入操作。如果程序实现了自己的反序列化函数,应该注意适当处理重复的账户。 - -指令数据是正在处理的指令的通用字节数组。 - -## 堆 - -`Rust`程序通过定义自定义的堆直接实现 - -根据具体需求,程序可以根据自己的需要实现自定义堆。有关更多信息,请参考自定义堆示例。 - -## 限制 - -链上的`Rust`程序支持大部分`Rust`的`libstd`、`libcore`和`liballoc`,以及许多第三方的`crate`。 - -由于这些程序在资源受限、单线程环境下运行,并且具有确定性,因此存在一些限制 - -- 无法访问 - - `rand` - - `std::fs` - - `std::net` - - `std::future` - - `std::process` - - `std::sync` - - `std::task` - - `std::thread` - - `std::time` -- 有限访问: - - `std::hash` - - `std::os` -- `Bincode`在计算上非常昂贵,无论是在循环还是调用深度方面,都应该避免使用 -- 应避免使用字符串格式化,因为它也会消耗计算资源。 -- 不支持 `println!` , `print!` ,应使用 `Solana` 日志助手。 -- 运行时在处理一条指令时对程序执行的指令数量施加了限制。有关更多信息,请参阅[计算预算](https://docs.solana.com/developing/programming-model/runtime#compute-budget)。 - -## 根据`rand`的情况而定 - -程序受限于确定性运行,因此无法使用随机数。有时,一个程序可能依赖于一个依赖于 `rand` 的包,即使该程序并未使用任何随机数功能。如果一个程序依赖于 `rand` ,编译将失败,因为`Solana`不支持 `get-random` 。错误通常会显示如下: - -```bash -error: target is not supported, for more information see: https://docs.rs/getrandom/#unsupported-targets - --> /Users/jack/.cargo/registry/src/github.com-1ecc6299db9ec823/getrandom-0.1.14/src/lib.rs:257:9 - | -257 | / compile_error!("\ -258 | | target is not supported, for more information see: \ -259 | | https://docs.rs/getrandom/#unsupported-targets\ -260 | | "); - | |___________^ -``` - -为了解决这个依赖问题,请将以下依赖项添加到程序的 `Cargo.toml` 中: - -```toml -getrandom = { version = "0.1.14", features = ["dummy"] } -``` - -如果依赖于 `getrandom v0.2`,请添加: - -```toml -getrandom = { version = "0.2.2", features = ["custom"] } -``` - -## 记录 - -`Rust`的 `println!` 宏在计算上是昂贵的且不受支持。相反,提供了辅助宏 msg! 。 - -`msg!` 有两种形式: - -```rust -msg!("Hello, world!"); -``` -或者 -```rust -msg!(0_64, 1_64, 2_64, 3_64, 4_64); -``` - -两种形式都将结果输出到程序日志中。如果程序希望,它们可以通过使用 `format!` 来模拟 `println!` 。 - -```rust -msg!("Some variable: {:?}", variable); -``` - -**调试部分**包含有关使用程序日志的更多信息,**Rust示例**中包含了一个日志示例。 - -## 惊慌失措 - -`Rust`的 `panic!` 、 `assert!` 和内部`panic`结果默认会打印到程序日志中。 - -```bash -INFO solana_runtime::message_processor] Finalized account CGLhHSuWsp1gT4B7MY2KACqp9RUwQRhcUFfVSuxpSajZ -INFO solana_runtime::message_processor] Call SBF program CGLhHSuWsp1gT4B7MY2KACqp9RUwQRhcUFfVSuxpSajZ -INFO solana_runtime::message_processor] Program log: Panicked at: 'assertion failed: `(left == right)` - left: `1`, - right: `2`', rust/panic/src/lib.rs:22:5 -INFO solana_runtime::message_processor] SBF program consumed 5453 of 200000 units -INFO solana_runtime::message_processor] SBF program CGLhHSuWsp1gT4B7MY2KACqp9RUwQRhcUFfVSuxpSajZ failed: BPF program panicked -``` - -### 自定义恐慌处理程序 - -程序可以通过提供自己的实现来覆盖默认的`panic`处理程序。 - -首先在程序的 `Cargo.toml` 中定义 `custom-panic` 功能 - -```toml -[features] -default = ["custom-panic"] -custom-panic = [] -``` - -然后提供一个自定义的`panic`处理程序的实现: - -```rust -#[cfg(all(feature = "custom-panic", target_os = "solana"))] -#[no_mangle] -fn custom_panic(info: &core::panic::PanicInfo<'_>) { - solana_program::msg!("program custom panic enabled"); - solana_program::msg!("{}", info); -} -``` - -在上面的片段中,展示了默认的实现,但开发人员可以用更适合自己需求的内容来替换它。 - -默认支持完整的恐慌消息的副作用之一是程序会承担将更多的Rust实现引入程序共享对象的成本。典型的程序已经引入了相当数量的 `libstd` ,可能不会注意到共享对象大小的增加。但是,那些明确试图通过避免 `libstd` 来保持非常小的程序可能会受到显著影响(约`25kb`)。为了消除这种影响,程序可以提供自己的自定义恐慌处理程序,并进行空实现。 - -```rust -#[cfg(all(feature = "custom-panic", target_os = "solana"))] -#[no_mangle] -fn custom_panic(info: &core::panic::PanicInfo<'_>) { - // Do nothing to save space -} -``` - -## 计算预算 - -使用系统调用 [`sol_log_compute_units()`](https://github.com/solana-labs/solana/blob/d9b0fc0e3eec67dfe4a97d9298b15969b2804fab/sdk/program/src/log.rs#L141) 记录一个包含程序在执行停止之前可以消耗的剩余计算单元数量的消息 - -请查看[计算预算](https://docs.solana.com/developing/programming-model/runtime#compute-budget)以获取更多信息。 - -## ELF Dump - -`SBF`共享对象的内部可以`dump`到文本文件中,以更深入地了解程序的组成和运行时的操作。转储文件将包含ELF信息以及所有符号和实现它们的指令的列表。`BPF`加载器的一些错误日志消息将引用错误发生的特定指令编号。可以在`ELF`转储中查找这些引用,以确定有问题的指令及其上下文。 - -创建一个`dump`文件: - -```bash -$ cd -$ cargo build-bpf --dump -``` - -## 例子 - -[Solana程序库](https://github.com/solana-labs/solana-program-library/tree/master/examples/rust)的github仓库包含了一系列的Rust示例。 diff --git a/other_docs/ibc-protocol/README.md b/other_docs/ibc-protocol/README.md deleted file mode 100644 index 9b0bb0d0f..000000000 --- a/other_docs/ibc-protocol/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# IBC Protocol - -![banner](./assets/interchain-standards-image.jpg) - -## 概要 - -本仓库是跨链通信协议(IBC) 开发和文档的规范仓库。 - -本仓库应用于整合 IBC 的设计原理、协议语义和编码描述,包括核心传输、身份验证和排序层(IBC/TAO),以及描述数据包编码和处理语义的应用层 (IBC/APP)。 - -欢迎参与贡献。有关贡献指南,请参阅 [CONTRIBUTING.md](meta/CONTRIBUTING.md) 。 - -请参阅 [ROADMAP.md](meta/ROADMAP.md) 获取路线图最新公开版本。 - -## 跨链标准 - -处于或通过“草案”阶段的所有标准在此处按其 ICS 编号的顺序列出,并按类别排序。 - -### Meta - -Interchain Standard Number | Standard Title | Stage ---- | --- | --- -[1](spec/ics-001-ics-standard/README.md) | ICS Specification Standard | N/A - -### Core - -Interchain Standard Number | Standard Title | Stage ---- | --- | --- -[2](./spec/core/ics-002-client-semantics/README.md) | Client Semantics | Candidate -[3](./spec/core/ics-003-connection-semantics/README.md) | Connection Semantics | Candidate -[4](./spec/core/ics-004-channel-and-packet-semantics/README.md) | Channel & Packet Semantics | Candidate -[5](./spec/core/ics-005-port-allocation/README.md) | Port Allocation | Candidate -[23](./spec/core/ics-023-vector-commitments/README.md) | Vector Commitments | Candidate -[24](./spec/core/ics-024-host-requirements/README.md) | Host Requirements | Candidate -[25](./spec/core/ics-025-handler-interface/README.md) | Handler Interface | Candidate -[26](./spec/core/ics-026-routing-module/README.md) | Routing Module | Candidate - -### Client - -Interchain Standard Number | Standard Title | Stage ---- | --- | --- -[6](./spec/client/ics-006-solo-machine-client/README.md) | Solo Machine Client | Candidate -[7](./spec/client/ics-007-tendermint-client/README.md) | Tendermint Client | Candidate -[8](./spec/client/ics-008-wasm-client/README.md) | Wasm Client | Draft -[9](./spec/client/ics-009-loopback-client/README.md) | Loopback Client | Candidate -[10](./spec/client/ics-010-grandpa-client/README.md) | GRANDPA Client | Draft - -### Relayer - -Interchain Standard Number | Standard Title | Stage ---- | --- | --- -[18](./spec/relayer/ics-018-relayer-algorithms/README.md) | Relayer Algorithms | Candidate - -### App - -Interchain Standard Number | Standard Title | Stage ---- | --- | --- -[20](./spec/app/ics-020-fungible-token-transfer/README.md) | Fungible Token Transfer | Candidate -[27](./spec/app/ics-027-interchain-accounts/README.md) | Interchain Accounts | Draft -[29](./spec/app/ics-029-fee-payment) | General Relayer Incentivisation Mechanism | Candidate -[30](./spec/app/ics-030-middleware) | IBC Application Middleware | Candidate diff --git a/other_docs/ibc-protocol/archive/README.md b/other_docs/ibc-protocol/archive/README.md deleted file mode 100644 index ecd8d4901..000000000 --- a/other_docs/ibc-protocol/archive/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# IBC archive - -This directory contains previous versions of the IBC specification that have been archived for further reference. - -1. [IBC v0.3.1](./v0_3_1_IBC.pdf) -1. [2020-05 IBC paper](./papers/2020-05/build/paper.pdf) diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/Makefile b/other_docs/ibc-protocol/archive/papers/2020-05/Makefile deleted file mode 100644 index bf1e02e7c..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/Makefile +++ /dev/null @@ -1,28 +0,0 @@ -FILES = src/paper.md \ - src/metadata.yaml - -OUTPUT = build - -FLAGS = --bibliography=src/bibliography.bib \ - --csl=src/bibliography.csl \ - --filter pandoc-include \ - -s \ - -f markdown - -FLAGS_PDF = --template=src/template.latex - -all: pdf - -pdf: - pandoc -o $(OUTPUT)/paper.pdf $(FLAGS) $(FLAGS_PDF) $(FILES) - -wc: - wc src/*.md - -spellcheck: - find ./src -type f -name "*.md" -exec aspell -p ./aspell_dict -x -d en_GB -c {} \; - -clean: - rm -rf build/* - -.PHONY: all pdf wc clean diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/README.md b/other_docs/ibc-protocol/archive/papers/2020-05/README.md deleted file mode 100644 index 776e7f1b2..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/README.md +++ /dev/null @@ -1,13 +0,0 @@ -Drafting an IBC paper. See [draft](./build/paper.pdf). - -Submission options: -- [CES CfP (May 2020)](https://cryptoeconomicsystems.pubpub.org/spring20-journalcfp) - - Up to 15,000 words - - A4 style, submitted as PDF - - Exclusive submission only - - Remove author names from paper - - Pre-prints OK - - Prior work: - - https://assets.pubpub.org/asuzx3df/11581974880753.pdf - - https://assets.pubpub.org/a7tcrzro/31581340169699.pdf - - https://assets.pubpub.org/g84cchsz/11581340329396.pdf diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/aspell_dict b/other_docs/ibc-protocol/archive/papers/2020-05/aspell_dict deleted file mode 100644 index 30be6a0e2..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/aspell_dict +++ /dev/null @@ -1,127 +0,0 @@ -personal_ws-1.1 en 126 -Adi -Aditya -Aggarwal -Agoric -Anca -Axner -BFT -Buchman -ChanCloseConfirm -ChanCloseInit -ChanOpenAck -ChanOpenConfirm -ChanOpenInit -ChanOpenTry -ConnOpenAck -ConnOpenConfirm -ConnOpenInit -ConnOpenTry -DNS -Datagram -Dev -Ethereum -GmbH -Hibbert -HotStuff -IAVL -IBC -INIT -Jae -Juwoon -Kunze -Kwon -Manian -Meher -Merkle -Merklized -Nakamoto -Ojha -Polkadot's -QUIC -SDK -Seredinschi -Sripal -TCP -TRYOPEN -Tendermint -Tribble -UDP -UX -WASM -XCMP -Yun -Zaki -Zamfir -Zampolin -Zarko -backend -blockchain -blockchains -bytestring -channelOnB -channelOnD -computable -counterintuitive -counterparty -cryptographic -dataflow -datagram -datagrams -datastream -dba -denom -encodings -escrowed -escrowing -exfiltration -fungibility -incentivisation -instantiations -interblockchain -interchain -interoperating -interoperation -introspectable -lifecycle -liveness -loopback -md -mempools -onecolumn -pagebreak -permissioned -permissioning -permissionless -permissionlessly -portOnB -portOnD -pseudocode -recv -relayer -relayers -ruleset -sharded -sig -src -stateful -statefulness -subprotocol -subprotocols -texttt -thresholding -tokenholder -tokenise -tokenised -tokenising -topologies -transactional -twocolumn -unbonding -unescrowed -unescrows -unreceived -validator -verifier -vspace -whitepaper diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/build/paper.pdf b/other_docs/ibc-protocol/archive/papers/2020-05/build/paper.pdf deleted file mode 100644 index dba21c143..000000000 Binary files a/other_docs/ibc-protocol/archive/papers/2020-05/build/paper.pdf and /dev/null differ diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/LICENSE b/other_docs/ibc-protocol/archive/papers/2020-05/src/LICENSE deleted file mode 100644 index 6c59dbd0e..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016 Santos Gallegos - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/acknowledgements.md b/other_docs/ibc-protocol/archive/papers/2020-05/src/acknowledgements.md deleted file mode 100644 index 35f1d6848..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/acknowledgements.md +++ /dev/null @@ -1,5 +0,0 @@ -The original idea of IBC was first outlined in the Cosmos whitepaper [@cosmos_whitepaper], and realisation of the protocol is made possible in practice by the Byzantine-fault-tolerant consensus and efficient light client verification of Tendermint, introduced in *Tendermint: Consensus without Mining* [@tendermint_consensus_without_mining] and updated in *The latest gossip on BFT consensus* [@tendermint_latest_gossip]. An earlier version of the IBC specification [@ethan_frey_ibc_spec] was written by Ethan Frey. - -Many current and former employees of All in Bits (dba Tendermint Inc.), Agoric Systems, the Interchain Foundation, Informal Systems, and Interchain GmbH participated in brainstorming and reviews of the IBC protocol. Thanks are due in particular to Ethan Buchman, Jae Kwon, Ethan Frey, Juwoon Yun, Anca Zamfir, Zarko Milosevic, Zaki Manian, Aditya Sripal, Federico Kunze, Dean Tribble, Mark Miller, Brian Warner, Chris Hibbert, Michael FIG, Sunny Aggarwal, Dev Ojha, Colin Axner, and Jack Zampolin. Thanks also to Meher Roy at Chorus One. Thanks to Zaki Manian, Sam Hart, and Adi Seredinschi for reviewing this paper. - -This work was supported by the Interchain Foundation. diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/appendix-a.md b/other_docs/ibc-protocol/archive/papers/2020-05/src/appendix-a.md deleted file mode 100644 index 1d4fe0099..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/appendix-a.md +++ /dev/null @@ -1,115 +0,0 @@ -## Connection handshake - -### Initiating a handshake - -\vspace{3mm} - -```typescript -function connOpenInit( - identifier: Identifier, - desiredCounterpartyConnectionIdentifier: Identifier, - counterpartyPrefix: CommitmentPrefix, - clientIdentifier: Identifier, - counterpartyClientIdentifier: Identifier) { - abortTransactionUnless(validateConnectionIdentifier(identifier)) - abortTransactionUnless(provableStore.get(connectionPath(identifier)) == null) - state = INIT - connection = ConnectionEnd{state, desiredCounterpartyConnectionIdentifier, counterpartyPrefix, - clientIdentifier, counterpartyClientIdentifier, getCompatibleVersions()} - provableStore.set(connectionPath(identifier), connection) -} -``` - -### Responding to a handshake initiation - -\vspace{3mm} - -```typescript -function connOpenTry( - desiredIdentifier: Identifier, - counterpartyConnectionIdentifier: Identifier, - counterpartyPrefix: CommitmentPrefix, - counterpartyClientIdentifier: Identifier, - clientIdentifier: Identifier, - counterpartyVersions: string[], - proofInit: CommitmentProof, - proofConsensus: CommitmentProof, - proofHeight: uint64, - consensusHeight: uint64) { - abortTransactionUnless(validateConnectionIdentifier(desiredIdentifier)) - abortTransactionUnless(consensusHeight <= getCurrentHeight()) - expectedConsensusState = getConsensusState(consensusHeight) - expected = ConnectionEnd{INIT, desiredIdentifier, getCommitmentPrefix(), counterpartyClientIdentifier, - clientIdentifier, counterpartyVersions} - version = pickVersion(counterpartyVersions) - connection = ConnectionEnd{TRYOPEN, counterpartyConnectionIdentifier, counterpartyPrefix, - clientIdentifier, counterpartyClientIdentifier, version} - abortTransactionUnless( - connection.verifyConnectionState(proofHeight, proofInit, counterpartyConnectionIdentifier, expected)) - abortTransactionUnless(connection.verifyClientConsensusState( - proofHeight, proofConsensus, counterpartyClientIdentifier, consensusHeight, expectedConsensusState)) - previous = provableStore.get(connectionPath(desiredIdentifier)) - abortTransactionUnless( - (previous === null) || - (previous.state === INIT && - previous.counterpartyConnectionIdentifier === counterpartyConnectionIdentifier && - previous.counterpartyPrefix === counterpartyPrefix && - previous.clientIdentifier === clientIdentifier && - previous.counterpartyClientIdentifier === counterpartyClientIdentifier && - previous.version === version)) - identifier = desiredIdentifier - provableStore.set(connectionPath(identifier), connection) -} -``` - -### Acknowledging the response - -\vspace{3mm} - -```typescript -function connOpenAck( - identifier: Identifier, - version: string, - proofTry: CommitmentProof, - proofConsensus: CommitmentProof, - proofHeight: uint64, - consensusHeight: uint64) { - abortTransactionUnless(consensusHeight <= getCurrentHeight()) - connection = provableStore.get(connectionPath(identifier)) - abortTransactionUnless(connection.state === INIT || connection.state === TRYOPEN) - expectedConsensusState = getConsensusState(consensusHeight) - expected = ConnectionEnd{TRYOPEN, identifier, getCommitmentPrefix(), - connection.counterpartyClientIdentifier, connection.clientIdentifier, - version} - abortTransactionUnless(connection.verifyConnectionState(proofHeight, proofTry, - connection.counterpartyConnectionIdentifier, expected)) - abortTransactionUnless(connection.verifyClientConsensusState( - proofHeight, proofConsensus, connection.counterpartyClientIdentifier, - consensusHeight, expectedConsensusState)) - connection.state = OPEN - abortTransactionUnless(getCompatibleVersions().indexOf(version) !== -1) - connection.version = version - provableStore.set(connectionPath(identifier), connection) -} -``` - -### Finalising the connection - -\vspace{3mm} - -```typescript -function connOpenConfirm( - identifier: Identifier, - proofAck: CommitmentProof, - proofHeight: uint64) { - connection = provableStore.get(connectionPath(identifier)) - abortTransactionUnless(connection.state === TRYOPEN) - expected = ConnectionEnd{OPEN, identifier, getCommitmentPrefix(), - connection.counterpartyClientIdentifier, - connection.clientIdentifier, connection.version} - abortTransactionUnless(connection.verifyConnectionState( - proofHeight, proofAck, connection.counterpartyConnectionIdentifier, expected)) - connection.state = OPEN - provableStore.set(connectionPath(identifier), connection) -} -``` diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/appendix-b.md b/other_docs/ibc-protocol/archive/papers/2020-05/src/appendix-b.md deleted file mode 100644 index 3966d962b..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/appendix-b.md +++ /dev/null @@ -1,205 +0,0 @@ -## Channel handshake - -### Initiating a handshake - -\vspace{3mm} - -```typescript -function chanOpenInit( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - version: string): CapabilityKey { - abortTransactionUnless(validateChannelIdentifier(portIdentifier, channelIdentifier)) - abortTransactionUnless(connectionHops.length === 1) - abortTransactionUnless(provableStore.get(channelPath(portIdentifier, channelIdentifier)) === null) - connection = provableStore.get(connectionPath(connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(authenticateCapability(portPath(portIdentifier), portCapability)) - channel = ChannelEnd{INIT, order, counterpartyPortIdentifier, - counterpartyChannelIdentifier, connectionHops, version} - provableStore.set(channelPath(portIdentifier, channelIdentifier), channel) - channelCapability = newCapability(channelCapabilityPath(portIdentifier, channelIdentifier)) - provableStore.set(nextSequenceSendPath(portIdentifier, channelIdentifier), 1) - provableStore.set(nextSequenceRecvPath(portIdentifier, channelIdentifier), 1) - provableStore.set(nextSequenceAckPath(portIdentifier, channelIdentifier), 1) - return channelCapability -} -``` - -### Responding to a handshake initiation - -\vspace{3mm} - -```typescript -function chanOpenTry( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - version: string, - counterpartyVersion: string, - proofInit: CommitmentProof, - proofHeight: uint64): CapabilityKey { - abortTransactionUnless(validateChannelIdentifier(portIdentifier, channelIdentifier)) - abortTransactionUnless(connectionHops.length === 1) - previous = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless( - (previous === null) || - (previous.state === INIT && - previous.order === order && - previous.counterpartyPortIdentifier === counterpartyPortIdentifier && - previous.counterpartyChannelIdentifier === counterpartyChannelIdentifier && - previous.connectionHops === connectionHops && - previous.version === version) - ) - abortTransactionUnless(authenticateCapability(portPath(portIdentifier), portCapability)) - connection = provableStore.get(connectionPath(connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - expected = ChannelEnd{INIT, order, portIdentifier, - channelIdentifier, - [connection.counterpartyConnectionIdentifier], - counterpartyVersion} - abortTransactionUnless(connection.verifyChannelState( - proofHeight, - proofInit, - counterpartyPortIdentifier, - counterpartyChannelIdentifier, - expected - )) - channel = ChannelEnd{TRYOPEN, order, counterpartyPortIdentifier, - counterpartyChannelIdentifier, connectionHops, version} - provableStore.set(channelPath(portIdentifier, channelIdentifier), channel) - channelCapability = newCapability(channelCapabilityPath(portIdentifier, channelIdentifier)) - provableStore.set(nextSequenceSendPath(portIdentifier, channelIdentifier), 1) - provableStore.set(nextSequenceRecvPath(portIdentifier, channelIdentifier), 1) - provableStore.set(nextSequenceAckPath(portIdentifier, channelIdentifier), 1) - return channelCapability -} -``` - -### Acknowledging the response - -\vspace{3mm} - -```typescript -function chanOpenAck( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyVersion: string, - proofTry: CommitmentProof, - proofHeight: uint64) { - channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless(channel.state === INIT || channel.state === TRYOPEN) - abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability)) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - expected = ChannelEnd{TRYOPEN, channel.order, portIdentifier, - channelIdentifier, - [connection.counterpartyConnectionIdentifier], - counterpartyVersion} - abortTransactionUnless(connection.verifyChannelState( - proofHeight, - proofTry, - channel.counterpartyPortIdentifier, - channel.counterpartyChannelIdentifier, - expected - )) - channel.state = OPEN - channel.version = counterpartyVersion - provableStore.set(channelPath(portIdentifier, channelIdentifier), channel) -} -``` - -### Finalising a channel - -\vspace{3mm} - -```typescript -function chanOpenConfirm( - portIdentifier: Identifier, - channelIdentifier: Identifier, - proofAck: CommitmentProof, - proofHeight: uint64) { - channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state === TRYOPEN) - abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability)) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - expected = ChannelEnd{OPEN, channel.order, portIdentifier, - channelIdentifier, - [connection.counterpartyConnectionIdentifier], - channel.version} - abortTransactionUnless(connection.verifyChannelState( - proofHeight, - proofAck, - channel.counterpartyPortIdentifier, - channel.counterpartyChannelIdentifier, - expected - )) - channel.state = OPEN - provableStore.set(channelPath(portIdentifier, channelIdentifier), channel) -} -``` - -### Initiating channel closure - -\vspace{3mm} - -```typescript -function chanCloseInit( - portIdentifier: Identifier, - channelIdentifier: Identifier) { - abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability)) - channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state !== CLOSED) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - channel.state = CLOSED - provableStore.set(channelPath(portIdentifier, channelIdentifier), channel) -} -``` - -### Confirming channel closure - -\vspace{3mm} - -```typescript -function chanCloseConfirm( - portIdentifier: Identifier, - channelIdentifier: Identifier, - proofInit: CommitmentProof, - proofHeight: uint64) { - abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability)) - channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state !== CLOSED) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - expected = ChannelEnd{CLOSED, channel.order, portIdentifier, - channelIdentifier, - [connection.counterpartyConnectionIdentifier], - channel.version} - abortTransactionUnless(connection.verifyChannelState( - proofHeight, - proofInit, - channel.counterpartyPortIdentifier, - channel.counterpartyChannelIdentifier, - expected - )) - channel.state = CLOSED - provableStore.set(channelPath(portIdentifier, channelIdentifier), channel) -} -``` diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/appendix-c.md b/other_docs/ibc-protocol/archive/papers/2020-05/src/appendix-c.md deleted file mode 100644 index 61912d3c3..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/appendix-c.md +++ /dev/null @@ -1,212 +0,0 @@ -## Packet Handling - -### Sending a packet - -\vspace{3mm} - -```typescript -function sendPacket(packet: Packet) { - channel = provableStore.get(channelPath(packet.sourcePort, packet.sourceChannel)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state !== CLOSED) - abortTransactionUnless(authenticateCapability( - channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability)) - abortTransactionUnless(packet.destPort === channel.counterpartyPortIdentifier) - abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - latestClientHeight = provableStore.get(clientPath(connection.clientIdentifier)).latestClientHeight() - abortTransactionUnless(packet.timeoutHeight === 0 || latestClientHeight < packet.timeoutHeight) - nextSequenceSend = provableStore.get(nextSequenceSendPath(packet.sourcePort, packet.sourceChannel)) - abortTransactionUnless(packet.sequence === nextSequenceSend) - nextSequenceSend = nextSequenceSend + 1 - provableStore.set(nextSequenceSendPath(packet.sourcePort, packet.sourceChannel), nextSequenceSend) - provableStore.set(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence), - hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp)) -} -``` - -### Receiving a packet - -\vspace{3mm} - -```typescript -function recvPacket( - packet: OpaquePacket, - proof: CommitmentProof, - proofHeight: uint64, - acknowledgement: bytes): Packet { - channel = provableStore.get(channelPath(packet.destPort, packet.destChannel)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state === OPEN) - abortTransactionUnless( - authenticateCapability(channelCapabilityPath(packet.destPort, packet.destChannel), capability)) - abortTransactionUnless(packet.sourcePort === channel.counterpartyPortIdentifier) - abortTransactionUnless(packet.sourceChannel === channel.counterpartyChannelIdentifier) - abortTransactionUnless(provableStore.get(packetAcknowledgementPath(packet.destPort, - packet.destChannel, packet.sequence) === null)) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - abortTransactionUnless(packet.timeoutHeight === 0 || getConsensusHeight() < packet.timeoutHeight) - abortTransactionUnless(packet.timeoutTimestamp === 0 || currentTimestamp() < packet.timeoutTimestamp) - abortTransactionUnless(connection.verifyPacketData( - proofHeight, - proof, - packet.sourcePort, - packet.sourceChannel, - packet.sequence, - concat(packet.data, packet.timeoutHeight, packet.timeoutTimestamp) - )) - if (acknowledgement.length > 0 || channel.order === UNORDERED) - provableStore.set( - packetAcknowledgementPath(packet.destPort, packet.destChannel, packet.sequence), - hash(acknowledgement) - ) - if (channel.order === ORDERED) { - nextSequenceRecv = provableStore.get(nextSequenceRecvPath(packet.destPort, packet.destChannel)) - abortTransactionUnless(packet.sequence === nextSequenceRecv) - nextSequenceRecv = nextSequenceRecv + 1 - provableStore.set(nextSequenceRecvPath(packet.destPort, packet.destChannel), nextSequenceRecv) - } - return packet -} -``` - -### Acknowledging a packet - -\vspace{3mm} - -```typescript -function acknowledgePacket( - packet: OpaquePacket, - acknowledgement: bytes, - proof: CommitmentProof, - proofHeight: uint64): Packet { - channel = provableStore.get(channelPath(packet.sourcePort, packet.sourceChannel)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state === OPEN) - abortTransactionUnless(authenticateCapability( - channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability)) - abortTransactionUnless(packet.destPort === channel.counterpartyPortIdentifier) - abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourcePort, - packet.sourceChannel, packet.sequence)) - === hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp)) - abortTransactionUnless(connection.verifyPacketAcknowledgement( - proofHeight, - proof, - packet.destPort, - packet.destChannel, - packet.sequence, - acknowledgement - )) - if (channel.order === ORDERED) { - nextSequenceAck = provableStore.get(nextSequenceAckPath(packet.sourcePort, packet.sourceChannel)) - abortTransactionUnless(packet.sequence === nextSequenceAck) - nextSequenceAck = nextSequenceAck + 1 - provableStore.set(nextSequenceAckPath(packet.sourcePort, packet.sourceChannel), nextSequenceAck) - } - provableStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) - return packet -} -``` - -### Handling a timed-out packet - -\vspace{3mm} - -```typescript -function timeoutPacket( - packet: OpaquePacket, - proof: CommitmentProof, - proofHeight: uint64, - nextSequenceRecv: Maybe): Packet { - channel = provableStore.get(channelPath(packet.sourcePort, packet.sourceChannel)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state === OPEN) - abortTransactionUnless(authenticateCapability( - channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability)) - abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(packet.destPort === channel.counterpartyPortIdentifier) - abortTransactionUnless( - (packet.timeoutHeight > 0 && proofHeight >= packet.timeoutHeight) || - (packet.timeoutTimestamp > 0 && - connection.getTimestampAtHeight(proofHeight) > packet.timeoutTimestamp)) - abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourcePort, - packet.sourceChannel, packet.sequence)) - === hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp)) - if channel.order === ORDERED { - abortTransactionUnless(nextSequenceRecv <= packet.sequence) - abortTransactionUnless(connection.verifyNextSequenceRecv( - proofHeight, - proof, - packet.destPort, - packet.destChannel, - nextSequenceRecv - )) - } else - abortTransactionUnless(connection.verifyPacketAcknowledgementAbsence( - proofHeight, - proof, - packet.destPort, - packet.destChannel, - packet.sequence - )) - provableStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) - if channel.order === ORDERED { - channel.state = CLOSED - provableStore.set(channelPath(packet.sourcePort, packet.sourceChannel), channel) - } - return packet -} -``` - -### Cleaning up packet data - -\vspace{3mm} - -```typescript -function cleanupPacket( - packet: OpaquePacket, - proof: CommitmentProof, - proofHeight: uint64, - nextSequenceRecvOrAcknowledgement: Either): Packet { - channel = provableStore.get(channelPath(packet.sourcePort, packet.sourceChannel)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state === OPEN) - abortTransactionUnless(authenticateCapability( - channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability)) - abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(packet.destPort === channel.counterpartyPortIdentifier) - abortTransactionUnless(nextSequenceRecv > packet.sequence) - abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourcePort, - packet.sourceChannel, packet.sequence)) - === hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp)) - if channel.order === ORDERED - abortTransactionUnless(connection.verifyNextSequenceRecv( - proofHeight, - proof, - packet.destPort, - packet.destChannel, - nextSequenceRecvOrAcknowledgement - )) - else - abortTransactionUnless(connection.verifyPacketAcknowledgement( - proofHeight, - proof, - packet.destPort, - packet.destChannel, - packet.sequence, - nextSequenceRecvOrAcknowledgement - )) - provableStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) - return packet -} -``` diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/bibliography.bib b/other_docs/ibc-protocol/archive/papers/2020-05/src/bibliography.bib deleted file mode 100644 index 7e6d2c970..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/bibliography.bib +++ /dev/null @@ -1,214 +0,0 @@ -@misc{cosmos_whitepaper, - title = {Cosmos: A Network of Distributed Ledgers}, - timestamp = {2016-09-21T00:00:00Z}, - urldate = {2020-05-25T00:00:00Z}, - howpublished = {\url{https://cosmos.network/cosmos-whitepaper.pdf}}, - author = {{Jae Kwon, Ethan Buchman}}, - month = sep, - year = {2016} -} - -@misc{tendermint_consensus_without_mining, - title = {Tendermint: Consensus without Mining}, - timestamp = {}, - urldate = {2020-05-25T00:00:00Z}, - howpublished = {\url{https://tendermint.com/static/docs/tendermint.pdf}}, - author = {{Jae Kwon}}, - month = sep, - year = {2014} -} - -@misc{tendermint_latest_gossip, - title = {The latest gossip on BFT consensus}, - timestamp = {2019-10-22T00:00:00Z}, - urldate = {2020-05-25T00:00:00Z}, - howpublished = {\url{https://arxiv.org/pdf/1807.04938}}, - author = {{Ethan Buchman, Jae Kwon, Zarko Milosevic}}, - month = nov, - year = {2019} -} - -@misc{ethan_frey_ibc_spec, - title = {IBC Protocol Specification v0.3.1}, - timestamp = {2017-10-30T00:00:00Z}, - urldate = {2020-05-25T00:00:00Z}, - howpublished = {\url{https://github.com/cosmos/ics/blob/master/archive/v0_3_1_IBC.pdf}}, - author = {{Ethan Frey}}, - month = nov, - year = {2017} -} - -@misc{hard_problems_sharding_part_one, - title = {The Authoritative Guide to Blockchain Sharding: Part 1}, - timestamp = {2018-12-05T00:00:00Z}, - urldate = {2020-05-25T00:00:00Z}, - howpublished = {\url{https://medium.com/nearprotocol/the-authoritative-guide-to-blockchain-sharding-part-1-1b53ed31e060}}, - author = {{Near Protocol}}, - month = {dec}, - year = {2018} -} - -@misc{hard_problems_sharding_part_two, - title = {The Authoritative Guide to Blockchain Sharding: Part 2}, - timestamp = {2018-12-12T00:00:00Z}, - urldate = {2020-05-25T00:00:00Z}, - howpublished = {\url{https://medium.com/nearprotocol/unsolved-problems-in-blockchain-sharding-2327d6517f43}}, - author = {{Near Protocol}}, - month = {dec}, - year = {2018} -} - -@misc{ibc_cosmos_sdk, - title = {The Cosmos SDK: x/ibc}, - timestamp = {2020-05-25T00:00:00Z}, - urldate = {2020-05-25T00:00:00Z}, - howpublished = {\url{https://github.com/cosmos/cosmos-sdk/tree/master/x/ibc}}, - author = {{Cosmos SDK Contributors}}, - month = {may}, - year = {2020} -} - -@misc{ibc_rust, - title = {Rust implementation of IBC modules and relayer}, - timestamp = {2020-05-25T00:00:00Z}, - urldate = {2020-05-25T00:00:00Z}, - howpublished = {\url{https://github.com/informalsystems/ibc-rs}}, - author = {{Informal Systems}}, - month = {may}, - year = {2020} -} - -@misc{relayer_go, - title = {Server-side IBC relayer}, - timestamp = {2020-05-25T00:00:00Z}, - urldate = {2020-05-25T00:00:00Z}, - howpublished = {\url{https://github.com/iqlusioninc/relayer}}, - author = {{Iqlusion}}, - month = {may}, - year = {2020} -} - -@misc{game_of_zones, - title = {Game of Zones}, - timestamp = {2020-05-25T00:00:00Z}, - urldate = {2020-05-25T00:00:00Z}, - howpublished = {\url{https://goz.cosmosnetwork.dev/}}, - author = {{GoZ Contributors}}, - month = {may}, - year = {2020} -} - -@misc{map_of_zones, - title = {Map of Zones}, - timestamp = {2020-05-25T00:00:00Z}, - urldate = {2020-05-25T00:00:00Z}, - howpublished = {\url{https://mapofzones.com/}}, - author = {{Bitquasar & Ztake}}, - month = {may}, - year = {2020} -} - -@misc{grandpa_consensus, - title = {GRANDPA Finality Gadget}, - timestamp = {2020-05-02T00:00:00Z}, - urldate = {2020-05-25T00:00:00Z}, - howpublished = {\url{https://github.com/w3f/consensus/blob/master/pdf/grandpa.pdf}}, - author = {{Alistair Stewart}}, - month = {may}, - year = {2020} -} - -@misc{polkadot_xcmp, - title = {Web3 Foundation Research: XCMP}, - timestamp = {2020-05-01T00:00:00Z}, - urldate = {2020-05-25T00:00:00Z}, - howpublished = {\url{https://research.web3.foundation/en/latest/polkadot/XCMP.html}}, - author = {{Alistair Stewart and Fatemeh Shirazi and Leon Groot Bruinderink}}, - month = {may}, - year = {2020} -} - -@misc{hotstuff_consensus, - urldate = {2020-05-25T00:00:00Z}, - howpublished = {\url{https://arxiv.org/pdf/1803.05069}}, - author = {Maofan Yin and Dahlia Malkhi and Michael K. Reiter and Guy Golan Gueta and Ittai Abraham}, - title = {HotStuff: BFT Consensus in the Lens of Blockchain}, - year = {2018}, - eprint = {arXiv:1803.05069}, -} - -@misc{iavl_plus_tree, - urldate = {2020-05-25T00:00:00Z}, - howpublished = {\url{https://github.com/tendermint/iavl}}, - author = {Tendermint}, - title = {IAVL+ Tree: A versioned, snapshottable (immutable) AVL+ tree for persistent data.}, - year = {2020} -} - -@misc{patricia_tree, - urldate = {2020-05-25T00:00:00Z}, - author = {Ethereum}, - howpublished = {\url{https://github.com/ethereum/wiki/wiki/Patricia-Tree}}, - title = {Ethereum Modified Merkle Patricia Trie Specification}, - year = {2020} -} - -@misc{object_capabilities, - author = {Chip Morningstar}, - howpublished = {\url{http://habitatchronicles.com/2017/05/what-are-capabilities/}}, - title = {What Are Capabilities?}, - year = {2017}, - urldate = {2020-05-25T00:00:00Z} -} - -@misc{coda_protocol, - author = {Izaak Meckler and Evan Shapiro}, - howpublished = {\url{https://cdn.codaprotocol.com/v2/static/coda-whitepaper-05-10-2018-0.pdf}}, - title = {Coda: Decentralized cryptocurrency at scale}, - year = {2018}, - urldate = {2020-05-25T00:00:00Z} -} - -@misc{substrate, - author = {Parity Technologies}, - howpublished = {\url{https://github.com/paritytech/substrate}}, - title = {Substrate: The platform for blockchain innovators}, - year = {2020}, - urldate = {2020-05-25T00:00:00Z} -} - -@misc{bitcoin, - added-at = {2014-04-17T08:33:06.000+0200}, - author = {Nakamoto, Satoshi}, - biburl = {https://www.bibsonomy.org/bibtex/23db66df0fc9fa2b5033f096a901f1c36/ngnn}, - interhash = {423c2cdff70ba0cd0bca55ebb164d770}, - intrahash = {3db66df0fc9fa2b5033f096a901f1c36}, - keywords = {imported}, - timestamp = {2014-04-17T08:33:06.000+0200}, - title = {Bitcoin: A peer-to-peer electronic cash system}, - url = {http://www.bitcoin.org/bitcoin.pdf}, - year = 2009 -} - -@misc{rfc793, - series = {Request for Comments}, - number = 793, - howpublished = {RFC 793}, - publisher = {RFC Editor}, - doi = {10.17487/RFC0793}, - url = {https://rfc-editor.org/rfc/rfc793.txt}, - author = {}, - title = {{Transmission Control Protocol}}, - pagetotal = 91, - year = 1981, - month = sep, - abstract = {}, -} - -@misc{ethereum_2_cross_shard, - title = {Ethereum Sharding Research Compendium: Cross-shard communication}, - year = 2020, - month = march, - author = {Ethereum 2.0 Contributors}, - howpublished = {\url{https://notes.ethereum.org/@serenity/H1PGqDhpm?type=view#Cross-shard-communication}} -} diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/bibliography.csl b/other_docs/ibc-protocol/archive/papers/2020-05/src/bibliography.csl deleted file mode 100644 index 9d967b0bf..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/bibliography.csl +++ /dev/null @@ -1,339 +0,0 @@ - - \ No newline at end of file diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/channels.md b/other_docs/ibc-protocol/archive/papers/2020-05/src/channels.md deleted file mode 100644 index 5d70071dc..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/channels.md +++ /dev/null @@ -1,319 +0,0 @@ -The *channel* abstraction provides message delivery semantics to the interblockchain communication protocol in three categories: ordering, exactly-once delivery, and module permissioning. A channel serves as a conduit for packets passing between a module on one ledger and a module on another, ensuring that packets are executed only once, delivered in the order in which they were sent (if necessary), and delivered only to the corresponding module owning the other end of the channel on the destination ledger. Each channel is associated with a particular connection, and a connection may have any number of associated channels, allowing the use of common identifiers and amortising the cost of header verification across all the channels utilising a connection and light client. - -Channels are payload-agnostic. The modules which send and receive IBC 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 transactions to apply according to what data the packet contains. - -\vspace{3mm} - -### Motivation - -\vspace{3mm} - -The interblockchain communication protocol uses a cross-ledger message passing model. IBC *packets* are relayed from one ledger to the other by external relayer processes. Two ledgers, A and B, confirm new blocks independently, and packets from one ledger to the other may be delayed, censored, or re-ordered arbitrarily. Packets are visible to relayers and can be read from a ledger by any relayer process and submitted to any other ledger. - -The IBC protocol must provide ordering (for ordered channels) and exactly-once delivery guarantees to allow applications to reason about the combined state of connected modules on two ledgers. For example, an application may wish to allow a single tokenised asset to be transferred between and held on multiple ledgers while preserving fungibility and conservation of supply. The application can mint asset vouchers on ledger B when a particular IBC packet is committed to ledger B, and require outgoing sends of that packet on ledger A to escrow an equal amount of the asset on ledger A until the vouchers are later redeemed back to ledger 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 ledgers and that any vouchers minted on ledger B can later be redeemed back to ledger A. A more detailed explanation of this example is provided later on. - -\vspace{3mm} - -### Definitions - -\vspace{3mm} - -A *channel* is a pipeline for exactly-once packet delivery between specific modules on separate ledgers, which has at least one end capable of sending packets and one end capable of receiving packets. - -An *ordered* channel is a channel where packets are delivered exactly in the order which they were sent. - -An *unordered* channel is a channel where packets can be delivered in any order, which may differ from the order in which they were sent. - -All channels provide exactly-once packet delivery, meaning that a packet sent on one end of a channel is delivered no more and no less than once, eventually, to the other end. - -A *channel end* is a data structure storing metadata associated with one end of a channel on one of the participating ledgers, defined as follows: - -```typescript -interface ChannelEnd { - state: ChannelState - ordering: ChannelOrder - counterpartyPortIdentifier: Identifier - counterpartyChannelIdentifier: Identifier - nextSequenceSend: uint64 - nextSequenceRecv: uint64 - nextSequenceAck: uint64 - connectionHops: [Identifier] - version: string -} -``` - -- The `state` is the current state of the channel end. -- The `ordering` field indicates whether the channel is ordered or unordered. This is an enumeration instead of a boolean in order to allow additional kinds of ordering to be easily supported in the future. -- The `counterpartyPortIdentifier` identifies the port on the counterparty ledger which owns the other end of the channel. -- The `counterpartyChannelIdentifier` identifies the channel end on the counterparty ledger. -- The `nextSequenceSend`, stored separately, tracks the sequence number for the next packet to be sent. -- The `nextSequenceRecv`, stored separately, tracks the sequence number for the next packet to be received. -- The `nextSequenceAck`, stored separately, tracks the sequence number for the next packet to be acknowledged. -- The `connectionHops` stores the list of connection identifiers, in order, along which packets sent on this channel will travel. At the moment this list must be of length 1. In the future multi-hop channels may be supported. -- The `version` string stores an opaque channel version, which is agreed upon during the handshake. This can determine module-level configuration such as which packet encoding is used for the channel. This version is not used by the core IBC protocol. - -Channel ends have a *state*: - -```typescript -enum ChannelState { - INIT, - TRYOPEN, - OPEN, - CLOSED, -} -``` - -- A channel end in `INIT` state has just started the opening handshake. -- A channel end in `TRYOPEN` state has acknowledged the handshake step on the counterparty ledger. -- A channel end in `OPEN` state has completed the handshake and is ready to send and receive packets. -- A channel end in `CLOSED` state has been closed and can no longer be used to send or receive packets. - -A `Packet`, encapsulating opaque data to be transferred from one module to another over a channel, is a particular interface defined as follows: - -```typescript -interface Packet { - sequence: uint64 - timeoutHeight: uint64 - timeoutTimestamp: uint64 - sourcePort: Identifier - sourceChannel: Identifier - destPort: Identifier - destChannel: Identifier - data: bytes -} -``` - -- The `sequence` number corresponds to the order of sends and receives, where a packet with an earlier sequence number must be sent and received before a packet with a later sequence number. -- The `timeoutHeight` indicates a consensus height on the destination ledger after which the packet will no longer be processed, and will instead count as having timed-out. -- The `timeoutTimestamp` indicates a timestamp on the destination ledger after which the packet will no longer be processed, and will instead count as having timed-out. -- The `sourcePort` identifies the port on the sending ledger. -- The `sourceChannel` identifies the channel end on the sending ledger. -- The `destPort` identifies the port on the receiving ledger. -- The `destChannel` identifies the channel end on the receiving ledger. -- The `data` is an opaque value which can be defined by the application logic of the associated modules. - -Note that a `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 IBC handler. - -\vspace{3mm} - -### Properties - -\vspace{3mm} - -#### Efficiency - -As channels impose no flow control of their own, the speed of packet transmission and confirmation is limited only by the speed of the underlying ledgers. - -\vspace{3mm} - -#### Exactly-once delivery - -IBC packets sent on one end of a channel are delivered no more than exactly once to the other end. -No network synchrony assumptions are required for exactly-once safety. -If one or both of the ledgers halt, packets may be delivered no more than once, and once the ledgers resume packets will be able to flow again. - -\vspace{3mm} - -#### Ordering - -On ordered channels, packets are be sent and received in the same order: if packet `x` is sent before packet `y` by a channel end on ledger A, packet `x` will be received before packet `y` by the corresponding channel end on ledger B. - -On unordered channels, packets may be sent and received in any order. Unordered packets, like ordered packets, have individual timeouts specified in terms of the destination ledger's height or timestamp. - -\vspace{3mm} - -#### Permissioning - -Channels are permissioned to one module on each end, determined during the handshake and immutable afterwards (higher-level logic could tokenise channel ownership by tokenising ownership of the port). -Only the module which owns the port associated with a channel end is able to send or receive on the channel. - -\vspace{3mm} - -### Channel lifecycle management - -\vspace{3mm} - -#### Opening handshake - -The channel opening handshake, between two ledgers `A` and `B`, with state formatted as `(A, B)`, flows as follows: - -| Datagram | Prior state | Posterior state | -| ---------------- | --------------- | ---------------- | -| `ChanOpenInit` | `(-, -)` | `(INIT, -)` | -| `ChanOpenTry` | `(INIT, -)` | `(INIT, TRYOPEN)` | -| `ChanOpenAck` | `(INIT, TRYOPEN)` | `(OPEN, TRYOPEN)` | -| `ChanOpenConfirm` | `(OPEN, TRYOPEN)` | `(OPEN, OPEN)` | - -`ChanOpenInit`, executed on ledger A, initiates a channel opening handshake from a module on ledger A to a module on ledger B, -providing the identifiers of the local channel identifier, local port, remote port, and remote channel identifier. ledger A -stores a channel end object in its state. - -`ChanOpenTry`, executed on ledger B, relays notice of a channel handshake attempt to the module on ledger B, providing the -pair of channel identifiers, a pair of port identifiers, and a desired version. ledger B verifies a proof that ledger A has stored these identifiers -as claimed, looks up the module which owns the destination port, calls that module to check that the version requested is compatible, -and stores a channel end object in its state. - -`ChanOpenAck`, executed on ledger A, relays acceptance of a channel handshake attempt back to the module on ledger A, -providing the identifier which can now be used to look up the channel end. ledger A verifies a proof that ledger B -has stored the channel metadata as claimed and marks its end of the channel as `OPEN`. - -`ChanOpenConfirm`, executed on ledger B, confirms opening of a channel from ledger A to ledger B. -Ledger B simply checks that ledger A has executed `ChanOpenAck` and marked the channel as `OPEN`. -Ledger B subsequently marks its end of the channel as `OPEN`. After execution of `ChanOpenConfirm`, -the channel is open on both ends and can be used immediately. - -When the opening handshake is complete, the module which initiates the handshake will own the end of the created channel on the host ledger, and the counterparty module which -it specifies will own the other end of the created channel on the counterparty ledger. Once a channel is created, ownership can only be changed by changing ownership of the associated ports. - -\vspace{3mm} - -#### Versioning - -During the handshake process, two ends of a channel come to agreement on a version bytestring associated -with that channel. The contents of this version bytestring are opaque to the IBC core protocol. -Host ledgers may utilise the version data to indicate supported application-layer protocols, agree on packet -encoding formats, or negotiate other channel-related metadata related to custom logic on top of IBC. -Host ledgers may also safely ignore the version data or specify an empty string. - -\vspace{3mm} - -#### Closing handshake - -The channel closing handshake, between two ledgers `A` and `B`, with state formatted as `(A, B)`, flows as follows: - -| Datagram | Prior state | Posterior state | -| ---------------- | -------------- | ----------------- | -| `ChanCloseInit` | \texttt{\small{(OPEN, OPEN)}} | \texttt{\small{(CLOSED, OPEN)}} | -| `ChanCloseConfirm` | \texttt{\small{(CLOSED, OPEN)}} | \texttt{\small{(CLOSED, CLOSED)}} | - -`ChanCloseInit`, executed on ledger A, closes the end of the channel on ledger A. - -`ChanCloseInit`, executed on ledger B, simply verifies that the channel has been -marked as closed on ledger A and closes the end on ledger B. - -Any in-flight packets can be timed-out as soon as a channel is closed. - -Once closed, channels cannot be reopened and identifiers cannot be reused. Identifier reuse is prevented because -we want to prevent potential replay of previously sent packets. The replay problem is analogous to using sequence -numbers with signed messages, except where the light client algorithm "signs" the messages (IBC packets), and the replay -prevention sequence is the combination of port identifier, channel identifier, and packet sequence — hence we cannot -allow the same port identifier and channel identifier to be reused again with a sequence reset to zero, since this -might allow packets to be replayed. It would be possible to safely reuse identifiers if timeouts of a particular -maximum height/time were mandated and tracked, and future protocol versions may incorporate this feature. - -\vspace{3mm} - -### Sending packets - -\vspace{3mm} - -The `sendPacket` function is called by a module in order to send an IBC packet on a channel end owned by the calling module to the corresponding module on the counterparty ledger. - -Calling modules must execute application logic atomically in conjunction with calling `sendPacket`. - -The IBC handler performs the following steps in order: - -- Checks that the channel and connection are open to send packets -- Checks that the calling module owns the sending port -- Checks that the packet metadata matches the channel and connection information -- Checks that the timeout height specified has not already passed on the destination ledger -- Increments the send sequence counter associated with the channel (in the case of ordered channels) -- Stores a constant-size commitment to the packet data and packet timeout - -Note that the full packet is not stored in the state of the ledger — merely a short hash-commitment to the data and timeout value. The packet data can be calculated from the transaction execution and possibly returned as log output which relayers can index. - -\vspace{3mm} - -### Receiving packets - -\vspace{3mm} - -The `recvPacket` function is called by a module in order to receive and process an IBC packet sent on the corresponding channel end on the counterparty ledger. - -Calling modules must execute application logic atomically in conjunction with calling `recvPacket`, likely beforehand to calculate the acknowledgement value. - -The IBC handler performs the following steps in order: - -- Checks that the channel and connection are open to receive packets -- Checks that the calling module owns the receiving port -- Checks that the packet metadata matches the channel and connection information -- Checks that the packet sequence is the next sequence the channel end expects to receive (for ordered channels) -- Checks that the timeout height has not yet passed -- Checks the inclusion proof of packet data commitment in the outgoing ledger's state -- Sets the opaque acknowledgement value at a store path unique to the packet (if the acknowledgement is non-empty or the channel is unordered) -- Increments the packet receive sequence associated with the channel end (for ordered channels) - -\vspace{3mm} - -#### Acknowledgements - -\vspace{3mm} - -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 ledger. `acknowledgePacket` also cleans up the packet commitment, -which is no longer necessary since the packet has been received and acted upon. - -Calling modules may atomically execute appropriate application acknowledgement-handling logic in conjunction with calling `acknowledgePacket`. - -The IBC handler performs the following steps in order: - -- Checks that the channel and connection are open to acknowledge packets -- Checks that the calling module owns the sending port -- Checks that the packet metadata matches the channel and connection information -- Checks that the packet was actually sent on this channel -- Checks that the packet sequence is the next sequence the channel end expects to acknowledge (for ordered channels) -- Checks the inclusion proof of the packet acknowledgement data in the receiving ledger's state -- Deletes the packet commitment (cleaning up state and preventing replay) -- Increments the next acknowledgement sequence (for ordered channels) - -\vspace{3mm} - -### Timeouts - -\vspace{3mm} - -Application semantics may require some timeout: an upper limit to how long the ledger will wait for a transaction to be processed before considering it an error. Since the two ledgers have different local clocks, this is an obvious attack vector for a double spend — an attacker may delay the relay of the receipt or wait to send the packet until right after the timeout — so applications cannot safely implement naive timeout logic themselves. In order to avoid any possible "double-spend" attacks, the timeout algorithm requires that the destination ledger is running and reachable. The timeout must be proven on the recipient ledger, not simply the absence of a response on the sending ledger. - -\vspace{3mm} - -#### Sending end - -The `timeoutPacket` function is called by a module which originally attempted to send a packet to a counterparty module, -where the timeout height or timeout timestamp has passed on the counterparty ledger without the packet being committed, to prove that the packet -can no longer be executed and to allow the calling module to safely perform appropriate state transitions. - -Calling modules may atomically execute appropriate application timeout-handling logic in conjunction with calling `timeoutPacket`. - -The IBC handler performs the following steps in order: - -- Checks that the channel and connection are open to timeout packets -- Checks that the calling module owns the sending port -- Checks that the packet metadata matches the channel and connection information -- Checks that the packet was actually sent on this channel -- Checks a proof that the packet has not been confirmed on the destination ledger -- Checks a proof that the destination ledger has exceeded the timeout height or timestamp -- Deletes the packet commitment (cleaning up state and preventing replay) - -In the case of an ordered channel, `timeoutPacket` additionally closes the channel if a packet has timed out. Unordered channels are expected to continue in the face of timed-out packets. - -If relations are enforced between timeout heights of subsequent packets, safe bulk timeouts of all packets prior to a timed-out packet can be performed. - -\vspace{3mm} - -#### Timing-out on close - -If a channel is closed, in-flight packets can never be received and thus can be safely timed-out. -The `timeoutOnClose` function is called by a module in order to prove that the channel -to which an unreceived packet was addressed has been closed, so the packet will never be received -(even if the `timeoutHeight` or `timeoutTimestamp` has not yet been reached). Appropriate -application-specific logic may then safely be executed. - -\vspace{3mm} - -#### Cleaning up state - -If an acknowledgement is not written (as handling the acknowledgement would clean up state in that case), `cleanupPacket` may be called by a module in order to remove a received packet commitment from storage. The receiving end must have already processed the packet (whether regularly or past timeout). - -In the ordered channel case, `cleanupPacket` cleans-up a packet on an ordered channel by proving that the receive sequence has passed the packet's sequence on the other end. - -In the unordered channel case, `cleanupPacket` cleans-up a packet on an unordered channel by proving that the associated acknowledgement has been written. diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/clients.md b/other_docs/ibc-protocol/archive/papers/2020-05/src/clients.md deleted file mode 100644 index def79fb41..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/clients.md +++ /dev/null @@ -1,191 +0,0 @@ -The *client* abstraction encapsulates the properties that consensus algorithms of ledgers implementing the interblockchain -communication protocol are required to satisfy. These properties are necessary for efficient and safe -state verification in the higher-level protocol abstractions. The algorithm utilised in IBC to verify the -consensus transcript and state sub-components of another ledger is referred to as a "validity predicate", -and pairing it with a state that the verifier assumes to be correct forms a "light client" (colloquially shortened to "client"). - -\vspace{3mm} - -### Motivation - -\vspace{3mm} - -In the IBC protocol, an actor, which may be an end user, an off-ledger process, or ledger, -needs to be able to verify updates to the state of another ledger -which the other ledger's consensus algorithm has agreed upon, and reject any possible updates -which the other ledger's consensus algorithm has not agreed upon. A light client is the algorithm -with which an actor can do so. The client abstraction formalises this model's interface and requirements, -so that the IBC protocol can easily integrate with new ledgers which are running new consensus algorithms -as long as associated light client algorithms fulfilling the listed requirements are provided. - -Beyond the properties described in this specification, IBC does not impose any requirements on -the internal operation of ledgers and their consensus algorithms. A ledger may consist of a -single process signing operations with a private key, a quorum of processes signing in unison, -many processes operating a Byzantine fault-tolerant consensus algorithm (a replicated, or distributed, ledger), or other configurations yet to be invented -— from the perspective of IBC, a ledger is defined entirely by its light client validation and equivocation detection logic. -Clients will generally not include validation of the state transition logic in general -(as that would be equivalent to simply executing the other state machine), but may -elect to validate parts of state transitions in particular cases, and can validate -the entire state transition if doing so is asymptotically efficient, perhaps through compression using a SNARK [@coda_protocol]. - -Externally, however, the light client verification functions used by IBC clients -must have *finality*, such that verified blocks (subject to the usual consensus safety assumptions), -once verified, cannot be reverted. The safety of higher abstraction layers of the IBC protocol -and guarantees provided to the applications using the protocol depend on this property of finality. - -In order to graft finality onto Nakamoto consensus algorithms, such as used in Bitcoin [@bitcoin], -clients can act as thresholding views of internal, non-finalising clients. In the case where -modules utilising the IBC protocol to interact with probabilistic-finality consensus algorithms -which might require different finality thresholds for different applications, one write-only -client could be created to track headers and many read-only clients with different finality -thresholds (confirmation depths after which state roots are considered final) could use that same state. -Of course, this will introduce different security assumptions than those required of full nodes running the consensus algorithm, -and trade-offs which must be balanced by the user on the basis of their application-specific security needs. - -The client protocol is designed to support third-party introduction. Consider the general example: -Alice, a module on a ledger, wants to introduce Bob, a second module on a second ledger who Alice knows (and who knows Alice), -to Carol, a third module on a third ledger, who Alice knows but Bob does not. Alice must utilise -an existing channel to Bob to communicate the canonically-serialisable validity predicate for -Carol, with which Bob can then open a connection and channel so that Bob and Carol can talk directly. -If necessary, Alice may also communicate to Carol the validity predicate for Bob, prior to Bob's -connection attempt, so that Carol knows to accept the incoming request. - -Client interfaces are constructed so that custom validation logic can be provided safely -to define a custom client at runtime, as long as the underlying ledger can provide an -appropriate gas metering mechanism to charge for compute and storage. On a host ledger -which supports WASM execution, for example, the validity predicate and equivocation predicate -could be provided as executable WASM functions when the client instance is created. - -\vspace{3mm} - -### Definitions - -\vspace{3mm} - -A *validity predicate* is an opaque function defined by a client type to verify headers depending on the current consensus state. Using the validity predicate should be far more computationally efficient than replaying the full consensus algorithm and state machine for the given parent header and the list of network messages. - -A *consensus state* is an opaque type representing the state of a validity predicate. -The light client validity predicate algorithm in combination with a particular consensus state must be able to verify state updates agreed upon by the associated consensus algorithm. -The consensus state must also be serialisable in a canonical fashion so that third parties, such as counterparty ledgers, -can check that a particular ledger has stored a particular state. It must also be -introspectable by the ledger which it is for, such that the ledger can look up its -own consensus state at a past height and compare it to a stored consensus state in another ledger's client. - -A *commitment root* is an inexpensive way for downstream logic to verify whether key/value -pairs are present or absent in a state at a particular height. Often this will be instantiated as the root of a Merkle tree. - -A *header* is an opaque data structure defined by a client type which provides information to update a consensus state. -Headers can be submitted to an associated client to update the stored consensus state. They likely contain a height, a proof, -a new commitment root, and possibly updates to the validity predicate. - -A *misbehaviour predicate* is an opaque function defined by a client type, used to check if data constitutes a violation of the consensus protocol. This might be two signed headers with different state roots but the same height, a signed header containing invalid state transitions, or other evidence of malfeasance as defined by the consensus algorithm. - -\vspace{3mm} - -### Desired properties - -\vspace{3mm} - -Light clients must provide a secure algorithm to verify other ledgers' canonical headers, -using the existing consensus state. The higher level abstractions will then be able to verify -sub-components of the state with the commitment roots stored in the consensus state, which are -guaranteed to have been committed by the other ledger's consensus algorithm. - -Validity predicates are expected to reflect the behaviour of the full nodes which are running the -corresponding consensus algorithm. Given a consensus state and a list of messages, if a full node -accepts a new header, then the light client must also accept it, and if a full node rejects it, -then the light client must also reject it. - -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 which proves the divergence between the validity predicate -and the full node can be generated and submitted to the ledger so that the ledger can safely deactivate the -light client, invalidate past state roots, and await higher-level intervention. - -The validity of the validity predicate is dependent on the security model of the -consensus algorithm. For example, the consensus algorithm could be BFT proof-of-authority -with a trusted operator set, or BFT proof-of-stake with a tokenholder set, each of which -have a defined threshold above which Byzantine behaviour may result in divergence. - -Clients may have time-sensitive validity predicates, such that if no header is provided for a period of time -(e.g. an unbonding period of three weeks in a proof-of-stake system) it will no longer be possible to update the client. - -\vspace{3mm} - -### State verification - -\vspace{3mm} - -Client types must define functions to authenticate internal state of the ledger which the client tracks. -Internal implementation details may differ (for example, a loopback client could simply read directly from the state and require no proofs). -Externally-facing clients will likely verify signature or vector commitment proofs. - -\vspace{3mm} - -### Example client instantiations - -\vspace{3mm} - -#### Loopback - -A loopback client of a local ledger merely reads from the local state, to which it must have access. This is analogous to `localhost` or `127.0.0.1` in TCP/IP. - -\vspace{3mm} - -#### Simple signatures - -A client of a solo machine running a non-replicated ledger with a known public key checks signatures on messages sent by that local machine. -Multi-signature or threshold signature schemes can also be used in such a fashion. - -\vspace{3mm} - -#### Proxy clients - -Proxy clients verify another (proxy) ledger's verification of the target ledger, by including in the -proof first a proof of the client state on the proxy ledger, and then a secondary proof of the sub-state of -the target ledger with respect to the client state on the proxy ledger. This allows the proxy client to -avoid storing and tracking the consensus state of the target ledger itself, at the cost of adding -security assumptions of proxy ledger correctness. - -\vspace{3mm} - -#### BFT consensus and verifiable state - -For the immediate application of interoperability between sovereign, fault-tolerant distributed ledgers, the most common and most useful client type will be light clients for instances of BFT consensus algorithms such as Tendermint [@tendermint_consensus_without_mining], GRANDPA [@grandpa_consensus], or HotStuff [@hotstuff_consensus], with ledgers utilising Merklized state trees such as an IAVL+ tree [@iavl_plus_tree] or a Merkle Patricia tree [@patricia_tree]. The client algorithm for such instances will utilise the BFT consensus algorithm's light client validity predicate and treat at minimum consensus equivocation (double-signing) as misbehaviour, along with other possible misbehaviour types specific to the proof-of-authority or proof-of-stake system involved. - -\vspace{3mm} - -### Client lifecycle - -\vspace{3mm} - -#### Creation - -Clients can be created permissionlessly by anyone at any time by specifying an identifier, -client type, and initial consensus state. - -\vspace{3mm} - -#### Update - -Updating a client is done by submitting a new header. When a new header is verified -with the stored client state's validity predicate and consensus state, the client will -update its internal state accordingly, possibly finalising commitment roots and -updating the signature authority logic in the stored consensus state. - -If a client can no longer be updated (if, for example, the unbonding period has passed), -it will no longer be possible to send any packets over connections and channels associated -with that client, or timeout any packets in-flight (since the height and timestamp on the -destination ledger can no longer be verified). Manual intervention must take place to -reset the client state or migrate the connections and channels to another client. This -cannot safely be done automatically, but ledgers implementing IBC could elect -to allow governance mechanisms to perform these actions -(perhaps even per-client/connection/channel with a controlling multi-signature or contract). - -\vspace{3mm} - -#### Misbehaviour - -If the client detects evidence of misbehaviour, the client can be take appropriate action, possibly invalidating -previously valid commitment roots and preventing future updates. What precisely constitutes misbehaviour will -depend on the consensus algorithm which the validity predicate is validating the output of. diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/connections.md b/other_docs/ibc-protocol/archive/papers/2020-05/src/connections.md deleted file mode 100644 index 5ca388d8e..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/connections.md +++ /dev/null @@ -1,105 +0,0 @@ -The *connection* abstraction encapsulates two stateful objects (*connection ends*) on two separate ledgers, each associated with a light client of the other ledger, which together facilitate cross-ledger sub-state verification and packet relay (through channels). Connections are safely established in an unknown, dynamic topology using a handshake subprotocol. - -\vspace{3mm} - -### Motivation - -\vspace{3mm} - -The IBC protocol provides *authorisation* and *ordering* semantics for packets: guarantees, respectively, that packets have been committed on the sending ledger (and according state transitions executed, such as escrowing tokens), and that they have been committed exactly once in a particular order and can be delivered exactly once in that same order. The *connection* abstraction in conjunction with the *client* abstraction defines the *authorisation* semantics of IBC. Ordering semantics are provided by channels. - -\vspace{3mm} - -### Definitions - -\vspace{3mm} - -A *connection end* is state tracked for an end of a connection on one ledger, defined as follows: - -```typescript -enum ConnectionState { - INIT, - TRYOPEN, - OPEN, -} -``` - -```typescript -interface ConnectionEnd { - state: ConnectionState - counterpartyConnectionIdentifier: Identifier - counterpartyPrefix: CommitmentPrefix - clientIdentifier: Identifier - counterpartyClientIdentifier: Identifier - version: string -} -``` - -- The `state` field describes the current state of the connection end. -- The `counterpartyConnectionIdentifier` field identifies the connection end on the counterparty ledger associated with this connection. -- The `counterpartyPrefix` field contains the prefix used for state verification on the counterparty ledger associated with this connection. -- The `clientIdentifier` field identifies the client associated with this connection. -- The `counterpartyClientIdentifier` field identifies the client on the counterparty ledger associated with this connection. -- The `version` field is an opaque string which can be utilised to determine encodings or protocols for channels or packets utilising this connection. - -\vspace{3mm} - -### Opening handshake - -\vspace{3mm} - -The opening handshake subprotocol allows each ledger to verify the identifier used to reference the connection on the other ledger, enabling modules on each ledger to reason about the reference on the other ledger. - -The opening handshake consists of four datagrams: `ConnOpenInit`, `ConnOpenTry`, `ConnOpenAck`, and `ConnOpenConfirm`. - -A correct protocol execution, between two ledgers `A` and `B`, with connection states formatted as `(A, B)`, flows as follows: - -| Datagram | Prior state | Posterior state | -| ----------------- | ----------------- | ----------------- | -| `ConnOpenInit` | `(-, -)` | `(INIT, -)` | -| `ConnOpenTry` | `(INIT, none)` | `(INIT, TRYOPEN)` | -| `ConnOpenAck` | `(INIT, TRYOPEN)` | `(OPEN, TRYOPEN)` | -| `ConnOpenConfirm` | `(OPEN, TRYOPEN)` | `(OPEN, OPEN)` | - -At the end of an opening handshake between two ledgers implementing the subprotocol, the following properties hold: - -- Each ledger has each other's correct consensus state as originally specified by the initiating actor. -- Each ledger has knowledge of and has agreed to its identifier on the other ledger. -- Each ledger knows that the other ledger has agreed to the same data. - -Connection handshakes can safely be performed permissionlessly, modulo anti-spam measures (paying gas). - -`ConnOpenInit`, executed on ledger A, initialises a connection attempt on ledger A, specifying a pair of identifiers -for the connection on both ledgers and a pair of identifiers for existing light clients (one for -each ledger). ledger A stores a connection end object in its state. - -`ConnOpenTry`, executed on ledger B, relays notice of a connection attempt on ledger A to ledger B, -providing the pair of connection identifiers, the pair of client identifiers, and a desired version. -Ledger B verifies that these identifiers are valid, checks that the version is compatible, verifies -a proof that ledger A has stored these identifiers, and verifies a proof that the light client ledger A -is using to validate ledger B has the correct consensus state for ledger B. ledger B stores a connection -end object in its state. - -`ConnOpenAck`, executed on ledger A, relays acceptance of a connection open attempt from ledger B back to ledger A, -providing the identifier which can now be used to look up the connection end object. ledger A verifies -that the version requested is compatible, verifies a proof that ledger B has stored the same identifiers -ledger A has stored, and verifies a proof that the light client ledger B is using to validate ledger A has the -correct consensus state for ledger A. - -`ConnOpenConfirm`, executed on ledger B, confirms opening of a connection on ledger A to ledger B. -Ledger B simply checks that ledger A has executed `ConnOpenAck` and marked the connection as `OPEN`. -Ledger B subsequently marks its end of the connection as `OPEN`. After execution of `ConnOpenConfirm` -the connection is open on both ends and can be used immediately. - -\vspace{3mm} - -### Versioning - -\vspace{3mm} - -During the handshake process, two ends of a connection come to agreement on a version bytestring associated -with that connection. At the moment, the contents of this version bytestring are opaque to the IBC core protocol. -In the future, it might be used to indicate what kinds of channels can utilise the connection in question, or -what encoding formats channel-related datagrams will use. Host ledgers may utilise the version data -to negotiate encodings, priorities, or connection-specific metadata related to custom logic on top of IBC. -Host ledgers may also safely ignore the version data or specify an empty string. diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/fungible-token-transfer.md b/other_docs/ibc-protocol/archive/papers/2020-05/src/fungible-token-transfer.md deleted file mode 100644 index 9a0e50ff9..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/fungible-token-transfer.md +++ /dev/null @@ -1,85 +0,0 @@ -The section specifies packet data structure and state machine handling logic for the transfer of fungible tokens over an IBC channel between two modules on separate ledgers. The state machine logic presented allows for safe multi-ledger denomination handling with permissionless channel opening. This logic constitutes a "fungible token transfer bridge module", interfacing between the IBC routing module and an existing asset tracking module on the host ledger. - -\vspace{3mm} - -### Motivation - -\vspace{3mm} - -Users of a set of ledgers connected over the IBC protocol might wish to utilise an asset issued on one ledger on another ledger, perhaps to make use of additional features such as exchange or privacy protection, while retaining fungibility with the original asset on the issuing ledger. This application-layer protocol allows for transferring fungible tokens between ledgers connected with IBC in a way which preserves asset fungibility, preserves asset ownership, limits the impact of Byzantine faults, and requires no additional permissioning. - -\vspace{3mm} - -### Properties - -\vspace{3mm} - -- Preservation of fungibility (two-way peg) -- Preservation of total supply (constant or inflationary on a single source ledger and module) -- Permissionless token transfers, no need to whitelist connections, modules, or denominations -- Symmetric (all ledgers implement the same logic) -- Fault containment: prevents Byzantine-inflation of tokens originating on ledger A, as a result of ledger B's Byzantine behaviour (though any users who sent tokens to ledger B may be at risk) - -\vspace{3mm} - -### Packet definition - -\vspace{3mm} - -Only one packet data type, `FungibleTokenPacketData`, which specifies the denomination, amount, sending account, receiving account, and whether the sending ledger is the source of the asset, is required: - -```typescript -interface FungibleTokenPacketData { - denomination: string - amount: uint256 - sender: string - receiver: string -} -``` - -The acknowledgement data type describes whether the transfer succeeded or failed, and the reason for failure (if any): - - -```typescript -interface FungibleTokenPacketAcknowledgement { - success: boolean - error: Maybe -} -``` - -\vspace{3mm} - -### Packet handling semantics - -\vspace{3mm} - -The protocol logic is symmetric, so that denominations originating on either ledger can be converted to vouchers on the other, and then redeemed back again later. - -- When acting as the source ledger, the bridge module escrows an existing local asset denomination on the sending ledger and mints vouchers on the receiving ledger. -- When acting as the sink ledger, the bridge module burns local vouchers on the sending ledgers and unescrows the local asset denomination on the receiving ledger. -- When a packet times-out, local assets are unescrowed back to the sender or vouchers minted back to the sender appropriately. -- Acknowledgement data is used to handle failures, such as invalid denominations or invalid destination accounts. Returning - an acknowledgement of failure is preferable to aborting the transaction since it more easily enables the sending ledger - to take appropriate action based on the nature of the failure. - -This implementation preserves both fungibility and supply. If tokens have been sent to the counterparty ledger, they can be redeemed back in the same denomination and amount on the source ledger. -The combined supply of unlocked tokens of a particular on both ledgers is constant, since each send-receive packet pair locks and mints the same amount (although the source ledger of a particular -asset could change the supply outside of the scope of this protocol). - -\vspace{3mm} - -### Fault containment - -\vspace{3mm} - -Ledgers could fail to follow the fungible transfer token protocol outlined here in one of two ways: the full nodes running the consensus algorithm could diverge from the light client, or the ledger's state machine could incorrectly implement the escrow & voucher logic (whether inadvertently or intentionally). Consensus divergence should eventually result in evidence of misbehaviour which can be used to freeze the client, but may not immediately do so (and no guarantee can be made that such evidence would be submitted before more packets), so from the perspective of the protocol's goal of isolating faults these cases must be handled in the same way. No guarantees can be made about asset recovery — users electing to transfer tokens to a ledger take on the risk of that ledger failing — but containment logic can easily be implemented on the interface boundary by tracking incoming and outgoing supply of each asset, and ensuring that no ledger is allowed to redeem vouchers for more tokens than it had initially escrowed. In essence, particular channels can be treated as accounts, where a module on the other end of a channel cannot spend more than it has received. Since isolated Byzantine sub-graphs of a multi-ledger fungible token transfer system will be unable to transfer out any more tokens than they had initially received, this prevents any supply inflation of source assets, and ensures that users only take on the consensus risk of ledgers they intentionally connect to. - -\vspace{3mm} - -### Multi-ledger transfer paths - -\vspace{3mm} - -This protocol does not directly handle the "diamond problem", where a user sends a token originating on ledger A to ledger B, then to ledger D, and wants to return it through the path `D -> C -> A` — since the supply is tracked as owned by ledger B (and the voucher denomination will be `"{portD}/{channelD}/{portB}/{channelB}/denom"`), ledger C cannot serve as the intermediary. This is necessary due to the fault containment desiderata outlined above. Complexities arising from long redemption paths may lead to the emergence of central ledgers in the network topology or automated markets to exchange assets with different redemption paths. - -In order to track all of the denominations moving around the network of ledgers in various paths, it may be helpful for a particular ledger to implement a registry which will track the "global" source ledger for each denomination. End-user service providers (such as wallet authors) may want to integrate such a registry or keep their own mapping of canonical source ledgers and human-readable names in order to improve UX. diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/host-requirements.md b/other_docs/ibc-protocol/archive/papers/2020-05/src/host-requirements.md deleted file mode 100644 index 0e74cc513..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/host-requirements.md +++ /dev/null @@ -1,79 +0,0 @@ -### Module system - -\vspace{3mm} - -The host ledger 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 controller module or execution environment. - -\vspace{3mm} - -### Key/value Store - -\vspace{3mm} - -The host ledger must provide a key/value store interface allowing values to be read, written, and deleted. - -These functions must be permissioned to the IBC handler module so that only the IBC handler module can write or delete a certain subset of paths. -This will likely be implemented as a sub-store (prefixed key-space) of a larger key/value store used by the entire ledger. - -Host ledgers must provide an instance of this interface which is provable, such that the light client algorithm for the host ledger -can verify presence or absence of particular key-value pairs which have been written to it. - -This interface does not necessitate any particular storage backend or backend data layout. ledgers 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. - -\vspace{3mm} - -### Consensus state introspection - -\vspace{3mm} - -Host ledgers must provide the ability to introspect their current height, current -consensus state (as utilised by the host ledger's light client algorithm), and a bounded -number of recent consensus states (e.g. past headers). These are used to prevent man-in-the-middle -attacks during handshakes to set up connections with other ledgers — each ledger checks that the other -ledger is in fact authenticating data using its consensus state. - -\vspace{3mm} - -### Timestamp access - -\vspace{3mm} - -In order to support timestamp-based timeouts, host ledgers must provide a current Unix-style timestamp. -Timeouts in subsequent headers must be non-decreasing. - -\vspace{3mm} - -### Port system - -\vspace{3mm} - -Host ledgers must implement a port system, where the IBC handler can allow different modules in the host ledger to bind to uniquely named ports. Ports are identified by an identifier, and must be permissioned so that: - -- Once a module has bound to a port, no other modules can use that port until the module releases it -- A single module can bind to multiple ports -- Ports are allocated first-come first-serve -- "Reserved" ports for known modules can be bound when the ledger is first started - -This permissioning can be implemented with unique references (object capabilities [@object_capabilities]) for each port, with source-based authentication(a la `msg.sender` in Ethereum contracts), or with some other method of access control, in any case enforced by the host ledger. - -Ports are not generally intended to be human-readable identifiers — just as DNS name resolution and standardised port numbers for particular applications exist to abstract away the details of IP addresses and ports from TCP/IP users, ledger name resolution and standardised ports for particular applications may be created in order to abstract away the details of ledger identification and port selection. Such an addressing system could easily be built on top of IBC itself, such that an initial connection to the addressing system over IBC would then enable name resolution for subsequent connections to other ledgers and applications. - -\vspace{3mm} - -### Exception/rollback system - -\vspace{3mm} - -Host ledgers must support an exception or rollback 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 and fee payments as appropriate. - -\vspace{3mm} - -### Data availability - -\vspace{3mm} - -For deliver-or-timeout safety, host ledgers must have eventual data availability, such that any key/value pairs in state can be eventually retrieved by relayers. For exactly-once safety, data availability is not required. - -For liveness of packet relay, host ledgers must have bounded transactional liveness, such that incoming transactions are confirmed within a block height or timestamp bound (in particular, less than the timeouts assigned to the packets). - -IBC packet data, and other data which is not directly stored in the Merklized state but is relied upon by relayers, must be available to and efficiently computable by relayer processes. diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/introduction.md b/other_docs/ibc-protocol/archive/papers/2020-05/src/introduction.md deleted file mode 100644 index eea9febd2..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/introduction.md +++ /dev/null @@ -1,13 +0,0 @@ -By virtue of their nature as replicated state machines across which deterministic execution and thus continued agreement on an exact deterministic ruleset must be maintained, individual distributed ledgers are limited in their throughput & flexibility, must trade off application-specific optimisations for general-purpose capabilities, and can only offer a single security model to applications built on top of them. In order to support the transaction throughput, application diversity, cost efficiency, and fault tolerance required to facilitate wide deployment of distributed ledger applications, execution and storage must be split across many independent ledgers which can run concurrently, upgrade independently, and each specialise in different ways, in a manner such that the ability of different applications to communicate with one another, essential for permissionless innovation and complex multi-part contracts, is maintained. - -One multi-ledger design direction is to shard a single logical ledger across separate consensus instances, referred to as "shards", which execute concurrently and store disjoint partitions of the state. In order to reason globally about safety and liveness, and in order to correctly route data and code between shards, these designs must take a "top-down approach" — constructing a particular network topology, usually a single root ledger and a star or tree of shards, and engineering protocol rules and incentives to enforce that topology. Message passing can then be implemented on top of such a sharded topology by systems such as Polkadot's XCMP [@polkadot_xcmp] and Ethereum 2.0's cross-shard communication [@ethereum_2_cross_shard]. This approach possesses advantages in simplicity and predictability, but faces hard technical problems in assuring the validity of state transitions [@hard_problems_sharding_part_two], requires the adherence of all shards to a single validator set (or randomly elected subset thereof) and a single virtual machine, and faces challenges in upgrading itself over time due to the necessity of reaching global consensus on alterations to the network topology or ledger ruleset. Additionally, such sharded systems are brittle: if the fault tolerance threshold is exceeded, the system needs to coordinate a global halt & restart, and possibly initiate complex state transition rollback procedures — it is not possible to safely isolate Byzantine portions of the network graph and continue operation. - -The *interblockchain communication protocol* (IBC) provides a mechanism by which separate, sovereign replicated ledgers can safely, voluntarily interact while sharing only a minimum requisite common interface. The protocol design approaches a differently formulated version of the scaling and interoperability problem: enabling safe, reliable interoperation of a network of heterogeneous distributed ledgers, arranged in an unknown topology, preserving data secrecy where possible, where the ledgers can diversify, develop, and rearrange independently of each other or of a particular imposed topology or ledger design. In a wide, dynamic network of interoperating ledgers, sporadic Byzantine faults are expected, so the protocol must also detect, mitigate, and contain the potential damage of Byzantine faults in accordance with the requirements of the applications and ledgers involved without requiring the use of additional trusted parties or global coordination. - -To facilitate this heterogeneous interoperation, the interblockchain communication protocol utilises a bottom-up approach, specifying the set of requirements, functions, and properties necessary to implement interoperation between two ledgers, and then specifying different ways in which multiple interoperating ledgers might be composed which preserve the requirements of higher-level protocols. IBC thus presumes nothing about and requires nothing of the overall network topology, and of the implementing ledgers requires only that a known, minimal set of functions with specified properties are available. Ledgers within IBC are defined as their light client consensus validation functions, thus expanding the range of what a "ledger" can be to include single machines and complex consensus algorithms alike. IBC implementations are expected to be co-resident with higher-level modules and protocols on the host ledger. Ledgers hosting IBC must provide a certain set of functions for consensus transcript verification and cryptographic commitment proof generation, and IBC packet relayers (off-ledger processes) are expected to have access to network protocols and physical data-links as required to read the state of one ledger and submit data to another. - -The data payloads in IBC packets are opaque to the protocol itself — modules on each ledger determine the semantics of the packets which are sent between them. For cross-ledger token transfer, packets could contain fungible token information, where assets are locked on one ledger to mint corresponding vouchers on another. For cross-ledger governance, packets could contain vote information, where accounts on one ledger could vote in the governance system of another. For cross-ledger account delegation, packets could contain transaction authorisation information, allowing an account on one ledger to be controlled by an account on another. For a cross-ledger decentralised exchange, packets could contain order intent information or trade settlement information, such that assets on different ledgers could be exchanged without leaving their host ledgers by transitory escrow and a sequence of packets. - -This bottom-up approach is quite similar to, and directly inspired by, the TCP/IP specification [@rfc793] for interoperability between hosts in packet-switched computer networks. Just as TCP/IP defines the protocol by which two hosts communicate, and higher-level protocols knit many bidirectional host-to-host links into complex topologies, IBC defines the protocol by which two ledgers communicate, and higher-level protocols knit many bidirectional ledger-to-ledger links into gestalt multi-ledger applications. Just as TCP/IP packets contain opaque payload data with semantics interpreted by the processes on each host, IBC packets contain opaque payload data with semantics interpreted by the modules on each ledger. Just as TCP/IP provides reliable, ordered data transmission between processes, allowing a process on one host to reason about the state of a process on another, IBC provides reliable, ordered data transmission between modules, allowing a module on one ledger to reason about the state of a module on another. - -This paper is intended as an overview of the abstractions defined by the IBC protocol and the mechanisms by which they are composed. We first outline the structure of the protocol, including scope, interfaces, and operational requirements. Subsequently, we detail the abstractions defined by the protocol, including modules, ports, clients, connections, channels, packets, and relayers, and describe the subprotocols for opening and closing handshakes, packet relay, edge-case handling, and relayer operations. After explaining the internal structure of the protocol, we define the interface by which applications can utilise IBC, and sketch an example application-level protocol for fungible token transfer. Finally, we recount testing and deployment efforts of the protocol thus far. Appendices include pseudocode for the connection handshake, channel handshake, and packet relay algorithms. diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/metadata.yaml b/other_docs/ibc-protocol/archive/papers/2020-05/src/metadata.yaml deleted file mode 100644 index 380415788..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/metadata.yaml +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: 'The Interblockchain Communication Protocol: An Overview' -author: - - name: Christopher Goes - affiliation: Interchain GmbH - location: Berlin, Germany - email: cwgoes@interchain.berlin -keywords: - - ibc - - interblockchain - - dlt -numbersections: yes -lang: en -babel-lang: english -abstract: | - The interblockchain communication protocol (IBC) is an end-to-end, connection-oriented, stateful protocol for reliable, ordered, and authenticated communication between modules on separate distributed ledgers. - IBC is designed for interoperation between heterogenous ledgers arranged in an unknown, dynamic topology, operating with varied consensus algorithms and state machines. The protocol realises this by specifying - the sufficient set of data structures, abstractions, and semantics of a communication protocol which once implemented by participating ledgers will allow them to safely communicate. IBC is payload-agnostic - and provides a cross-ledger asynchronous communication primitive which can be used as a constituent building block by a wide variety of applications. - -... - diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/paper.md b/other_docs/ibc-protocol/archive/papers/2020-05/src/paper.md deleted file mode 100644 index 878db392d..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/paper.md +++ /dev/null @@ -1,63 +0,0 @@ -# Introduction - -!include src/introduction.md - -# Protocol scope & properties - -!include src/structure.md - -# Host ledger requirements - -!include src/host-requirements.md - -# Protocol structure - -## Clients - -!include src/clients.md - -## Connections - -!include src/connections.md - -## Channels - -!include src/channels.md - -## Relayers - -!include src/relayers.md - -# Usage patterns - -!include src/usage-patterns.md - -# Example application-level module - -!include src/fungible-token-transfer.md - -# Testing & deployment - -!include src/testing-and-deployment.md - -# Acknowledgements - -!include src/acknowledgements.md - -\onecolumn - -# Appendices - -!include src/appendix-a.md - -!include src/appendix-b.md - -!include src/appendix-c.md - -\pagebreak - -\twocolumn - -# References - - diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/relayers.md b/other_docs/ibc-protocol/archive/papers/2020-05/src/relayers.md deleted file mode 100644 index 8fb325d4c..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/relayers.md +++ /dev/null @@ -1,110 +0,0 @@ -Relayer algorithms are the "physical" connection layer of IBC — off-ledger processes responsible for relaying data between two ledgers running the IBC protocol by scanning the state of each ledger, constructing appropriate datagrams, and executing them on the opposite ledger as allowed by the protocol. - -\vspace{3mm} - -### Motivation - -\vspace{3mm} - -In the IBC protocol, one ledger can only record the intention to send particular data to another ledger — it does not have direct access to a network transport layer. Physical datagram relay must be performed by off-ledger infrastructure with access to a transport layer such as TCP/IP. This standard defines the concept of a *relayer* algorithm, executable by an off-ledger process with the ability to query ledger state, to perform this relay. - -A *relayer* is an off-ledger process with the ability to read the state of and submit transactions to some set of ledgers utilising the IBC protocol. - -\vspace{3mm} - -### Properties - -\vspace{3mm} - -- No exactly-once or deliver-or-timeout safety properties of IBC depend on relayer behaviour (Byzantine relayers are assumed) -- Packet relay liveness properties of IBC depend only on the existence of at least one correct, live relayer -- Relaying can safely be permissionless, all requisite verification is performed by the ledger itself -- Requisite communication between the IBC user and the relayer is minimised -- Provision for relayer incentivisation are not included in the core protocol, but are possible at the application layer - -\vspace{3mm} - -### Basic relayer algorithm - -\vspace{3mm} - -The relayer algorithm is defined over a set of ledgers implementing the IBC protocol. Each relayer may not necessarily have access to read state from and write datagrams to all ledgers in the multi-ledger network (especially in the case of permissioned or private ledgers) — different relayers may relay between different subsets. - -Every so often, although no more frequently than once per block on either ledger, a relayer calculates the set of all valid datagrams to be relayed from one ledger to another based on the state of both ledgers. The relayer must possess prior knowledge of what subset of the IBC protocol is implemented by the ledgers in the set for which they are relaying (e.g. by reading the source code). Datagrams can be submitted individually as single transactions or atomically as a single transaction if the ledger supports it. - -Different relayers may relay between different ledgers — as long as each pair of ledgers has at least one correct and live relayer and the ledgers remain live, all packets flowing between ledgers in the network will eventually be relayed. - -\vspace{3mm} - -### Packets, acknowledgements, timeouts - -\vspace{3mm} - -#### Relaying packets in an ordered channel - -Packets in an ordered channel can be relayed in either an event-based fashion or a query-based fashion. -For the former, the relayer should watch the source ledger for events emitted whenever packets are sent, -then compose the packet using the data in the event log. For the latter, the relayer should periodically -query the send sequence on the source ledger, and keep the last sequence number relayed, so that any sequences -in between the two are packets that need to be queried and then relayed. In either case, subsequently, the relayer process -should check that the destination ledger has not yet received the packet by checking the receive sequence, and then relay it. - -\vspace{3mm} - -#### Relaying packets in an unordered channel - -Packets in an unordered channel can most easily be relayed in an event-based fashion. -The relayer should watch the source ledger for events emitted whenever packets -are send, then compose the packet using the data in the event log. Subsequently, -the relayer should check whether the destination ledger has received the packet -already by querying for the presence of an acknowledgement at the packet's sequence -number, and if one is not yet present the relayer should relay the packet. - -\vspace{3mm} - -#### Relaying acknowledgements - -Acknowledgements can most easily be relayed in an event-based fashion. The relayer should -watch the destination ledger for events emitted whenever packets are received and acknowledgements -are written, then compose the acknowledgement using the data in the event log, -check whether the packet commitment still exists on the source ledger (it will be -deleted once the acknowledgement is relayed), and if so relay the acknowledgement to -the source ledger. - -\vspace{3mm} - -#### Relaying timeouts - -Timeout relay is slightly more complex since there is no specific event emitted when -a packet times-out — it is simply the case that the packet can no longer be relayed, -since the timeout height or timestamp has passed on the destination ledger. The relayer -process must elect to track a set of packets (which can be constructed by scanning event logs), -and as soon as the height or timestamp of the destination ledger exceeds that of a tracked -packet, check whether the packet commitment still exists on the source ledger (it will -be deleted once the timeout is relayed), and if so relay a timeout to the source ledger. - -\vspace{3mm} - -#### Ordering constraints - -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 finalise the stored consensus state and 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 ledgers between which they are relaying in order to determine what must be relayed when. - -\vspace{3mm} - -#### Bundling - -If the host ledger supports it, the relayer process can bundle many datagrams into a single transaction, which will cause them to be executed in sequence, and amortise any overhead costs (e.g. signature checks for fee payment). - -\vspace{3mm} - -#### Race conditions - -Multiple relayers relaying between the same pair of modules and ledgers 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. - -\vspace{3mm} - -#### Incentivisation - -The relay process must have access to accounts on both ledgers with sufficient balance to pay for transaction fees. Relayers may employ application-level methods to recoup these fees, such by including a small payment to themselves in the packet data. - -Any number of relayer processes may be safely run in parallel (and indeed, it is expected that separate relayers will serve separate subsets of the multi-ledger network). However, they may consume unnecessary fees if they submit the same proof multiple times, so some minimal coordination may be ideal (such as assigning particular relayers to particular packets or scanning mempools for pending transactions). diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/structure.md b/other_docs/ibc-protocol/archive/papers/2020-05/src/structure.md deleted file mode 100644 index 9ac9f52ce..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/structure.md +++ /dev/null @@ -1,71 +0,0 @@ -## Scope - -IBC handles authentication, transport, and ordering of opaque data packets relayed between modules on separate ledgers — ledgers can be run on solo machines, replicated by many nodes running a consensus algorithm, or constructed by any process whose state can be verified. The protocol is defined between modules on two ledgers, but designed for safe simultaneous use between any number of modules on any number of ledgers connected in arbitrary topologies. - -## Interfaces - -IBC sits between modules — smart contracts, other ledger components, or otherwise independently executed pieces of application logic on ledgers — on one side, and underlying consensus protocols, blockchains, and network infrastructure (e.g. TCP/IP), on the other side. - -IBC provides to modules a set of functions much like the functions which might be provided to a module for interacting with another module on the same ledger: sending data packets and receiving data packets on an established connection and channel, in addition to calls to manage the protocol state: opening and closing connections and channels, choosing connection, channel, and packet delivery options, and inspecting connection and channel status. - -IBC requires certain functionalities and properties of the underlying ledgers, primarily finality (or thresholding finality gadgets), cheaply-verifiable consensus transcripts (such that a light client algorithm can verify the results of the consensus process with much less computation & storage than a full node), and simple key/value store functionality. On the network side, IBC requires only eventual data delivery — no authentication, synchrony, or ordering properties are assumed. - -## Operation - -The primary purpose of IBC is to provide reliable, authenticated, ordered communication between modules running on independent host ledgers. This requires protocol logic in the areas of data relay, data confidentiality and legibility, reliability, flow control, authentication, statefulness, and multiplexing. - -\vspace{3mm} - -### Data relay - -\vspace{3mm} - -In the IBC architecture, modules are not directly sending messages to each other over networking infrastructure, but rather are creating messages to be sent which are then physically relayed from one ledger to another by monitoring "relayer processes". IBC assumes the existence of a set of relayer processes with access to an underlying network protocol stack (likely TCP/IP, UDP/IP, or QUIC/IP) and physical interconnect infrastructure. These relayer processes monitor a set of ledgers implementing the IBC protocol, continuously scanning the state of each ledger and requesting transaction execution on another ledger when outgoing packets have been committed. For correct operation and progress in a connection between two ledgers, IBC requires only that at least one correct and live relayer process exists which can relay between the ledgers. - -\vspace{3mm} - -### Data confidentiality and legibility - -\vspace{3mm} - -The IBC protocol requires only that the minimum data necessary for correct operation of the IBC protocol be made available and legible (serialised in a standardised format) to relayer processes, and the ledger may elect to make that data available only to specific relayers. This data consists of consensus state, client, connection, channel, and packet information, and any auxiliary state structure necessary to construct proofs of inclusion or exclusion of particular key/value pairs in state. All data which must be proved to another ledger must also be legible; i.e., it must be serialised in a standardised format agreed upon by the two ledgers. - -\vspace{3mm} - -### Reliability - -\vspace{3mm} - -The network layer and relayer processes may behave in arbitrary ways, dropping, reordering, or duplicating packets, purposely attempting to send invalid transactions, or otherwise acting in a Byzantine fashion, without compromising the safety or liveness of IBC. This is achieved by assigning a sequence number to each packet sent over an IBC channel, which is checked by the IBC handler (the part of the ledger implementing the IBC protocol) on the receiving ledger, and providing a method for the sending ledger to check that the receiving ledger has in fact received and handled a packet before sending more packets or taking further action. Cryptographic commitments are used to prevent datagram forgery: the sending ledger commits to outgoing packets, and the receiving ledger checks these commitments, so datagrams altered in transit by a relayer will be rejected. IBC also supports unordered channels, which do not enforce ordering of packet receives relative to sends but still enforce exactly-once delivery. - -\vspace{3mm} - -### Flow control - -\vspace{3mm} - -IBC does not provide specific protocol-level provisions for compute-level or economic-level flow control. The underlying ledgers are expected to have compute throughput limiting devices and flow control mechanisms of their own such as gas markets. Application-level economic flow control — limiting the rate of particular packets according to their content — may be useful to ensure security properties and contain damage from Byzantine faults. For example, an application transferring value over an IBC channel might want to limit the rate of value transfer per block to limit damage from potential Byzantine behaviour. IBC provides facilities for modules to reject packets and leaves particulars up to the higher-level application protocols. - -\vspace{3mm} - -### Authentication - -\vspace{3mm} - -All data sent over IBC are authenticated: a block finalised by the consensus algorithm of the sending ledger must commit to the outgoing packet via a cryptographic commitment, and the receiving ledger's IBC handler must verify both the consensus transcript and the cryptographic commitment proof that the datagram was sent before acting upon it. - -\vspace{3mm} - -### Statefulness - -\vspace{3mm} - -Reliability, flow control, and authentication as described above require that IBC initialises and maintains certain status information for each datastream. This information is split between three abstractions: clients, connections, and channels. Each client object contains information about the consensus state of the counterparty ledger. Each connection object contains a specific pair of named identifiers agreed to by both ledgers in a handshake protocol, which uniquely identifies a connection between the two ledgers. Each channel, specific to a pair of modules, contains information concerning negotiated encoding and multiplexing options and state and sequence numbers. When two modules wish to communicate, they must locate an existing connection and channel between their two ledgers, or initialise a new connection and channel(s) if none yet exist. Initialising connections and channels requires a multi-step handshake which, once complete, ensures that only the two intended ledgers are connected, in the case of connections, and ensures that two modules are connected and that future datagrams relayed will be authenticated, encoded, and sequenced as desired, in the case of channels. - -\vspace{3mm} - -### Multiplexing - -\vspace{3mm} - -To allow for many modules within a single host ledger to use an IBC connection simultaneously, IBC allows any number of channels to be associated with a single connection. Each channel uniquely identifies a datastream over which packets can be sent in order (in the case of an ordered channel), and always exactly once, to a destination module on the receiving ledger. Channels are usually expected to be associated with a single module on each ledger, but one-to-many and many-to-one channels are also possible. The number of channels per connection is unbounded, facilitating concurrent throughput limited only by the throughput of the underlying ledgers with only a single connection and pair of clients necessary to track consensus information (and consensus transcript verification cost thus amortised across all channels using the connection). diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/template.latex b/other_docs/ibc-protocol/archive/papers/2020-05/src/template.latex deleted file mode 100644 index e30763d58..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/template.latex +++ /dev/null @@ -1,337 +0,0 @@ -\documentclass[$if(fontsize)$$fontsize$,$endif$$if(lang)$$babel-lang$,$endif$$if(papersize)$$papersize$paper,$endif$$for(classoption)$$classoption$$sep$,$endfor$,conference]{IEEEtran} -$if(beamerarticle)$ -\usepackage{beamerarticle} % needs to be loaded first -$endif$ - -\usepackage{titlesec} -\titleformat*{\subsection}{\sc} -\titlespacing*{\paragraph}{0pt}{-0.5ex}{-3ex} - -\usepackage{graphicx} -\usepackage{subcaption} -\usepackage{listings} -\lstdefinelanguage{JavaScript}{ - basicstyle=\ttfamily\small, - morekeywords=[1]{break, continue, delete, else, for, function, if, in, - new, return, this, typeof, var, void, while, with}, - % Literals, primitive types, and reference types. - morekeywords=[2]{false, null, true, boolean, number, undefined, - Array, Boolean, Date, Math, Number, String, Object}, - % Built-ins. - morekeywords=[3]{eval, parseInt, parseFloat, escape, unescape}, - sensitive, - morecomment=[s]{/*}{*/}, - morecomment=[l]//, - morecomment=[s]{/**}{*/}, % JavaDoc style comments - morestring=[b]', - morestring=[b]" -}[keywords, comments, strings] - - - -$if(csl-refs)$ -\newlength{\cslhangindent} -\setlength{\cslhangindent}{1.5em} -\newenvironment{cslreferences}% - {$if(csl-hanging-indent)$\setlength{\parindent}{0pt}% - \everypar{\setlength{\hangindent}{\cslhangindent}}\ignorespaces$endif$}% - {\par} -$endif$ -$if(fontfamily)$ -\usepackage[$for(fontfamilyoptions)$$fontfamilyoptions$$sep$,$endfor$]{$fontfamily$} -$else$ -\usepackage{lmodern} -$endif$ -$if(linestretch)$ -\usepackage{setspace} -\setstretch{$linestretch$} -$endif$ -\usepackage{amssymb,amsmath} -\usepackage{ifxetex,ifluatex} -\usepackage{fixltx2e} % provides \textsubscript -\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex - \usepackage[$if(fontenc)$$fontenc$$else$T1$endif$]{fontenc} - \usepackage[utf8]{inputenc} -$if(euro)$ - \usepackage{eurosym} -$endif$ -\else % if luatex or xelatex - \ifxetex - \usepackage{mathspec} - \else - \usepackage{fontspec} - \fi - \defaultfontfeatures{Ligatures=TeX,Scale=MatchLowercase} -$for(fontfamilies)$ - \newfontfamily{$fontfamilies.name$}[$fontfamilies.options$]{$fontfamilies.font$} -$endfor$ -$if(euro)$ - \newcommand{\euro}{€} -$endif$ -$if(mainfont)$ - \setmainfont[$for(mainfontoptions)$$mainfontoptions$$sep$,$endfor$]{$mainfont$} -$endif$ -$if(sansfont)$ - \setsansfont[$for(sansfontoptions)$$sansfontoptions$$sep$,$endfor$]{$sansfont$} -$endif$ -$if(monofont)$ - \setmonofont[Mapping=tex-ansi$if(monofontoptions)$,$for(monofontoptions)$$monofontoptions$$sep$,$endfor$$endif$]{$monofont$} -$endif$ -$if(mathfont)$ - \setmathfont(Digits,Latin,Greek)[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$} -$endif$ -$if(CJKmainfont)$ - \usepackage{xeCJK} - \setCJKmainfont[$for(CJKoptions)$$CJKoptions$$sep$,$endfor$]{$CJKmainfont$} -$endif$ -\fi -% use upquote if available, for straight quotes in verbatim environments -\IfFileExists{upquote.sty}{\usepackage{upquote}}{} -% use microtype if available -\IfFileExists{microtype.sty}{% -\usepackage{microtype} -\UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts -}{} -$if(geometry)$ -\usepackage[$for(geometry)$$geometry$$sep$,$endfor$]{geometry} -$endif$ -\usepackage[unicode=true]{hyperref} -$if(colorlinks)$ -\PassOptionsToPackage{usenames,dvipsnames}{color} % color is loaded by hyperref -$endif$ -\hypersetup{ -$if(title-meta)$ - pdftitle={$title-meta$}, -$endif$ -$if(author-meta)$ - pdfauthor={$author-meta$}, -$endif$ -$if(keywords)$ - pdfkeywords={$for(keywords)$$keywords$$sep$, $endfor$}, -$endif$ -$if(colorlinks)$ - colorlinks=true, - linkcolor=$if(linkcolor)$$linkcolor$$else$Maroon$endif$, - citecolor=$if(citecolor)$$citecolor$$else$Blue$endif$, - urlcolor=$if(urlcolor)$$urlcolor$$else$Blue$endif$, -$else$ - pdfborder={0 0 0}, -$endif$ - breaklinks=true} -\urlstyle{same} % don't use monospace font for urls -$if(lang)$ -\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex - \usepackage[shorthands=off,$for(babel-otherlangs)$$babel-otherlangs$,$endfor$main=$babel-lang$]{babel} -$if(babel-newcommands)$ - $babel-newcommands$ -$endif$ -\else - \usepackage{polyglossia} - \setmainlanguage[$polyglossia-lang.options$]{$polyglossia-lang.name$} -$for(polyglossia-otherlangs)$ - \setotherlanguage[$polyglossia-otherlangs.options$]{$polyglossia-otherlangs.name$} -$endfor$ -\fi -$endif$ -$if(natbib)$ -\usepackage{natbib} -\bibliographystyle{$if(biblio-style)$$biblio-style$$else$plainnat$endif$} -$endif$ -$if(biblatex)$ -\usepackage[$if(biblio-style)$style=$biblio-style$,$endif$$for(biblatexoptions)$$biblatexoptions$$sep$,$endfor$]{biblatex} -$for(bibliography)$ -\addbibresource{$bibliography$} -$endfor$ -$endif$ -$if(listings)$ -\usepackage{listings} -$endif$ -$if(lhs)$ -\lstnewenvironment{code}{\lstset{language=Haskell,basicstyle=\small\ttfamily}}{} -$endif$ -$if(highlighting-macros)$ -$highlighting-macros$ -$endif$ -$if(verbatim-in-note)$ -\usepackage{fancyvrb} -\VerbatimFootnotes % allows verbatim text in footnotes -$endif$ -$if(tables)$ -\usepackage{longtable,booktabs} -\usepackage{supertabular} -\let\longtable\supertabular -\let\endlongtable\endsupertabular -\let\endhead\relax -% Fix footnotes in tables (requires footnote package) -\IfFileExists{footnote.sty}{\usepackage{footnote}\makesavenoteenv{long table}}{} -$endif$ -$if(graphics)$ -\usepackage{graphicx,grffile} -\makeatletter -\def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth\else\Gin@nat@width\fi} -\def\maxheight{\ifdim\Gin@nat@height>\textheight\textheight\else\Gin@nat@height\fi} -\makeatother -% Scale images if necessary, so that they will not overflow the page -% margins by default, and it is still possible to overwrite the defaults -% using explicit options in \includegraphics[width, height, ...]{} -\setkeys{Gin}{width=\maxwidth,height=\maxheight,keepaspectratio} -$endif$ -$if(links-as-notes)$ -% Make links footnotes instead of hotlinks: -\renewcommand{\href}[2]{#2\footnote{\url{#1}}} -$endif$ -$if(strikeout)$ -\usepackage[normalem]{ulem} -% avoid problems with \sout in headers with hyperref: -\pdfstringdefDisableCommands{\renewcommand{\sout}{}} -$endif$ -$if(indent)$ -$else$ -\IfFileExists{parskip.sty}{% -\usepackage{parskip} -}{% else -\setlength{\parindent}{0pt} -\setlength{\parskip}{6pt plus 2pt minus 1pt} -} -$endif$ -\setlength{\emergencystretch}{3em} % prevent overfull lines -\providecommand{\tightlist}{% - \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} -$if(numbersections)$ -\setcounter{secnumdepth}{$if(secnumdepth)$$secnumdepth$$else$5$endif$} -$else$ -\setcounter{secnumdepth}{0} -$endif$ -$if(subparagraph)$ -$else$ -% Redefines (sub)paragraphs to behave more like sections -\ifx\paragraph\undefined\else -\let\oldparagraph\paragraph -\renewcommand{\paragraph}[1]{\oldparagraph{#1}\mbox{}} -\fi -\ifx\subparagraph\undefined\else -\let\oldsubparagraph\subparagraph -\renewcommand{\subparagraph}[1]{\oldsubparagraph{#1}\mbox{}} -\fi -$endif$ -$if(dir)$ -\ifxetex - % load bidi as late as possible as it modifies e.g. graphicx - $if(latex-dir-rtl)$ - \usepackage[RTLdocument]{bidi} - $else$ - \usepackage{bidi} - $endif$ -\fi -\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex - \TeXXeTstate=1 - \newcommand{\RL}[1]{\beginR #1\endR} - \newcommand{\LR}[1]{\beginL #1\endL} - \newenvironment{RTL}{\beginR}{\endR} - \newenvironment{LTR}{\beginL}{\endL} -\fi -$endif$ - -% set default figure placement to htbp -\makeatletter -\def\fps@figure{htbp} -\makeatother - -$for(header-includes)$ -$header-includes$ -$endfor$ - -\newcommand*{\TitleFont}{% - \usefont{\encodingdefault}{\rmdefault}{b}{n}% - \fontsize{18}{20}% - \selectfont} - -$if(title)$ -\title{\TitleFont $title$$if(thanks)$\thanks{$thanks$}$endif$} -$endif$ -$if(subtitle)$ -\providecommand{\subtitle}[1]{} -\subtitle{$subtitle$} -$endif$ - -$if(author)$ -\author{ - $for(author)$ - \IEEEauthorblockN{$author.name$} - \IEEEauthorblockA{% - $author.affiliation$ \\ - $author.location$ \\ - $author.email$} - $sep$ \and - $endfor$ -} -$endif$ - -$if(institute)$ -\providecommand{\institute}[1]{} -\institute{$for(institute)$$institute$$sep$ \and $endfor$} -$endif$ -\date{$date$} - -\begin{document} -$if(title)$ -\maketitle -$endif$ -$if(abstract)$ -\begin{abstract} -$abstract$ -\end{abstract} -$endif$ - -$if(keywords)$ -\begin{IEEEkeywords} -$for(keywords)$ - $keywords$$sep$; -$endfor$ -\end{IEEEkeywords} -$endif$ - -$for(include-before)$ -$include-before$ - -$endfor$ -$if(toc)$ -{ -$if(colorlinks)$ -\hypersetup{linkcolor=$if(toccolor)$$toccolor$$else$black$endif$} -$endif$ -\setcounter{tocdepth}{$toc-depth$} -\tableofcontents -} -$endif$ -$if(lot)$ -\listoftables -$endif$ -$if(lof)$ -\listoffigures -$endif$ -$body$ - -$if(natbib)$ -$if(bibliography)$ -$if(biblio-title)$ -$if(book-class)$ -\renewcommand\bibname{$biblio-title$} -$else$ -\renewcommand\refname{$biblio-title$} -$endif$ -$endif$ -\bibliography{$for(bibliography)$$bibliography$$sep$,$endfor$} - -$endif$ -$endif$ -$if(biblatex)$ -\printbibliography$if(biblio-title)$[title=$biblio-title$]$endif$ - -$endif$ -$for(include-after)$ -$include-after$ - -$endfor$ -\end{document} - diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/testing-and-deployment.md b/other_docs/ibc-protocol/archive/papers/2020-05/src/testing-and-deployment.md deleted file mode 100644 index 355dd9154..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/testing-and-deployment.md +++ /dev/null @@ -1,3 +0,0 @@ -A full version of the interblockchain protocol has been implemented in Go in the Cosmos SDK [@ibc_cosmos_sdk], an implementation is in progress in Rust [@ibc_rust], and implementations are planned for other languages in the future. An off-ledger relayer daemon has also been implemented in Go [@relayer_go]. Game of Zones [@game_of_zones], a live test of the initial software release, is currently in progress. Over one hundred simulated zones (separate consensus instances and ledgers) have been successfully linked together [@map_of_zones]. - -Production release and deployment to the Cosmos Network is planned for later this summer. As IBC is a permissionless, opt-in protocol, adoption will be dependent on ledgers voluntarily electing to support the specification, in full or in part. Adoption of IBC does not require connection to the Cosmos Hub, usage of any particular token, or even usage of any other piece of Cosmos software — IBC can be implemented on top of other state machine frameworks such as Substrate [@substrate], or by standalone ledgers using custom logic — adherence to the correct protocol is both necessary and sufficient for successful interoperation. diff --git a/other_docs/ibc-protocol/archive/papers/2020-05/src/usage-patterns.md b/other_docs/ibc-protocol/archive/papers/2020-05/src/usage-patterns.md deleted file mode 100644 index b7f18582f..000000000 --- a/other_docs/ibc-protocol/archive/papers/2020-05/src/usage-patterns.md +++ /dev/null @@ -1,47 +0,0 @@ -## Call receiver - -Essential to the functionality of the IBC handler is an interface to other modules -running on the same ledger, so that it can accept requests to send packets and can -route incoming packets to modules. This interface should be as minimal as possible -in order to reduce implementation complexity and requirements imposed on host ledgers. - -For this reason, the core IBC logic uses a receive-only call pattern that differs -slightly from the intuitive dataflow. As one might expect, modules call into the IBC handler to create -connections, channels, and send packets. However, instead of the IBC handler, upon receipt -of a packet from another ledger, selecting and calling into the appropriate module, -the module itself must call `recvPacket` on the IBC handler (likewise for accepting -channel creation handshakes). When `recvPacket` is called, the IBC handler will check -that the calling module is authorised to receive and process the packet (based on included proofs and -known state of connections / channels), perform appropriate state updates (incrementing -sequence numbers to prevent replay), and return control to the module or throw on error. -The IBC handler never calls into modules directly. - -Although a bit counterintuitive to reason about at first, this pattern has a few notable advantages: - -- It minimises requirements of the host ledger, since the IBC handler need not understand how to call - into other modules or store any references to them. -- It avoids the necessity of managing a module lookup table in the handler state. -- It avoids the necessity of dealing with module return data or failures. If a module does not want to - receive a packet (perhaps having implemented additional authorisation on top), it simply never calls - `recvPacket`. If the routing logic were implemented in the IBC handler, the handler would need to deal - with the failure of the module, which is tricky to interpret. - -It also has one notable disadvantage: without an additional abstraction, the relayer logic becomes more complex, since off-ledger -relayer processes will need to track the state of multiple modules to determine when packets -can be submitted. - -For this reason, ledgers may implement an additional IBC "routing module" which exposes a call dispatch interface. - -## Call dispatch - -For common relay patterns, an "IBC routing module" can be implemented which maintains a module dispatch table and simplifies the job of relayers. - -In the call dispatch pattern, datagrams (contained within transaction types defined by the host ledger) are relayed directly -to the routing module, which then looks up the appropriate module (owning the channel and port to which the datagram was addressed) -and calls an appropriate function (which must have been previously registered with the routing module). This allows modules to -avoid handling datagrams directly, and makes it harder to accidentally screw-up the atomic state transition execution which must -happen in conjunction with sending or receiving a packet (since the module never handles packets directly, but rather exposes -functions which are called by the routing module upon receipt of a valid packet). - -Additionally, the routing module can implement default logic for handshake datagram handling (accepting incoming handshakes -on behalf of modules), which is convenient for modules which do not need to implement their own custom logic. diff --git a/other_docs/ibc-protocol/archive/v0_3_1_IBC.pdf b/other_docs/ibc-protocol/archive/v0_3_1_IBC.pdf deleted file mode 100644 index 13ba6e8a5..000000000 Binary files a/other_docs/ibc-protocol/archive/v0_3_1_IBC.pdf and /dev/null differ diff --git a/other_docs/ibc-protocol/assets/interchain-standards-image.jpg b/other_docs/ibc-protocol/assets/interchain-standards-image.jpg deleted file mode 100644 index 10d39ebf8..000000000 Binary files a/other_docs/ibc-protocol/assets/interchain-standards-image.jpg and /dev/null differ diff --git a/other_docs/ibc-protocol/meta/CONTRIBUTING.md b/other_docs/ibc-protocol/meta/CONTRIBUTING.md deleted file mode 100644 index 1e996f3e3..000000000 --- a/other_docs/ibc-protocol/meta/CONTRIBUTING.md +++ /dev/null @@ -1,61 +0,0 @@ -# Contribution Guidelines - -Thanks for your interest in contributing to IBC! Contributions are always welcome. - -Contributing to this repo can mean many things such as participating in discussion or proposing changes. To ensure a smooth workflow for all contributors, the general procedure for contributing has been established: - -- Feel free to [open](https://github.com/cosmos/ibc/issues/new) an issue to raise a question, explain a concern, or discuss a possible future feature, protocol change, or standard. - - Make sure first that the issue does not already [exist](https://github.com/cosmos/ibc/issues). - - Participate in thoughtful discussion on the issue. - -- If you would like to contribute in fixing / closing an issue: - - If the issue is a proposal, ensure that the proposal has been accepted. - - Ensure that nobody else has already begun working on this issue. If they have, make sure to contact them to collaborate. - - If nobody has been assigned for the issue and you would like to work on it, make a comment on the issue to inform the community of your intentions to begin work. - - Follow standard Github best practices: fork the repo, branch from the HEAD of `master`, make some commits, and submit a PR to `master`. - For more details, see the [Pull Requests](#pull-requests) section below. - - Be sure to submit the PR early in `Draft` mode, even if it's incomplete as this indicates to the community you're working on something and allows them to provide comments at an early stage. - - When the PR is complete it can be marked `Ready for Review`. - -- If you would like to propose a new standard for inclusion in the IBC standards, please take a look at [PROCESS.md](./PROCESS.md) for a detailed description of the standardisation process. - - To start a new standardisation document, copy the *template* and open a PR. - -If you have any questions, you can usually find some IBC team members on the [Cosmos Discord](https://discord.gg/rPFPxVJmUZ). - -## Pull Requests - -To accommodate review process we suggest that PRs are categorically broken up. -Each PR should address only a single issue and **a single standard**. -The PR name should be prefixed by the standard number, -e.g., `ICS4: Some improvements` should contain only changes to [ICS 4](../spec/core/ics-004-channel-and-packet-semantics/README.md). -If fixing an issue requires changes to multiple standards, create multiple PRs and mention the inter-dependencies. - -### Process for reviewing PRs - -All PRs require an approval from at least two members of the [standardisation committee](./STANDARDS_COMMITTEE.md) before merge. -The PRs submitted by one of the members of the standardisation committee require an approval from only one other member before merge. -When reviewing PRs please use the following review explanations: -- `Approval` through the GH UI means that you understand all the changes proposed in the PR. In addition: - - You must also think through anything which ought to be included but is not. - - You must think through any potential security issues or incentive-compatibility flaws introduced by the changes. - - The changes must be consistent with the other IBC standards, especially the [core IBC standards](../README.md#core). - - The modified standard must be consistent with the description from [ICS 1](../spec/ics-001-ics-standard/README.md). -- If you are only making "surface level" reviews, submit any notes as `Comments` without adding a review. - -### PR Targeting - -Ensure that you base and target your PR on the `master` branch. - -### Development Procedure - -- The latest state of development is on `master`. -- Create a development branch either on `github.com/cosmos/ibc` or your fork (using `git remote add fork`). - - To ensure a clear ownership of branches on the ibc repo, branches must be named with the convention `{moniker}/{issue#}-branch-name` -- Before submitting a pull request, begin `git rebase` on top of `master`. - **Since standards cannot be compiled, make sure that the changes in your PR remains consistent with the new commits on the `master` branch**. - -### Pull Merge Procedure - -- Ensure all github requirements pass. -- Squash and merge pull request. - diff --git a/other_docs/ibc-protocol/meta/PROCESS.md b/other_docs/ibc-protocol/meta/PROCESS.md deleted file mode 100644 index 7c296befe..000000000 --- a/other_docs/ibc-protocol/meta/PROCESS.md +++ /dev/null @@ -1,83 +0,0 @@ -## IBC Standardisation Process - -IBC standardisation will follow an adaptation of the [TC 39](https://tc39.github.io/process-document/) process used by the ECMAScript steering committee. - -#### Stage 1 - `Strawman` - -- _**Purpose**_: Start the specification process -- _**Entrance Criteria**_: [Open an issue](https://github.com/cosmos/ibc/issues/new) on this repository with a short outline of your proposal and a specification name. -- _**Acceptance Requirements**_: No acceptance required to move to the next stage. Keep the issue around to track the specification status, and close it when the final specification is merged or the proposal abandoned. -- _**Spec Quality**_: Outline only. Link to any prior documentation, discussion, or reference materials. -- _**Changes Expected Post-Acceptance**_: N/A -- _**Implementation Types Expected**_: None required, but link to any existing - -#### Stage 2 - `Draft` - -- _**Purpose**_: - * Make the case for the addition of this specification to the IBC ecosystem - * Describe the shape of a potential solution - * Identify challenges to this proposal -- _**Entrance Criteria**_: - * Prose outlining the problem or need and the general shape of a solution in a PR to a `./spec/{area}/ics-{{ .Spec.Number }}-{{ .Spec.Name }}/README.md` file in this repository. - This file should contain: - - List of expected projects & users within the Cosmos ecosystem who might make use of the specification along with any particular requirements they have - - Discussion of key algorithms, abstractions, and semantics - - High-level application interface outline, where applicable - - Identification of potential design trade-offs and implementation challenges/complexity - - For a more detailed description of standard requirements, see [ICS 1](../spec/ics-001-ics-standard). - - For more details on submitting a PR, take a look at the [Pull Requests](./CONTRIBUTING.md#pull-requests) section in the contribution guidelines. - * Identified `author(s)` who will advance the proposal in the header of the standard file - * Any additional reference documentation or media in the `./spec/ics-{{ .Spec.Number }}-{{ .Spec.Name }}` directory - * The specification team expects that this proposal will be finalised and eventually included in the IBC standard set. -- _**Spec Quality**_: - * Follows the structure laid out in ICS 1 and provides a reasonable overview of the proposed addition. -- _**Acceptance Requirements**_: - * The PR has received two approvals from members of the [core specification committee](./STANDARDS_COMMITTEE.md), at which point it can be merged into the IBC repository. -- _**Changes Expected Post-Acceptance**_: - * Changes to details but not to the key concepts are expected after a standard enters draft stage. Implementers should work with the spec authors as work continues on spec development. -- _**Implementation Types Expected**_: - * Tightly bounded demos, example repositories showing reproduction steps for issues fixed by proposal - -#### Stage 3 - `Candidate` - -- _**Purpose**_: - * Indicate that further refinement will require feedback from implementations and users -- _**Entrance Criteria**_: - * Everything from stages 1 & 2 - * Complete specification text - * At least one specification-compatible implementation exists - * All relevant ecosystem stakeholders have been given a chance to review and provide feedback on the standard - * The solution is complete and no further work is possible without implementation experience, significant usage and external feedback. -- _**Spec Quality**_: - * Complete: all semantics, syntax and API are completed as described -- _**Acceptance Requirements**_: - * The PR changing the stage to "candidate" has been approved by two members of the core specification team. -- _**Changes Expected Post-Acceptance**_: - * Limited: only those deemed critical based on implementation experiences. -- _**Implementation Types Expected**_: - * Specification-compliant - -#### Stage 4 - `Finalised` - -- _**Purpose**_: - * Indicate that the addition is included in the formal ICS standard set -- _**Entrance Criteria**_: - * Everything from stages 1,2,3 - * At least two specification-compatible implementations exist, and they have been tested against each other - * All relevant ecosystem stakeholders approve the specification (any holdout can block this stage) - * Acceptance tests are written and merged into the relevant repositories - * All files in the `./spec/ics-{{ .Spec.Number }}-{{ .Spec.Name}}/` directory are up to date and merged into the `cosmos/ics` repository -- _**Acceptance Requirements**_: - * The PR changing the stage to "finalised" has been approved by representatives from all relevant ecosystem stakeholders, and all members of the core specification team. -- _**Spec Quality**_: - * Final: All changes as a result of implementation experience are integrated -- _**Changes Expected Post-Acceptance**_: - * None -- _**Implementation Types Expected**_: - * Shipping/Production - -### Calls for implementation and feedback - -When an addition is accepted at the “candidate” (stage 3) maturity level, the committee is signifying that it believes design work is complete and further refinement will require implementation experience, significant usage and external feedback. diff --git a/other_docs/ibc-protocol/meta/ROADMAP.md b/other_docs/ibc-protocol/meta/ROADMAP.md deleted file mode 100644 index ade9b61a3..000000000 --- a/other_docs/ibc-protocol/meta/ROADMAP.md +++ /dev/null @@ -1,19 +0,0 @@ -# Roadmap IBC specs - -_Lastest update: April 6th, 2022_ - -This document endeavours to inform the wider IBC community about plans and priorities for the specification work of IBC. This roadmap should be read as a high-level guide, rather than a commitment to schedules and deliverables. The degree of specificity is inversely proportional to the timeline. We will update this document periodically to reflect the status and plans. - -This roadmap reflects the major activities that the [standards committee](STANDARDS_COMMITTEE.md) is engaged with in the coming quarters. It is, by no means, a thorough reflection of all the specification work that is happening in the broad ecosystem, as many other parties work as well in specs that eventually end up in this repository. - -## Q2 - 2022 - -- Work on general readability improvements and inconsistency fixes in some of the specs ([ICS02](https://github.com/cosmos/ibc/blob/master/spec/core/ics-002-client-semantics/README.md), [ICS06](https://github.com/cosmos/ibc/blob/master/spec/client/ics-006-solo-machine-client/README.md), [ICS07](https://github.com/cosmos/ibc/blob/master/spec/client/ics-007-tendermint-client/README.md)). This is a first step on the long-term plan to make the specs easier to understand to qualified developers. -- The [connection](https://github.com/cosmos/ibc/pull/621) and [channel](https://github.com/cosmos/ibc/pull/677) upgradability specs have been merged, but they need some small fixes. The spec team will also help with the planning of the implementation of channel upgradability in [ibc-go](https://github.com/cosmos/ibc-go). -- Finish writing the spec for [ordered channels that support timeouts](https://github.com/cosmos/ibc/pull/636). -- Start writing the spec to support state trees without absence proofs. -- The implementation of [ICS29](https://github.com/cosmos/ibc/tree/master/spec/app/ics-029-fee-payment) in ibc-go will be finished in Q2 and the spec might need some updates to reflect the latest status. -- Finish [ICS28](https://github.com/cosmos/ibc/pull/666) (Cross-chain validation) spec. -- Review and possibly merge [ICS721](https://github.com/cosmos/ibc/pull/615) spec for NFT transfers. -- Review and possibly merge the spec for [IBC queries](https://github.com/cosmos/ibc/pull/647). -- Write and add to the repository a high level overview of what IBC is. This can be used as an entry point for newcomers to IBC to understand its general principles. \ No newline at end of file diff --git a/other_docs/ibc-protocol/meta/STANDARDS_COMMITTEE.md b/other_docs/ibc-protocol/meta/STANDARDS_COMMITTEE.md deleted file mode 100644 index 6a6606b3c..000000000 --- a/other_docs/ibc-protocol/meta/STANDARDS_COMMITTEE.md +++ /dev/null @@ -1,8 +0,0 @@ -## Standards Committee - -Currently, the core standardisation committee consists of: -- Aditya Sripal (@adityasripal) -- Christopher Goes (@cwgoes) -- Marius Poke (@mpoke) - -A two-of-three quorum is needed to approve pull requests to this repository. diff --git a/other_docs/ibc-protocol/spec/app/ics-020-fungible-token-transfer/README.md b/other_docs/ibc-protocol/spec/app/ics-020-fungible-token-transfer/README.md deleted file mode 100644 index db69efc38..000000000 --- a/other_docs/ibc-protocol/spec/app/ics-020-fungible-token-transfer/README.md +++ /dev/null @@ -1,373 +0,0 @@ ---- -ics: '20' -title: 同质通证转移 -stage: 草案 -category: IBC/APP -requires: 25, 26 -kind: 实例化 -author: Christopher Goes -created: '2019-07-15' -modified: '2020-02-24' ---- - -## 概要 - -该标准规定了通过 IBC 通道在各自链上的两个模块之间进行通证转移的数据包的数据结构、状态机处理逻辑以及编码细节。本文所描述的状态机逻辑允许在无许可通道打开的情况下安全的处理多个链的通证。该逻辑通过在节点状态机上的 IBC 路由模块和一个现存的资产跟踪模块之间建立实现了一个同质通证转移的桥接模块。 - -### 动机 - -基于 IBC 协议连接的一组链的用户可能希望在一条链上能利用在另一条链上发行的资产来使用该链上的附加功能,例如交易或隐私保护,同时保持发行链上的原始资产的同质性。该应用层标准描述了一个在基于 IBC 连接的链间转移同质通证的协议,该协议保留了资产的同质性和资产所有权,限制了拜占庭错误(Byzantine faults)的影响,并且无需额外许可。 - -### 定义 - -[ICS 25](../../core/ics-025-handler-interface) 和 [ICS 26](../../core/ics-026-routing-module) 分别定义了 IBC 处理程序接口和 IBC 路由模块接口。 - -### 所需属性 - -- 保持同质性(双向锚定)。 -- 保持供应量不变(在单一源链和模块上保持不变或通胀)。 -- 无许可的通证转移,无需将连接(connections)、模块或通证面额加入白名单。 -- 对称(所有链实现相同的逻辑,hubs 和 zones 无协议差别)。 -- 故障遏制:防止源自链`A`的通证由于链`B`的拜占庭行为而发生拜占庭膨胀(尽管任何将通证发送到链`B`的用户都可能面临风险)。 - -## 技术规范 - -### 数据结构 - -仅需要一个数据包数据类型`FungibleTokenPacketData`,该类型指定了面额,数量,发送账户,接受账户以及发送链是否为资产的发行链。 - -```typescript -interface FungibleTokenPacketData { - denomination: string - amount: uint256 - sender: string - receiver: string -} -``` - -当通证使用 ICS 20 协议跨链发送时,它们开始记录它们已使用的通道。此信息被编码到`denom`字段中。 - -ics20 通证面额以`{ics20Port}/{ics20Channel}/{denom}`形式表示,其中`ics20Port`和`ics20Channel`是当前链上资金使用的 ics20 端口和通道。前缀端口和通道表示资金先前通过哪个通道发送。如果`{denom}`包含`/` ,那么它也必须是 ics20 形式,表示该通证具有多跳记录。请注意,这要求在非 IBC 通证面额名称中禁止使用`/` (斜线字符)。 - -发送链可以充当源zone或接收zone。当链通过不等于最后一个前缀端口和通道对的端口和通道发送通证时,它充当源zone。当从源zone发送通证时,目标端口和通道将作为面额的前缀(一旦接收到通证),将另一个跃点添加到通证记录。当链通过端口和通道发送通证时,它等于最后一个前缀端口和通道对,它充当接收zone。当通证从接收zone发送时,面额上的最后一个前缀端口和通道对被删除(一旦收到通证),撤消通证记录中的最后一跳。 [ibc-go implementation ](https://github.com/cosmos/ibc-go/blob/457095517b7832c42ecf13571fee1e550fec02d0/modules/apps/transfer/keeper/relay.go#L18-L49)中有更完整的解释。 - -回执数据类型描述转账是成功还是失败,以及失败的原因(如果有)。 - -```typescript -type FungibleTokenPacketAcknowledgement = FungibleTokenPacketSuccess | FungibleTokenPacketError; - -interface FungibleTokenPacketSuccess { - // 这是二进制 0x01 base64 编码 - result: "AQ==" -} - -interface FungibleTokenPacketError { - error: string -} -``` - -请注意,当`FungibleTokenPacketData`和`FungibleTokenPacketAcknowledgement`序列化为数据包数据时,它们都必须是 JSON 编码的(不是 Protobuf 编码的)。另请注意, `uint256`在转换为 JSON 时是字符串编码的,但必须是`[0-9]+`形式的有效十进制数。 - -同质化通证转移桥模块跟踪与特定通道相关的托管地址。假定`ModuleState`的字段在范围内。 - -```typescript -interface ModuleState { - channelEscrowAddresses: Map -} -``` - -### 子协议 - -本文所述的子协议应该在“同质通证转移桥接”模块中实现,并且可以访问 bank 模块和 IBC 路由模块。 - -#### 端口 & 通道设置 - -当创建“同质通证转移桥接”模块时(也可能是区块链本身初始化时),必须仅调用一次`setup`函数用于绑定到对应的端口并创建一个托管地址(该地址由模块所有)。 - -```typescript -function setup() { - capability = routingModule.bindPort("bank", ModuleCallbacks{ - onChanOpenInit, - onChanOpenTry, - onChanOpenAck, - onChanOpenConfirm, - onChanCloseInit, - onChanCloseConfirm, - onRecvPacket, - onTimeoutPacket, - onAcknowledgePacket, - onTimeoutPacketClose - }) - claimCapability("port", capability) -} -``` - -调用`setup`函数后,通过在不同链上的同质通证转移模块之间的 IBC 路由模块创建通道。 - -管理员(具有在节点的状态机上创建连接和通道的权限)负责在本地链与其他链的状态机之间创建连接,在本地链与其他链的该模块(或支持该接口的其他模块)的实例之间创建通道。本规范仅定义了数据包处理语义,模块本身在任意时间点都无需关心连接或通道是否存在。 - -#### 路由模块回调 - -##### 通道生命周期管理 - -机器`A`和机器`B`在当且仅当以下情况下接受来自第三台机器上任何模块的新通道创建请求: - -- 创建的通道是无序的。 -- 版本是`ics20-1` 。 - -```typescript -function onChanOpenInit( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - version: string) { - // 只允许无序通道 - abortTransactionUnless(order === UNORDERED) - // 断言版本是“ics20-1” - abortTransactionUnless(version === "ics20-1") - // 分配一个托管地址 - channelEscrowAddresses[channelIdentifier] = newAddress() -} -``` - -```typescript -function onChanOpenTry( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - version: string, - counterpartyVersion: string) { - // 只允许无序通道 - abortTransactionUnless(order === UNORDERED) - // 断言:版本是“ics20-1” - abortTransactionUnless(version === "ics20-1") - abortTransactionUnless(counterpartyVersion === "ics20-1") - // 分配一个托管地址 - channelEscrowAddresses[channelIdentifier] = newAddress() -} -``` - -```typescript -function onChanOpenAck( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string) { - // 端口已经被验证 - // 断言:交易对手选择的版本是“ics20-1” - abortTransactionUnless(counterpartyVersion === "ics20-1") -} -``` - -```typescript -function onChanOpenConfirm( - portIdentifier: Identifier, - channelIdentifier: Identifier) { - // 接受通道确认,端口已经验证,版本已经验证 -} -``` - -```typescript -function onChanCloseInit( - portIdentifier: Identifier, - channelIdentifier: Identifier) { - // 总是中止交易 - abortTransactionUnless(FALSE) -} -``` - -```typescript -function onChanCloseConfirm( - portIdentifier: Identifier, - channelIdentifier: Identifier) { - // 无需操作 -} -``` - -##### 数据包中继 - -用简单文字来描述就是,在 `A` 和`B`两个链间: - -- 在源 zone 上,桥接模块会在发送链上托管现有的本地资产面额,并在接收链上生成凭证。 -- 在接收 zone 上,桥接模块会在发送链上销毁本地凭证,并在接收链上解除对本地资产面额的托管。 -- 当数据包超时时,本地资产将解除托管并退还给发送者,或将凭证发回给发送者。 -- 回执数据用于处理失败,例如无效面额或无效目标帐户。返回失败的回执比终止交易更可取,因为它更容易使发送链根据失败的类型而采取适当的措施。 - -`sendFungibleTokens`必须由模块中的交易处理程序调用,该处理程序对于宿主状态机上特定的帐户所有者,执行适当的签名检查。 - -```typescript -function sendFungibleTokens( - denomination: string, - amount: uint256, - sender: string, - receiver: string, - sourcePort: string, - sourceChannel: string, - timeoutHeight: Height, - timeoutTimestamp: uint64) { - prefix = "{sourcePort}/{sourceChannel}/" - // 如果面额没有前缀,我们就是源链 - source = denomination.slice(0, len(prefix)) !== prefix - if source { - // 确定托管账户 - escrowAccount = channelEscrowAddresses[sourceChannel] - // 托管源通证(如果余额不足,则假定失败) - bank.TransferCoins(sender, escrowAccount, denomination, amount) - } else { - // 接收者为源链,销毁凭证 - bank.BurnCoins(sender, denomination, amount) - } - - // 创建 FungibleTokenPacket 数据 - data = FungibleTokenPacketData{denomination, amount, sender, receiver} - - // 使用 ICS4 中定义的接口发送数据包 - handler.sendPacket( - getCapability("port"), - sourcePort, - sourceChannel, - timeoutHeight, - timeoutTimestamp, - data - ) -} -``` - -当路由模块收到一个数据包后调用`onRecvPacket`。 - -```typescript -function onRecvPacket(packet: Packet) { - FungibleTokenPacketData data = packet.data - // 构造默认的成功回执 - FungibleTokenPacketAcknowledgement ack = FungibleTokenPacketAcknowledgement{true, null} - prefix = "{packet.sourcePort}/{packet.sourceChannel}/" - // 如果数据包以发送链为前缀,我们就是源链 - source = data.denom.slice(0, len(prefix)) === prefix - if source { - // 接收者是源链:取消托管通证 - // 确定托管账户 - escrowAccount = channelEscrowAddresses[packet.destChannel] - // 将通证取消托管并发给接收者(如果余额不足,则失败) - err = bank.TransferCoins(escrowAccount, data.receiver, data.denom.slice(len(prefix)), data.amount) - if (err !== nil) - ack = FungibleTokenPacketAcknowledgement{false, "transfer coins failed"} - } else { - prefix = "{packet.destPort}/{packet.destChannel}/" - prefixedDenomination = prefix + data.denom - // 发送者是来源,将凭证铸造给接收者(如果余额不足,则失败) - err = bank.MintCoins(data.receiver, prefixedDenomination, data.amount) - if (err !== nil) - ack = FungibleTokenPacketAcknowledgement{false, "mint coins failed"} - } - return ack -} -``` - -当由路由模块发送的数据包被确认后,该模块调用`onAcknowledgePacket`。 - -```typescript -function onAcknowledgePacket( - packet: Packet, - acknowledgement: bytes) { - // 如果转账失败,退还通证 - if (!ack.success) - refundTokens(packet) -} -``` - -当由路由模块发送的数据包超时(例如数据包没有被目标链接收到)后,路由模块调用`onTimeoutPacket`。 - -```typescript -function onTimeoutPacket(packet: Packet) { - // 数据包超时,所以退还通证 - refundTokens(packet) -} -``` - -`refundTokens`会在两处被调用,失败时的`onAcknowledgePacket` 和`onTimeoutPacket`,用来退还托管的通证给原始发送者。 - -```typescript -function refundTokens(packet: Packet) { - FungibleTokenPacketData data = packet.data - prefix = "{packet.sourcePort}/{packet.sourceChannel}/" - // 如果面额没有前缀,我们就是来源 - source = data.denom.slice(0, len(prefix)) !== prefix - if source { - // 发送人是源链,取消托管通证返回给发送人 - escrowAccount = channelEscrowAddresses[packet.srcChannel] - bank.TransferCoins(escrowAccount, data.sender, data.denom, data.amount) - } else { - // 接收者是源链,将铸造凭证返还给发送者 - bank.MintCoins(data.sender, data.denom, data.amount) - } -} -``` - -```typescript -function onTimeoutPacketClose(packet: Packet) { - // 不会发生,只允许无序通道 -} -``` - -#### 原理 - -##### 正确性 - -该实现保持了同质性和供应量不变。 - -同质性:如果通证已发送到目标链,则可以以相同面额和数量兑换回源链。 - -供应量:将供应重新定义为未锁定的通证。所有源链的发送量等于目标链的接受量。源链可以改变通证的供应量。 - -##### 多链注意事项 - -此规范不能直接处理“菱形问题”,在该问题中,用户将源自链 A 的通证发送到链 B,然后又发送给链 D,并希望通过 D-> C-> A 归还它,由于此时通证的供应量被认为是由链 B 控制(面额将为“ {portOnD} / {channelOnD} / {portOnB} / {channelOnB} / denom”),链 C 不能充当中介。尚不清楚该场景是否应按协议处理—可能只需要原路返回就可以了(如果在这两个途径上都有频繁的流动性和一定的结余,菱形路径将在大多数情况下适用)。较长的赎回路径引起的复杂性可能导致网络拓扑结构中出现中心链。 - -为了跟踪沿着各种路径在链网络中移动的所有面额,对于特定的链实现一个注册表将有助于跟踪每个面额的“全局”源链。最终用户服务提供商(例如钱包作者)可能希望集成这样的注册表,或保留自己的典范源链和人类可读名称的映射,以改善用户体验。 - -#### 可选附录 - -- 每个本地链都可以选择保留一个查找表,以在状态中使用简短,用户友好的本地面额,在发送和接收数据包时,它们会与较长的面额进行转换。 -- 可以对可以连接哪些其他机器以及可以建立哪些通道施加额外的限制。 - -## 向后兼容性 - -不适用。 - -## 向前兼容性 - -此初始标准在通道握手中使用版本“ ics20-1”。 - -该标准的未来版本可以在通道握手中使用其他版本,并安全的更改数据包数据格式和数据包处理程序的语义。 - -## 示例实现 - -即将到来。 - -## 其他实现 - -即将到来。 - -## 历史 - -2019年7月15 - 草案完成 - -2019年7月29 - 主要修订;整理 - -2019年8月25 - 主要修订;进一步整理 - -2020年2月3日-进行修订,以处理对成功和失败的回执 - -2020年2月24日-用来推断来源字段的修订,包括版本字符串 - -2020年7月27日-重新添加源字段 - -## 版权 - -本文中的所有内容均根据 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 获得许可。 diff --git a/other_docs/ibc-protocol/spec/app/ics-027-interchain-accounts/README.md b/other_docs/ibc-protocol/spec/app/ics-027-interchain-accounts/README.md deleted file mode 100644 index 14f986aad..000000000 --- a/other_docs/ibc-protocol/spec/app/ics-027-interchain-accounts/README.md +++ /dev/null @@ -1,585 +0,0 @@ ---- -ics: '27' -title: 链间账户 -stage: 草案 -category: IBC/APP -requires: 25, 26 -kind: 实例化 -author: Tony Yun , Dogemos , Sean King -created: '2019-08-01' -modified: '2020-07-14' ---- - -## 概要 - -该标准指定了不同链之间 IBC 通道之上的帐户管理系统的数据包数据结构,状态机处理逻辑和编码详细信息。 - -### 动机 - -ICS-27链间帐户标准规定了基于IBC的跨链账户管理协议。具备ICS-27功能的区块链可以通过交易(而不是用私钥签名)在其他具备ICS-27功能的区块链上创建并管理账户。链间账户保留了普通账户的所有功能(例如:质押,投票,转帐,发交易),但这是由另外一条链通过IBC的方式管理的,这使得在控制链上的所有者账户能够完全操控它在主链上注册的链间账户。 - -### 定义 - -- 主链:链间账户在主链上注册。主链监听来自控制链的 IBC 数据包,数据包内含有链间账户可执行的控制指令(例如:Cosmos SDK信息)。 -- 控制链:控制链在主链上注册并管理账户。控制链通过向主链发送 IBC 数据包来控制主链上的账户。 -- 链间账户:链间账户是主链上的账户。链间账户拥有普通账户的所有功能。但控制链并不通过私钥签发交易,而是通过向主链发送 IBC 数据包,指示链间账户处理交易。 -- 链间账户所有者:控制链上的账户。主链上的每个链间账户在控制链上都有一个对应的所有者账户。 - -IBC 处理程序接口和 IBC 中继模块接口分别在 [ICS 25](../../core/ics-025-handler-interface) 和 [ICS 26](../../core/ics-026-routing-module) 中定义。 - -### 所需属性 - -- 无需许可:链间账户可以由任意的参与者创建,并且无需第三方的许可(如链上治理)。需要注意的是:不同的创建方法可能有不同的许可方案,IBC 协议对于这些方案的安全性未作规定。 -- 故障隔离:一条控制链无法管理其它控制链注册的控制账户。比如,如果一条控制链受到了分叉攻击,只有分叉链注册的链间账户会受到影响。 -- 发送至主链链间账户的交易顺序必须保持不变。链间账户执行交易的顺序必须和控制链发送交易顺序一致。 -- 如果一条通道关闭,控制链必须有能力通过创建一条新通道来重新访问已注册的链间账户。 -- 每个链间账户都隶属于控制链上的一个所有者账户。只有所有者账户有权控制隶属于自己的链间账户。相应的权限控制由控制链施行。 -- 控制链必须存储所有隶属于自己的链间账户地址。 -- 主链必须有能力限制链上的链间账户的功能(例如,主链可以决定链上的链间账户能不能参与质押)。 - -## 技术规范 - -### 总体设计 - -一条链可以同时使用链间账户协议的两部分(控制链协议和主链协议)或其中任意一部分。在其它主链上注册链间账户的控制链不一定要允许其他控制链在本链注册链间账户,反之亦然。 - -该标准定义了注册链间账户和发送交易数据的总体方法,其中的交易数据将会代表所有者账户被链间账户执行。主链负责反序列化并执行交易数据;控制链在发送交易数据之前必须知道主链会如何处理交易数据,这是在创建通道的过程中控制链和主链通过握手达成的。 - -### 控制链合约 - -#### **RegisterInterchainAccount** - -`RegisterInterchainAccount` 是注册链间账户的切入点。它可以用所有者账户地址生成新的控制者portID。它将绑定一个控制者portID并且调用04-channel的`ChanOpenInit`。控制者portID如果已经被占用,`RegisterInterchainAccount`会返回错误。 `ChannelOpenInit`事件将被触发并被链下进程(如中继器)检测到。链间账户通过`OnChanOpenTry`步骤在主链上注册。这一方法必须在`OPEN`的连接被创建之后才能使用给定的connectionID来调用。调用者必须提供完整的通道版本,其中必须包括带有完整元数据的 ICA 版本并且可能包括其他中间件的版本,其中中间件的作用是在通道两端包装 ICA。这将会需要通道两端的中间件信息。所以,建议 ICA 认证的应用自动构建 ICA 版本并且允许用户启用额外的中间件版本号更新。 - -```typescript -function RegisterInterchainAccount(connectionId: Identifier, owner: string, version: string) returns (error) { -} -``` - -#### **SendTx** - -`SendTx` 用于发送IBC数据包,数据包中包含链间账户所有者发给链间账户的指令(消息)。 - -```typescript -function SendTx( - capability: CapabilityKey, - connectionId: Identifier, - portId: Identifier, - icaPacketData: InterchainAccountPacketData, - timeoutTimestamp uint64) { - // 检查该 portId 与 connectionId当前是否有活动通道 - // 如有,则意味着已通过该 portId 与 connectionId注册过跨链账户 - activeChannelID, found = GetActiveChannelID(portId, connectionId) - abortTransactionUnless(found) - - // 验证 timeoutTimestamp - abortTransactionUnless(timeoutTimestamp <= currentTimestamp()) - - // 验证 icaPacketData - abortTransactionUnless(icaPacketData.type == EXECUTE_TX) - abortTransactionUnless(icaPacketData.data != nil) - - // 将 icaPacketData 通过活动通道处理程序发送至主链 - sendPacket( - capability, - portId, // 源端口 ID - activeChannelID, // 源通道 ID - 0, - timeoutTimestamp, - icaPacketData - ) -} -``` - -### 主链合约 - -#### **RegisterInterchainAccount** - -在通过握手创建通道的过程中,`RegisterInterchainAccount`在执行`OnChanOpenTry`时被调用。 - -```typescript -function RegisterInterchainAccount(counterpartyPortId: Identifier, connectionID: Identifier) returns (nil) { -// 检查以确保帐户尚未注册 -// 在给定交易对手端口 ID 和底层连接 ID 的情况下,在链上创建一个新地址 -// 调用 SetInterchainAccountAddress() -} -``` - -#### **AuthenticateTx** - -`AuthenticateTx` 在执行`ExecuteTx`之前被调用。 `AuthenticateTx` 核实特定消息的签名者为链间账户,并且该链间账户与发送IBC数据包的对端通道portID相关联。 - -```typescript -function AuthenticateTx(msgs []Any, connectionId string, portId string) returns (error) { - // GetInterchainAccountAddress(portId, connectionId) - // if interchainAccountAddress != msgSigner return error -} -``` - -#### **ExecuteTx** - -执行所有者账户在控制链上发送的每则消息。 - -```typescript -function ExecuteTx(sourcePort: Identifier, channel Channel, msgs []Any) returns (resultString, error) { -// 验证每条消息 -// 通过传入的源端口和通道的 connectionID 检索给定通道的链间帐户 -// 验证跨链账户是每条消息的授权签名者 -// 执行每条消息 -// 返回交易结果 -} -``` - -### 实用函数 - -```typescript -// 为给定的 portID 和 connectionID 设置活动通道。 -function SetActiveChannelID(portId: Identifier, connectionId: Identifier, channelId: Identifier) returns (error){ -} - -// 根据 portID 和 connectionID,返回活动通道的 ID(如果存在)。 -function GetActiveChannelID(portId: Identifier, connectionId: Identifier) returns (Identifier, boolean){ -} - -// 在状态中存储链间账户的地址。 -function SetInterchainAccountAddress(portId: Identifier, connectionId: Identifier, address: string) returns (string) { -} - -// 从状态中检索链间帐户。 -function GetInterchainAccountAddress(portId: Identifier, connectionId: Identifier) returns (string, bool){ -} -``` - -### 注册与控制流程 - -#### 注册链间账户的流程 - -要注册链间账户,我们需要一个链下进程(中继器)来监听`ChannelOpenInit`事件,并且有能力根据给定的连接来握手,从而创建通道。 - -1. 控制器链将新的 IBC 端口与给定链间*帐户所有者地址*的控制器端口 ID 绑定。 - -这个端口将被用来在控制链和主链之间为一对特定的所有者账户/链间账户创建通道。只有链间账户的`{owner-account-address}`与绑定的端口相匹配才会被授权使用相应的通道(该通道是根据控制链的portID创建的)发送IBC数据包。由每个控制链在链上施行此端口注册和访问。 - -1. 在给定连接的情况下,控制链会发出一个事件信号,在此端口上打开一个新通道。 -2. 监听`ChannelOpenInit`事件的中继器将继续为创建通道而进行握手。 -3. 在主链的`OnChanOpenTry`回调过程中,一个链间账户将被注册,并将链间账户地址到所有者帐户地址的映射存储在账户状态中(用于在执行时验证主链上的交易)。 -4. 在控制链的`OnChanOpenAck`回调过程中,一个链间账户之前在主链上的`OnChanOpenTry`注册的记录会被写入到所有者的状态中,记录中包含从 portID -> 链间账户地址的映射。实现细节请参见以下的[元数据协商](#Metadata-negotiation)部分。 -5. 在控制链和主链上分别进行`OnChanOpenAck`和`OnChanOpenConfirm`回调期间,此链间帐户/所有者对的[活动通道](#Active-channels)将被写进链间账户/所有者的状态。 - -#### 活动通道 - -控制链和主链必须跟踪每个注册的链间帐户的`active-channel` 。 `active-channel`是在为创建通道而握手的过程中设置的。这是一种安全机制,允许控制链在通道关闭的情况下重新获得对主链上链间帐户的访问权限。 - -控制链上的活动通道的数据结果示例: - -```typescript -{ - // 控制链 - SourcePortId: `icacontroller-`, - SourceChannelId: ``, - // 主链 - CounterpartyPortId: `icahost`, - CounterpartyChannelId: ``, -} -``` - -如果一条通道关闭,控制链可以使用和之前的通道同样的端口和底层连接,通过握手创建一条新通道, 来取代现有的活动通道。ICS-27通道只能在两种情况下被关闭:即超时(如果通道是有序通道)或者轻客户端受到攻击(可能性很小)时。因此控制链必须具有以下两种功能: 创建新的ICS-27通道;重置某一对端口号(包含`{owner-account-address}`)和连接对应的活动通道。 - -控制链和主链必须验证任何新通道与之前的活动通道保持相同的元数据,以确保链间帐户的参数即使在更换活动通道后也保持不变。不应验证元数据的`Address`,因为其在 INIT 阶段应为空,且主链将在 TRY 上重新生成完全相同的地址,因为它会从控制链端口 ID 确定性地生成链间帐户地址和connectionID(两者均须保持不变)。 - -#### **元数据协商** - -ICS-27 利用[ICS-04 通道版本协商](../../core/ics-004-channel-and-packet-semantics/README.md)在通道握手期间协商元数据和通道参数。元数据将包含编码格式以及交易类型,以便交易对手可以就跨链交易的结构和编码达成一致。在 TRY 步骤从主链发送的元数据也将包含链间帐户地址,以便可以将其中继到控制链。在通道握手结束时,控制链和主链都会存储控制链 portID 到新注册的链间账户地址的映射([账户注册流程](#Register-account-flow))。 - -ICS-04 允许每个应用程序通道有特定版本协商协议。对于链间账户来说,通道版本将是一个 JSON 结构的字符串,其中包含所有相关元数据,这些元数据旨在在通道握手期间转发给交易对手([参见下文摘要](#Metadata-negotiation-summary))。 - -结合每个跨链帐户绑定一个通道的规定,这种元数据协商方法允许我们将链间帐户的地址传递回控制链,并在`OnChanOpenAck`回调期间创建从控制链端口 ID 到链间帐户地址的映射。如[控制流程](#Controlling-flow)中所述,控制链需要知道已注册链间帐户的地址,以便将交易发送到主链上的链间帐户。 - -#### **元数据协商总结** - -`interchain-account-address`是控制链在主链上注册的链间账户地址。 - -- **INIT** - -发起者:控制链 - -数据报:ChanOpenInit - -作用于链:控制链 - -版本: - -```json -{ - "Version": "ics27-1", - "ControllerConnectionId": "self_connection_id", - "HostConnectionId": "counterparty_connection_id", - "Address": "", - "Encoding": "requested_encoding_type", - "TxType": "requested_tx_type", -} -``` - -注释:地址留空,因为地址将由主链生成并传回。数据报必须包含连接标识符,以便在需要打开新通道(以防活动通道超时)时确保使用同一连接。这将确保链间账户始终连接到同一个交易对手链。 - -- **TRY** - -发起者:中继者 - -数据报:ChanOpenTry - -被作用链:主链 - -版本: - -```json -{ - "Version": "ics27-1", - "ControllerConnectionId": "counterparty_connection_id", - "HostConnectionId": "self_connection_id", - "Address": "interchain_account_address", - "Encoding": "negotiated_encoding_type", - "TxType": "negotiated_tx_type", -} -``` - -注释:如果控制链在 INIT 中设置了交易对手版本,则主链上的 ICS-27 应用程序负责返回此版本。主链必须同意控制链请求的单一编码类型和单一交易类型(例如包含在交易对手版本中)。如果不支持请求的编码或交易类型,则主链必须返回错误并中止握手。主链还必须生成链间账户地址,并使用链间账户地址字符串填充版本中的地址字段。 - -- **ACK** - -发起者:中继者 - -数据报:ChanOpenAck - -被作用链:控制链 - -交易对手版本: - -```json -{ - "Version": "ics27-1", - "ControllerConnectionId": "self_connection_id", - "HostConnectionId": "counterparty_connection_id", - "Address": "interchain_account_address", - "Encoding": "negotiated_encoding_type", - "TxType": "negotiated_tx_type", -} -``` - -注释:在 ChanOpenAck 步骤中,控制链上的 ICS27 应用程序必须验证主链在 ChanOpenTry 中选择的版本字符串。控制链必须验证该字符串可支持主链选择的协商编码和 tx 类型。如果其中任何一个不受支持,则必须返回错误并中止握手。如果两者都支持,则控制链必须存储从通道的 portID 到所提供的链间帐户地址的映射,并返回成功。 - -#### 控制流程 - -一旦在主链上注册了链间帐户,控制链就可以开始向主链发送指令(消息)以控制该帐户。 - -1. 控制链调用`SendTx`并传递将由关联的链间帐户在主链执行的消息(由控制链端的端口标识符确定) - -Cosmos SDK 伪代码示例: - -```golang -interchainAccountAddress := GetInterchainAccountAddress(portId) -msg := &banktypes.MsgSend{FromAddress: interchainAccountAddress, ToAddress: ToAddress, Amount: amount} -icaPacketData = InterchainAccountPacketData{ - Type: types.EXECUTE_TX, - Data: serialize(msg), - Memo: "memo", -} - -// 发送信息到主链,信息将最终在主链被执行 -SendTx(ownerAddress, connectionId, portID, data, timeout) -``` - -1. 主链收到 IBC 数据包后会调用`DeserializeTx` 。 - -2. 然后主链将为每条消息调用`AuthenticateTx`和`ExecuteTx`,并返回包含成功或错误的回执。 - -通过获取控制链端口标识符,并调用`GetInterchainAccountAddress(controllerPortId)`以获取当前控制链端口的预期链间帐户地址,在主链上对消息进行身份验证。如果此消息的签名者与预期的帐户地址不匹配,则身份验证失败。 - -### 数据包数据 - -`InterchainAccountPacketData`包含一个链间帐户可以执行的消息数组和一个发送到主链的备忘录字符串以及数据包`type` 。 ICS-27 版本1只有一种类型的`EXECUTE_TX` 。 - -```proto -message InterchainAccountPacketData { - enum type - bytes data = 1; - string memo = 2; -} -``` - -回执包结构在[ics4](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto#L135-L148)中定义。如果主链上发生错误,则回执包含错误消息。 - -```proto -message Acknowledgement { - // 响应包含结果或错误,并且必须为非空 - oneof response { - bytes result = 21; - string error = 22; - } -} -``` - -### 自定义逻辑 - -ICS-27 通过[ICS-30 中间件架构](../ics-030-middleware)允许应用程序开发人员自定义 ICS-27 数据包执行成功或失败的处理逻辑。 - -控制链将包装`OnAcknowledgementPacket`和`OnTimeoutPacket`以处理 ICS-27 数据包执行成功或失败的情况。 - -### 端口和通道设置 - -主链上的链间帐户模块必须始终绑定到 id 为 `icahost`的端口。控制链将动态绑定端口,如标识符格式[部分](#identifer-formats)中所指定。 - -下方示例假设一个模块正在实现整个`InterchainAccountModule`接口。 `setup`函数必须在创建模块时(可能是在区块链本身在初始化时)仅调用一次以绑定到对应端口。 - -```typescript -function setup() { - capability = routingModule.bindPort("icahost", ModuleCallbacks{ - onChanOpenInit, - onChanOpenTry, - onChanOpenAck, - onChanOpenConfirm, - onChanCloseInit, - onChanCloseConfirm, - onRecvPacket, - onTimeoutPacket, - onAcknowledgePacket, - onTimeoutPacketClose - }) - claimCapability("port", capability) -} -``` - -一旦调用了`setup`函数,就可以通过 IBC 路由模块创建通道。 - -### 通道生命周期管理 - -链间帐户模块将接受来自另一台机器上任何模块的新通道,当且仅当: - -- 正在创建的通道是有序的。 -- 控制链正在进行通道初始化。 - -```typescript -// 在控制链上被InitInterchainAccount调用 -function onChanOpenInit( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - version: string) { - // 只允许有序通道 - abortTransactionUnless(order === ORDERED) - // 验证端口格式 - abortTransactionUnless(validateControllerPortParams(portIdentifier)) - // 只允许在交易对手链上的“icahost”端口上创建通道 - abortTransactionUnless(counterpartyPortIdentifier === "icahost") - // 只有在没有设置活动通道(状态为 OPEN)时才打开通道 - abortTransactionUnless(activeChannel === nil) - - // 验证元数据 - metadata = UnmarshalJSON(version) - abortTransactionUnless(metadata.Version === "ics27-1") - // 必须支持编码列表和 tx 类型列表中的所有元素 - abortTransactionUnless(IsSupportedEncoding(metadata.Encoding)) - abortTransactionUnless(IsSupportedTxType(metadata.TxType)) - - // connectionID和交易对手connectionID在通道中是可获取的 - abortTransactionUnless(metadata.ControllerConnectionId === connectionId) - abortTransactionUnless(metadata.HostConnectionId === counterpartyConnectionId) -} -``` - -```typescript -// 在主链上由中继器调用 -function onChanOpenTry( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string) (version: string) { - // 只允许有序通道 - abortTransactionUnless(order === ORDERED) - // 验证端口ID - abortTransactionUnless(portIdentifier === "icahost") - // 只有当对方端口ID采用预期的控制链端口ID 格式时才允许在主链上创建通道 - abortTransactionUnless(validateControllerPortParams(counterpartyPortIdentifier)) - // 使用交易对手端口标识符以及主链上的底层connectionID创建链间账户 - address = RegisterInterchainAccount(counterpartyPortIdentifier, connectionID) - - cpMetadata = UnmarshalJSON(counterpartyVersion) - abortTransactionUnless(cpMetadata.Version === "ics27-1") - // 如果主链不支持初始化链请求的编码或 txType,则握手失败并中止交易 - abortTransactionUnless(IsSupportedEncoding(cpMetadata.Encoding)) - abortTransactionUnless(IsSupportedTxType(cpMetadata.TxType)) - - // connectionID和交易对手connectionID在通道中是可获取的 - abortTransactionUnless(cpMetadata.ControllerConnectionId === counterpartyConnectionId) - abortTransactionUnless(cpMetadata.HostConnectionId === connectionId) - - metadata = { - "Version": "ics27-1", - "ControllerConnectionId": cpMetadata.ControllerConnectionId, - "HostConnectionId": cpMetadata.HostConnectionId, - "Address": address, - "Encoding": cpMetadata.Encoding, - "TxType": cpMetadata.TxType, - } - - return string(MarshalJSON(metadata)) -} -``` - -```typescript -// 由中继器在控制链上调用 -function onChanOpenAck( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannelIdentifier, - counterpartyVersion: string) { - - // 验证主链的交易对手元数据 - metadata = UnmarshalJSON(version) - abortTransactionUnless(metadata.Version === "ics27-1") - abortTransactionUnless(IsSupportedEncoding(metadata.Encoding)) - abortTransactionUnless(IsSupportedTxType(metadata.TxType)) - abortTransactionUnless(metadata.ControllerConnectionId === connectionId) - abortTransactionUnless(metadata.HostConnectionId === counterpartyConnectionId) - - - // 状态更改以记录成功注册的跨链帐户 - SetInterchainAccountAddress(portID, metadata.Address) - // 设置此所有者/链间帐户对的活动通道 - setActiveChannel(SourcePortId) -} -``` - -```typescript -// 由中继器在主链上调用 -function onChanOpenConfirm( - portIdentifier: Identifier, - channelIdentifier: Identifier) { - // 设置此所有者/链间帐户对的活动通道 - setActiveChannel(portIdentifier) -} -``` - -```typescript -// 控制器端口 ID 必须具有以下格式:`icacontroller-{ownerAddress}` -function validateControllerPortParams(portIdentifier: Identifier) { - split(portIdentifier, "-") - abortTransactionUnless(portIdentifier[0] === "icacontroller") - abortTransactionUnless(IsValidAddress(portIdentifier[1])) -} -``` - -### 结束握手 - -```typescript -function onChanCloseInit( - portIdentifier: Identifier, - channelIdentifier: Identifier) { - // 不允许用户发起的通道关闭链间账户通道 - return err -} -``` - -```typescript -function onChanCloseConfirm( - portIdentifier: Identifier, - channelIdentifier: Identifier) { -} -``` - -### 数据包中继 - -路由模块收到数据包后调用`onRecvPacket` 。 - -```typescript -function OnRecvPacket(packet Packet) { - ack = NewResultAcknowledgement([]byte{byte(1)}) - - // 仅在数据包数据成功解码时尝试应用程序逻辑 - switch data.Type { - case types.EXECUTE_TX: - msgs, err = types.DeserializeTx(data.Data) - if err != nil { - return NewErrorAcknowledgement(err) - } - - // ExecuteTx 调用上面定义的 AuthenticateTx 函数 - result, err = ExecuteTx(ctx, packet.SourcePort, packet.DestinationPort, packet.DestinationChannel, msgs) - if err != nil { - // 注意:网络中的节点放置在确认中的错误字符串必须在所有内容中保持一致 - // ,否则状态机中会有一个分叉。 - return NewErrorAcknowledgement(err) - } - - // 在主链上执行后返回包含交易结果的确认 - return NewAcknowledgement(result) - - default: - return NewErrorAcknowledgement(ErrUnknownDataType) - } -} -``` - -在路由模块发送的数据包被确认后,该模块将调用`onAcknowledgePacket`。 - -```typescript -function onAcknowledgePacket( - packet: Packet, - acknowledgement: bytes) { - // 调用底层应用的 OnAcknowledgementPacket 回调 - // 更多信息请参见 ICS-30 中间件 -} -``` - -```typescript -function onTimeoutPacket(packet: Packet) { - // 调用底层应用的 OnTimeoutPacket 回调 - // 更多信息请参见 ICS-30 中间件 -} -``` - -### 标识符格式 - -链间账户通道两侧的端口标识符必须遵循的这些格式,才能被正确的链间账户模块接受。 - -控制链端口标识符: `icacontroller-{owner-account-address}` - -主链端口标识符: `icahost` - -## 示例实现 - -ICS-27 的 Cosmos-SDK 实现的代码库:https://github.com/cosmos/ibc-go - -## 未来的改进 - -未来的链间账户可能会通过引入一种 IBC 通道类型来大大简化,该通道类型是有序通道,但不会在超时时关闭通道,而是继续接收下一个数据包。如果核心 IBC 提供了这种通道类型,链间账户可能请求使用这种通道类型并删除与“活动通道”相关的所有逻辑和状态。元数据格式中的底层连接标识符的引用也可以被删除,由此元数据格式可以得到简化。 - -设置和取消“活动通道”在当前是必要的,旨在允许链间帐户所有者创建一个新通道,防止当前活动通道在通道超时情况下被关闭。连接标识符是元数据的一部分,旨在确保被启用的新通道均建立在原始连接上。如果要让通道有序**且**不可关闭,只能通过向核心 IBC 引入新的通道类型来实现,新通道类型实现后,这些逻辑就变得不必要了。 - -## 历史 - -2019年8月1日-讨论概念 - -2019年9月24日-建议草案 - -2019年11月8日-重大修订 - -2019年12月2日-较小修订(在以太坊上添加更多具体描述并添加链间账户) - -2020年7月14日-主要修订 - -2021年4月27日-重新设计ics27规范 - -2021年11月11日-根据代码实现的最新变化更新翻译 - -2021年12月14日-根据审计和维护人员的审查对规范进行修订 - -## 版权 - -本文中的所有内容均根据[Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0)获得许可。 diff --git a/other_docs/ibc-protocol/spec/app/ics-029-fee-payment/README.md b/other_docs/ibc-protocol/spec/app/ics-029-fee-payment/README.md deleted file mode 100644 index 2fabae157..000000000 --- a/other_docs/ibc-protocol/spec/app/ics-029-fee-payment/README.md +++ /dev/null @@ -1,452 +0,0 @@ ---- -ics: '29' -title: 通用性费用支付 -stage: 草案 -category: IBC/APP -requires: 4, 25, 26, 30 -kind: 实例化 -author: Aditya Sripal , Ethan Frey -created: '2021-06-01' -modified: '2021-06-18' ---- - -## 大纲 - -该标准文档规定了基于 ICS 应用协议处理费用支付的数据包数据结构、状态机处理逻辑和编码细节。规范需要对回执信息(acknowledgement)进行一些更改,但可以被任何应用程序采用,而不会强制其他应用程序使用此实现。 - -### 动机 - -关于中继器的通用激励机制已经有很多讨论。此前曾有一个简单提案提议[扩展 ICS20 以激励目标链上的中继器](https://github.com/cosmos/ibc/pull/577)。但是提案只针对 ICS20,不适用于其他协议。随后,该提案扩展为可被任何 ICS 应用协议采用的[更加通用的费用支付设计](https://github.com/cosmos/ibc/issues/578)。 - -一般来说,除非有明确的方式来激励中继器,否则跨链版图很难扩大。我们寻求定义一个清晰的接口,使任何应用都可以轻松采用,同时不排除不使用通证(token)的链。 - -### 所需属性 - -- 对及时传送数据包的行为进行激励(调用`recvPacket` ) -- 对中继数据包回执(acknowledegement)信息的行为进行激励(调用`acknowledgePacket`) -- 对在数据包送达之前已过期的中继超时(timeout)信息的行为进行激励(如当数据包接收费用过低时)(调用`timeoutPacket`) -- 不产生额外的 IBC 数据包 -- 即使目标链不支持同质化通证(fungible token)概念,单向传送仍有效 -- 需选用(opt-in)实现此规范的每一条链。例如链 A 上支持费用的 ICS27 功能可以连接到链 B 上不支持费用的 ICS27 功能 -- 实现此扩展规范的每条链均须具有标准接口 -- 需在同一框架内支持自定义费用处理逻辑 -- 中继器地址不可伪造 -- 启用无需许可或需要许可的中继服务 - -### 定义 - -`forward relayer`: 为数据包提交`recvPacket` 消息的中继器 - -`reverse relayer`: 为数据包提交`acknowledgePacket`消息的中继器 - -`timeout relayer`: 为数据包提交`timeoutPacket` 或`timeoutOnClose` 消息的中继器 - -`source address`: 发送数据包的链上中继器地址 - -`destination address`: 接收数据包的链上中继器地址 - -## 技术规范 - -### 总体设计 - -为避免因应用程序数据包数量顺序产生额外的费用数据包,并提供选用方法,我们仅在源链上存储所有费用支付信息。源链是数据包发送者唯一能够提供通证用以奖励数据包的链。费用分配可能仅针对性的实现,因此不必出现在 IBC 规范中(文档中只需出现高级要求)。 - -对于所有数据包相关信息,我们要求[中继器地址必须对应用模块可见](https://github.com/cosmos/ibc/pull/579),以便模块能够激励数据包中继器。由此,`acknowledgePacket`、`timeoutPacket`,和`timeoutOnClose` 消息能够获得中继器地址,并向这些地址发送托管通证(escrowed tokens). 但是,我们需要一种可靠的方式来获取将`recvPacket`消息从目标链提交至源链的中继器地址。实际上,我们需要的是中继器用以付款的源链地址,而不是为数据包签名的目标链地址。 - -为赋予应用开发者和区块链团队最大的灵活度,费用机制支付将采用 IBC 中间件的形式实现(见 ICS-30)。 - -鉴于此,流程如下: - -1. 中继器通过目标链上的费用中间件注册其目标链地址到源链地址的映射。 -2. 用户/模块在`source`链上提交一个数据包发送命令,并向费用中间件模块提交一条消息以及通证和费用分配信息。费用通证均在费用模块中托管。 -3. 中继器 A 在`destination`链上提交 `RecvPacket` 。 -4. 目标链费用中间件将获取中继器目标链地址对应的源链地址(此映射已被注册),并将其添加到回执信息中。 -5. 中继器 B 提交 `AcknowledgePacket` ,该消息在消息发送方的源链上提供反向(reverse)中继器地址,以及嵌在回执信息中的前向(forward)中继器的源链地址。 -6. 源链费用中间件可将(1)中托管的通证分配至前向及反向中继器,并将剩余通证退回至原始费用支付方。 - -替代流程: - -1. 用户/模块在`source`链提交数据包发送命令、通证以及分配信息 -2. 中继器提交`OnTimeout`,该消息在源链提供中继器地址。 -3. 源链应用可将(1)中托管的通证分配至该中继器,并可能将剩余通证退回至原始支付方。 - -### 费用细节 - -以 Cosmos SDK 中的实现举例,我们考虑三种潜在的费用支付方式,并给出定义。每种支付方式使用不同通证进行支付。想象 IRISnet 和 Cosmos Hub 之间的连接。可定义: - -- 接收费用(ReceiveFee):0.003 channel-7/ATOM 凭证(ATOM 已实现通过 ICS20 跨链至 IRISnet ) -- 回执费用(AckFee):0.001 IRIS -- 超时费用(TimeoutFee):0.002 IRIS - -理想情况下,费用可以通过双方的原生通证兑换,但中继器可能会选择其他通证。在该示例中,中继器拥有相当多的 IRIS,足够覆盖中继费用和其他成本。同时,中继器还从许多数据包中收集了 channel-7/ATOM 凭证,在中继了几千个数据包后,Cosmos Hub 上的账户余额不足,因此中继器将通过 channel-7 将这些channel-7/ATOM 凭证发回其 Hub 帐户,以补充余额。 - -发送链将托管费用支付方账户中的 0.003 channel-7/ATOM 凭证和 0.002 IRIS。若前向中继器提交`recvPacket`,反向中继器提交`ackPacket`,则前向中继器会获得 0.003 channel-7/ATOM 凭证奖励,反向中继器获得 0.001 IRIS,剩余 0.001 IRIS 将退回至原始费用支付方。当数据包超时时,超时中继器将获得 0.002 IRIS,剩余 0.003 channel-7/ATOM 凭证将退回至原始支付方。 - -向用户收取费用随后支付给相关中继器的逻辑由单独的费用模块封装,并可能因不同实现而异。但是,所有费用模块都必须实现统一接口,以便 ICS-4 处理程序可以正确支付中继器费用,并便于中继器轻松确定中继数据包的预期费用。 - -### 费用中间件合约 - -虽然各个费用模块的详细信息可能有所不同,但所有费用模块都**必须**确保执行以下操作: - -- 必须允许中继器注册其在对手链上的地址。 -- 必须托管所有未中继的数据包可能支付的最高费用(或必须有能力铸造所需数量的通证) -- 必须为数据包向在`PayFee`回调中指定的前向中继器支付接收费用(如果未指定中继器,则必须将费用退回原始支付方) -- 必须为数据包向在`PayFee`回调中指定的反向中继器支付回执费用 -- 必须为数据包向`PayTimeoutFee`回调中指定的超时中继器支付超时费用 -- 如有费用剩余,则必须将托管中的所有剩余费用退回原始支付方 - -```typescript -// RegisterCounterpartyAddress 由每个 channelEnd 上的中继器调用,并允许中继器在中继之前指定其对手链上的地址。 -// 这确保了它们将因前向中继得到适当补偿,因为目标链必须在回执信息中发回中继器的源地址(对手链地址)。 -// 此函数可能被中继器多次调用,此时一般使用最新的对手链地址。 -function RegisterCounterpartyAddress(address: string, counterPartyAddress: string) { - // 设置源地址和对手链地址之间的映射 -} - -// EscrowPacketFee 是一个开放回调,任何希望托管资金的模块/用户都可以调用它,以便 -// 激励中继数据包。 -// 注意:这些费用是在之前为数据包托管的任何金额之外托管的。在之前的金额为零的情况下, -// 提供的费用是初始托管金额。 -// 可以设置单独的 receiveFee、ackFee 和 timeoutFee -// 为数据包流中的每个环节进行支付。调用方必须将 max(receiveFee+ackFee, timeoutFee) 发送到费用模块 -// 随后这些费用将被托管,以便支付潜在数据包流程费用。 -// 调用方可选择指定一个中继地址数组。费用模块可以使用它并根据最终中继器地址来修改费用支付逻辑。 -// 例如,费用模块可能选择仅在 `EscrowPacketFee`中指定中继器地址时支付费用。 -function EscrowPacketFee(packet: Packet, receiveFee: Fee, ackFee: Fee, timeoutFee: Fee, relayers: []string) { - // 为此数据包托管 max(receiveFee+ackFee, timeoutFee) - // 如有必要,使用提供的中继器地址执行自定义逻辑 -} - -// PayFee 是由 ICS-4 AcknowledgePacket 处理程序调用的费用模块实现的回调。 -function PayFee(packet: Packet, forward_relayer: string, reverse_relayer: string) { - // 向前向中继地址支付前向中继费用 - // 向反向中继地址支付反向中继费用 - // 将剩余通证退还至原始费用支付方 - // 注意:若前向中继地址为空,则将前向中继费用退还给原始费用支付方。 -} - -// PayFee 是由 ICS-4 TimeoutPacket 处理程序调用的费用模块实现的回调。 -function PayTimeoutFee(packet: Packet, timeout_relayer: string) { - // 向超时中继地址支付超时中继费用 - // 将剩余通证退还给原始费用支付方 -} -``` - -费用模块应公开以下信息,以便中继器查询预期费用: - -```typescript -// 获取为数据包提交 ReceivePacket msg 的预期费用 -// 若费用取决于特定的中继器,调用方应提供预期的中继器地址。 -function GetReceiveFee(portID, channelID, sequence, relayer) Fee - -// 获取为数据包提交 AcknowledgePacket msg 的预期费用 -// 若费用取决于特定的中继器,调用方应提供预期的中继器地址。 -function GetAckFee(portID, channelID, sequence, relayer) Fee - -// 获取为数据包提交 TimeoutPacket msg 的预期费用 -// 若费用取决于特定的中继器,调用方应提供预期的中继器地址。 -function GetTimeoutFee(portID, channelID, sequence, relayer) Fee -``` - -由于不同链对同质化通证(fungible tokens)有不同的表示方法,且该信息不会发送至其他链,因此本规范对`Fee`表示方法不作特别指定。每条链都可以选择自己的表示方法,中继器有责任正确解释费用。 - -默认表示方法将具有以下结构: - -```typescript -interface Fee { - denom: string, - amount: uint256, -} -``` - -### IBC 模块包装类(Wrapper) - -费用中间件将实现其自己的 ICS-26 回调,用于打包特定应用模块的回调以及被底层应用调用的 ICS-4 处理程序函数。这一费用中间件将确保对手模块支持激励,并将实现所有费用相关逻辑。随后该中间件将请求传递给嵌入式应用模块,用于进一步回调处理。 - -通过这种方式,自定义费用处理逻辑可以连接到 IBC 数据包流程逻辑,而无需将代码放置在 ICS-4 处理程序或应用程序代码中。这是有价值的,因为 ICS-4 处理程序应只关心核心 IBC 功能(传输、认证和排序)的正确性,且应用处理程序不应处理在所有其他被激励应用中通用的费用逻辑。事实上,一个给定的应用程序模块应该能够连接到任何费用模块,而无需对应用程序本身作进一步更改。 - -#### 费用协议协商 - -费用中间件将通过把自己的版本附加到应用程序版本来与对手模块协商其费用协议版本。 - -通道版本:`fee_v{fee_protocol_version}:{application_version}` - -前一版本:`fee_v1:ics20-1` - -费用中间件的握手回调确保了两个模块就兼容费用协议版本达成一致,并随后向嵌入式应用程序的握手回调发送特定应用版本字符串。 - -#### 握手回调 - -```typescript -function onChanOpenInit( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - version: string) { - // 移除前缀并将应用特定版本传给应用回调。 - // 否则,直接将版本传给应用回调。 - feeVersion, appVersion = splitFeeVersion(version) - // 检查是否支持 feeVersion - if !isSupported(feeVersion) { - return error - } - app.OnChanOpenInit( - order, - connectionHops, - portIdentifier, - channelIdentifier, - counterpartyPortIdentifier, - counterpartyChannelIdentifier, - appVersion, - ) -} - -function OnChanOpenTry( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - version: string, - counterpartyVersion: string) { - // 选择相互兼容的收费版本 - cpFeeVersion, cpAppVersion = splitFeeVersion(counterpartyVersion) - feeVersion, appVersion = splitFeeVersion(version) - if !isCompatible(cpFeeVersion, feeVersion) { - return error - } - selectFeeVersion(cpFeeVersion, feeVersion) - - // 调用底层应用的 OnChanOpenTry 回调 - app.OnChanOpenTry( - order, - connectionHops, - portIdentifier, - channelIdentifier, - counterpartyPortIdentifier, - counterpartyChannelIdentifier, - cpAppVersion, - appVersion, - ) -} - -function onChanOpenAck( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string) { - feeVersion, appVersion = splitFeeVersion(counterpartyVersion) - if !isSupported(feeVersion) { - return error - } - - // 调用底层应用程序 OnChanOpenAck 回调 - app.OnChanOpenAck(portIdentifier, channelIdentifier, appVersion) -} - -function onChanOpenConfirm( - portIdentifier: Identifier, - channelIdentifier: Identifier) { - // 费用中间件对 ChanOpenConfirm 不执行操作, - // 只调用底层回调 - app.onChanOpenConfirm(portIdentifier, channelIdentifier) - } - -function splitFeeVersion(version: string): []string { - if hasPrefix(version, "fee") { - splitVersions = split(version, ":") - feeVersion = version[0] - appVersion = join(version[1:], ":") - // 若版本有费用前缀 - // 则将第一个拆分部分作为费用版本返回,将字符串的其余部分作为应用版本返回 - return []string{feeVersion, appVersion} - } - // 否则返回空费用版本和完整版本作为应用程序版本 - return []string{"", version} -} -``` - -#### 数据包回调 - -```typescript -function onRecvPacket(packet: Packet, relayer: string): bytes { - app_acknowledgement = app.onRecvPacket(packet, relayer) - - // 通过检索存储在费用中间件中的此中继器的对手链地址来获取源地址。 - // 注意:源地址可能为空或无效, - // 此时对手链必须退回费用 - sourceAddress = getCounterpartyAddress(relayer) - - // 在异步回执的情况下,必须存储中继器地址,以便稍后检索并写入回执。 - if app_acknowledgement == nil { - privateStore.set(forwardRelayerPath(packet), sourceAddress) - } - - // 用前向中继器包装回执信息并返回编组的字节 - //constructIncentivizedAck 接受应用程序特定的回执和接收数据包中继器(前向中继器) - // 并构造激励回执结构,其中嵌入了前向中继器和应用程序特定的回执信息。 - ack = constructIncentivizedAck(app_acknowledgment, sourceAddress) - return marshal(ack) -} - -function onAcknowledgePacket(packet: Packet, acknowledgement: bytes, relayer: string) { - // 回执是一个编组结构,其中包含作为字符串的前向中继器地址(forward_relayer), - // 以及对手链上的应用程序模块(app_ack)返回的原始回执字节。 - - // 从回执消息中获取前向中继器 - // 并向前向和反向中继器支付费用。 - // reverse_relayer 是函数参数中提供的 - // 回执信息的提交者 - // 注意:费用可能为 0 - ack = unmarshal(acknowledgement) - forward_relayer = getForwardRelayer(ack) - PayFee(packet, forward_relayer, relayer) - - // 展开对手链上应用程序发送的原始回执字节并将其传递给应用程序回调。 - app_ack = getAppAcknowledgement(acknowledgement) - - app.OnAcknowledgePacket(packet, app_ack, relayer) -} - -function onTimeoutPacket(packet: Packet, relayer: string) { - // 从函数参数中获取超时中继器 - // 并支付超时费用。 - // 注意:费用可能为 0 - PayTimeoutFee(packet, relayer) - app.OnTimeoutPacket(packet, relayer) -} - -function onTimeoutPacketClose(packet: Packet, relayer: string) { - // 从函数参数中获取超时中继器 - // 并支付超时费用。 - // 注意:费用可能为 0 - PayTimeoutFee(packet, relayer) - app.onTimeoutPacketClose(packet, relayer) -} - -function constructIncentivizedAck(app_ack: bytes, forward_relayer: string): Acknowledgement { - // TODO:见 https://github.com/cosmos/ibc/pull/582 -} - -function getForwardRelayer(ack: Acknowledgement): string { - // TODO:见 https://github.com/cosmos/ibc/pull/582 -} - -function getAppAcknowledgement(ack: Acknowledgement): bytes { - // TODO:见 https://github.com/cosmos/ibc/pull/582 -} -``` - -#### ICS-4 调用 ICS-4 的嵌入式应用程序 - -注意,如果嵌入式应用程序使用异步回执,则应用程序中的`WriteAcknowledgement`调用必须调用费用中间件的`WriteAcknowledgement`,而非直接调用 ICS-4 处理程序的`WriteAcknowledgement`函数。 - -```typescript -// 费用中间件 writeAcknowledgement 函数 -function writeAcknowledgement( - packet: Packet, - acknowledgement: bytes) { - // 检索存储在 onRecvPacket 中的前向中继器 - relayer = privateStore.get(forwardRelayerPath(packet)) - ack = constructIncentivizedAck(acknowledgment, relayer) - ack_bytes marshal(ack) - // ics4Wrapper 可能是核心 IBC 或更高级的中间件 - return ics4Wrapper.writeAcknowledgement(packet, ack_bytes) -} - -// 费用中间件 sendPacket 函数只将数据转发给 ics-4 处理程序 -function sendPacket( - capability: CapabilityKey, - sourcePort: Identifier, - sourceChannel: Identifier, - timeoutHeight: Height, - timeoutTimestamp: uint64, - data: bytes) { - // ics4Wrapper 可能是核心 IBC 或更高级的中间件 - return ics4Wrapper.sendPacket( - capability, - sourcePort, - sourceChannel, - timeoutHeight, - timeoutTimestamp, - data) -} -``` - -### 用户与费用中间件的交互 - -**用户发送数据包** - -用户可以在数据包提交期间原子提交费用支付消息和应用程序特定的“发送数据包”消息(如 ICS-20 MsgTransfer)来指定用于激励中继的费用。费用中间件将托管原子创建的数据包费用。本文档未指定费用支付消息本身,因为费用支付消息可能因实现方式不同而有很大差异。在某些中间件中,如果费用是从利他池(altruisic pool)中支付的,则可能根本没有费用支付消息。 - -由于费用中间件不需要修改传出数据包,所以费用支付消息可以放在发送包消息之前或之后。但是,为保持与其他中间件消息的一致性,建议费用中间件要求将其消息放在数据包消息之前,并为给定通道上的下一个序列托管费用。这样,当消息被原子提交时,通道的下一个序列是用户发送的发送数据包消息,并且用户会对所创建数据包的费用进行托管。 - -如果用户想要在创建数据包后为其支付费用,费用中间件应该提供一条消息,允许用户为具有指定序列、通道和端口标识符的数据包支付费用。这允许用户对已经创建的数据包进行唯一标识,以便费用中间件在事件后托管该数据包的费用。 - -**中继器发送 RecvPacket** - -中继器在一条通道上开始中继之前,应使用下列标准消息注册其对手链消息: - -```typescript -interface RegisterCounterpartyAddressMsg { - channelID: string - portID: string - counterpartyAddress: string - address: string -} -``` - -数据包接收链应负责认证消息来源是`address`的所有者。接收链必须存储给定通道上`address -> counterpartyAddress`的映射。随后,目标链费用中间件中的`onRecvPacket`可以查询`recvPacket`消息发送者的对手地址,以获取前向中继器的源地址。该源地址将被嵌入回执信息。 - -如果中继器没有注册其对手地址,或注册了一个无效的地址,并不会影响回执信息的接收和处理,但中继费用将退还给原始费用支付方。 - -#### 向后兼容性 - -直接在费用模块中保持与无激励链的向后兼容性,要求顶层费用模块协商不包含费用版本的版本,并与激励和非激励模块进行通信。随着嵌套应用的增加,这种模式会导致不必要的复杂性。 - -相反,费用模块将只连接到其对手费用模块,由此简化了费用模块逻辑,并且不需要模仿底层的嵌套应用。 - -为保持与非激励链特定应用(如 ICS-20)的向后兼容性,被激励的链应同时运行一个顶层 ICS-20 模块和一个顶层费用模块,其中,费用模块应嵌套一个 ICS-20 应用程序,每个应用程序都应绑定到唯一的端口。 - -#### 推论 - -提案满足所需属性。数据包流程中的所有部分(接收/回执/超时)都可以得到适当的激励和奖励。协议没有预先指定中继器,因此激励可采用无需许可或需要许可的形式。资金的托管和分配完全在源链上处理,因此不需要额外的 IBC 数据包或在费用协议中使用 ICS-20。费用协议仅假设源链上存在同质化通证。通过为相同的基础应用程序创建应用程序堆栈(一个带有费用中间件,一个没有),我们可以获得向后兼容性。 - -##### 正确性 - -费用模块负责将资金正确托管和分配给相应的中继器。回执和超时信息的中继器很容易检索,因为它们是回执和超时消息的发送者。前向中继器负责在发送`recvPacket`消息之前注册其源地址,以便目标费用中间件可以将此地址嵌入到回执信息中。然后,源链上的费用中间件将使用回执信息中的地址向源链上的前向中继器付款。 - -对于前向中继地址,源链将使用“最大努力(best efforts)”方法。由于该地址不是由对手链直接验证,而是作为一个字符串在回执信息中传回,因此前向中继器注册的地址可能不是有效的源链地址。在这种情况下,将舍弃无效地址,退回接收费用,并继续处理回执信息。中继器有责任将其源地址正确注册到对手链。如果对手链发送的前向中继器地址错误,将导致中继器无法在源链上收取中继数据包的费用。缺乏激励驱动的中继器将停止中继服务,直到回执信息逻辑修复,但通道仍会正常工作。 - -源地址无效时无法返回错误信息,因为这将永久阻止源链处理在对手链上正确接收、处理和回执的数据包的回执信息。IBC 协议规定未正确工作或恶意的中继器所产生的影响仅限于用户数据包的活跃度。在这种情况下,数据包回执不成功将使数据包流程处于永久不完整状态,这对于某些 IBC 应用程序(如 ICS-20)可能会产生重大影响。 - -因此,前向中继器的奖励取决于发送`receive_packet`消息时提供正确的`payOnSender`地址。即使费用支付不成功 ,数据包流程也会继续成功处理。 - -当前向中继器正确嵌入回执信息,且反向中继器和超时中继器在消息中直接可用时,费用中间件将为相关的中继器准确托管并分配费用。 - -#### 可选附录 - -## 向前兼容性 - -不适用。 - -## 示例实现 - -即将到来。 - -## 其他实现 - -即将到来 - -## 历史 - -2021 年 6 月 8 日 - 从直接在 ICS-4 中实现回调切换到中间件解决方案。 -2021 年 6 月 1 日 - 完成草案 - -## 版权 - -本规范所有内容均采用 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 许可授权。 diff --git a/other_docs/ibc-protocol/spec/app/ics-030-middleware/README.md b/other_docs/ibc-protocol/spec/app/ics-030-middleware/README.md deleted file mode 100644 index 8a2f86050..000000000 --- a/other_docs/ibc-protocol/spec/app/ics-030-middleware/README.md +++ /dev/null @@ -1,276 +0,0 @@ ---- -ics: '30' -title: IBC 中间件 -stage: 草案 -category: IBC/APP -requires: 4, 25, 26 -kind: 实例化 -author: Aditya Sripal , Ethan Frey -created: '2021-06-01' -modified: '2021-06-18' ---- - -## 概要 - -该标准文档规定了一个模块必须实现的接口和状态机逻辑,以便充当核心 IBC 和下层应用程序之间的中间件。 IBC 中间件可实现对应用程序功能的任意扩展,而无需更改应用程序或核心 IBC。 - -### 动机 - -IBC 应用程序设计为自成一体的模块,通过一组与核心 IBC 处理程序的接口来实现自己的应用程序特定逻辑。反过来,这些核心 IBC 处理程序设计为确保 IBC 的正确性属性(传输、身份验证、排序),同时将所有应用所专有的处理委托给 IBC 应用程序模块。但是,在某些情况下,许多应用程序可能需要某些功能,但不适合放在核心 IBC 中。最可预见的例子是通用性费用支付协议。大多数应用都希望选用一种协议来激励中继器在其通道上中继数据包。但是,有些应用可能不希望启用此功能,而另一些应用则希望实现自己的自定义费用处理程序。 - -如果没有中间件方案,开发人员必须选择是将此扩展置于每个相关应用的内部逻辑中或将这段应用逻辑放在核心 IBC 中。将它放在每个应用程序中是冗余的并且容易出错;将逻辑放置在核心 IBC 中需要所有应用程序都选用,而且违反了核心 IBC (TAO) 和应用程序之间的抽象隔离。随着扩展数量的增加,这两种情况都不可扩展,因为这都必然会使应用程序或核心 IBC 处理程序中的代码膨胀。 - -中间件允许开发人员将扩展定义为可以包裹基础应用程序的单独模块。因此,该中间件可以执行自己的自定义逻辑,并将数据传递给应用程序,以便它可以在不知道中间件存在的情况下运行其逻辑。这允许应用程序和中间件实现自己隔离的逻辑,同时仍然能够作为一个数据包流程的一个环节来运行。 - -### 定义 - -`Middleware` :在数据包执行期间,位于核心 IBC 和下层 IBC 应用程序之间的自成一体的模块。核心 IBC 和下层应用程序之间的所有消息都必须流经中间件,中间件可以执行自己的自定义逻辑。  - -`Underlying Application` :下层应用程序是直接连接到相关中间件的应用程序。该下层应用程序自身可能是链接到基础应用程序的中间件。 - -`Base Application` :基础应用程序是不包含任何中间件的 IBC 应用程序。它可以被 0 个或多个中间件嵌套,形成一个应用栈。 - -`Application Stack (or stack)` :栈是连接到核心 IBC 的一整套应用程序逻辑(一个或多个中间件 + 基础应用程序)。栈可能只是一个基础应用程序,也可能是一系列嵌套了基础应用程序的中间件。 - -### 所需属性 - -- 中间件支持应用程序逻辑的任意扩展 -- 中间件可以任意嵌套,以形成一个由应用扩展组成的链条 -- 核心 IBC 无需更改 -- 基本应用程序逻辑不需要改变 - -## 技术规范 - -### 总体设计 - -为了实现 IBC 中间件的功能,模块必须实现 IBC 应用程序回调并将预处理数据传递给嵌套的应用程序。模块还必须实现`WriteAcknowledgement`和`SendPacket` 。它们将由终端应用程序调用,以便模块可以在将数据传递到核心 IBC 之前对信息进行后处理。 - -当嵌套应用程序时,模块必须确保它处于核心 IBC 与应用程序双向通信的中间位置。开发人员应该通过直接向 IBC 路由器(而不是任何嵌套应用程序)注册顶级模块来做到这一点。反过来,嵌套应用程序必须只能访问中间件的`WriteAcknowledgement`和`SendPacket` ,而不是直接访问核心 IBC 处理程序。 - -此外,中间件必须注意确保应用程序逻辑可以执行自己的版本协商,而不会受到嵌套中间件的干扰。为了做到这一点,中间件将使用自己的中间件版本字符串预先添加版本。在应用回调中,中间件必须对前缀进行自己的版本协商,然后在将数据交给嵌套应用的回调之前去掉前缀。该要求仅限于中间件期望在交易对手栈上的同一级别上具有兼容的交易对手中间件时的情况。只在通道的单侧执行的中间件,不得修改通道版本。 - -版本: `{middleware_version}:{app_version}` - -每个应用程序栈都必须为核心 IBC 保留自己的唯一端口。因此,具有相同基础应用程序的两个栈必须绑定到不同的端口。 - -#### 接口 - -```typescript -// 中间件实现 ICS26 模块接口 -interface Middleware extends ICS26Module { - app: ICS26Module // 中间件可以访问可能被更多中间件包装的底层应用程序 - ics4Wrapper: ICS4Wrapper // 中间件可以访问 ICS4Wrapper,它可能是核心 IBC 通道处理程序或包装此中间件的更高级别的中间件。 -} -``` - -```typescript -// 由 ICS4 和所有包装基础应用程序的中间件实现。 -// 基础应用程序将调用它们直接上层的中间件的 `sendPacket` 或 `writeAcknowledgement` -// 这些方法将调用下一个中间件,直到调用到核心 IBC 处理程序。 -interface ICS4Wrapper { - sendPacket( - capability: CapabilityKey, - sourcePort: Identifier, - sourceChannel: Identifier, - timeoutHeight: Height, - timeoutTimestamp: uint64, - data: bytes) - writeAcknowledgement(packet: Packet, ack: Acknowledgement) -} -``` - -#### 握手回调 - -```typescript -function onChanOpenInit( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - version: string) { - middlewareVersion, appVersion = splitMiddlewareVersion(version) - doCustomLogic() - app.OnChanOpenInit( - order, - connectionHops, - portIdentifier, - channelIdentifier, - counterpartyPortIdentifier, - counterpartyChannelIdentifier, - appVersion, // 注意这里我们只传递应用版本 - ) -} - -function OnChanOpenTry( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - version: string, - counterpartyVersion: string) { - cpMiddlewareVersion, cpAppVersion = splitMiddlewareVersion(counterpartyVersion) - middlewareVersion, appVersion = splitMiddlewareVersion(version) - if !isCompatible(cpMiddlewareVersion, middlewareVersion) { - return error - } - doCustomLogic() - - // 调用底层应用的 OnChanOpenTry 回调 - app.OnChanOpenTry( - order, - connectionHops, - portIdentifier, - channelIdentifier, - counterpartyPortIdentifier, - counterpartyChannelIdentifier, - cpAppVersion, // 注意这里我们只传递交易对手的应用版本 - appVersion, // 只传递应用版本 - ) -} - -function onChanOpenAck( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string) { - middlewareVersion, appVersion = splitMiddlewareVersion(counterpartyVersion) - if !isCompatible(middlewareVersion) { - return error - } - doCustomLogic() - - // 调用底层应用的 OnChanOpenTry 回调 - app.OnChanOpenAck(portIdentifier, channelIdentifier, appVersion) -} - -function OnChanOpenConfirm( - portIdentifier: Identifier, - channelIdentifier: Identifier) { - doCustomLogic() - - app.OnChanOpenConfirm(portIdentifier, channelIdentifier) -} - -function splitMiddlewareVersion(version: string): []string { - splitVersions = split(version, ":") - middlewareVersion = version[0] - appVersion = join(version[1:], ":") - return []string{middlewareVersion, appVersion} -} -``` - -注意:不需要与远程栈上的交易对手中间件协商的中间件,将不会实现版本拆分和协商,而将简单地在回调上执行自己的自定义逻辑,并不依赖于交易对手也有类似行为。 - -#### 数据包回调 - -```typescript -function onRecvPacket(packet: Packet, relayer: string): bytes { - doCustomLogic() - - app_acknowledgement = app.onRecvPacket(packet, relayer) - - // 中间件可以修改回执 - ack = doCustomLogic(app_acknowledgement) - - return marshal(ack) -} - -function onAcknowledgePacket(packet: Packet, acknowledgement: bytes, relayer: string) { - doCustomLogic() - - // 中间件可以修改回执 - app_ack = getAppAcknowledgement(acknowledgement) - - app.OnAcknowledgePacket(packet, app_ack, relayer) - - doCustomLogic() -} - -function onTimeoutPacket(packet: Packet, relayer: string) { - doCustomLogic() - - app.OnTimeoutPacket(packet, relayer) - - doCustomLogic() -} - -function onTimeoutPacketClose(packet: Packet, relayer: string) { - doCustomLogic() - - app.onTimeoutPacketClose(packet, relayer) - - doCustomLogic() -} -``` - -注意:中间件可以对 ICS-26 中定义的所有 IBC 模块回调的底层应用程序数据进行预处理和后处理。 - -#### ICS-4 包装类 - -```typescript -function writeAcknowledgement( - packet: Packet, - acknowledgement: bytes) { - // 中间件可以修改回执 - ack_bytes = doCustomLogic(acknowledgement) - - return ics4.writeAcknowledgement(packet, ack_bytes) -} -``` - -```typescript -function sendPacket( - capability: CapabilityKey, - sourcePort: Identifier, - sourceChannel: Identifier, - timeoutHeight: Height, - timeoutTimestamp: uint64, - app_data: bytes) { - // 中间件可以修改数据包 - data = doCustomLogic(app_data) - - return ics4.sendPacket( - capability, - sourcePort, - sourceChannel, - timeoutHeight, - timeoutTimestamp, - data) -} -``` - -### 用户交互 - -在中间件需要一些用户输入以修改来自底层应用程序的传出数据包消息时,中间件必须在从底层应用程序接收数据包消息之前从用户那里获取此输入信息。随后中间件必须自行对用户输入信息进行验证,并确保提供给中间件的用户输入与正确的传出数据包消息相匹配。为实现以上功能,中间件可以要求用户对中间件的输入和发送到底层应用程序的数据包消息采用原子发送,并按照从最外层的中间件到基础应用程序的顺序排序。 - -### 安全模型 - -如上所示,IBC 中间件可以任意修改来自底层应用程序的任何传入或传出数据。因此,开发人员不应在其应用程序栈中使用任何不受信任的中间件。 - -## 向后兼容性 - -中间件设计方案是当前 IBC 已经启用的设计模式。该 ICS 旨在标准化对 IBC 中间件的特定设计模式。核心 IBC 或任何现有应用程序无需更改。 - -## 向前兼容性 - -不适用。 - -## 示例实现 - -即将到来。 - -## 其他实现 - -即将到来。 - -## 历史 - -2021 年 6 月 22 日 - 提交草案 - -## 版权 - -本文中的所有内容均根据 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 获得许可。 diff --git a/other_docs/ibc-protocol/spec/app/ics-721-nft-transfer/README.md b/other_docs/ibc-protocol/spec/app/ics-721-nft-transfer/README.md deleted file mode 100644 index 44e8e55a4..000000000 --- a/other_docs/ibc-protocol/spec/app/ics-721-nft-transfer/README.md +++ /dev/null @@ -1,460 +0,0 @@ ---- -ics: '721' -title: 非同质化通证转移 -stage: 草案 -category: IBC/APP -requires: 25, 26 -kind: 实例化 -author: Haifeng Xi -created: '2021-11-10' -modified: '2022-05-18' ---- - -> 该标准文档遵循与[ICS 20](../ics-020-fungible-token-transfer)相同的设计原则,并从中继承了大部分内容,同时使用`nft`模块替换基于`bank`模块的资产跟踪逻辑。 - -## 概览 - -本标准文档规定了通过 IBC 通道在各自链上的两个模块之间进行非同质化通证转移的数据包的数据结构、状态机处理逻辑以及编码细节。本文所描述的状态机逻辑允许在无需许可通道打开的情况下安全的处理多个链的`classId`。该逻辑通过在主链状态机上的 IBC 路由模块和一个现存的资产跟踪模块之间建立实现了一个非同质化通证转移的桥接模块。该模块既可以是 Cosmos 式的原生模块,也可以是在虚拟机中运行的智能合约。 - -### 动机 - -基于 IBC 协议连接的一组链上的用户可能希望在通证发行原始链以外的区块链上使用非同质化通证,以使用该链上的附加功能,例如交易、版税支付或隐私保护。该应用层标准描述了一个在基于 IBC 连接的区块链之间转移非同质化通证的协议,该协议保留了资产的非同质性和资产所有权,限制了拜占庭错误(Byzantine faults)的影响,并且无需额外许可。 - -### 定义 - -[ICS 25](../../core/ics-025-handler-interface) 和 [ICS 26](../../core/ics-026-routing-module) 分别定义了 IBC 处理程序接口和 IBC 路由模块接口。 - -### 所需属性 - -- 保持非同质性(即在所有基于 IBC 连接的区块链中,任何通证只有一个 *活跃* 实例)。 -- 无需许可的通证转移,无需将连接、模块或 `classId` 加入白名单。 -- 对称性(即所有链实现相同的逻辑,hubs 和 zones 无协议差别)。 -- 容错:防止由于链 `B` 的拜占庭行为造成源自链 `A` 的通证的拜占庭式通货膨胀。 - -## 技术规范 - -### 数据结构 - -仅需要一个数据包数据类型: `NonFungibleTokenPacketData` ,该类型指定了类别 id,类别 uri,通证 id,通证 uri,发送地址和接收地址。 - -```typescript -interface NonFungibleTokenPacketData { - classId: string - classUri: string - tokenIds: []string - tokenUris: []string - sender: string - receiver: string -} -``` - -`classId` 唯一标识了正被转移的通证在发送链中所属的类别/系列集合。例如,对于兼容 ERC-1155 的智能合约,这可能是通证 ID 的高 128 位字符串表示形式。 - -`classUri` 是可选的,但这对于和 OpenSea 等 NFT 市场的跨链互操作性是非常有益的,在这些 NFT 市场中可以添加 [ 类别/系列集合元数据](https://docs.opensea.io/docs/contract-level-metadata) 以提供更好的用户体验。 - -`tokenIds` 唯一标识了正被转移的特定类别的一些通证。例如,对于兼容 ERC-1155 的智能合约,`tokenId` 可以是通证 ID 的低 128 位字符串表示形式。 - -每个 `tokenId` 在 `tokenUris` 中都有一个对应的条目,它指的是链外资源,通常是包含通证元数据的不可变 JSON 文件。 - -当通证使用 ICS-721 协议进行跨链传送时,会开始累积通证已传输的通道记录。这些记录信息会被编码到`classId` 字段中。 - -ICS-721 通证类别以 `{ics721Port}/{ics721Channel}/{classId}` 的形式表示,其中 `ics721Port` 和 `ics721Channel` 标识了通证到达的当前链上的通道。如果 `{classId}` 包含 `/`,那么它也必须采用 ICS-721 形式,以表明通证具有多跳记录。需要注意的是,这要求在非 IBC 通证的 `classId` 中禁止使用 `/`(斜杠字符)。 - -发送链可以充当源 zone 或目标 zone。当一条链不是通过最后一个前缀端口和通道对发送通证时,它充当了源 zone。当通证从源 zone 被发出,目标端口和通道将作为 `classId` 的前缀,(收到通证后) 将另一个跃点添加到通证记录中。当一条链通过最后一个前缀端口和通道发送通证时,它就充当了目标 zone。当通证从目标 zone 被发出, `classId` 上的最后一个前缀端口和通道对会被移除,(收到通证后)将撤消通证记录中的最后一个跃点。 - -例如,假设发生以下转移步骤: - -A -> B -> C -> A -> C -> B -> A - -1. A(p1,c1) -> (p2,c2)B : A 是源 zone。B 中的 `classId` : 'p2/c2/nftClass' -2. B(p3,c3) -> (p4,c4)C : B 是源 zone。C 中的 `classId` : 'p4/c4/p2/c2/nftClass' -3. C(p5,c5) -> (p6,c6)A : C 是源 zone。A 中的 `classId` : 'p6/c6/p4/c4/p2/c2/nftClass' -4. A(p6,c6) -> (p5,c5)C : A 是目标 zone。C 中的 `classId` : 'p4/c4/p2/c2/nftClass' -5. C(p4,c4) -> (p3,c3)B : C 是目标 zone。B 中的 `classId` : 'p2/c2/nftClass' -6. B(p2,c2) -> (p1,c1)A : B 是目标 zone。A 中的 `classId` : 'nftClass' - -回执数据类型描述了转移是成功还是失败,以及失败的原因(如果有的话)。 - -```typescript -type NonFungibleTokenPacketAcknowledgement = NonFungibleTokenPacketSuccess | NonFungibleTokenPacketError; - -interface NonFungibleTokenPacketSuccess { - // 这是使用二进制 0x01 base64 进行编码的 - success: "AQ==" -} - -interface NonFungibleTokenPacketError { - error: string -} -``` - -需要注意的是,当 `NonFungibleTokenPacketData` 和 `NonFungibleTokenPacketAcknowledgement` 序列化到数据包中,两者都必须是 JSON 编码(而非 Protobuf 编码的)。 - -非同质化通证转移桥接模块为每个 NFT 通道维护一个单独的托管地址。 - -```typescript -interface ModuleState { - channelEscrowAddresses: Map -} -``` - -### 子协议 - -此处描述的子协议应在“非同质化通证转移桥接”模块中实现,并具有对 NFT 资产跟踪模块和 IBC 路由模块的访问权限。 - -NFT 资产跟踪模块应实现以下功能: - -```typescript -function SaveClass( - classId: string, - classUri: string) { - // 创建一个由 classId 标识的新 NFT 类别 -} -``` - -```typescript -function Mint( - classId: string, - tokenId: string, - tokenUri: string, - receiver: string) { - // 创建一个由 标识的新 NFT - // 接收方成为新铸造的 NFT 的所有者 -} -``` - -```typescript -function Transfer( - classId: string, - tokenId: string, - receiver: string) { - // 将由 标识的 NFT 传输给接收方 - // 接收方成为 NFT 的新所有者 -} -``` - -```typescript -function Burn( - classId: string, - tokenId: string) { - // 销毁由 标识的 NFT -} -``` - -```typescript -function GetOwner( - classId: string, - tokenId: string) { - // 返回由 标识的 NFT 现任所有者 -} -``` - -```typescript -function GetNFT( - classId: string, - tokenId: string) { - // 返回由 标识的 NFT -} -``` - -```typescript -function HasClass(classId: string) { - // 如果由 classId 标识的 NFT 类别已存在,则返回 true; - // 否则返回 false -} -``` - -```typescript -function GetClass(classId: string) { - // 返回由 classId 标识的 NFT 类别 -} -``` - -#### 端口及通道设置 - -`setup` 功能必须在创建模块时(可能是在初始化区块链本身时)恰好只调用一次,以绑定到适当的(由模块拥有的)端口。 - -```typescript -function setup() { - capability = routingModule.bindPort("nft", ModuleCallbacks{ - onChanOpenInit, - onChanOpenTry, - onChanOpenAck, - onChanOpenConfirm, - onChanCloseInit, - onChanCloseConfirm, - onRecvPacket, - onTimeoutPacket, - onAcknowledgePacket, - onTimeoutPacketClose - }) - claimCapability("port", capability) -} -``` - -一旦 `setup` 功能已被调用,可以通过 IBC 路由模块在各自链上的非同质化通证转移模块实例之间创建通道。 - -此规范仅定义数据包处理语义,并以“模块本身无需担心在任何时间点,哪些 connection 或通道可能不存在”的方式对其进行定义。 - -#### 路由模块回调 - -##### 通道生命周期管理 - -当且仅当满足以下条件时,机器 A 和 B 都能接受来自另一台机器上任何模块的新通道: - -- 创建的通道是无序的。 -- 版本字符串为 `ics721-1`. - -```typescript -function onChanOpenInit( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - version: string) { - // 仅允许无序通道 - abortTransactionUnless(order === UNORDERED) - // 版本为 "ics721-1" - abortTransactionUnless(version === "ics721-1") -} -``` - -```typescript -function onChanOpenTry( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string) { - // 仅允许无序通道 - abortTransactionUnless(order === UNORDERED) - // 版本为 "ics721-1" - abortTransactionUnless(counterpartyVersion === "ics721-1") -} -``` - -```typescript -function onChanOpenAck( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string) { - // 端口已被验证 - // 版本为 "ics721-1" - abortTransactionUnless(counterpartyVersion === "ics721-1") - // 分配托管地址 - channelEscrowAddresses[channelIdentifier] = newAddress() -} -``` - -```typescript -function onChanOpenConfirm( - portIdentifier: Identifier, - channelIdentifier: Identifier) { - // 接收通道确认信息,端口和版本都已被验证 - // 分配托管地址 - channelEscrowAddresses[channelIdentifier] = newAddress() -} -``` - -```typescript -function onChanCloseInit( - portIdentifier: Identifier, - channelIdentifier: Identifier) { - // 中止并返回错误,以防止用户关闭通道 - abortTransactionUnless(FALSE) -} -``` - -```typescript -function onChanCloseConfirm( - portIdentifier: Identifier, - channelIdentifier: Identifier) { - // 无需执行任何操作 -} -``` - -##### 数据包中继 - -- 当一个非同质化通证从其源链转出时,桥接模块会在发送链上托管该通证,并在接收链上铸造相应的凭证。 -- 当一个非同质化通证被转回其源链时,桥接模块会在发送链上销毁该通证,并在接收链上取消对相应锁定通证的托管。 -- 当数据包超时时,其中所表示的通证会被取消托管或适当地铸造回发送方 - 具体取决于通证是从其源链转出还是转回。 -- 回执数据用于处理失败,例如无效的目标帐户。返回失败回执比终止交易更可取,因为这更容易使发送链根据失败的性质采取适当的操作。 - -模块中对主链状态机上的账户所有者进行签名验证的交易处理程序必须调用 `createOutgoingPacket` 。 - -```typescript -function createOutgoingPacket( - classId: string, - tokenIds: []string, - sender: string, - receiver: string, - source: boolean, - destPort: string, - destChannel: string, - sourcePort: string, - sourceChannel: string, - timeoutHeight: Height, - timeoutTimestamp: uint64) { - prefix = "{sourcePort}/{sourceChannel}/" - // 如果 classId 不以 sourcePort 和 sourceChannel 为前缀,则我们是源链 - source = classId.slice(0, len(prefix)) !== prefix - tokenUris = [] - for (let tokenId in tokenIds) { - // 确保发送者为通证持有者 - abortTransactionUnless(sender === nft.GetOwner(classId, tokenId)) - if source { - // 托管通证 - nft.Transfer(classId, tokenId, channelEscrowAddresses[sourceChannel]) - } else { - // 我们是接收链, 销毁凭证 - nft.Burn(classId, tokenId) - } - tokenUris.push(nft.GetNFT(classId, tokenId).GetUri()) - } - NonFungibleTokenPacketData data = NonFungibleTokenPacketData{classId, nft.GetClass(classId).GetUri(), tokenIds, tokenUris, sender, receiver} - ics4Handler.sendPacket(Packet{timeoutHeight, timeoutTimestamp, destPort, destChannel, sourcePort, sourceChannel, data}, getCapability("port")) -} -``` - -路由模块收到数据包后调用`onRecvPacket` 。 - -```typescript -function onRecvPacket(packet: Packet) { - NonFungibleTokenPacketData data = packet.data - // 建立默认的成功回执 - NonFungibleTokenPacketAcknowledgement ack = NonFungibleTokenPacketAcknowledgement{true, null} - err = ProcessReceivedPacketData(data) - if (err !== null) { - ack = NonFungibleTokenPacketAcknowledgement{false, err.Error()} - } - return ack -} - -function ProcessReceivedPacketData(data: NonFungibleTokenPacketData) { - prefix = "{packet.sourcePort}/{packet.sourceChannel}/" - // 如果 classId 以数据包的 sourcePort 和 sourceChannel 为前缀,则我们是源链 - source = data.classId.slice(0, len(prefix)) === prefix - for (var i in data.tokenIds) { - if source { - // 取消接收方通证托管 - nft.Transfer(data.classId.slice(len(prefix)), data.tokenIds[i], data.receiver) - } else { // we are sink chain - prefixedClassId = "{packet.destPort}/{packet.destChannel}/" + data.classId - // 如果尚不存在,则创建 NFT 类别 - if (nft.HasClass(prefixedClassId) === false) { - nft.SaveClass(data.classId, data.classUri) - } - // 铸造凭证给接收方 - nft.Mint(prefixedClassId, data.tokenIds[i], data.tokenUris[i], data.receiver) - } - } -} -``` - -在路由模块发送的数据包被确认后,该模块将调用`onAcknowledgePacket`。 - -```typescript -function onAcknowledgePacket( - packet: Packet, - acknowledgement: bytes) { - // 若转移失败,则退回通证 - if (!ack.success) - refundToken(packet) -} -``` - -当路由模块发出的数据包超时(使得数据包没有被目标链接收),路由模块会调用 `onTimeoutPacket` 。 - -```typescript -function onTimeoutPacket(packet: Packet) { - // 数据包超时,因此退回通证 - refundToken(packet) -} -``` - -`refundToken` 会在两种情况下被调用,`onAcknowledgePacket` 失败时和 `onTimeoutPacket` 时,用以将托管的通证退还给原始发送者。 - -```typescript -function refundToken(packet: Packet) { - NonFungibleTokenPacketData data = packet.data - prefix = "{packet.sourcePort}/{packet.sourceChannel}/" - // 如果 classId 不以数据包的 sourcePort 和 sourceChannel 为前缀,则我们是源链 - source = data.classId.slice(0, len(prefix)) !== prefix - for (var i in data.tokenIds) { { - if source { - // 取消通证托管给发送方 - nft.Transfer(data.classId, data.tokenIds[i], data.sender) - } else { - // 我们是目标链,铸造凭证给发送方 - nft.Mint(data.classId, data.tokenIds[i], data.tokenUris[i], data.sender) - } - } -} -``` - -```typescript -function onTimeoutPacketClose(packet: Packet) { - // 不会发生,只允许无序通道 -} -``` - -#### 推论 - -##### 正确性 - -该实现保留了通证的非同质性和可兑换性。 - -- 非同质性:在所有基于 IBC 连接的区块链中,任何通证只有一个 *活跃* 实例)。 -- 可兑换性:如果通证已被发送到交易对手链上,仍可以在源链上的同一 `classId` 和 `tokenId` 中将其兑换回去。 - -#### 可选附录 - -- 每条本地链都可以选择保留一个查询表,以在状态中使用简短的、用户友好的本地 `classId`,在发送和接收数据包时,会与较长的 `classId` 进行相互转换。 -- 可能会对可与哪些其他状态机连接,以及建立哪些通道施加额外限制。 - -## 进一步讨论 - -在此规范的基础上,可以支持扩展和复杂的用例,例如版税,市场或许可转移。解决方案可以是模块、钩子、[IBC 中间件](../ics-030-middleware) 等。与此相关的指南不在本标准讨论范围内。 - -假设主链状态机中的应用逻辑将负责确保根据此规范所铸造的 IBC 通证的元数据是不可变的。对于任何 IBC 通证,强烈建议 NFT 应用检查上游区块链(直到其源头),以确保其元数据在此过程中未被修改。如果决定在未来的某个时候,通过 IBC 适应 NFT 元数据的可变性,我们也许将通过使用进阶 DID 功能来更新此规范或创建一个全新的规范。 - -## 向后兼容性 - -不适用。 - -## 向前兼容性 - -此初始标准在通道握手中使用版本 “ics721-1”。 - -此标准的未来版块可以在通道握手中使用其他版本,并安全地更改数据包数据格式和数据包处理程序的语义。 - -## 示例实现 - -即将到来。 - -## 其他实现 - -即将到来。 - -## 历史 - -日期 | 描述 ---- | --- -2021 年 11 月 10 日 | 初始草案 - 改编自 ICS 20 规范 -2021 年 11 月 17 日 | 进行修订,以更好地适应智能合约 -2021 年 11 月 17 日 | 从 ICS 21 更名为 ICS 721 -2021 年 11 月 18 日 | 进行修订,以允许一个数据包中包含多种通证 -2022 年 2 月 10 日 | 进行修订,以纳入 IBC 团队的反馈 -2022 年 3 月 3 日 | 进行修订,使 TRY 回调与 PR#629 保持一致 -2022 年 3 月 11 日 | 添加示例,以说明前缀概念 -2022 年 3 月 30 日 | 添加 NFT 模块定义,并修复伪代码错误 -2022 年 5 月 18 日 | 添加了有关 NFT 元数据可变性的段落 - -## 版权 - -本规范所有内容均采用 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 许可授权。 diff --git a/other_docs/ibc-protocol/spec/client/ics-006-solo-machine-client/README.md b/other_docs/ibc-protocol/spec/client/ics-006-solo-machine-client/README.md deleted file mode 100644 index 76805f558..000000000 --- a/other_docs/ibc-protocol/spec/client/ics-006-solo-machine-client/README.md +++ /dev/null @@ -1,356 +0,0 @@ ---- -ics: '6' -title: 单机客户端 -stage: 草案 -category: IBC/TAO -kind: 实例化 -implements: '2' -author: Christopher Goes -created: '2019-12-09' -modified: '2019-12-09' ---- - -## 概要 - -本标准描述了具有单个可更新公钥的单机客户端(验证算法),该客户端实现了 [ICS 2](../../core/ics-002-client-semantics) 接口。 - -### 动机 - -单机——可能是诸如手机,浏览器或笔记本电脑之类的设备,它们希望与使用 IBC 的其他机器和多副本帐本进行交互,并且可以通过统一的客户端接口来实现。 - -单机客户端大致类似于”隐式帐户”,可以用来代替账本上的“常规交易”,从而允许所有交易通过 IBC 的统一接口进行。 - -### 定义 - -函数和术语定义见 [ICS 2](../../core/ics-002-client-semantics) 。 - -### 所需属性 - -该规范必须满足 [ICS 2](../../core/ics-002-client-semantics) 中定义的客户端接口。 - -从概念上讲,我们假设有一个“全局的大签名表”(生成的签名是公开的)并相应的包含了重放保护。 - -## 技术指标 - -该规范包含 [ICS 2](../../core/ics-002-client-semantics) 定义的所有函数的实现。 - -### 客户端状态 - -单机的`ClientState`就是简单的指客户端是否被冻结。 - -```typescript -interface ClientState { - frozen: boolean - consensusState: ConsensusState -} -``` - -### 共识状态 - -单机的`ConsensusState`由当前公钥、当前区分符、序列号和时间戳组成。 - -区分符是一个任意字符串,在创建客户端时被选定,旨在允许相同的公钥在不同的单机客户端(可能在不同的链上)重复使用,而不会被视为不当行为。 - -```typescript -interface ConsensusState { - sequence: uint64 - publicKey: PublicKey - diversifier: string - timestamp: uint64 -} -``` - -### 区块高度 - -单机的`Height`只是一个`uint64` ,可以用来做普通的比较操作。 - -### 区块头 - -当机器希望更新公钥或区分符时, `Header` 必须由单机提供。 - -```typescript -interface Header { - sequence: uint64 - timestamp: uint64 - signature: Signature - newPublicKey: PublicKey - newDiversifier: string -} -``` - -### 不良行为 - -单机器的`不良行为`包括一个序号和该序号上不同消息的两个签名。 - -```typescript -interface SignatureAndData { - sig: Signature - data: []byte -} - -interface Misbehaviour { - sequence: uint64 - signatureOne: SignatureAndData - signatureTwo: SignatureAndData -} -``` - -### 签名 - -签名在客户端状态验证功能的`Proof`字段中提供。其中的数据和时间戳也必须签名。 - -```typescript -interface Signature { - data: []byte - timestamp: uint64 -} -``` - -### 客户端初始化 - -单机客户端`initialise`函数以初始共识状态启动一个未冻结的客户端。 - -```typescript -function initialise(consensusState: ConsensusState): ClientState { - return { - frozen: false, - consensusState - } -} -``` - -单机客户端`latestClientHeight`函数返回最新的序号。 - -```typescript -function latestClientHeight(clientState: ClientState): uint64 { - return clientState.consensusState.sequence -} -``` - -### 合法性判定 - -单机客户端的`checkValidityAndUpdateState`函数检查当前注册的公共密钥是否对新的公共密钥和正确的序号进行了签名。 - -```typescript -function checkValidityAndUpdateState( - clientState: ClientState, - header: Header) { - assert(header.sequence === clientState.consensusState.sequence) - assert(header.timestamp >= clientstate.consensusState.timestamp) - assert(checkSignature(header.newPublicKey, header.sequence, header.diversifier, header.signature)) - clientState.consensusState.publicKey = header.newPublicKey - clientState.consensusState.diversifier = header.newDiversifier - clientState.consensusState.timestamp = header.timestamp - clientState.consensusState.sequence++ -} -``` - -### 不良行为判定 - -任何当前公钥在不同消息上的重复签名都会冻结单机客户端。 - -```typescript -function checkMisbehaviourAndUpdateState( - clientState: ClientState, - misbehaviour: Misbehaviour) { - h1 = misbehaviour.h1 - h2 = misbehaviour.h2 - pubkey = clientState.consensusState.publicKey - diversifier = clientState.consensusState.diversifier - timestamp = clientState.consensusState.timestamp - // 断言:时间戳可能已经欺骗了轻客户端 - assert(misbehaviour.h1.signature.timestamp >= timestamp) - assert(misbehaviour.h2.signature.timestamp >= timestamp) - // 断言:签名数据不同 - assert(misbehaviour.h1.signature.data !== misbehaviour.h2.signature.data) - // 断言:签名有效 - assert(checkSignature(pubkey, misbehaviour.sequence, diversifier, misbehaviour.h1.signature.data)) - assert(checkSignature(pubkey, misbehaviour.sequence, diversifier, misbehaviour.h2.signature.data)) - // 冻结客户端 - clientState.frozen = true -} -``` - -### 状态验证函数 - -所有单机客户端状态验证函数都仅检查签名,该签名必须由单机提供。 - -请注意,值的拼接应该以特定的状态机的转义方式实现。 - -```typescript -function verifyClientState( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - clientIdentifier: Identifier, - counterpartyClientState: ClientState) { - path = applyPrefix(prefix, "clients/{clientIdentifier}/clientState") - // ICS 003 在连接验证后不会增加证明高度 - // 单机客户端必须增加证明高度以确保匹配签名中使用的预期序列 - abortTransactionUnless(height + 1 == clientState.consensusState.sequence) - abortTransactionUnless(!clientState.frozen) - abortTransactionUnless(proof.timestamp >= clientState.consensusState.timestamp) - value = clientState.consensusState.sequence + clientState.consensusState.diversifier + proof.timestamp + path + counterpartyClientState - assert(checkSignature(clientState.consensusState.pubKey, value, proof.sig)) - clientState.consensusState.sequence++ - clientState.consensusState.timestamp = proof.timestamp -} - -function verifyClientConsensusState( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - clientIdentifier: Identifier, - consensusStateHeight: uint64, - consensusState: ConsensusState) { - path = applyPrefix(prefix, "clients/{clientIdentifier}/consensusState/{consensusStateHeight}") - // ICS 003 在连接或客户端状态验证后不会增加证明高度 - // 单机客户端必须将证明高度增加 2 以确保匹配签名中使用的预期序列 - abortTransactionUnless(height + 2 == clientState.consensusState.sequence) - abortTransactionUnless(!clientState.frozen) - abortTransactionUnless(proof.timestamp >= clientState.consensusState.timestamp) - value = clientState.consensusState.sequence + clientState.consensusState.diversifier + proof.timestamp + path + consensusState - assert(checkSignature(clientState.consensusState.pubKey, value, proof.sig)) - clientState.consensusState.sequence++ - clientState.consensusState.timestamp = proof.timestamp -} - -function verifyConnectionState( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - connectionIdentifier: Identifier, - connectionEnd: ConnectionEnd) { - path = applyPrefix(prefix, "connection/{connectionIdentifier}") - abortTransactionUnless(height == clientState.consensusState.sequence) - abortTransactionUnless(!clientState.frozen) - abortTransactionUnless(proof.timestamp >= clientState.consensusState.timestamp) - value = clientState.consensusState.sequence + clientState.consensusState.diversifier + proof.timestamp + path + connectionEnd - assert(checkSignature(clientState.consensusState.pubKey, value, proof.sig)) - clientState.consensusState.sequence++ - clientState.consensusState.timestamp = proof.timestamp -} - -function verifyChannelState( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - channelEnd: ChannelEnd) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}") - abortTransactionUnless(height == clientState.consensusState.sequence) - abortTransactionUnless(!clientState.frozen) - abortTransactionUnless(proof.timestamp >= clientState.consensusState.timestamp) - value = clientState.consensusState.sequence + clientState.consensusState.diversifier + proof.timestamp + path + channelEnd - assert(checkSignature(clientState.consensusState.pubKey, value, proof.sig)) - clientState.consensusState.sequence++ - clientState.consensusState.timestamp = proof.timestamp -} - -function verifyPacketData( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64, - data: bytes) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/packets/{sequence}") - abortTransactionUnless(height == clientState.consensusState.sequence) - abortTransactionUnless(!clientState.frozen) - abortTransactionUnless(proof.timestamp >= clientState.consensusState.timestamp) - value = clientState.consensusState.sequence + clientState.consensusState.diversifier + proof.timestamp + path + data - assert(checkSignature(clientState.consensusState.pubKey, value, proof.sig)) - clientState.consensusState.sequence++ - clientState.consensusState.timestamp = proof.timestamp -} - -function verifyPacketAcknowledgement( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64, - acknowledgement: bytes) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/acknowledgements/{sequence}") - abortTransactionUnless(height == clientState.consensusState.sequence) - abortTransactionUnless(!clientState.frozen) - abortTransactionUnless(proof.timestamp >= clientState.consensusState.timestamp) - value = clientState.consensusState.sequence + clientState.consensusState.diversifier + proof.timestamp + path + acknowledgement - assert(checkSignature(clientState.consensusState.pubKey, value, proof.sig)) - clientState.consensusState.sequence++ - clientState.consensusState.timestamp = proof.timestamp -} - -function verifyPacketReceiptAbsence( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/receipts/{sequence}") - abortTransactionUnless(height == clientState.consensusState.sequence) - abortTransactionUnless(!clientState.frozen) - abortTransactionUnless(proof.timestamp >= clientState.consensusState.timestamp) - value = clientState.consensusState.sequence + clientState.consensusState.diversifier + proof.timestamp + path - assert(checkSignature(clientState.consensusState.pubKey, value, proof.sig)) - clientState.consensusState.sequence++ - clientState.consensusState.timestamp = proof.timestamp -} - -function verifyNextSequenceRecv( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - nextSequenceRecv: uint64) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/nextSequenceRecv") - abortTransactionUnless(height == clientState.consensusState.sequence) - abortTransactionUnless(!clientState.frozen) - abortTransactionUnless(proof.timestamp >= clientState.consensusState.timestamp) - value = clientState.consensusState.sequence + clientState.consensusState.diversifier + proof.timestamp + path + nextSequenceRecv - assert(checkSignature(clientState.consensusState.pubKey, value, proof.sig)) - clientState.consensusState.sequence++ - clientState.consensusState.timestamp = proof.timestamp -} -``` - -### 属性与不变性 - -实例化[ICS 2](../../core/ics-002-client-semantics)中定义的接口。 - -## 向后兼容性 - -不适用。 - -## 向前兼容性 - -不适用。更改客户端验证算法将需要新的客户端标准。 - -## 示例实现 - -暂无。 - -## 其他实现 - -目前暂无。 - -## 历史 - -2019年12月9日-2019年12月17日初始版本-最后一份初稿 - -## 版权 - -本规范所有内容均采用 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 许可授权。 diff --git a/other_docs/ibc-protocol/spec/client/ics-007-tendermint-client/README.md b/other_docs/ibc-protocol/spec/client/ics-007-tendermint-client/README.md deleted file mode 100644 index 6a3a91f4c..000000000 --- a/other_docs/ibc-protocol/spec/client/ics-007-tendermint-client/README.md +++ /dev/null @@ -1,492 +0,0 @@ ---- -ics: '7' -title: Tendermint 客户端 -stage: 草案 -category: IBC/TAO -kind: 实例化 -implements: '2' -author: Christopher Goes -created: '2019-12-10' -modified: '2019-12-19' ---- - -## 概要 - -本标准描述了使用 Tendermint 共识的区块链客户端(验证算法)。 - -### 动机 - -使用 Tendermint 共识算法的各种状态机可能希望与其他使用 IBC 的状态机或单机进行交互。 - -### 定义 - -函数和术语定义见 [ICS 2](../../core/ics-002-client-semantics)。 - -`currentTimestamp`定义见 [ICS 24](../../core/ics-024-host-requirements)。 - -Tendermint 轻客户端使用 ICS 8 中定义的通用Merkle证明格式。 - -`hash`是一种通用的抗碰撞哈希函数,可以轻松配置。 - -### 所需属性 - -该规范必须满足 ICS 2 中定义的客户端接口。 - -#### 关于“可能被欺骗了”逻辑的注释 - -“可能被欺骗了”检测的基本思想是,它允许我们更加保守,当我们知道网络上其他地方的另一个轻客户端使用了略有不同的更新模式时,会冻结我们的轻客户端。因为可能已经被欺骗了,即使我们实际没有被欺骗。 - -现在假设有三个链`A` 、`B`和`C`的拓扑,以及`A_1`和`A_2`两个链`A`的客户端,它们分别在链`B`和`C`上运行。依次发生以下事件: - -- 链`A`在高度`h_0` 处生成一个块(正确)。 -- 客户端`A_1`和`A_2`被更新到高度为`h_0`的块。 -- 链`A`在高度`h_0 + n` 生成一个块(正确)。 -- 客户端`A_1`已更新到高度为`h_0 + n`的块(客户端`A_2`尚未更新)。 -- 链`A`生成了第二个 (矛盾的) 高度为`h_0 + k`的区块,并且`k <= n`。 - -*如果没有* “可能被欺骗了”,则客户端`A_2`会冻结(因为在高度`h_0 + k`处有两个有效块,它们比`A_2`的最新的区块头要新),但是*无法*冻结`A_1` ,因为`A_1`已经超过了`h_0 + k` 。 - -可以说,这是不利的,因为`A_1`只是“幸运”的被更新了,而`A_2`没有,并且明显一些拜占庭式的错误已经发生,应该由人或治理体系来干预处理。 “可能被欺骗了”的想法是通过让`A_1`从可配置的过去区块头开始以检测不良行为来侦测此类错误(因此,在这种情况下, `A_1`若能够从`h_0`开始检测,那么也将被冻结 )。 - -这有一个灵活的参数,即`A_1`希望从多久前开始检查(当已更新到`h_0 + n`,`n`会是多大时,`A_1`仍然会愿意查找`h_0` )?还存在一个反作用的担忧,即在解除绑定期之后,双签被认为是无成本的,我们并不想为 IBC 客户开放一个拒绝服务的媒介。 - -因此,必要条件是`A_1`应该查找已存储的最早的区块头,但还应对证据进行“解除期限”检查,如果证据早于解除期限,则应避免冻结客户端(相对于客户端的本地时间戳)。如果担心“时钟偏差”,可以添加一个轻微的增量。 - -## 技术指标 - -该规范依赖于[Tendermint 共识算法](https://github.com/tendermint/spec/blob/master/spec/consensus/consensus.md)和[轻客户端算法](https://github.com/tendermint/spec/blob/master/spec/consensus/light-client.md)的正确实例化。 - -### 客户端状态 - -Tendermint 客户端状态会跟踪当前的验证人集合、信任期、解除绑定期、最新区块高度、最新时间戳(区块时间)以及可能的冻结区块高度。 - -```typescript -interface ClientState { - chainID: string - validatorSet: List> - trustLevel: Rational - trustingPeriod: uint64 - unbondingPeriod: uint64 - latestHeight: Height - latestTimestamp: uint64 - frozenHeight: Maybe - upgradeCommitmentPrefix: CommitmentPrefix - upgradeKey: []byte - maxClockDrift: uint64 - proofSpecs: []ProofSpec -} -``` - -### 共识状态 - -Tendermint 客户端会跟踪所有先前已验证的共识状态的时间戳(区块时间)、验证人集合和承诺根(在取消绑定期之后可以将其清除,但不应该在此之前清除)。 - -```typescript -interface ConsensusState { - timestamp: uint64 - validatorSet: List> - commitmentRoot: []byte -} -``` - -### 区块高度 - -Tendermint 客户端的区块高度由两个`uint64`组成:即修订号和修订的高度。 - -```typescript -interface Height { - revisionNumber: uint64 - revisionHeight: uint64 -} -``` - -高度之间的比较如下: - -```typescript -function compare(a: TendermintHeight, b: TendermintHeight): Ord { - if (a.revisionNumber < b.revisionNumber) - return LT - else if (a.revisionNumber === b.revisionNumber) - if (a.revisionHeight < b.revisionHeight) - return LT - else if (a.revisionHeight === b.revisionHeight) - return EQ - return GT -} -``` - -在这样的设计下,当修订号增加 1时高度仍然允许被重置为`0` ,进而使得在升级时即使高度为零,超时机制仍然有效。 - -### 区块头 - -Tendermint 客户端头包括区块高度、时间戳、承诺根、完整的验证人集合以及提交该块的验证人的签名。 - -```typescript -interface Header { - height: uint64 - timestamp: uint64 - commitmentRoot: []byte - validatorSet: List> - signatures: []Signature -} -``` - -### 不良行为判定 - -`Misbehaviour`类型用于检测不良行为并冻结客户端 (如果适用)- 以防止进一步的数据流动。 Tendermint `Misbehaviour`客户端的不良行为检查决定于在相同高度的两个冲突区块头是否都会通过轻客户端的验证。 - -```typescript -function latestClientHeight(clientState: ClientState): uint64 { - return clientState.latestHeight -} -``` - -### 客户端初始化 - -Tendermint 客户端初始化要求(主观选择的)最新的共识状态,包括完整的验证人集合。 - -```typescript -function initialise( - chainID: string, consensusState: ConsensusState, - validatorSet: List>, trustLevel: Fraction, - height: Height, trustingPeriod: uint64, unbondingPeriod: uint64, - upgradeCommitmentPrefix: CommitmentPrefix, upgradeKey: []byte, - maxClockDrift: uint64, proofSpecs: []ProofSpec): ClientState { - assert(trustingPeriod < unbondingPeriod) - assert(height > 0) - assert(trustLevel > 0 && trustLevel < 1) - set("clients/{identifier}/consensusStates/{height}", consensusState) - return ClientState{ - chainID, - validatorSet, - trustLevel, - latestHeight: height, - latestTimestamp: consensusState.timestamp, - trustingPeriod, - unbondingPeriod, - frozenHeight: null, - upgradeCommitmentPrefix, - upgradeKey, - maxClockDrift, - proofSpecs - } -} -``` - -Tendermint 客户端`latestClientHeight`函数返回最新存储的高度,该高度在每次验证了新的(较新的)区块头时都会更新。 - -```typescript -function latestClientHeight(clientState: ClientState): uint64 { - return clientState.latestHeight -} -``` - -### 合法性判定式 - -Tendermint 客户端有效性检查使用[Tendermint 规范](https://github.com/tendermint/spec/tree/master/spec/consensus/light-client)中描述的二分算法。如果提供的区块头有效,那么会将更新客户端状态并将新验证的承诺写入存储。 - -```typescript -function checkValidityAndUpdateState( - clientState: ClientState, - revision: uint64, - header: Header) { - // 断言:修订版本是正确的 - assert(revision === clientState.currentHeight.revision) - // 检查修订版本是否正确编码 - assert(revision === clientState.chainID.regex('[a-z]*-(0)')) - // 断言:信任期尚未过去 - assert(currentTimestamp() - clientState.latestTimestamp < clientState.trustingPeriod) - // 断言:区块头时间戳小于未来的信任期。这应该使用中间区块头 -来解决。 - assert(header.timestamp - clientState.latestTimeStamp < trustingPeriod) - // 断言:区块头时间戳曾经是当前时间戳 - assert(header.timestamp > clientState.latestTimestamp) - // 断言:区块头高度比我们所知道的要新 - assert(header.height > clientState.latestHeight) - // 调用 `verify` 函数 - assert(verify(clientState.validatorSet, clientState.latestHeight, clientState.trustingPeriod, maxClockDrift, header)) - // 更新验证人集合 - clientState.validatorSet = header.validatorSet - // 更新最新高度 - clientState.latestHeight = header.height - // 更新最新的时间戳 - clientState.latestTimestamp = header.timestamp - // 创建记录的共识状态,保存 - consensusState = ConsensusState{header.timestamp, header.validatorSet, header.commitmentRoot} - set("clients/{identifier}/consensusStates/{header.height}", consensusState) - set("clients/{identifier}/processedTimes/{header.height}", currentTimestamp()) - set("clients/{identifier}/processedHeights/{header.height}", currentHeight()) - // 保存客户端 - set("clients/{identifier}", clientState) -} -``` - -### 不良行为判定 - -Tendermint 客户端的不良行为检查决定于在相同高度的两个冲突区块头是否都会通过轻客户端的验证。 - -```typescript -function checkMisbehaviourAndUpdateState( - clientState: ClientState, - misbehaviour: Misbehaviour) { - // 断言:高度相同 - assert(misbehaviour.h1.height === misbehaviour.h2.height) - // 断言:承诺是不同的 - assert(misbehaviour.h1.commitmentRoot !== misbehaviour.h2.commitmentRoot) - // 获取先前验证的承诺根和验证人集合 - consensusState = get("clients/{identifier}/consensusStates/{misbehaviour.fromHeight}") - // 断言:时间戳不早于一个信任期之前 - assert(currentTimestamp() - misbehaviour.timestamp < clientState.trustingPeriod) - // 检查轻客户端是否“会被愚弄” - assert( - verify(consensusState.validatorSet, misbehaviour.fromHeight, misbehaviour.h1) && - verify(consensusState.validatorSet, misbehaviour.fromHeight, misbehaviour.h2) - ) - // 设置冻结高度 - clientState.frozenHeight = min(clientState.frozenHeight, misbehaviour.h1.height) // which is same as h2.height - // 保存客户端 - set("clients/{identifier}", clientState) -} -``` - -### 升级 - -这个轻客户端所追踪的链可以选择在状态中写入一个特殊的预定密钥, 以允许轻客户端在准备升级时更新其客户端状态(例如,使用新的链ID或修订版本). - -由于客户端状态的改变将立即进行, 一旦新的客户端状态信息被写入预定密钥, 客户端将不再能够跟踪旧链上的区块, 所以它必须及时升级. - -```typescript -function upgradeClientState( - clientState: ClientState, - newClientState: ClientState, - height: Height, - proof: CommitmentPrefix) { - // 断言:信任期尚未过去 - assert(currentTimestamp() - clientState.latestTimestamp < clientState.trustingPeriod) - // 检查修订版本是否已增加 - assert(newClientState.latestHeight.revisionNumber > clientState.latestHeight.revisionNumber) - // 根据预定的承诺前缀和密钥检查更新客户端状态的证明 - path = applyPrefix(clientState.upgradeCommitmentPrefix, clientState.upgradeKey) - // 检查客户端的高度是否足够 - assert(clientState.latestHeight >= height) - // 检查客户端是否解冻或冻结在更高的高度 - assert(clientState.frozenHeight === null || clientState.frozenHeight > height) - // 获取先前验证的承诺根并验证成员资格 - root = get("clients/{identifier}/consensusStates/{height}") - // 验证提供的共识状态是否已存储 - assert(root.verifyMembership(path, newClientState, proof)) - // 更新客户端状态 - clientState = newClientState - set("clients/{identifier}", clientState) -} -``` - -### 状态验证函数 - -Tendermint 客户端状态验证函数对照先前已验证的承诺根检查Merkle证明。 - -这些函数使用初始化客户端的`proofSpecs` 。 - -```typescript -function verifyClientConsensusState( - clientState: ClientState, - height: Height, - prefix: CommitmentPrefix, - proof: CommitmentProof, - clientIdentifier: Identifier, - consensusStateHeight: Height, - consensusState: ConsensusState) { - path = applyPrefix(prefix, "clients/{clientIdentifier}/consensusState/{consensusStateHeight}") - // 检查客户端是否处于足够的高度 - assert(clientState.latestHeight >= height) - // 检查客户端是否解冻或冻结在更高的高度 - assert(clientState.frozenHeight === null || clientState.frozenHeight > height) - // 获取先前验证的承诺根并验证成员资格 - root = get("clients/{identifier}/consensusStates/{height}") - // 验证提供的共识状态是否已存储 - assert(root.verifyMembership(path, consensusState, proof)) -} - -function verifyConnectionState( - clientState: ClientState, - height: Height, - prefix: CommitmentPrefix, - proof: CommitmentProof, - connectionIdentifier: Identifier, - connectionEnd: ConnectionEnd) { - path = applyPrefix(prefix, "connections/{connectionIdentifier}") - // 检查客户端是否处于足够的高度 - assert(clientState.latestHeight >= height) - // 检查客户端是否解冻或冻结在更高的高度 - assert(clientState.frozenHeight === null || clientState.frozenHeight > height) - // 获取先前验证的承诺根并验证成员资格 - root = get("clients/{identifier}/consensusStates/{height}") - // 验证提供的连接端是否已存储 - assert(root.verifyMembership(path, connectionEnd, proof)) -} - -function verifyChannelState( - clientState: ClientState, - height: Height, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - channelEnd: ChannelEnd) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}") - // 检查客户端是否处于足够的高度 - assert(clientState.latestHeight >= height) - // 检查客户端是否解冻或冻结在更高的高度 - assert(clientState.frozenHeight === null || clientState.frozenHeight > height) - // 获取先前验证的承诺根并验证成员资格 - root = get("clients/{identifier}/consensusStates/{height}") - // 验证提供的通道端是否已存储 - assert(root.verifyMembership(clientState.proofSpecs, path, channelEnd, proof)) -} - -function verifyPacketData( - clientState: ClientState, - height: Height, - delayPeriodTime: uint64, - delayPeriodBlocks: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64, - data: bytes) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/packets/{sequence}") - // 检查客户端是否处于足够的高度 - assert(clientState.latestHeight >= height) - // 检查客户端是否解冻或冻结在更高的高度 - assert(clientState.frozenHeight === null || clientState.frozenHeight > height) - // 获取处理时间 - processedTime = get("clients/{identifier}/processedTimes/{height}") - // 获取处理后的高度 - processedHeight = get("clients/{identifier}/processedHeights/{height}") - // 断言:足够的时间已经过去 - assert(currentTimestamp() >= processedTime + delayPeriodTime) - // 断言:已经过去了足够多的块 - assert(currentHeight() >= processedHeight + delayPeriodBlocks) - // 获取先前验证的承诺根并验证成员资格 - root = get("clients/{identifier}/consensusStates/{height}") - // 验证提供的承诺是否已被存储 - assert(root.verifyMembership(clientState.proofSpecs, path, hash(data), proof)) -} - -function verifyPacketAcknowledgement( - clientState: ClientState, - height: Height, - delayPeriodTime: uint64, - delayPeriodBlocks: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64, - acknowledgement: bytes) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/acknowledgements/{sequence}") - // 检查客户端是否处于足够的高度 - assert(clientState.latestHeight >= height) - // 检查客户端是否解冻或冻结在更高的高度 - assert(clientState.frozenHeight === null || clientState.frozenHeight > height) - // 获取处理时间 - processedTime = get("clients/{identifier}/processedTimes/{height}") - // 获取处理后的高度 - processedHeight = get("clients/{identifier}/processedHeights/{height}") - // 断言足够的时间已经过去 - assert(currentTimestamp() >= processedTime + delayPeriodTime) - // 断言已经过去了足够多的块 - assert(currentHeight() >= processedHeight + delayPeriodBlocks) - // 获取先前验证的承诺根并验证成员资格 - root = get("clients/{identifier}/consensusStates/{height}") - // 验证提供的确认是否已存储 - assert(root.verifyMembership(clientState.proofSpecs, path, hash(acknowledgement), proof)) -} - -function verifyPacketReceiptAbsence( - clientState: ClientState, - height: Height, - delayPeriodTime: uint64, - delayPeriodBlocks: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/receipts/{sequence}") - // 检查客户端是否处于足够的高度 - assert(clientState.latestHeight >= height) - // 检查客户端是否解冻或冻结在更高的高度 - assert(clientState.frozenHeight === null || clientState.frozenHeight > height) - // 获取处理时间 - processedTime = get("clients/{identifier}/processedTimes/{height}") - // 获取处理后的高度 - processedHeight = get("clients/{identifier}/processedHeights/{height}") - // 断言:足够的时间已经过去 - assert(currentTimestamp() >= processedTime + delayPeriodTime) - // 断言已经过去了足够多的块 - assert(currentHeight() >= processedHeight + delayPeriodBlocks) - // 获取先前验证的承诺根并验证成员资格 - root = get("clients/{identifier}/consensusStates/{height}") - // 验证没有回执被存储 - assert(root.verifyNonMembership(clientState.proofSpecs, path, proof)) -} - -function verifyNextSequenceRecv( - clientState: ClientState, - height: Height, - delayPeriodTime: uint64, - delayPeriodBlocks: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - nextSequenceRecv: uint64) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/nextSequenceRecv") - // 检查客户端是否处于足够的高度 - assert(clientState.latestHeight >= height) - // 检查客户端是否解冻或冻结在更高的高度 - assert(clientState.frozenHeight === null || clientState.frozenHeight > height) - // 获取处理时间 - processedTime = get("clients/{identifier}/processedTimes/{height}") - // 获取处理后的高度 - processedHeight = get("clients/{identifier}/processedHeights/{height}") - // 断言足够的时间已经过去 - assert(currentTimestamp() >= processedTime + delayPeriodTime) - // 断言已经过去了足够多的块 - assert(currentHeight() >= processedHeight + delayPeriodBlocks) - // 获取先前验证的承诺根并验证成员资格 - root = get("clients/{identifier}/consensusStates/{height}") - // 验证 nextSequenceRecv 是否如声明的那样 - assert(root.verifyMembership(clientState.proofSpecs, path, nextSequenceRecv, proof)) -} -``` - -### 属性与不变性 - -正确性保证和 Tendermint 轻客户端算法相同。 - -## 向后兼容性 - -不适用。 - -## 向前兼容性 - -不适用。更改客户端验证算法将需要新的客户端标准。 - -## 示例实现 - -暂无。 - -## 其他实现 - -目前暂无。 - -## 历史 - -2019年12月10日-2019年12月19日初始版本-最后初稿 - -## 版权 - -本规范所有内容均采用 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 许可授权。 diff --git a/other_docs/ibc-protocol/spec/client/ics-008-wasm-client/README.md b/other_docs/ibc-protocol/spec/client/ics-008-wasm-client/README.md deleted file mode 100644 index 1dce003dd..000000000 --- a/other_docs/ibc-protocol/spec/client/ics-008-wasm-client/README.md +++ /dev/null @@ -1,411 +0,0 @@ ---- -ics: '8' -title: Wasm 客户端 -stage: 草案 -category: IBC/TAO -kind: 实例化 -implements: '2' -author: Parth Desai , Mateusz Kaczanowski -created: '2020-10-13' -modified: '2020-10-13' ---- - -## 概要 - -本规范文档描述了客户端(验证算法)的接口,存储为区块链的 Wasm 字节码。 - -### 动机 - -目前,添加新的客户端实现或升级已有的客户端都需要进行硬分叉升级,因为客户端的实现是内置在链上静态二进制文件中的。链上任何客户端代码的更改都依赖于链上治理的批准,方可正式部署。 - -对于增加新客户端类型来说,这是可以接受的,毕竟当前支持的共识算法数量较少。但是对于升级轻客户端来说则是比较麻烦的做法。 - -对于没有动态升级能力的轻客户端来说, 想要实现共识算法的升级(并暂停已有的轻客户端)需要等待其他链首先完成硬分叉升级(以便能够支持新的客户端)。共识暂停的升级的一个例子包括从Tendermint V1升级到Tendermint V2(更新轻客户端),以及从Tendermint共识切换到Honeybadger。 对内部状态机逻辑的改变不会影响共识,例如,对staking模块的改变不需要进行IBC升级。 - -这种要求相关联链的二进制文件都添加新的客户端实现的方式无疑会拖慢IBC网络的升级进程。 因为这会导致每一个实验性、快速迭代的链的部署过程由于这个过于保守的机制而停滞不前。 - -一旦IBC网络广泛采用可动态升级的客户端,一条链可以随时升级其共识算法,中继器可以升级所有对手链的客户端代码,而不需要对手链自己进行升级。这就避免了在升级自己的共识算法时对于对手链的依赖。 - -### 定义 - -函数和术语定义见 [ICS 2](../../core/ics-002-client-semantics)。 - -`currentTimestamp` 定义见 [ICS 24](../../core/ics-024-host-requirements)。 - -`Wasm Client Code` 指的是存储在client store里的Wasm字节码,它提供了[ICS 2](../../core/ics-002-client-semantics) 目标区块链的具体实现。 - -`Wasm Client` 指的是定义在元组 `(Wasm Client Code, ClientID)`中`Wasm Client Code`的特定实例。 - -`Wasm VM` 指的是能够执行有效Wasm字节码的虚拟机。 - -### 所需属性 - -该规范必须满足ICS 2中定义的客户端接口。 - -## 技术规范 - -该规范取决于 `Wasm client` 的正确实例化,并且和任何目标 `blockchain` 的共识算法的具体实现解耦。 - -### 客户端状态 - -Wasm客户端状态通过`codeId`跟踪Wasm字节码的位置。`data`字段表示二进制数据,该数据不透明且仅能通过Wasm Client Code解析。 `type` 代表客户端类型。 `type` 和 `codeId` 都是不可变的。 - -```typescript -interface ClientState { - codeId: []byte - data: []byte - latestHeight: Height -} -``` - -### 共识状态 - -Wasm共识状态跟踪时间戳(区块时间)和`Wasm Client code` 特定字段以及之前已经验证的共识状态的承诺根(commitment root)。 `type` 和 `codeId` 都是不可变的。 - -```typescript -interface ConsensusState { - codeId: []byte - data: []byte - timestamp: uint64 -} -``` - -### 区块高度 - -Wasm轻客户端实例的Height由两个`uint64`组成:修订号和修订的区块高度。 - -```typescript -interface Height { - revisionNumber: uint64 - revisionHeight: uint64 -} -``` - -高度之间的比较按以下方式实现: - -```typescript -function compare(a: Height, b: Height): Ord { - if (a.revisionNumber < b.revisionNumber) - return LT - else if (a.revisionNumber === b.revisionNumber) - if (a.revisionHeight < b.revisionHeight) - return LT - else if (a.revisionHeight === b.revisionHeight) - return EQ - return GT -} -``` - -这旨在允许高度重置为 `0` ,而修订号增加1,以便通过零高度的升级保持超时状态。 - -### 区块头 - -Wasm客户端的区块头依赖 `Wasm Client Code`。 - -```typescript -interface Header { - data: []byte - height: Height -} -``` - -### 不良行为 - -`Misbehaviour` 类型用于检测不良行为并冻结客户端——并阻止数据包流(如适用)。Wasm 客户端的 `Misbehaviour` 由两个冲突的区块头组成, 且这两个区块头都被轻客户端认可有效。 - -```typescript -interface Misbehaviour { - clientId: string - h1: Header - h2: Header -} -``` - -### 客户端初始化 - -Wasm客户端初始化需要一个主观选择的最新共识状态, 由Wasm客户端代码负责解析。`wasmCodeId` 字段是`Wasm Client Code` 的唯一标识符,`initializationData` 指的是不透明数据,该数据用来初始化由Wasm客户端代码管理的特定客户端。 - -```typescript -function initialise( - wasmCodeId: String, - initializationData: []byte, - consensusState: []byte, - ): ClientState { - codeHandle = getWasmCode(wasmCodeId) - assert(codeHandle.isInitializationDataValid(initializationData, consensusState)) - - store = getStore("clients/{identifier}") - return codeHandle.initialise(store, initializationData, consensusState) -} -``` - -`latestClientHeight` 函数返回最新的存储高度,该高度在每次新的高度(最近的)被验证的时候也会随之更新。 - -```typescript -function latestClientHeight(clientState: ClientState): Height { - codeHandle = clientState.codeHandle(); - codeHandle.latestClientHeight(clientState) -} -``` - -### 合法性判定 - -Wasm客户端合法性检测使用的是底层Wasm客户端代码。在确认所提供的区块头有效之后,会更新客户端状态,同时将刚刚验证的承诺(commitment)写入存储。 - -```typescript -function checkValidityAndUpdateState( - clientState: ClientState, - header: Header) { - store = getStore("clients/{identifier}") - codeHandle = clientState.codeHandle() - - // 验证所提供的区块头有效且状态被保存 - assert(codeHandle.validateHeaderAndCreateConsensusState(store, clientState, header)) -} -``` - -### 不良行为判定 - -Wasm客户端的不良行为检测将决定在同一个高度的冲突区块头哪个将得到轻客户端的认可。 - -```typescript -function checkMisbehaviourAndUpdateState( - clientState: ClientState, - misbehaviour: Misbehaviour) { - store = getStore("clients/{identifier}") - codeHandle = clientState.codeHandle() - assert(codeHandle.handleMisbehaviour(store, clientState, misbehaviour)) -} -``` - -### 升级 - -该轻客户端所追踪的链可以选择在状态中写入一个特殊的预定密钥,以允许轻客户端在准备升级时更新其客户端状态(例如使用新的链ID或修订版本)。 - -由于客户端的状态改变会被立即执行,一旦新的客户端状态信息被写入预定密钥,客户端将不再能够跟踪旧链上的区块,所以它必须及时升级。 - -```typescript -function upgradeClientState( - clientState: ClientState, - newClientState: ClientState, - height: Height, - proof: CommitmentPrefix) { - codeHandle = clientState.codeHandle() - assert(codeHandle.verifyNewClientState(clientState, newClientState, height, proof)) - - // 更新客户端状态 - clientState = newClientState - set("clients/{identifier}", clientState) -} -``` - -对于 Wasm客户端,也可以通过区块链特定的管理功能升级 Wasm 客户端代码。 - -### 状态验证函数 - -Wasm客户端状态验证函数根据先前验证的承诺根来检查Merkle证明。 - -```typescript -function verifyClientConsensusState( - clientState: ClientState, - height: Height, - prefix: CommitmentPrefix, - proof: CommitmentProof, - clientIdentifier: Identifier, - consensusStateHeight: Height, - consensusState: ConsensusState) { - codeHandle = getCodeHandleFromClientID(clientIdentifier) - assert(codeHandle.verifyClientConsensusState(clientState, height, prefix, clientIdentifier, proof, consensusStateHeight, consensusState)) -} - -function verifyConnectionState( - clientState: ClientState, - height: Height, - prefix: CommitmentPrefix, - proof: CommitmentProof, - connectionIdentifier: Identifier, - connectionEnd: ConnectionEnd) { - codeHandle = clientState.codeHandle() - assert(codeHandle.verifyConnectionState(clientState, height, prefix, proof, connectionIdentifier, connectionEnd)) -} - -function verifyChannelState( - clientState: ClientState, - height: Height, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - channelEnd: ChannelEnd) { - codeHandle = clientState.codeHandle() - assert(codeHandle.verifyChannelState(clientState, height, prefix, proof, portIdentifier, channelIdentifier, channelEnd)) -} - -function verifyPacketCommitment( - clientState: ClientState, - height: Height, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64, - commitment: bytes) { - codeHandle = clientState.codeHandle() - assert(codeHandle.verifyPacketCommitment(clientState, height, prefix, proof, portIdentifier, channelIdentifier, sequence, commitment)) -} - -function verifyPacketAcknowledgement( - clientState: ClientState, - height: Height, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64, - acknowledgement: bytes) { - codeHandle = clientState.codeHandle() - assert(codeHandle.verifyPacketAcknowledgement(clientState, height, prefix, proof, portportIdentifier, channelIdentifier, sequence, acknowledgement)) -} - -function verifyPacketReceiptAbsence( - clientState: ClientState, - height: Height, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64) { - codeHandle = clientState.codeHandle() - assert(codeHandle.verifyPacketReceiptAbsence(clientState, height, prefix, proof, portIdentifier, channelIdentifier, sequence)) -} - -function verifyNextSequenceRecv( - clientState: ClientState, - height: Height, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - nextSequenceRecv: uint64) { - codeHandle = clientState.codeHandle() - assert(codeHandle.verifyNextSequenceRecv(clientState, height, prefix, proof, portIdentifier, channelIdentifier, nextSequenceRecv)) -} -``` - -### Wasm 客户端代码接口 - -#### 什么是代码句柄? - -代码句柄是一个对象,能够方便在Wasm代码和Go代码之间进行交互。例如,`isValidClientState` 方法可以通过以下方式实现: - -```go -func (c *CodeHandle) isValidClientState(clientState ClientState, height u64) { - clientStateData := json.Serialize(clientState) - packedData := pack(clientStateData, height) - // 调用Wasm合约的特定VM代码 - -} -``` - -#### Wasm客户端接口 - -每个Wasm客户端代码都需要支持以下消息的接收,以便用作轻客户端。 - -```rust -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct MisbehaviourMessage { - pub client_state: Vec, - pub consensus_state: Vec, - pub height: Height, - pub header1: Vec, - pub header2: Vec, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct CreateConsensusMessage { - pub client_state: Vec, - pub height: Height -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct InitializeClientStateMessage { - pub initialization_data: Vec, - pub consensus_state: Vec -} - - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum HandleMsg { - HandleMisbehaviour(MisbehaviourMessage), - TryCreateConsensusState(CreateConsensusMessage), - InitializeClientState(InitializeClientStateMessage) -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct ValidateClientStateMessage { - client_state: Vec, - height: Height -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct ValidateNewClientStateMessage { - client_state: Vec, - new_client_state: Vec, - height: Height -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct ValidateInitializationDataMessage { - init_data: Vec, - consensus_state: Vec -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ValidityPredicate { - ClientState(ValidateClientStateMessage), - NewClientState(ValidateNewClientStateMessage), - InitializationData(ValidateInitializationDataMessage), -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - IsValid(ValidityPredicate), - LatestClientHeight(Vec), -} - -``` - -### 属性与不变性 - -`Wasm Client Code`实现的底层算法提供的正确性保证。 - -## 向后兼容性 - -不适用. - -## 向前兼容性 - -只要`Wasm Client Code`与`ICS 02`保持接口一致, 它就是向前兼容的. - -## 示例实现 - -暂无。 - -## 其他实现 - -目前暂无。 - -## 历史 - -## 版权 - -本规范所有内容均采用 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 许可授权。 diff --git a/other_docs/ibc-protocol/spec/client/ics-009-loopback-client/README.md b/other_docs/ibc-protocol/spec/client/ics-009-loopback-client/README.md deleted file mode 100644 index 8efe53a5c..000000000 --- a/other_docs/ibc-protocol/spec/client/ics-009-loopback-client/README.md +++ /dev/null @@ -1,196 +0,0 @@ ---- -ics: '9' -title: 回环客户端 -stage: 草案 -category: IBC/TAO -kind: 实例化 -author: Christopher Goes -created: '2020-01-17' -modified: '2020-01-17' -requires: '2' -implements: '2' ---- - -## 概要 - -本规范描述了一种回环客户端,该客户端旨在通过 IBC 接口与同一帐本中存在的模块进行交互。 - -### 动机 - -如果调用模块不了解目标模块的确切位置,并且希望使用统一的 IBC 消息传递接口(类似于 TCP/IP 中的 `127.0.0.1` ),则回环客户端可能会派上用场。 - -### 定义 - -函数和术语定义见 [ICS 2](../../core/ics-002-client-semantics)。 - -### 所需属性 - -应保留预期的客户端语义,而且回环抽象的成本应可忽略不计。 - -## 技术指标 - -### 数据结构 - -回环客户端不需要客户端状态、共识状态、区块头或证据数据结构。 - -```typescript -type ClientState object - -type ConsensusState object - -type Header object - -type Evidence object -``` - -### 客户端初始化 - -回环客户端不需要初始化。将返回一个空状态。 - -```typescript -function initialise(): ClientState { - return {} -} -``` - -### 合法性判定 - -在回环客户端中,无需进行合法性检查;该函数永远不应该被调用。 - -```typescript -function checkValidityAndUpdateState( - clientState: ClientState, - header: Header) { - assert(false) -} -``` - -### 不良行为判定 - -在回环客户端中无需进行任何不良行为检查;该函数永远不应该被调用。 - -```typescript -function checkMisbehaviourAndUpdateState( - clientState: ClientState, - evidence: Evidence) { - return -} -``` - -### 状态验证函数 - -回环客户端状态验证函数仅读取本地状态。请注意,他们将需要(只读)访问客户端前缀之外的键。 - -```typescript -function verifyClientConsensusState( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - clientIdentifier: Identifier, - consensusState: ConsensusState) { - path = applyPrefix(prefix, "consensusStates/{clientIdentifier}") - assert(get(path) === consensusState) -} - -function verifyConnectionState( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - connectionIdentifier: Identifier, - connectionEnd: ConnectionEnd) { - path = applyPrefix(prefix, "connection/{connectionIdentifier}") - assert(get(path) === connectionEnd) -} - -function verifyChannelState( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - channelEnd: ChannelEnd) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}") - assert(get(path) === channelEnd) -} - -function verifyPacketData( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64, - data: bytes) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/packets/{sequence}") - assert(get(path) === commit(data)) -} - -function verifyPacketAcknowledgement( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64, - acknowledgement: bytes) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/acknowledgements/{sequence}") - assert(get(path) === acknowledgement) -} - -function verifyPacketAcknowledgementAbsence( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/acknowledgements/{sequence}") - assert(get(path) === nil) -} - -function verifyNextSequenceRecv( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - nextSequenceRecv: uint64) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/nextSequenceRecv") - assert(get(path) === nextSequenceRecv) -} -``` - -### 属性与不变性 - -语义上类似一个本地帐本的远程客户端。 - -## 向后兼容性 - -不适用。 - -## 向前兼容性 - -不适用。更改客户端算法将需要新的客户端标准。 - -## 示例实现 - -即将到来。 - -## 其他实现 - -目前暂无。 - -## 历史 - -2020-01-17-初始版本 - -## 版权 - -本规范所有内容均采用 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 许可授权。 diff --git a/other_docs/ibc-protocol/spec/client/ics-010-grandpa-client/README.md b/other_docs/ibc-protocol/spec/client/ics-010-grandpa-client/README.md deleted file mode 100644 index ecce1d422..000000000 --- a/other_docs/ibc-protocol/spec/client/ics-010-grandpa-client/README.md +++ /dev/null @@ -1,370 +0,0 @@ ---- -ics: '10' -title: GRANDPA 客户端 -stage: 草案 -category: IBC/TAO -kind: 实例化 -author: Yuanchao Sun , John Wu -created: '2020-03-15' -implements: '2' ---- - -## 概要 - -本规范描述了使用 GRANDPA 最终性小工具的区块链客户端(验证算法)。 - -GRANDPA(GHOST-based Recursive Ancestor Deriving Prefix Agreement)是 Polkadot 中继链将会使用的一个最终性小工具。它现在有一个 Rust 语言实现,并且是 Substrate 框架的一部分,因此使用 Substrate 构建的区块链很可能会使用 GRANDPA 作为其最终性小工具。 - -### 动机 - -使用 GRANDPA 最终性小工具的区块链可能希望通过 IBC 与其他状态机或单机进行交互。 - -### 定义 - -函数和术语定义见 [ICS 2](../../core/ics-002-client-semantics) 。 - -### 所需属性 - -该规范必须满足 ICS 2 中定义的客户端接口。 - -## 技术指标 - -该规范依赖于 [GRANDPA 最终性小工具](https://github.com/w3f/consensus/blob/master/pdf/grandpa.pdf)及其轻客户端算法的正确实例化。 - -### 客户端状态 - -GRANDPA 客户端状态会跟踪最新区块高度和可能的冻结区块高度。 - -```typescript -interface ClientState { - latestHeight: uint64 - frozenHeight: Maybe -} -``` - -### 权威集合 - -GRANDPA 的一组权威账户。 - -```typescript -interface AuthoritySet { - // 每次集合更改时都会递增 - setId: uint64 - authorities: List> -} -``` - -### 共识状态 - -GRANDPA 客户端跟踪所有先前已验证的共识状态的权威集合和承诺根。 - -```typescript -interface ConsensusState { - authoritySet: AuthoritySet - commitmentRoot: []byte -} -``` - -### 区块头 - -GRANDPA 客户端区块头包括区块高度、承诺根、区块的确定性证明和权威集合。(实际上,区块头中包含的是一个权威集合的证明,而不是权威集合本身,但是我们可以使用一个固定的键来验证证明并提取出真实集合,本规范不包含相关细节) - -```typescript -interface Header { - height: uint64 - commitmentRoot: []byte - justification: Justification - authoritySet: AuthoritySet -} -``` - -### 确定性证明 - -一个 GRANDPA 的区块确定性证明包括一个commit信息和一个祖先证明(ancestry proof),其中包括所有预提交目标块到提交目标块之间的所有区块头。例如,最新的块是 A-B-C-D-E-F,其中 A 是最后敲定的块,F 是可以收集到多数投票的位置(投票可能在 B,C,D,E,F 上)。那么证明需要包括从 F 到 A 的所有区块头。 - -```typescript -interface Justification { - round: uint64 - commit: Commit - votesAncestries: []Header -} -``` - -### Commit 信息 - -Commit消息是已签名的预提交的汇总。 - -```typescript -interface Commit { - precommits: []SignedPrecommit -} - -interface SignedPrecommit { - targetHash: Hash - signature: Signature - id: AuthorityId -} -``` - -### 不良行为 - -`Evidence`类型用于检测不良行为并冻结客户端-以防止进一步的数据包流(如果适用)。 GRANDPA 客户端`Evidence`由两个高度相同的、且由轻客户端判定有效的区块头组成。 - -```typescript -interface Evidence { - fromHeight: uint64 - h1: Header - h2: Header -} -``` - -### 客户初始化 - -GRANDPA 客户端初始化要求(主观选择)一个最新的共识状态,包括完整的权威集合。 - -```typescript -function initialise(identifier: Identifier, height: uint64, consensusState: ConsensusState): ClientState { - set("clients/{identifier}/consensusStates/{height}", consensusState) - return ClientState{ - latestHeight: height, - frozenHeight: null, - } -} -``` - -GRANDPA 客户端的`latestClientHeight`函数返回最新存储的区块高度,该高度在每次验证一个新的(更接近现在的)区块头时都会更新。 - -```typescript -function latestClientHeight(clientState: ClientState): uint64 { - return clientState.latestHeight -} -``` - -### 合法性判定式 - -GRANDPA 客户端合法性检查将验证区块头是否由当前权威集合签名,并验证权威集合证明以确定是否存在对权威集合的更改。如果所提供的区块头有效,那么将更新客户端状态并将新验证的承诺写入存储。 - -```typescript -function checkValidityAndUpdateState( - clientState: ClientState, - header: Header) { - // 断言:区块头高度比我们所知道的要新 - assert(header.height > clientState.latestHeight) - consensusState = get("clients/{identifier}/consensusStates/{clientState.latestHeight}") - // 验证提供的区块头是否有效 - assert(verify(consensusState.authoritySet, header)) - // 更新最新高度 - clientState.latestHeight = header.height - // 创建记录的共识状态,并保存 - consensusState = ConsensusState{header.authoritySet, header.commitmentRoot} - set("clients/{identifier}/consensusStates/{header.height}", consensusState) - // 保存客户端 - set("clients/{identifier}", clientState) -} - -function verify( - authoritySet: AuthoritySet, - header: Header): boolean { - let visitedHashes: Hash[] - for (const signedPrecommit of Header.justification.commit.precommits) { - if (checkSignature(authoritySet, signedPrecommit)) { - visitedHashes.push(signedPrecommit.targetHash) - } - } - return visitedHashes.equals(Header.justification.votesAncestries.map(hash)) -} -``` - -### 不良行为判定 - -GRANDPA 客户端的不良行为检测将确定在相同高度的两个冲突的区块头是否都被轻客户端认定有效。 - -```typescript -function checkMisbehaviourAndUpdateState( - clientState: ClientState, - evidence: Evidence) { - // 断言:高度相同 - assert(evidence.h1.height === evidence.h2.height) - // 断言:承诺是不同的 - assert(evidence.h1.commitmentRoot !== evidence.h2.commitmentRoot) - // 获取先前验证的承诺根和权威集合 - consensusState = get("clients/{identifier}/consensusStates/{evidence.fromHeight}") - // 检查轻客户端是否“会被愚弄” - assert( - verify(consensusState.authoritySet, evidence.h1) && - verify(consensusState.authoritySet, evidence.h2) - ) - // 设置冻结高度 - clientState.frozenHeight = min(clientState.frozenHeight, evidence.h1.height) // which is same as h2.height - //保存客户端 - set("clients/{identifier}", clientState) -} -``` - -### 状态验证函数 - -GRANDPA 客户端状态验证函数对照先前已验证的承诺根检查Merkle证明。 - -```typescript -function verifyClientConsensusState( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - clientIdentifier: Identifier, - consensusStateHeight: uint64, - consensusState: ConsensusState) { - path = applyPrefix(prefix, "clients/{clientIdentifier}/consensusState/{consensusStateHeight}") - // 检查客户端是否处于足够的高度 - assert(clientState.latestHeight >= height) - // 检查客户端是否解冻或冻结在更高的高度 - assert(clientState.frozenHeight === null || clientState.frozenHeight > height) - // 获取先前验证的承诺根并验证成员资格 - root = get("clients/{identifier}/consensusStates/{height}") - // 验证提供的共识状态是否已存储 - assert(root.verifyMembership(path, consensusState, proof)) -} - -function verifyConnectionState( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - connectionIdentifier: Identifier, - connectionEnd: ConnectionEnd) { - path = applyPrefix(prefix, "connections/{connectionIdentifier}") - // 检查客户端是否处于足够的高度 - assert(clientState.latestHeight >= height) - // 检查客户端是否解冻或冻结在更高的高度 - assert(clientState.frozenHeight === null || clientState.frozenHeight > height) - // 获取先前验证的承诺根并验证成员资格 - root = get("clients/{identifier}/consensusStates/{height}") - // 验证提供的连接端是否已存储 - assert(root.verifyMembership(path, connectionEnd, proof)) -} - -function verifyChannelState( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - channelEnd: ChannelEnd) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}") - // 检查客户端是否处于足够的高度 - assert(clientState.latestHeight >= height) - // 检查客户端是否解冻或冻结在更高的高度 - assert(clientState.frozenHeight === null || clientState.frozenHeight > height) - // 获取先前验证的承诺根并验证成员资格 - root = get("clients/{identifier}/consensusStates/{height}") - // 验证提供的通道端是否已存储 - assert(root.verifyMembership(path, channelEnd, proof)) -} - -function verifyPacketData( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64, - data: bytes) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/packets/{sequence}") - // 检查客户端是否处于足够的高度 - assert(clientState.latestHeight >= height) - // 检查客户端是否解冻或冻结在更高的高度 - assert(clientState.frozenHeight === null || clientState.frozenHeight > height) - // 获取先前验证的承诺根并验证成员资格 - root = get("clients/{identifier}/consensusStates/{height}") - // 验证提供的承诺是否已被存储 - assert(root.verifyMembership(path, hash(data), proof)) -} - -function verifyPacketAcknowledgement( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64, - acknowledgement: bytes) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/acknowledgements/{sequence}") - // 检查客户端是否处于足够的高度 - assert(clientState.latestHeight >= height) - // 检查客户端是否解冻或冻结在更高的高度 - assert(clientState.frozenHeight === null || clientState.frozenHeight > height) - // 获取先前验证的承诺根并验证成员资格 - root = get("clients/{identifier}/consensusStates/{height}") - // 验证提供的回执是否已存储 - assert(root.verifyMembership(path, hash(acknowledgement), proof)) -} - -function verifyPacketAcknowledgementAbsence( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/acknowledgements/{sequence}") - // 检查客户端是否处于足够的高度 - assert(clientState.latestHeight >= height) - // 检查客户端是否解冻或冻结在更高的高度 - assert(clientState.frozenHeight === null || clientState.frozenHeight > height) - // 获取先前验证的承诺根并验证成员资格 - root = get("clients/{identifier}/consensusStates/{height}") - // 验证没有承诺被存储 - assert(root.verifyNonMembership(path, proof)) -} - -function verifyNextSequenceRecv( - clientState: ClientState, - height: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - nextSequenceRecv: uint64) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/nextSequenceRecv") - // 检查客户端是否处于足够的高度 - assert(clientState.latestHeight >= height) - // 检查客户端是否解冻或冻结在更高的高度 - assert(clientState.frozenHeight === null || clientState.frozenHeight > height) - // 获取先前验证的承诺根并验证成员资格 - root = get("clients/{identifier}/consensusStates/{height}") - // 验证 nextSequenceRecv 是否如声明的那样 - assert(root.verifyMembership(path, nextSequenceRecv, proof)) -} -``` - -### 属性与不变性 - -正确性保证和 GRANDPA 轻客户端算法相同。 - -## 向后兼容性 - -不适用。 - -## 向前兼容性 - -不适用。更改客户端验证算法将需要新的客户端标准。 - -## 示例实现 - -暂无。 - -## 其他实现 - -目前暂无。 - -## 历史 - -2020年3月15日-初始版本 - -## 版权 - -本规范所有内容均采用 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 许可授权。 diff --git a/other_docs/ibc-protocol/spec/core/ics-002-client-semantics/README.md b/other_docs/ibc-protocol/spec/core/ics-002-client-semantics/README.md deleted file mode 100644 index 57787bbce..000000000 --- a/other_docs/ibc-protocol/spec/core/ics-002-client-semantics/README.md +++ /dev/null @@ -1,790 +0,0 @@ ---- -ics: '2' -title: 客户端语义 -stage: 草案 -category: IBC/TAO -kind: 接口 -requires: 23, 24 -required-by: '3' -author: Juwoon Yun , Christopher Goes -created: '2019-02-25' -modified: '2020-01-13' ---- - -## 概要 - -该标准规定了实现跨链通信(IBC)协议的状态机的共识算法必须满足的属性。这些属性对更高层协议抽象中的有效安全的验证而言是必需的。IBC 中用于验证远程状态机的状态更新的算法称为“合法性判定”。将合法性判定与一个可信的状态(例如:一种被验证者认可的状态)组合在一起,即实现了远程状态机基于本地状态机的“轻客户端”(“轻客户端”经常简称为“客户端”)功能。 除了验证状态的更新,每个轻客户端也有能力通过“不良行为判定”来检测不良行为。 - -除了上述属性,IBC 未规定其它对于状态机与共识算法的内部操作。一个状态机可能是一个使用密钥进行签名的独立的进程(称为单机客户端),可能是一组协同签名的进程,可能是达成的拜占廷容错共识算法(如 Tendermint)的相互独立的多个进程,或将来 IBC 规定的其他方式的状态机;一个状态机是由它的轻客户端验证功能和不良行为检测逻辑定义的。 - -该标准还规定了如何注册轻客户端功能,如何存储和更新轻客户端的数据。 所存储的客户端实例允许第三方参与者进行检视,例如,用户检查状态机的状态并确定是否发送 IBC 数据包。 - -### 动机 - -在 IBC 协议中,参与者(可能是终端用户、链下进程或状态机的一个模块)需要能够对另一台状态机(例如:远程状态机)的状态更新进行验证。这要求参与者只能接收与远程状态机共识算法一致的状态更新。远程状态机的轻客户端是能够让参与者验证该状态机状态更新的算法。需要注意的是,轻客户端的验证通常不会覆盖完整的状态转换逻辑(因为这就等同于重新完整实现了一台状态机),但在特定情况下,客户端可以选择验证部分状态转换。该标准正式规定了轻客户端模型和需求。因此,只要提供能满足上述需求的轻客户端算法,IBC 协议就易于被集成到运行不同共识算法的新状态机中。 - -IBC 协议可用于与概率最终性共识算法进行交互。在这种情况下,不同的应用程序可能需要不同的合法性判定。对于概率最终性共识,合法性判定由最终性阈值定义(例如,阈值定义了在某个区块之后需要产生多少区块才具有最终性)。因此,客户端可以充当其他客户端的*阈值视图*:一个*只写*客户端可用于存储状态更新(但无法验证它们),而许多具有不同最终性阈值(状态更新能被认定为达到最终性的确认深度值)的*只读*客户端则用于验证状态更新。 - -客户端协议还应该支持第三方引荐。例如:A,B和C是三台不同的状态机;Alice是A的一个模块,Bob是B的一个模块,Carol是C的一个模块。如果Alice认识Bob和Carol,但Bob只认识Alice而不认识Carol。这样,Alice 可以利用现有的通道传送给 Bob 用于和 Carol 通信的标准序列化的合法性判定,然后 Bob 可以通过合法性判定与 Carol 建立连接和通道并直接通信。 如有必要,在 Bob 进行连接尝试之前,Alice 还可以向 Carol 传送 Bob 的合法性判定,使得 Carol 获悉并接受传入的请求。 - -也应当提供构建客户端的接口,这样就可以安全的提供自定义验证逻辑并在运行时定义定制的客户端,只要基础状态机可以提供适当的 gas 计量机制来为计算和存储收费。例如,在支持 WASM 执行的主机状态机上,可以在创建客户端实例时将合法性判定和不良行为判定作为可执行的 WASM 函数提供。 - -### 定义 - -- `get`, `set`, `Path`, 和 `Identifier` 在 [ICS 24](../ics-024-host-requirements) 中定义。 - -- `CommitmentRoot` 与 [ICS 23](../ics-023-vector-commitments) 中的定义相同,它必须为下游逻辑提供一种低成本的方式,以验证键值对是否包含在特定高度的状态中。 - -- `ConsensusState` 是表示合法性判定状态的不透明类型。`ConsensusState` 必须能够验证相关共识算法所达成一致的状态更新,同时必须以标准方式实现可序列化,以便第三方(例如远程状态机)检查状态机是否存储了特定的共识状态。它最终必须能被使用它的状态机检视,比如状态机可以查看某个过去高度的共识状态。 - -- `ClientState` 是表示一个客户端状态的不透明类型。 `ClientState` 必须公开查询函数,以验证处于特定高度的状态下是否包含键值对,并且能够获取当前的共识状态。 - -### 所需属性 - -轻客户端必须提供安全的算法,使用现有的`ConsensusState`来验证其他链的标准区块头 。然后,更高级别的抽象将能够验证存储在`ConsensusState`中的`CommitmentRoot`的状态的子组件,并确定是由其他链的共识算法提交的。 - -合法性判定应反映正在运行相应的共识算法的全节点的行为。给定`ConsensusState`和消息列表,如果一个全节点接受由`Commit`生成的新`Header` ,那么轻客户端也必须接受它,如果一个全节点拒绝它,那么轻客户端也必须拒绝它。 - -由于轻客户端不是重新执行整个消息记录,因此在出现共识不良行为的情况下有可能轻客户端的行为和全节点不同。在这种情况下,可生成一个用来证明合法性判定和全节点之间的差异的不良行为证明,并提交给链,以便链可以安全的停用轻客户端,使过去的状态根无效,并等待更高级别的干预。 - -## 技术规范 - -本规范概述了每种*客户端类型*必须定义的内容。客户端类型是操作轻客户端所需的数据结构、初始化逻辑、合法性判定和不良行为判定的一组定义。实现 IBC 协议的状态机可以支持任意数量的客户端类型,并且每种客户端类型都可以用不同的初始共识状态进行实例化,以跟踪不同的共识实例。为了在两个状态机之间建立连接(参见[ICS 3](../ics-003-connection-semantics) ),每个状态机都必须支持对应于另一个状态机的共识算法的客户端类型。 - -特定的客户端类型应在本规范之后的版本中定义,且该仓库中应存在一个标准客户端类型列表。实现了 IBC 协议的状态机应遵守这些客户端类型,但他们也可以选择仅支持一个子集。 - -### 数据结构 - -#### 共识状态 - -`ConsensusState` 是一个客户端类型定义的不透明数据结构,合法性判定用其验证新的提交和状态根。该结构可能包含共识过程产生的最后一次提交,包括签名和验证人集合元数据。 - -`ConsensusState` 必须由一个 `Consensus`实例生成,该实例为每个 `ConsensusState`分配唯一的高度(这样,每个高度恰好具有一个关联的共识状态)。如果没有一样的加密承诺根,则同一链上的两个`ConsensusState`不应具有相同的高度。此类事件称为“矛盾行为”,必须归类为不良行为。 如果发生这种情况,则应生成并提交证明,以便可以冻结客户端,并根据需要使先前的状态根无效。 - -区块链的 `ConsensusState` 必须具有标准序列化,以便其他链可以检查存储的共识状态是否与另一个共识状态相等(请参见 [ICS 24](../ics-024-host-requirements) 键表)。 - -```typescript -type ConsensusState = bytes -``` - -`ConsensusState` 必须存储在下面定义的指定的键下,这样其他链可以验证一个特定的共识状态是否已存储。 - -`ConsensusState` 必须定义一个 `getTimestamp()` 方法,该方法返回与该共识状态关联的时间戳: - -```typescript -type getTimestamp = ConsensusState => uint64 -``` - -#### 区块头 - -`Header` 是由客户端类型定义的不透明数据结构,它提供用来更新`ConsensusState`的信息。可以将区块头提交给关联的客户端以更新存储的`ConsensusState` 。区块头可能包含一个高度、一个证明、一个加密承诺根,还有可能的合法性判定更新。 - -```typescript -type Header = bytes -``` - -#### 共识 - -`Consensus` 是一个生成 `Header` 的函数,负责接受之前的 `ConsensusState` 和消息并返回结果。 - -```typescript -type Consensus = (ConsensusState, [Message]) => Header -``` - -### 区块链 - -区块链是一个生成有效`Header`的共识算法。它从创世`ConsensusState`开始通过各种消息生成一个唯一的区块头列表。 - -`Blockchain` 被定义为 - -```typescript -interface Blockchain { - genesis: ConsensusState - consensus: Consensus -} -``` - -其中 - -- `Genesis`是一个创世`ConsensusState` -- `Consensus`是一个生成区块头的函数 - -从`Blockchain`生成的区块头应满足以下条件: - -1. 每个`Header`对应的直接子区块不能超过一个 - -- 满足条件:最终性和安全性 -- 可能违反的场景:验证人双重签名,链重组(中本聪共识) - -1. 每个`Header`最终必须至少有一个直接子区块 - -- 满足条件:活跃性,轻客户端验证程序连续性 -- 可能的违规场景:同步暂停,不兼容的硬分叉 - -1. 每个`Header`必须由`Consensus`生成,以确保有效的状态转换 - -- 满足条件:正确生成区块;状态机状态正确 -- 可能的违规场景:不变性被破坏,超过多数验证人共谋 - -除非区块链满足以上所有条件,否则 IBC 协议可能无法按预期工作:链可能会收到多个冲突数据包、可能无法从超时事件中恢复,或可能会窃取用户的资产等。 - -合法性判定的合法性取决于`Consensus` 的安全模型。例如, `Consensus`也可以是受一个被信任的运营者管理的 PoA(proof of authority)共识,或质押价值不足的 PoS(proof of stake)共识。在这种情况下,安全假设可能被破坏, `Consensus`与合法性判定的关联就不复存在,并且合法性判定的行为变得不可定义。此外, `Blockchain`可能不再满足上述要求,这将导致区块链与 IBC 协议不再兼容。在这些导致故障的情况下,可生成一个不良行为证明并提交给包含客户端的区块链,以安全的冻结轻客户端,并防止之后的 IBC 数据包被中继。 - -#### 合法性判定 - -合法性判定是由一种客户端类型定义的一个不透明函数,用来根据当前`ConsensusState`验证 `Header` 。使用合法性判定应该比通过`Header` 和一系列网络消息进行完全共识算法重放的计算效率高很多。 - -合法性判定和客户端状态更新逻辑是合并在一个单独的 `checkValidityAndUpdateState`类型中的,它的定义如下: - -```typescript -type checkValidityAndUpdateState = (Header) => Void -``` - -`checkValidityAndUpdateState` 在输入区块头无效的情况下必须抛出异常。 - -如果给定的区块头有效,客户端必须改变内部状态来存储当前确认的共识根,以及更新必要的签名权威跟踪(例如对验证人集合的更新)以供后续的合法性判定调用。 - -客户端的合法性判定可能具有时效敏感性,因此,如果一段时间内(例如三周的解除绑定时间)都未提供区块头,将无法再更新客户端。在这种情况下,可以允许一个被许可的实体,例如链治理系统或可信多重签名介入,以解冻冻结的客户端并提供新的正确区块头。 - -#### 不良行为判定 - -一个不良行为判定是由一种客户端类型定义的不透明函数,用于检查数据是否对共识协议构成违规。可能是出现两个拥有不同状态根但在同一个区块高度的签名的区块头、一个包含无效状态转换的签名的区块头或其他由共识算法定义的不良行为的证据。 - -不良行为判定和客户端状态更新逻辑是合并在一个单独的`checkMisbehaviourAndUpdateState`类型中的,它的定义如下: - -```typescript -type checkMisbehaviourAndUpdateState = (bytes) => Void -``` - -`checkMisbehaviourAndUpdateState` 在给定不良行为证据无效的情况下必须抛出异常。 - -如果一个不良行为是有效的,客户端还必须根据不良行为的性质去更改内部状态,来标记先前认为有效的区块高度为无效。 - -一旦检测到不良行为,客户端应该被冻结,之后未来的任何更新都不能被提交。诸如链治理系统或可信多重签名之类的被许可的实体可能被允许干预,以解冻冻结的客户端并提供新的正确的区块头。 - -#### 高度 - -`Height`是一种由客户端定义的不透明的数据结构。 它必须是一种部分排序的集合,并提供对比的功能接口。 - -```typescript -type Height -``` - -```typescript -enum Ord { - LT - EQ - GT -} - -type compare = (h1: Height, h2: Height) => Ord -``` - -一个高度小于(`LT`),等于(`EQ`),或者大于(`GT`)另一个高度。 - -在本规范中,`>=`, `>`, `===`, `<`, `<=` 被定义为`compare`的别名。 - -高度类型必须有一个零高度,被称为`0`, 此高度小与所有其它的高度。 - -#### 客户端状态 - -`ClientState`是由一种客户端类型定义的不透明数据结构。它可以保留任意的内部状态去追踪已经被验证过的状态根和发生过的不良行为。 - -轻客户端是表现为不透明的——不同的共识算法可以定义不同的轻客户端更新算法,但是轻客户端必须对 IBC 处理程序公开一组通用的查询函数。 - -```typescript -type ClientState = bytes -``` - -客户类型必须定义一个方法用提供的共识状态初始化一个客户端状态,并根据情况写入到内部状态中。 - -```typescript -type initialise = (consensusState: ConsensusState) => ClientState -``` - -客户端类型必须定义一种方法来获取当前高度(最近验证的区块头的高度)。 - -```typescript -type latestClientHeight = ( - clientState: ClientState) - => Height -``` - -#### 承诺证明 - -`CommitmentProof` 是根据 [ICS 23](../ics-023-vector-commitments) 由一种客户端类型定义的不透明数据结构。它用于验证处于特定最终高度(必须与特定承诺根相关联)的状态中是否包含特定键值对。 - -#### 状态验证 - -客户端类型必须定义一系列函数去对客户端追踪的状态机的内部状态进行验证。内部实现细节可能存在差异(例如,一个回环客户端可以直接读取状态信息且不需要提供证明)。 - -- `delayPeriodTime`:该变量表示一个区块头被验证之后与数据包被处理前必须间隔的最短时间;该变量随着数据包被传给数据包相关的验证方法。 -- `delayPeriodBlocks`: 该变量是一个区块头被验证之后与数据包被处理前必须间隔的以区块为单位的时间段的数值;该变量随着数据包被传给数据包相关的验证方法。 - -##### 所需函数: - -`verifyClientConsensusState` 验证存储在目标状态机上的特定客户端的共识状态的证明。 - -```typescript -type verifyClientConsensusState = ( - clientState: ClientState, - height: Height, - proof: CommitmentProof, - clientIdentifier: Identifier, - consensusStateHeight: Height, - consensusState: ConsensusState) - => boolean -``` - -`verifyConnectionState` 验证存储在目标状态机上的特定连接端的连接状态的证明。 - -```typescript -type verifyConnectionState = ( - clientState: ClientState, - height: Height, - prefix: CommitmentPrefix, - proof: CommitmentProof, - connectionIdentifier: Identifier, - connectionEnd: ConnectionEnd) - => boolean -``` - -`verifyChannelState` 验证在存储在目标状态机上的指定通道端,特定端口下的的通道状态的证明。 - -```typescript -type verifyChannelState = ( - clientState: ClientState, - height: Height, - delayPeriodTime: uint64, - delayPeriodBlocks: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - channelEnd: ChannelEnd) - => boolean -``` - -`verifyPacketData`验证在指定的端口,指定的通道和指定的序号的传出数据包承诺的证明。 - -```typescript -type verifyPacketData = ( - clientState: ClientState, - height: Height, - delayPeriodTime: uint64, - delayPeriodBlocks: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64, - data: bytes) - => boolean -``` - -`verifyPacketAcknowledgement` 在指定的端口,指定的通道和指定的序号的传入数据包的回执的证明。 - -```typescript -type verifyPacketAcknowledgement = ( - clientState: ClientState, - height: Height, - delayPeriodTime: uint64, - delayPeriodBlocks: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64, - acknowledgement: bytes) - => boolean -``` - -`verifyPacketAcknowledgementAbsence` 验证在指定的端口、指定的通道和指定的序号的未收到传入数据包回执的证明。 - -```typescript -type verifyPacketAcknowledgementAbsence = ( - clientState: ClientState, - height: Height, - delayPeriodTime: uint64, - delayPeriodBlocks: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64) - => boolean -``` - -`verifyNextSequenceRecv` 验证在指定端口上和指定通道接收的下一个序号的证明。 - -```typescript -type verifyNextSequenceRecv = ( - clientState: ClientState, - height: Height, - delayPeriodTime: uint64, - delayPeriodBlocks: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - nextSequenceRecv: uint64) - => boolean -``` - -`verifyMembership`是一个通用的验证方法,可以验证一个给定 `CommitmentPath`在某个高度的存在性。 该方法的调用者必须通过一个`CommitmentPrefix`和一个标准化的路径 ([ICS 24](../ics-024-host-requirements/README.md#path-space))构建一个完整的`CommitmentPath`。如果调用者要求延时处理,他可以传入一个非零的`delayPeriodTime` 或者 `delayPeriodBlocks`。如果延时不是必须的,调用者传入的`delayPeriodTime` 和 `delayPeriodBlocks`的值都必须是0,这使得客户端可以不强制延时验证。 - -```typescript -type verifyMembership = ( - clientState: ClientState, - height: Height, - delayPeriodTime: uint64, - delayPeriodBlocks: uint64, - proof: CommitmentProof, - path: CommitmentPath, - value: bytes) - => boolean -``` - -`verifyNonMembership`是一个通用的验证方法,可以验证一个给定 `CommitmentPath`在某个高度的缺失。 该方法的调用者必须通过一个`CommitmentPrefix`和一个标准化的路径 ([ICS 24](../ics-024-host-requirements/README.md#path-space))构建一个完整的`CommitmentPath`。如果调用者要求延时处理,他可以传入一个非零的`delayPeriodTime` 或者 `delayPeriodBlocks`。如果延时不是必须的,调用者传入的`delayPeriodTime` 和 `delayPeriodBlocks`的值都必须是0,这使得客户端可以不强制延时验证。 - -```typescript -type verifyNonMembership = ( - clientState: ClientState, - height: Height, - delayPeriodTime: uint64, - delayPeriodBlocks: uint64, - proof: CommitmentProof, - path: CommitmentPath) - => boolean -``` - -#### 查询接口 - -##### 链信息查询 - -假定这些查询端点是由与特定客户端关联的链的节点通过 HTTP 或等效的 RPC API 公开的。 - -链必须定义 `queryHeader`,并由特定客户端验证,而且应允许按高度检索区块头。假定此端点是不受信任的。 - -```typescript -type queryHeader = (height: Height) => Header -``` - -链必须定义 `queryChainConsensusState`,并由特定客户端验证,以允许检索当前共识状态,该状态可用于构建新客户端。以这种方式使用时,返回的 `ConsensusState` 必须由查询实体手动确认,因为它是主观的。假定此端点是不受信任的。 `ConsensusState` 的确切性质可能因客户端类型而异。 - -```typescript -type queryChainConsensusState = (height: Height) => ConsensusState -``` - -请注意,按高度检索过去的共识状态(而不仅仅是当前的共识状态)会很方便,但不是必需的。 - -`queryChainConsensusState` 还可以返回创建客户端所需的其他数据,例如某些权益证明安全模型的“解除绑定期”。该数据也必须由查询实体进行验证。 - -##### 链上状态查询 - -该规范定义了一个通过标识符查询客户端状态的函数。 - -```typescript -function queryClientState(identifier: Identifier): ClientState { - return privateStore.get(clientStatePath(identifier)) -} -``` - -`ClientState` 类型应该公开其最新的已验证高度(如果需要,可以再使用 `queryConsensusState` 获取其共识状态)。 - -```typescript -type latestHeight = (state: ClientState) => Height -``` - -客户端类型应该定义以下标准化查询函数,以允许中继器和其他链下实体以标准API和链上状态进行对接。 - -`queryConsensusState` 允许按高度检索存储的共识状态。 - -```typescript -type queryConsensusState = ( - identifier: Identifier, - height: Height -) => ConsensusState -``` - -##### 证明的构造 - -每个客户端类型都应该定义一些函数,以允许中继器构造客户端状态验证算法所需的证明。构造方法可能采用不同的形式,具体取决于客户端类型。例如,Tendermint 客户端的证明可能与存储查询的键值数据一起返回,而单机客户端证明可能需要在单机上以交互式询问的方式构造(因为需要用户签名消息)。这些函数可以由通过 RPC 到全节点的外部查询以及本地计算或验证构成。 - -```typescript -type queryAndProveClientConsensusState = ( - clientIdentifier: Identifier, - height: Height, - prefix: CommitmentPrefix, - consensusStateHeight: Height) => ConsensusState, Proof - -type queryAndProveConnectionState = ( - connectionIdentifier: Identifier, - height: Height, - prefix: CommitmentPrefix) => ConnectionEnd, Proof - -type queryAndProveChannelState = ( - portIdentifier: Identifier, - channelIdentifier: Identifier, - height: Height, - prefix: CommitmentPrefix) => ChannelEnd, Proof - -type queryAndProvePacketData = ( - portIdentifier: Identifier, - channelIdentifier: Identifier, - height: Height, - prefix: CommitmentPrefix, - sequence: uint64) => []byte, Proof - -type queryAndProvePacketAcknowledgement = ( - portIdentifier: Identifier, - channelIdentifier: Identifier, - height: Height, - prefix: CommitmentPrefix, - sequence: uint64) => []byte, Proof - -type queryAndProvePacketAcknowledgementAbsence = ( - portIdentifier: Identifier, - channelIdentifier: Identifier, - height: Height, - prefix: CommitmentPrefix, - sequence: uint64) => Proof - -type queryAndProveNextSequenceRecv = ( - portIdentifier: Identifier, - channelIdentifier: Identifier, - height: Height, - prefix: CommitmentPrefix) => uint64, Proof -``` - -##### 实现策略 - -###### 回环 - -一个本地状态机的回环客户端仅需要读取本地状态,其必须具有访问权限。 - -###### 简单签名 - -具有已知公钥的单机状态机的客户端检查由该本地状态机发送的消息的签名,这些消息作为`Proof`参数提供。 `height`参数可以用作重放保护随机数。 - -这种方式里也可以使用多重签名或门限签名方案。 - -###### 代理客户端 - -代理客户端验证的是目标状态机的代理状态机的证明。通过包含首先是一个代理状态机上客户端状态的证明,然后是目标状态机的子状态相对于代理计算机上的客户端状态的证明。这使代理客户端可以避免存储和跟踪目标状态机本身的共识状态,但是要以代理状态机正确性的安全假设为代价。 - -###### 默克尔状态树 - -对于具有默克尔状态树的状态机的客户端,可以通过调用the [ICS-23](../ics-023-vector-commitments/README.md)`verifyMembership`或`verifyNonMembership`来实现这些功能。使用经过验证的存储在`ClientState`中的默克尔根,按照 [ICS 23](../ics-023-vector-commitments) 验证处于特定高度的状态中特定键/值对是否存在。 - -```typescript -type verifyMembership = (ClientState, Height, CommitmentProof, Path, Value) => boolean -``` - -```typescript -type verifyNonMembership = (ClientState, Height, CommitmentProof, Path) => boolean -``` - -### 子协议 - -IBC 处理程序必须实现以下定义的函数。 - -#### 标识符验证 - -客户端存储在唯一的`Identifier`前缀下。 本ICS不要求以特定方式生成客户端标识符,仅要求它们是唯一的即可。但是,如果需要,可以限制`Identifier`的空间。可能需要提供下面的验证函数`validateClientIdentifier` 。 - -```typescript -type validateClientIdentifier = (id: Identifier) => boolean -``` - -如果没有提供以上函数,默认的`validateClientIdentifier`会永远返回`true` 。 - -##### 利用过去的状态根 - -为了避免客户端更新(更改状态根)与握手中携带证明的交易或数据包收据之间的竞态条件,许多 IBC 处理程序允许调用方指定一个之前的状态根作为参考,这类 IBC 处理程序必须确保它们对调用者传入的区块高度执行任何必要的检查,以确保逻辑上的正确性。 - -#### 创建 - -通过调用`createClient`附带特定的标识符和初始化共识状态来创建一个客户端。 - -```typescript -function createClient( - id: Identifier, - clientType: ClientType, - consensusState: ConsensusState) { - abortTransactionUnless(validateClientIdentifier(id)) - abortTransactionUnless(privateStore.get(clientStatePath(id)) === null) - abortSystemUnless(provableStore.get(clientTypePath(id)) === null) - clientType.initialise(consensusState) - provableStore.set(clientTypePath(id), clientType) -} -``` - -#### 查询 - -可以通过标识符查询客户端共识状态和客户端内部状态,但是必须被查询的特定路径应由每种客户端类型定义。 - -#### 更新 - -客户端的更新是通过提交新的`Header`来完成的。`Identifier`用于指向逻辑将被更新的客户端状态。 当使用`ClientState`的合法性判定和`ConsensusState`验证新的`Header`时,客户端必须相应的更新其内部状态,还可能更新最终性承诺根和`ConsensusState`中的签名权威逻辑。 - -如果一个客户端无法继续更新(例如,如果超过了信任期),则将不能通过与该客户端关联的连接和通道再发送任何数据包,或者使在传输过程中的任何数据包超时(因为无法再验证目标链上的高度和时间戳)。必须进行手动干预才能重置客户端状态或将连接和通道迁移到另一个客户端。无法安全的完全自动完成此操作,但是实施 IBC 的链可以选择允许治理机制执行这些操作(甚至可能操作多签或合约中的单个客户端/连接/通道)。 - -```typescript -function updateClient( - id: Identifier, - header: Header) { - clientType = provableStore.get(clientTypePath(id)) - abortTransactionUnless(clientType !== null) - clientState = privateStore.get(clientStatePath(id)) - abortTransactionUnless(clientState !== null) - clientType.checkValidityAndUpdateState(clientState, header) -} -``` - -#### 不良行为 - -如果客户端检测到不良行为的证据,则会发出警报,比如说可以使先前有效的状态根变为无效并阻止其未来的更新。 - -```typescript -function submitMisbehaviourToClient( - id: Identifier, - misbehaviour: bytes) { - clientType = provableStore.get(clientTypePath(id)) - abortTransactionUnless(clientType !== null) - clientState = privateStore.get(clientStatePath(id)) - abortTransactionUnless(clientState !== null) - clientType.checkMisbehaviourAndUpdateState(clientState, misbehaviour) -} -``` - -### 示例实现 - -一个合法性判定示例是构建在运行单一运营者的共识算法的区块链上的,其中有效区块由这个运营者进行签名。区块链运行过程中运营者的签名密钥可以被改变。 - -客户端特定的类型定义如下: - -- `ConsensusState` 存储最新的区块高度和最新的公钥 -- `Header`包含一个区块高度、一个新的承诺根、一个运营者的签名以及可能还包括一个新的公钥 -- `checkValidityAndUpdateState` 检查已经提交的区块高度是否是单调递增的以及签名是否正确,并更改内部状态 -- `checkMisbehaviourAndUpdateState` 被用于检查两个相同区块高度但承诺根不同的区块头,并更改内部状态 - -```typescript -type Height = uint64 - -function compare(h1: Height, h2: Height): Ord { - if h1 < h2 - return LT - else if h1 === h2 - return EQ - else - return GT -} - -interface ClientState { - frozen: boolean - pastPublicKeys: Set - verifiedRoots: Map -} - -interface ConsensusState { - sequence: uint64 - publicKey: PublicKey -} - -interface Header { - sequence: uint64 - commitmentRoot: CommitmentRoot - signature: Signature - newPublicKey: Maybe -} - -interface Misbehaviour { - h1: Header - h2: Header -} - -// 操作员运行的用来提交一个新块的算法 -function commit( - commitmentRoot: CommitmentRoot, - sequence: uint64, - newPublicKey: Maybe): Header { - signature = privateKey.sign(commitmentRoot, sequence, newPublicKey) - header = {sequence, commitmentRoot, signature, newPublicKey} - return header -} - -// 由客户端类型定义的初始化函数 -function initialise(consensusState: ConsensusState): () { - clientState = { - frozen: false, - pastPublicKeys: Set.singleton(consensusState.publicKey), - verifiedRoots: Map.empty() - } - privateStore.set(identifier, clientState) -} - -// 客户端类型定义的有效性判定函数 -function checkValidityAndUpdateState( - clientState: ClientState, - header: Header) { - abortTransactionUnless(consensusState.sequence + 1 === header.sequence) - abortTransactionUnless(consensusState.publicKey.verify(header.signature)) - if (header.newPublicKey !== null) { - consensusState.publicKey = header.newPublicKey - clientState.pastPublicKeys.add(header.newPublicKey) - } - consensusState.sequence = header.sequence - clientState.verifiedRoots[sequence] = header.commitmentRoot -} - -function verifyClientConsensusState( - clientState: ClientState, - height: Height, - prefix: CommitmentPrefix, - proof: CommitmentProof, - clientIdentifier: Identifier, - consensusState: ConsensusState) { - path = applyPrefix(prefix, "clients/{clientIdentifier}/consensusStates/{height}") - abortTransactionUnless(!clientState.frozen) - return clientState.verifiedRoots[sequence].verifyMembership(path, consensusState, proof) -} - -function verifyConnectionState( - clientState: ClientState, - height: Height, - prefix: CommitmentPrefix, - proof: CommitmentProof, - connectionIdentifier: Identifier, - connectionEnd: ConnectionEnd) { - path = applyPrefix(prefix, "connections/{connectionIdentifier}") - abortTransactionUnless(!clientState.frozen) - return clientState.verifiedRoots[sequence].verifyMembership(path, connectionEnd, proof) -} - -function verifyChannelState( - clientState: ClientState, - height: Height, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - channelEnd: ChannelEnd) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}") - abortTransactionUnless(!clientState.frozen) - return clientState.verifiedRoots[sequence].verifyMembership(path, channelEnd, proof) -} - -function verifyPacketData( - clientState: ClientState, - height: Height, - delayPeriodTime: uint64, - delayPeriodBlocks: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64, - data: bytes) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/packets/{sequence}") - abortTransactionUnless(!clientState.frozen) - return clientState.verifiedRoots[sequence].verifyMembership(path, hash(data), proof) -} - -function verifyPacketAcknowledgement( - clientState: ClientState, - height: Height, - delayPeriodTime: uint64, - delayPeriodBlocks: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64, - acknowledgement: bytes) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/acknowledgements/{sequence}") - abortTransactionUnless(!clientState.frozen) - return clientState.verifiedRoots[sequence].verifyMembership(path, hash(acknowledgement), proof) -} - -function verifyPacketReceiptAbsence( - clientState: ClientState, - height: Height, - prefix: CommitmentPrefix, - delayPeriodTime: uint64, - delayPeriodBlocks: uint64, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/receipts/{sequence}") - abortTransactionUnless(!clientState.frozen) - return clientState.verifiedRoots[sequence].verifyNonMembership(path, proof) -} - -function verifyNextSequenceRecv( - clientState: ClientState, - height: Height, - delayPeriodTime: uint64, - delayPeriodBlocks: uint64, - prefix: CommitmentPrefix, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - nextSequenceRecv: uint64) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/nextSequenceRecv") - abortTransactionUnless(!clientState.frozen) - return clientState.verifiedRoots[sequence].verifyMembership(path, nextSequenceRecv, proof) -} - -// 客户端类型定义的不良行为验证函数 -// 过去或当前密钥的任何重复签名都会冻结客户端 -function checkMisbehaviourAndUpdateState( - clientState: ClientState, - misbehaviour: Misbehaviour) { - h1 = misbehaviour.h1 - h2 = misbehaviour.h2 - abortTransactionUnless(clientState.pastPublicKeys.contains(h1.publicKey)) - abortTransactionUnless(h1.sequence === h2.sequence) - abortTransactionUnless(h1.commitmentRoot !== h2.commitmentRoot || h1.publicKey !== h2.publicKey) - abortTransactionUnless(h1.publicKey.verify(h1.signature)) - abortTransactionUnless(h2.publicKey.verify(h2.signature)) - clientState.frozen = true -} -``` - -### 属性与不变性 - -- 客户标识符是不可变的,遵循「先到先得」原则。客户端无法删除(允许删除意味着如果使用之前用过的标识符,则可以之后重放过去的数据包)。 - -## 向后兼容性 - -不适用。 - -## 向前兼容性 - -只要新客户端类型符合该接口,就可以任意添加到 IBC 实现中。 - -## 示例实现 - -即将到来。 - -## 其他实现 - -即将到来。 - -## 历史 - -2019年3月5日-初稿已完成并提交 PR - -2019年5月29日-进行了多处修订,主要是多个承诺根 - -2019年8月15日-为使客户端接口内容更加清晰而进行了大量润色 - -2020年1月13日-客户端类型分离和路径更改的修订 - -2020年1月26日-添加查询接口 - -## 版权 - -本规范所有内容均采用 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 许可授权。 diff --git a/other_docs/ibc-protocol/spec/core/ics-003-connection-semantics/README.md b/other_docs/ibc-protocol/spec/core/ics-003-connection-semantics/README.md deleted file mode 100644 index 2d823d611..000000000 --- a/other_docs/ibc-protocol/spec/core/ics-003-connection-semantics/README.md +++ /dev/null @@ -1,475 +0,0 @@ ---- -ics: '3' -title: 连接语义 -stage: 草案 -category: IBC/TAO -kind: 实例化 -requires: 2, 24 -required-by: 4, 25 -author: Christopher Goes , Juwoon Yun -created: '2019-03-07' -modified: '2019-08-25' ---- - -## 概要 - -本标准文档对 IBC *连接*的抽象进行描述:即两条独立链上的两个有状态的对象(*连接端* ),彼此与另一条链上的轻客户端关联,并共同利于跨链子状态的验证和数据包的关联(通过通道)。本规范描述了用于在两条链上安全建立连接的协议。 - -### 动机 - -核心 IBC 协议对数据包提供了*身份认证*和*排序*语义:确保对各自来说,数据包在发送链上被提交(根据状态转换的执行,例如通证托管),并且数据包被有且仅有一次的按特定的顺序提交和有且仅有一次的被传递到接收链。本标准中的*连接*抽象与 [ICS 2](../ics-002-client-semantics) 中定义的*客户端*抽象一同定义了 IBC 的*身份认证*语义。排序语义在 [ICS 4](../ics-004-channel-and-packet-semantics) 中进行了描述。 - -### 定义 - -客户端相关的类型和函数的定义见 [ICS 2](../ics-002-client-semantics) 。 - -加密承诺证明相关的类型和函数的定义见[ICS 23](../ics-023-vector-commitments) 。 - -`Identifier`和其他主机状态机的要求如 [ICS 24](../ics-024-host-requirements) 所示。标识符不一定需要是人类可读的名称(基本上也不应该是,来防止对标识符的抢注或争夺)。 - -开放式握手协议允许每条链验证用于引用另一条链上连接的标识符,从而使每条链上的模块能够得知另一条链上的引用。 - -本规范中提到的*参与者*是能够执行数据报的实体,并为计算/存储付费(通过 gas 或类似的机制),但是是不被信任的。 可能的参与者包括: - -- 使用帐户密钥签名的最终用户 -- 自主或响应另一笔交易的链上智能合约 -- 响应其他交易或按计划方式运行的链上模块 - -### 所需属性 - -- 区块链实现应该安全的允许不受信的参与者建立或更新连接。 - -#### 连接建立前阶段 - -在建立连接之前: - -- 连接阶段之后的 IBC 子协议不应该能被操作,因为跨链子状态还没被验证。 -- 发起方(创建连接方)必须能够为被连接的链和连接的链指定初始共识状态(隐式的,例如通过发送交易)。 - -#### 握手期间 - -一旦握手协商开始: - -- 只有相关的握手数据报才可以按顺序被执行。 -- 没有第三条链可以伪装成正在发生握手的两条链中的一条 - -#### 完成握手后阶段 - -一旦握手协商完成: - -- 在两个链上创建的连接对象均包含发起方指定的共识状态。 -- 其他连接对象不能通过重放数据报的方式在其他链上恶意的被创建。 - -## 技术规范 - -### 数据结构 - -此 ICS 定义了`ConnectionState`和`ConnectionEnd`类型: - -```typescript -enum ConnectionState { - INIT, - TRYOPEN, - OPEN, -} -``` - -```typescript -interface ConnectionEnd { - state: ConnectionState - counterpartyConnectionIdentifier: Identifier - counterpartyPrefix: CommitmentPrefix - clientIdentifier: Identifier - counterpartyClientIdentifier: Identifier - version: string | []string - delayPeriodTime: uint64 - delayPeriodBlocks: uint64 -} -``` - -- `state`字段描述连接端的当前状态。 -- `counterpartyConnectionIdentifier`字段标识与此连接关联的对方链上的连接端。 -- `counterpartyPrefix`字段包含用于与此连接关联的对方链上的状态验证的前缀。链应该公开一个端点,以允许中继器查询连接前缀。如果没有指定,默认`counterpartyPrefix`的`"ibc"`应该被使用。 -- `clientIdentifier`字段标识与此连接关联的客户端。 -- `counterpartyClientIdentifier`字段标识与此连接关联的对方链上的客户端。 -- `version`字段是不透明的字符串,可用于确定使用此连接的通道或数据包的编码或协议。如果未指定,则应使用 `""`的默认`version`。 -- `delayPeriodTime`指示在验证区块头之后必须等待的时间,然后才能处理数据包、回执、接收证明或超时。 -- `delayPeriodBlocks`指示在验证区块头之后必须等待的以块为单位的时间段的数值,然后才能处理数据包、回执、接收证明或超时。 - -### 储存路径 - -连接路径存储在唯一标识符下。 - -```typescript -function connectionPath(id: Identifier): Path { - return "connections/{id}" -} -``` - -从客户端到一组连接(用于使用客户端查找所有连接)的反向映射存储在每个客户端的唯一前缀下: - -```typescript -function clientConnectionsPath(clientIdentifier: Identifier): Path { - return "clients/{clientIdentifier}/connections" -} -``` - -### 辅助函数 - -`addConnectionToClient`用于将连接标识符添加到与客户端关联的连接集合。 - -```typescript -function addConnectionToClient( - clientIdentifier: Identifier, - connectionIdentifier: Identifier) { - conns = privateStore.get(clientConnectionsPath(clientIdentifier)) - conns.add(connectionIdentifier) - privateStore.set(clientConnectionsPath(clientIdentifier), conns) -} -``` - -帮助函数由连接定义,将与连接关联的`CommitmentPrefix`传递给客户端提供的验证函数。在规范的其他部分,这些函数必须用于检查其他链的状态,而不是直接调用客户端上的验证函数。 - -```typescript -function verifyClientConsensusState( - connection: ConnectionEnd, - height: Height, - proof: CommitmentProof, - clientIdentifier: Identifier, - consensusStateHeight: Height, - consensusState: ConsensusState) { - client = queryClient(connection.clientIdentifier) - return client.verifyClientConsensusState(connection, height, connection.counterpartyPrefix, proof, clientIdentifier, consensusStateHeight, consensusState) -} - -function verifyConnectionState( - connection: ConnectionEnd, - height: Height, - proof: CommitmentProof, - connectionIdentifier: Identifier, - connectionEnd: ConnectionEnd) { - client = queryClient(connection.clientIdentifier) - return client.verifyConnectionState(connection, height, connection.counterpartyPrefix, proof, connectionIdentifier, connectionEnd) -} - -function verifyChannelState( - connection: ConnectionEnd, - height: Height, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - channelEnd: ChannelEnd) { - client = queryClient(connection.clientIdentifier) - return client.verifyChannelState(connection, height, connection.counterpartyPrefix, proof, portIdentifier, channelIdentifier, channelEnd) -} - -function verifyPacketData( - connection: ConnectionEnd, - height: Height, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64, - data: bytes) { - client = queryClient(connection.clientIdentifier) - return client.verifyPacketData(connection, height, connection.delayPeriodTime, connection.delayPeriodBlocks, connection.counterpartyPrefix, proof, portIdentifier, channelIdentifier, sequence, data) -} - -function verifyPacketAcknowledgement( - connection: ConnectionEnd, - height: Height, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64, - acknowledgement: bytes) { - client = queryClient(connection.clientIdentifier) - return client.verifyPacketAcknowledgement(connection, height, connection.delayPeriodTime, connection.delayPeriodBlocks, connection.counterpartyPrefix, proof, portIdentifier, channelIdentifier, sequence, acknowledgement) -} - -function verifyPacketReceiptAbsence( - connection: ConnectionEnd, - height: Height, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64) { - client = queryClient(connection.clientIdentifier) - return client.verifyPacketReceiptAbsence(connection, height, connection.delayPeriodTime, connection.delayPeriodBlocks, connection.counterpartyPrefix, proof, portIdentifier, channelIdentifier, sequence) -} - -// 可选:verifyPacketReceipt 仅需要支持 ORDERED 和 UNORDERED 之外的新通道类型。 -function verifyPacketReceipt( - connection: ConnectionEnd, - height: Height, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64, - receipt: bytes) { - client = queryClient(connection.clientIdentifier) - return client.verifyPacketReceipt(connection, height, connection.delayPeriodTime, connection.delayPeriodBlocks, connection.counterpartyPrefix, proof, portIdentifier, channelIdentifier, sequence, receipt) -} - -function verifyNextSequenceRecv( - connection: ConnectionEnd, - height: Height, - proof: CommitmentProof, - portIdentifier: Identifier, - channelIdentifier: Identifier, - sequence: uint64, - nextSequenceRecv: uint64) { - client = queryClient(connection.clientIdentifier) - return client.verifyNextSequenceRecv(connection, height, connection.delayPeriodTime, connection.delayPeriodBlocks, connection.counterpartyPrefix, proof, portIdentifier, channelIdentifier, sequence, nextSequenceRecv) -} - -function getTimestampAtHeight( - connection: ConnectionEnd, - height: Height) { - client = queryClient(connection.clientIdentifier) - return client.queryConsensusState(height).getTimestamp() -} -``` - -### 子协议 - -本 ICS 定义了建立握手子协议。一旦握手建立,连接将不能被关闭,标识符也无法被重新分配(这防止了数据包重放或者身份认证混乱)。 - -区块头追踪和不良行为检测定义见 [ICS 2](../ics-002-client-semantics) 。 - -![State Machine Diagram](https://github.com/octopus-network/ibc/blob/zh-cn-2022/spec/core/ics-003-connection-semantics/state.png?raw=true) - -#### 标识符验证 - -连接存储在唯一的`Identifier`前缀下。 可以提供验证函数`validateConnectionIdentifier`。 - -```typescript -type validateConnectionIdentifier = (id: Identifier) => boolean -``` - -如果未提供,默认的`validateConnectionIdentifier`函数将始终返回`true`。 - -#### 版本控制 - -在握手过程中,连接的两端需要对连接关联的版本字节串达成一致。目前,版本字节串的内容对于 IBC 核心协议是不透明的。将来,它可能被用于指示哪些类型的通道可以使用特定的连接,或者通道相关的数据报将使用哪种编码格式。目前,主机状态机可以利用版本数据来协商与 IBC 之上的自定义逻辑有关的编码、优先级或特定与连接的元数据。 - -主机状态机也可以安全地忽略版本数据或指定一个空字符串。假设运行开放握手的两条链至少有一个共同的兼容版本(即两条链的兼容版本必须有一个非空的交集)。如果两条链没有任何可以被双方都接受的版本,握手将失败。 - -该标准的一个实现必须定义一个函数`getCompatibleVersions` ,该函数返回它支持的版本列表,按优先级降序排列。 - -```typescript -type getCompatibleVersions = () => []string -``` - -实现必须定义一个函数`pickVersion`,该函数能从版本列表中选择一个版本。请注意,如果执行握手的两条链实现了不同的`pickVersion`函数,则(可能有不良行为的)中继器可能能够通过在两条链上执行`INIT`和`OPENTRY`来停止握手,此时它们将选择不同的版本并且无法继续。 - -```typescript -type pickVersion = ([]string) => string -``` - -#### 建立握手 - -建立握手子协议用于在两条链上初始化彼此的共识状态。 - -建立握手定义了四种数据报: *ConnOpenInit* , *ConnOpenTry* , *ConnOpenAck*和*ConnOpenConfirm* 。 - -一个正确的协议执行流程如下:(注意所有的请求都是按照 ICS 25 来制定的) - -发起人 | 数据报 | 作用链 | 之前状态(A,B) | 之后状态(A,B) ---- | --- | --- | --- | --- -Actor | `ConnOpenInit` | A | (none, none) | (INIT,none) -中继器 | `ConnOpenTry` | B | (INIT,none) | (INIT,TRYOPEN) -中继器 | `ConnOpenAck` | A | (INIT,TRYOPEN) | (OPEN, TRYOPEN) -中继器 | `ConnOpenConfirm` | B | (OPEN, TRYOPEN) | (OPEN, OPEN) - -在实现子协议的两个链之间的建立握手结束时,应具有以下属性: - -- 每条链都具有源自发起方所指定的对方链正确共识状态。 -- 每条链都知道且认同另一链上的标识符。 - -该子协议不需要经过许可,除了考虑反垃圾信息。 - -链必须实现一个函数`generateIdentifier` ,它选择一个标识符,例如通过增加一个计数器: - -```typescript -type generateIdentifier = () -> Identifier -``` - -可以选择将特定版本作为`version`传递,以确保握手或者携带版本信息一起成功或失败。 - -*ConnOpenInit* 初始化链 A 上的连接尝试。 - -```typescript -function connOpenInit( - counterpartyPrefix: CommitmentPrefix, - clientIdentifier: Identifier, - counterpartyClientIdentifier: Identifier, - version: string, - delayPeriodTime: uint64, - delayPeriodBlocks: uint64) { - identifier = generateIdentifier() - abortTransactionUnless(provableStore.get(connectionPath(identifier)) == null) - state = INIT - if version != "" { - // 手动选择的版本必须是我们可以支持的版本 - abortTransactionUnless(getCompatibleVersions().indexOf(version) > -1) - versions = [version] - } else { - versions = getCompatibleVersions() - } - connection = ConnectionEnd{state, "", counterpartyPrefix, - clientIdentifier, counterpartyClientIdentifier, versions, delayPeriodTime, delayPeriodBlocks} - provableStore.set(connectionPath(identifier), connection) - addConnectionToClient(clientIdentifier, identifier) -} -``` - -*ConnOpenTry*中继链 A 到链 B 的连接尝试的通知(此代码在链 B 上执行)。 - -```typescript -function connOpenTry( - previousIdentifier: Identifier, - counterpartyConnectionIdentifier: Identifier, - counterpartyPrefix: CommitmentPrefix, - counterpartyClientIdentifier: Identifier, - clientIdentifier: Identifier, - counterpartyVersions: string[], - delayPeriodTime: uint64, - delayPeriodBlocks: uint64, - proofInit: CommitmentProof, - proofConsensus: CommitmentProof, - proofHeight: Height, - consensusHeight: Height) { - if (previousIdentifier !== "") { - previous = provableStore.get(connectionPath(identifier)) - abortTransactionUnless( - (previous !== null) && - (previous.state === INIT && - previous.counterpartyConnectionIdentifier === "" && - previous.counterpartyPrefix === counterpartyPrefix && - previous.clientIdentifier === clientIdentifier && - previous.counterpartyClientIdentifier === counterpartyClientIdentifier && - previous.delayPeriodTime === delayPeriodTime - previous.delayPeriodBlocks === delayPeriodBlocks)) - identifier = previousIdentifier - } else { - // 如果传递的标识符是哨兵空字符串,则生成一个新的标识符 - identifier = generateIdentifier() - } - abortTransactionUnless(consensusHeight < getCurrentHeight()) - expectedConsensusState = getConsensusState(consensusHeight) - expected = ConnectionEnd{INIT, "", getCommitmentPrefix(), counterpartyClientIdentifier, - clientIdentifier, counterpartyVersions, delayPeriodTime, delayPeriodBlocks} - versionsIntersection = intersection(counterpartyVersions, previous !== null ? previous.version : getCompatibleVersions()) - version = pickVersion(versionsIntersection) // 如果没有交集则抛出错误 - connection = ConnectionEnd{TRYOPEN, counterpartyConnectionIdentifier, counterpartyPrefix, - clientIdentifier, counterpartyClientIdentifier, version, delayPeriodTime, delayPeriodBlocks} - abortTransactionUnless(connection.verifyConnectionState(proofHeight, proofInit, counterpartyConnectionIdentifier, expected)) - abortTransactionUnless(connection.verifyClientConsensusState( - proofHeight, proofConsensus, counterpartyClientIdentifier, consensusHeight, expectedConsensusState)) - provableStore.set(connectionPath(identifier), connection) - addConnectionToClient(clientIdentifier, identifier) -} -``` - -*ConnOpenAck* 对从链 B 返回链 A 的连接建立尝试的确认消息进行中继(此代码在链 A 上执行)。 - -```typescript -function connOpenAck( - identifier: Identifier, - version: string, - counterpartyIdentifier: Identifier, - proofTry: CommitmentProof, - proofConsensus: CommitmentProof, - proofHeight: Height, - consensusHeight: Height) { - abortTransactionUnless(consensusHeight < getCurrentHeight()) - connection = provableStore.get(connectionPath(identifier)) - abortTransactionUnless( - (connection.state === INIT && connection.version.indexOf(version) !== -1) - || (connection.state === TRYOPEN && connection.version === version)) - expectedConsensusState = getConsensusState(consensusHeight) - expected = ConnectionEnd{TRYOPEN, identifier, getCommitmentPrefix(), - connection.counterpartyClientIdentifier, connection.clientIdentifier, - version, connection.delayPeriodTime, connection.delayPeriodBlocks} - abortTransactionUnless(connection.verifyConnectionState(proofHeight, proofTry, counterpartyIdentifier, expected)) - abortTransactionUnless(connection.verifyClientConsensusState( - proofHeight, proofConsensus, connection.counterpartyClientIdentifier, consensusHeight, expectedConsensusState)) - connection.state = OPEN - connection.version = version - connection.counterpartyConnectionIdentifier = counterpartyIdentifier - provableStore.set(connectionPath(identifier), connection) -} -``` - -*ConnOpenConfirm*在两条链上都建立连接后确认链 A 与链 B 的连接的建立(此代码在链 B 上执行)。 - -```typescript -function connOpenConfirm( - identifier: Identifier, - proofAck: CommitmentProof, - proofHeight: uint64) { - connection = provableStore.get(connectionPath(identifier)) - abortTransactionUnless(connection.state === TRYOPEN) - expected = ConnectionEnd{OPEN, identifier, getCommitmentPrefix(), connection.counterpartyClientIdentifier, - connection.clientIdentifier, connection.version} - abortTransactionUnless(connection.verifyConnectionState(proofHeight, proofAck, connection.counterpartyConnectionIdentifier, expected)) - connection.state = OPEN - provableStore.set(connectionPath(identifier), connection) -} -``` - -#### 查询 - -可以使用标识符和`queryConnection`来查询连接。 - -```typescript -function queryConnection(id: Identifier): ConnectionEnd | void { - return provableStore.get(connectionPath(id)) -} -``` - -可以使用客户端标识符和`queryClientConnections`来查询与特定客户端关联的连接。 - -```typescript -function queryClientConnections(id: Identifier): Set { - return privateStore.get(clientConnectionsPath(id)) -} -``` - -### 属性与不变性 - -- 连接标识符是“先到先得”的:一旦连接被商定,两个链之间就会存在一对唯一的标识符。 -- 连接握手不能被另一条链的 IBC 处理程序作为中间人来进行干预。 - -## 向后兼容性 - -不适用。 - -## 向前兼容性 - -此 ICS 的未来版本将在建立握手中包括版本协商。建立连接并协商版本后,可以根据 ICS 6 协商将来的版本更新。 - -只能在建立连接时选择的共识协议定义的`updateConsensusState`函数允许的情况下更新共识状态。 - -## 示例实现 - -即将到来。 - -## 其他实现 - -即将到来。 - -## 历史 - -本文档的部分内容受到[先前 IBC 规范](../../../archive)的启发。 - -2019年3月29日-提交初稿 - -2019年5月17日-草稿定稿 - -2019年7月29日-修订版本以跟踪与客户端关联的连接集 - -## 版权 - -本规范所有内容均采用 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 许可授权。 diff --git a/other_docs/ibc-protocol/spec/core/ics-003-connection-semantics/UPGRADES.md b/other_docs/ibc-protocol/spec/core/ics-003-connection-semantics/UPGRADES.md deleted file mode 100644 index 8a2a04549..000000000 --- a/other_docs/ibc-protocol/spec/core/ics-003-connection-semantics/UPGRADES.md +++ /dev/null @@ -1,516 +0,0 @@ -# 升级连接 - -### 概要 - -本规范规定了 IBC 必须实现的接口和状态机逻辑,以便现存连接在初始连接握手后能够升级。 - -### 动机 - -随着新功能被添加到 IBC,链可能希望在不放弃现有连接的已积累的状态和网络效应的情况下,同时利用新的连接功能。提议的升级协议将允许链重新协商现有连接,这样可以使用新的功能而无需创建新连接,从而保留当前连接之上的所有当前已建立的通道。 - -### 所需属性 - -- 两条链都必须认同重新协商后的连接参数。 -- 两条链上的连接状态和逻辑应该或者使用旧参数或者新参数,而不能是一个中间状态,例如,当一个链的对方链期望新的证明路径时,这个链就绝不能将状态写入旧的证明路径。 -- 连接升级协议是原子性的,即 - - 要么不成功,然后连接必须回退到原始连接参数; - - 要么成功,然后连接两端必须采用新的连接参数并妥善地处理 IBC 数据。 -- 连接升级协议应该具有改变所有连接相关参数的能力;但是连接升级协议不能改变下层的`ClientState` 。连接升级协议不得修改连接标识符。 - -## 技术规范 - -### 数据结构 - -`ConnectionState`和`ConnectionEnd`定义见[ICS-3](./README.md)。为方便读者,在此重述。 `UPGRADE_INIT` , `UPGRADE_TRY`是新增的附加状态,以启用升级功能。 - -#### **ConnectionState(摘自[ICS-3](README.md) )** - -```typescript -enum ConnectionState { - INIT, - TRYOPEN, - OPEN, - UPGRADE_INIT, - UPGRADE_TRY, -} -``` - -- 提议升级的链应该将连接状态从`OPEN`修改置为`UPGRADE_INIT` -- 接受升级的对方链应将连接状态从`OPEN`修改置为`UPGRADE_TRY` - -#### **ConnectionEnd(摘自[ICS-3](README.md) )** - -```typescript -interface ConnectionEnd { - state: ConnectionState - counterpartyConnectionIdentifier: Identifier - counterpartyPrefix: CommitmentPrefix - clientIdentifier: Identifier - counterpartyClientIdentifier: Identifier - version: string | []string - delayPeriodTime: uint64 - delayPeriodBlocks: uint64 -} -``` - -连接升级协议不能修改下层客户端或连接标识符。这个所需要的属性意味着只有`ConnectionEnd`的某些字段可以被升级协议升级。 - -- `state` :状态由升级协议的握手步骤指定。 - -可修改项目: - -- `counterpartyPrefix`:这一前缀可以在升级协议中修改。对方链必须要么接受新提议的前缀值,要么在升级握手期间返回错误。 -- `version` :版本可以被升级协议修改。在初始连接握手中发生的相同版本协商可用于升级的握手中。 -- `delayPeriodTime` :延迟时间可以被升级协议修改。对方链必须要么接受新的提议值,要么在升级握手期间返回错误。 -- `delayPeriodBlocks` :延迟时间可以被升级协议修改。对方链必须要么接受新的提议值,要么在升级握手期间返回错误。 - -不可修改项目: - -- `counterpartyConnectionIdentifier`:对方链连接标识符不能被升级协议修改。 -- `clientIdentifier` :客户端标识符不能被升级协议修改 -- `counterpartyClientIdentifier`:升级协议不能修改对方链的客户端标识符 - -注意:如果升级将任何字段添加到`ConnectionEnd` ,这些是默认可修改的,并且可以被有权限发起升级的 Actor(例如链上治理)所任意选择。 - -可修改的字段也可以被完全删除。 - -#### **UpgradeTimeout** - -```typescript -interface UpgradeTimeout { - timeoutHeight: Height - timeoutTimestamp: uint64 -} -``` - -- `timeoutHeight` :超时高度表示在此区块高度上,对方链不能再进行升级握手。此后,两条链将保留其原始连接,而升级握手被中止。 -- `timeoutTimestamp` :超时时间戳表示对方链不能再进行升级握手的时间。此后,两条链将保留其原始连接,而升级握手被中止。 - -timeoutHeight 或 timeoutTimestamp,至少其中之一必须非零。 - -### 储存路径 - -#### 恢复连接路径 - -链必须存储之前的连接端,以便在升级握手失败时可以恢复它。该数据可以存储在私有存储中。 - -```typescript -function connectionRestorePath(id: Identifier): Path { - return "connections/{id}/restore" -} -``` - -#### 升级错误路径 - -升级错误路径是可以向对方链发出升级错误信号的公共路径。在成功的情况下它不存储任何内容,但在链不接受提议的升级的情况下,它将存储一个哨兵中止值。 - -```typescript -function connectionErrorPath(id: Identifier): Path { - return "connections/{id}/upgradeError" - -} -``` - -UpgradeError 必须有一个关联的验证成员和非成员函数添加到连接接口,以便对方链可以验证本链是否在 UpgradeError 路径中存储了该错误。 - -```typescript -// 连接端验证连接升级错误的方法 -function verifyConnectionUpgradeError( - connection: ConnectionEnd, - height: Height, - proof: CommitmentProof, - upgradeErrorReceipt: []byte, -) { - client = queryClient(connection.clientIdentifier) - // 构造 CommitmentPath - path = applyPrefix(connection.counterpartyPrefix, connectionErrorPath(connection.counterpartyConnectionIdentifier)) - // 验证 upgradeErrorReceipt 是否存储在构造好的 path - // 延迟时间对非数据包的验证不是必需的,因此对延迟时间的字段传入 0 - return client.verifyMembership(height, 0, 0, proof, path, upgradeErrorReceipt) -} -``` - -```typescript -// 连接端验证升级错误不存在的方法 -function verifyConnectionUpgradeErrorAbsence( - connection: ConnectionEnd, - height: Height, - proof: CommitmentProof, -) { - client = queryClient(connection.clientIdentifier) - // 构造 CommitmentPath - path = applyPrefix(connection.counterpartyPrefix, connectionErrorPath(connection.counterpartyConnectionIdentifier)) - // 验证升级错误的路径是否为空 - // 延迟时间对非数据包的验证不是必需的,因此对延迟时间的字段传入 0 - return client.verifyNonMembership(height, 0, 0, proof, path) -} -``` - -#### 超时路径 - -超时路径是升级发起者设置的公共路径,用于确定 TRY 步骤何时超时。它存储了`timeoutHeight`和`timeoutTimestamp` ,此时对方链必须已进入 TRY 步骤。在 TRY 成功的情况下,该路径将在对方链上得到证明,以确保没有超时。在超时的情况下,对方链证明已经超过了超时时间,并已经在其链上恢复连接。 - -```typescript -function timeoutPath(id: Identifier) Path { - return "connections/{id}/upgradeTimeout" -} -``` - -超时路径必须在连接接口上具有关联的验证方法,以便对方链证明该链存储了特定的`UpgradeTimeout` 。 - -```typescript -// 连接端验证连接升级超时的方法 -function verifyConnectionUpgradeTimeout( - connection: ConnectionEnd, - height: Height, - proof: CommitmentProof, - upgradeTimeout: UpgradeTimeout, -) { - client = queryClient(connection.clientIdentifier) - // 构造 CommitmentPath - path = applyPrefix(connection.counterpartyPrefix, connectionTimeoutPath(connection.counterpartyConnectionIdentifier)) - // 按照标准 protobuf 编码,将 upgradeTimeout 序列化为字节数组 - timeoutBytes = protobuf.marshal(upgradeTimeout) - client.verifyMembership(height, 0, 0, proof, path, timeoutBytes) -} -``` - -## 实用函数 - -`restoreConnection()`是一个实用函数,它允许链中止正在进行的升级握手,并将`connectionEnd`返回到其原始升级前状态,同时还设置`errorReceipt` 。然后,中继器可以向对方链发送`cancelUpgradeMsg` ,以便它也可以将其`connectionEnd`恢复到其升级前状态。一旦两个连接端都恢复到升级前的状态,连接将继续按照其原始连接参数进行处理。 - -```typescript -function restoreConnection() { - // 取消升级 - // 将一个错误收据写入错误路径中 - // 并且恢复原始连接 - errorReceipt = []byte{1} - provableStore.set(errorPath(identifier), errorReceipt) - originalConnection = privateStore.get(restorePath(identifier)) - provableStore.set(connectionPath(identifier), originalConnection) - provableStore.delete(timeoutPath(identifier)) - privateStore.delete(restorePath(identifier)) - // 调用者也应当返回 -} -``` - -## 子协议 - -连接升级过程由三个子协议组成: `UpgradeConnectionHandshake` 、 `CancelConnectionUpgrade`和`TimeoutConnectionUpgrade` 。在两条链都同意提议的升级的情况下,升级握手协议应该成功完成并且`ConnectionEnd`应该成功升级。 - -### 升级握手 - -升级握手定义了四个数据报文: *ConnUpgradeInit* 、 *ConnUpgradeTry* 、 *ConnUpgradeAck*和*ConnUpgradeConfirm* - -一个成功的协议执行流程如下(注意,所有调用都是通过 ICS 25 的模块进行的): - -发起者 | 数据报文 | 操作的链 | 先前状态(A,B) | 其后状态(A,B) ---- | --- | --- | --- | --- -Actor | `ConnUpgradeInit` | A | (OPEN, OPEN) | (UPGRADE_INIT, OPEN) -Actor | `ConnUpgradeTry` | B | (UPGRADE_INIT, OPEN) | (UPGRADE_INIT, UPGRADE_TRY) -Relayer | `ConnUpgradeAck` | A | (UPGRADE_INIT, UPGRADE_TRY) | (OPEN, UPGRADE_TRY) -Relayer | `ConnUpgradeConfirm` | B | (OPEN, UPGRADE_TRY) | (OPEN, OPEN) - -在两个实现子协议的链之间的升级握手结束时,以下属性成立: - -- 每条链都在运行它们新升级后的连接端,并根据升级后的参数处理升级后的逻辑和状态。 -- 每条链都知道并同意对方升级的连接参数。 - -如果一个链不同意提议的对方链`UpgradedConnection` ,它可以通过将错误收据写入`errorPath`并恢复原始连接来中止升级握手。错误收据可以有任意字节并且必须是非空的。 - -`errorPath(id) => error_receipt` - -之后中继器可以向对方链提交`CancelConnectionUpgradeMsg` 。收到此消息后,链必须验证对方链是否在其`UpgradeError`中写入了非空错误收据,如果成功,它将恢复其原始连接,从而取消升级。 - -如果一个升级消息在指定的超时后到达,则消息不能成功执行。中继器可以再次在`CancelConnectionUpgradeTimeoutMsg`中提交证明,以便对方链取消升级并恢复其原始连接。 - -```typescript -function connUpgradeInit( - identifier: Identifier, - proposedUpgradeConnection: ConnectionEnd, - counterpartyTimeoutHeight: Height, - counterpartyTimeoutTimestamp: uint64, -) { - // 当前连接必须为 OPEN - currentConnection = provableStore.get(connectionPath(identifier)) - abortTransactionUnless(currentConnection.state == OPEN) - - // 一个不可修改的字段被修改了,则中止交易 - // 升级后的连接状态必须为 `UPGRADE_INIT` - // 注意:任何新添加的字段默认为可修改的。 - abortTransactionUnless( - proposedUpgradeConnection.state == UPGRADE_INIT && - proposedUpgradeConnection.counterpartyConnectionIdentifier == currentConnection.counterpartyConnectionIdentifier && - proposedUpgradeConnection.clientIdentifier == currentConnection.clientIdentifier && - proposedUpgradeConnection.counterpartyClientIdentifier == currentConnection.counterpartyClientIdentifier - ) - - // 超时高度或超时时间戳必须为非零值 - abortTransactionUnless(counterpartyTimeoutHeight != 0 || counterpartyTimeoutTimestamp != 0) - - upgradeTimeout = UpgradeTimeout{ - timeoutHeight: counterpartyTimeoutHeight, - timeoutTimestamp: counterpartyTimeoutTimestamp, - } - - provableStore.set(timeoutPath(identifier), upgradeTimeout) - provableStore.set(connectionPath(identifier), proposedUpgradeConnection) - privateStore.set(restorePath(identifier), currentConnection) -} -``` - -注意:如何为`ConnUpgradeInit`函数提供访问控制取决于各个实现。例如链治理、许可参与者、DAO 等。对交易对手的访问控制应提供超时值的选择,即如果交易对手的`UpgradeTry`由链治理把控,则超时值应该很大。 - -```typescript -function connUpgradeTry( - identifier: Identifier, - proposedUpgradeConnection: ConnectionEnd, - counterpartyConnection: ConnectionEnd, - timeoutHeight: Height, - timeoutTimestamp: uint64, - proofConnection: CommitmentProof, - proofUpgradeTimeout: CommitmentProof, - proofHeight: Height -) { - // 当前连接必须为 OPEN 或 UPGRADE_INIT (交叉打招呼) - currentConnection = provableStore.get(connectionPath(identifier)) - abortTransactionUnless(currentConnection.state == OPEN || currentConnection.state == UPGRADE_INIT) - - // 如果一个不可修改的字段被修改了,则中止交易 - // 升级后的连接状态必须为 `UPGRADE_TRY` - // 注意:任何新添加的字段默认为可修改的。 - abortTransactionUnless( - proposedUpgradeConnection.state == UPGRADE_TRY && - proposedUpgradeConnection.counterpartyConnectionIdentifier == currentConnection.counterpartyConnectionIdentifier && - proposedUpgradeConnection.clientIdentifier == currentConnection.clientIdentifier && - proposedUpgradeConnection.counterpartyClientIdentifier == currentConnection.counterpartyClientIdentifier - ) - - // 从超时高度和超时时间戳构造出超时时间 - // 这样,我们可以证明他们是被对方链所设置的 - upgradeTimeout = UpgradeTimeout{ - timeoutHeight: timeoutHeight, - timeoutTimestamp: timeoutTimestamp, - } - - // 验证对方链状态的证明 - abortTransactionUnless(verifyConnectionState(currentConnection, proofHeight, proofConnection, currentConnection.counterpartyConnectionIdentifier, proposedUpgradeConnection)) - abortTransactionUnless(verifyConnectionUpgradeTimeout(currentConnection, proofHeight, proofUpgradeTimeout, upgradeTimeout)) - - // 验证对方链连接的不可修改的字段没有被修改,并且对方链的状态为 UPGRADE_INIT - abortTransactionUnless( - counterpartyConnection.state == UPGRADE_INIT && - counterpartyConnection.counterpartyConnectionIdentifier == identifier && - counterpartyConnection.clientIdentifier == currentConnection.counterpartyClientIdentifier && - counterpartyConnection.counterpartyClientIdentifier == currentConnection.clientIdentifier - ) - - if currentConnection.state == UPGRADE_INIT { - // 如果存在交叉打招呼的情况,即 UpgradeInit 被两个 connectionEnds 所调用, - // 则除连接状态之外(升级连接将会是 UPGRADE_TRY 而当前连接将会是 UPGRADE_INIT) - // 我们必须确保对方链的 proposedUpgrade 与 currentConnection 相同 - // 如果提议升级在任意一方是不兼容的,则我们要恢复连接并取消升级。 - currentConnection.state = UPGRADE_TRY - if !currentConnection.IsEqual(proposedUpgradeConnection) { - restoreConnection() - return - } - } else if currentConnection.state == OPEN { - // 这是在该链上升级握手中的第一条消息,所以我们必须在恢复路径中存储原始连接 - // 以备我们将来需要恢复连接。 - privateStore.set(restorePath(identifier), currentConnection) - } else { - // 如果当前连接不为 INIT 或 OPEN,则中止交易 - abortTransactionUnless(false) - } - - // 超时高度或超时时间戳必须为非零值 - // 如果升级功能在 TRY 的链上实现,则一个中继器可以在超时之后提交一个 TRY 交易。 - // 这会在执行链上恢复连接,并允许对方链来使用 CancelUpgradeMsg 来恢复他们的连接。 - if timeoutHeight == 0 && timeoutTimestamp == 0 { - restoreConnection() - return - } - - // 必须没有超过对方链指定的超时时间 - if (currentHeight() > timeoutHeight && timeoutHeight != 0) || - (currentTimestamp() > timeoutTimestamp && timeoutTimestamp != 0) { - restoreConnection() - return - } - - // 验证所选的版本是兼容的 - versionsIntersection = intersection(counterpartyConnection.version, proposedUpgradeConnection.version) - version = pickVersion(versionsIntersection) // aborts transaction if there is no intersection - - // 两个连接端必须相互兼容。 - // 该函数未被指定,因为它会取决于新连接的特定结构。 - // 实现时有责任来确保验证 - // 提议新连接在任何一方都按照所选的新版本被正确构造。 - if !IsCompatible(counterpartyConnection, proposedUpgradeConnection) { - restoreConnection() - return - } - - provableStore.set(connectionPath(identifier), proposedUpgradeConnection) -} -``` - -注意:如何为`ConnUpgradeTry`函数提供访问控制取决于各个实现。例如链上治理、许可的 actor、DAO 等。链可以决定是否有许可**或**无许可的`UpgradeTry` 。在许可的情况下,两个链都必须明确同意升级;在无许可的情况下,一条链发起升级,另一条链默认同意升级。在无许可的情况下,中继器可以提交`ConnUpgradeTry`数据报。 - -```typescript -function connUpgradeAck( - identifier: Identifier, - counterpartyConnection: ConnectionEnd, - proofConnection: CommitmentProof, - proofHeight: Height -) { - // 当前连接是 UPGRADE_INIT 或 UPGRADE_TRY (交叉打招呼) - currentConnection = provableStore.get(connectionPath(identifier)) - abortTransactionUnless(currentConnection.state == UPGRADE_INIT || currentConnection.state == UPGRADE_TRY) - - // 验证对方链状态的证明 - abortTransactionUnless(verifyConnectionState(currentConnection, proofHeight, proofConnection, currentConnection.counterpartyConnectionIdentifier, counterpartyConnection)) - - // 对方链必须是 TRY 状态 - if counterpartyConnection.State != UPGRADE_TRY { - restoreConnection() - return - } - - // 验证连接是互相兼容的 - // 这也会检查对方链所选的版本是有效的 - // 该函数未被指定,因为它会取决于新连接的特定结构。 - // 实现时有责任来确保验证 - // 提议新连接在任何一方都按照所选的新版本被正确构造。 - if !IsCompatible(counterpartyConnection, connection) { - restoreConnection() - return - } - - // 升级完成 - // 设置连接为 OPEN 并且删除不需要的状态 - currentConnection.state = OPEN - provableStore.set(connectionPath(identifier), currentConnection) - provableStore.delete(timeoutPath(identifier)) - privateStore.delete(restorePath(identifier)) -} -``` - -```typescript -function connUpgradeConfirm( - identifier: Identifier, - counterpartyConnection: ConnectionEnd, - proofConnection: CommitmentProof, - proofUpgradeError: CommitmentProof, - proofHeight: Height, -) { - // 当前连接是 UPGRADE_TRY - currentConnection = provableStore.get(connectionPath(identifier)) - abortTransactionUnless(currentConnection.state == UPGRADE_TRY) - - // 对方链必须是 OPEN 状态 - abortTransactionUnless(counterpartyConnection.State == OPEN) - - // 验证对方链状态的证明 - abortTransactionUnless(verifyConnectionState(currentConnection, proofHeight, proofConnection, currentConnection.counterpartyConnectionIdentifier, counterpartyConnection)) - - // 验证对方链没有恢复连接 - // 并在连接 upgradeError 路径中存储了一个升级错误 - abortTransactionUnless(verifyConnectionUpgradeErrorAbsence(currentConnection, proofHeight, proofUpgradeError)) - - // 升级完成 - // 设置连接为 OPEN 并且删除不必要的状态 - currentConnection.state = OPEN - provableStore.set(connectionPath(identifier), currentConnection) - provableStore.delete(timeoutPath(identifier)) - privateStore.delete(restorePath(identifier)) -} -``` - -注意:由于对方链已经升级成功并在`ACK`步骤中移动到`OPEN` ,我们无法在此处恢复连接。我们只用验证对方链升级成功,然后自己再升级。 - -### 取消升级过程 - -在升级握手期间,链可以通过将错误收据写入错误路径并将原始连接恢复到`OPEN`来取消升级。然后,对方链也必须恢复其与`OPEN`的连接。 - -连接端只能在升级协商过程(TRY,ACK)中取消升级。一旦另一条链已经完成升级并移至`OPEN` ,则不能在一端取消升级,因为这将导致连接处于无效状态。 - -中继器可以通过调用`CancelConnectionUpgrade`来推进该过程: - -```typescript -function cancelConnectionUpgrade( - identifier: Identifer, - errorReceipt: []byte, - proofUpgradeError: CommitmentProof, - proofHeight: Height, -) { - // 当前连接是 UPGRADE_INIT 或 UPGRADE_TRY - currentConnection = provableStore.get(connectionPath(identifier)) - abortTransactionUnless(currentConnection.state == UPGRADE_INIT || currentConnection.state == UPGRADE_TRY) - - abortTransactionUnless(!isEmpty(errorReceipt)) - - abortTransactionUnless(verifyConnectionUpgradeError(currentConnection, proofHeight, proofUpgradeError, errorReceipt)) - - // 取消升级 - // 并且恢复原始连接 - // 删除不必要的状态 - originalConnection = privateStore.get(restorePath(identifier)) - provableStore.set(connectionPath(identifier), originalConnection) - - // 删除辅助升级状态 - provableStore.delete(timeoutPath(identifier)) - privateStore.delete(restorePath(identifier)) -} -``` - -### 超时升级过程 - -如果 UPGRADE_TRY 交易根本无法传递给对方链,则连接升级过程可能会在 UPGRADE_TRY 上无限期停止;例如,对方链上可能未启用升级功能。 - -在这种情况下,我们不希望初始化链无限期地停留在`UPGRADE_INIT`步骤中。因此, `UpgradeInit`消息将包含`TimeoutHeight`和`TimeoutTimestamp` 。如果指定的超时时间已经过去,则对方链应拒绝`UpgradeTry`消息。 - -此后,中继器必须向发起链提交`UpgradeTimeout`消息,证明对方链仍处于其原始状态。如果证明成功,则发起链也应恢复其原始连接并取消升级。 - -```typescript -function timeoutConnectionUpgrade( - identifier: Identifier, - counterpartyConnection: ConnectionEnd, - proofConnection: CommitmentProof, - proofHeight: Height, -) { - // 当前连接必须为 UPGRADE_INIT - currentConnection = provableStore.get(connectionPath(identifier)) - abortTransactionUnles(currentConnection.state == UPGRADE_INIT) - - upgradeTimeout = provableStore.get(timeoutPath(identifier)) - - // 证明必须是来自一个超过超时时间的高度。超时高度或超时时间戳必须已定义。 - // 如果超时高度已定义,证明来自超时高度之前, - // 那么中止交易 - abortTransactionUnless(upgradeTimeout.timeoutHeight.IsZero() || proofHeight >= upgradeTimeout.timeoutHeight) - // 如果超时时间戳已定义,那么来自证明高度的共识时间必须大于超时时间戳 - abortTransactionUnless(upgradeTimeout.timeoutTimestamp.IsZero() || getTimestampAtHeight(currentConnection, proofHeight) >= upgradeTimeout.timestamp) - - // 对方链的连接必须被证明为仍处于 OPEN 或 UPGRADE_INIT 状态(交叉打招呼) - abortTransactionUnless(counterpartyConnection.State === OPEN || counterpartyConnection.State == UPGRADE_INIT) - abortTransactionUnless(verifyConnectionState(currentConnection, proofHeight, proofConnection, currentConnection.counterpartyConnectionIdentifier, counterpartyConnection)) - - // 因为超时验证已通过,我们必须恢复连接 - originalConnection = privateStore.get(restorePath(identifier)) - provableStore.set(connectionPath(identifier), originalConnection) - - // 删除辅助升级状态 - provableStore.delete(timeoutPath(identifier)) - privateStore.delete(restorePath(identifier)) -} -``` - -注意,超时逻辑仅适用于 INIT 步骤。这是为了防止升级链在交易对手无法成功执行 TRY 时卡在非 OPEN 状态。一旦 TRY 步骤成功,则保证双方都启用了升级功能。活性不再是一个问题,因为我们可以等到活性恢复后再执行 ACK 步骤,这必将会使连接进入 OPEN 状态(成功升级或回滚)。 - -TRY 的链将接收对方链在 INIT 上选择的超时参数,以便它可以拒绝在指定的超时时间后收到的任何 TRY 消息。这可以防止握手进入无效状态,在这种状态下,INIT 链成功处理超时并恢复其与`OPEN`的连接,而 TRY 的链将在之后的一个时间点成功写入`TRY`状态。 - -### 迁移 - -链可能必须要去更新其内部状态以与新升级的连接保持一致。在这种情况下,迁移的处理程序应该是升级过程之前链二进制文件的一部分,以便升级成功后链可以正确迁移其状态。如果一个升级需要迁移处理程序但该程序不可用,则执行链必须拒绝升级,以免进入无效状态。这种状态迁移不会被对方链验证,因为它只是假设如果连接升级到特定的连接版本,那么对方链的辅助状态也将被更新以匹配给定连接版本的规范。迁移只能在升级成功完成并且新连接`OPEN` (即在`ACK`和`CONFIRM`上)后运行。 diff --git a/other_docs/ibc-protocol/spec/core/ics-004-channel-and-packet-semantics/README.md b/other_docs/ibc-protocol/spec/core/ics-004-channel-and-packet-semantics/README.md deleted file mode 100644 index a28449417..000000000 --- a/other_docs/ibc-protocol/spec/core/ics-004-channel-and-packet-semantics/README.md +++ /dev/null @@ -1,976 +0,0 @@ ---- -ics: '4' -title: 通道和数据包语义 -stage: 草案 -category: IBC/TAO -kind: 实例化 -requires: 2, 3, 5, 24 -author: Christopher Goes -created: '2019-03-07' -modified: '2019-08-25' ---- - -## 概要 - -“通道”抽象为区块链间通信协议提供消息传递语义,分为三类:排序、仅一次传递和模块许可。通道充当数据包在一条链上的模块与另一条链上的模块之间传递的通道,从而确保数据包仅被执行一次,按照其发送顺序进行传递(如有必要),并仅传递给拥有目标链上通道的另一端的相应的模块。每个通道都与一个特定的连接关联,并且一个连接可以具有任意数量的关联通道,从而允许使用公共标识符,并利用连接和轻客户端在所有通道上分摊区块头验证的成本。 - -通道不关心其中传递的内容。发送和接收 IBC 数据包的模块决定如何构造数据包数据,以及如何对传入的数据包数据进行操作,并且必须利用其自身的应用程序逻辑来根据数据包中的数据来确定要应用的状态转换。 - -### 动机 - -区块链间通信协议使用跨链消息传递模型。 外部中继器进程将 IBC *数据包*从一条链中继到另一条链。链`A`和链`B`独立的确认新的块,并且从一个链到另一个链的数据包可能会被任意延迟、审查或重新排序。数据包对于中继器是可见的,并且可以被任何中继器进程读取,然后被提交给任何其他链。 - -IBC 协议必须保证顺序(对于有序通道)和仅有一次传递,以允许应用程序得知两条链上已连接模块的组合状态。 - -> **例如**: 一个应用程序可能希望允许单个通证化的资产在多个区块链之间转移并保留在多个区块链上,同时保留同质化和供应量。当特定的 IBC 数据包提交到链`B`时,应用程序可以在链`B`上铸造资产凭证,并要求链`A`将等额的资产托管在链`A`上,直到以后以相反的 IBC 数据包将凭证兑换回链`A`为止。这种顺序保证结合正确的应用逻辑,可以确保两个链上的资产总量不变,并且在链`B`上铸造的任何资产凭证都可以之后兑换回链`A`上。 - -为了向应用层提供所需的排序、仅有一次传递和模块许可语义,区块链间通信协议必须实现一种抽象以强制执行这些语义——通道就是这种抽象。 - -### 定义 - -`ConsensusState` 在 [ICS 2](../ics-002-client-semantics) 中被定义. - -`Connection` 在 [ICS 3](../ics-003-connection-semantics) 中被定义. - -`Port`和`authenticateCapability`在 [ICS 5](../ics-005-port-allocation) 中被定义。 - -`hash`是一种通用的抗碰撞哈希函数,其细节必须由使用通道的模块商定。 `hash`在不同的链可以有不同的定义。 - -`Identifier` , `get` , `set` , `delete` , `getCurrentHeight`和模块系统相关的原语在 [ICS 24](../ics-024-host-requirements) 中被定义。 - -*通道*是用于在单独的区块链上的特定模块之间进行仅一次数据包传递的管道,该模块至少具备数据包发送端和数据包接收端。 - -*双向*通道是数据包可以在两个方向上流动的通道:从`A`到`B`和从`B`到`A` - -*单向*通道是指数据包只能沿一个方向流动的通道:从`A`到`B` (或从`B`到`A` ,命名的顺序是任意的)。 - -*有序*通道是指完全按照发送顺序传递数据包的通道。 - -*无序*通道是指可以以任何顺序传递数据包的通道,该顺序可能与数据包的发送顺序不同。 - -```typescript -enum ChannelOrder { - ORDERED, - UNORDERED, -} -``` - -方向和顺序是无关的,因此可以说双向无序通道,单向有序通道等。 - -所有通道均提供有且仅有一次的数据包传递,这意味着在通道的一端发送的数据包最终将不多于且不少于一次的传递到另一端。 - -该规范仅涉及*双向*通道。*单向*通道可以使用几乎完全相同的协议,并将在以后的 ICS 中进行概述。 - -通道端是一条链上存储通道元数据的数据结构: - -```typescript -interface ChannelEnd { - state: ChannelState - ordering: ChannelOrder - counterpartyPortIdentifier: Identifier - counterpartyChannelIdentifier: Identifier - connectionHops: [Identifier] - version: string -} -``` - -- `state`是通道端的当前状态。 -- `ordering`字段指示通道是有序的还是无序的。 -- `counterpartyPortIdentifier`标识通道另一端的对应链上的端口号。 -- `counterpartyChannelIdentifier`标识对应链的通道端。 -- `nextSequenceSend`是单独存储的,追踪下一个要发送的数据包的序号。 -- `nextSequenceRecv`是单独存储的,追踪要接收的下一个数据包的序号。 -- `nextSequenceAck`是单独存储的,追踪下一个准备被确认的数据包的序号。 -- `connectionHops`按顺序的存储在此通道上发送的数据包将途径的连接标识符列表。目前,此列表的长度必须为 1。将来可能会支持多跳通道。 -- `version`字符串存储一个不透明的通道版本,在握手期间达成一致。这可以确定模块级配置,例如通道使用哪种数据包编码。核心 IBC 协议不使用此版本。如果版本字符串包含供应用程序解析和解释的结构化元数据,则最佳做法是在 JSON 结构中编码所有元数据并将编组后的字符串包含在版本字段中。 - -通道端具有以下*状态* : - -```typescript -enum ChannelState { - INIT, - TRYOPEN, - OPEN, - CLOSED, -} -``` - -- 处于`INIT`状态的通道端,表示刚刚开始了握手的建立。 -- 处于`TRYOPEN`状态的通道端表示已确认对方链的握手。 -- 处于`OPEN`状态的通道端,表示已完成握手,并为发送和接收数据包作好了准备。 -- 处于`CLOSED`状态的通道端,表示通道已关闭,不能再用于发送或接收数据包。 - -区块链间通信协议中的`Packet`是如下定义的特定接口: - -```typescript -interface Packet { - sequence: uint64 - timeoutHeight: uint64 - timeoutTimestamp: uint64 - sourcePort: Identifier - sourceChannel: Identifier - destPort: Identifier - destChannel: Identifier - data: bytes -} -``` - -- `sequence`对应于发送和接收的顺序,其中序号靠前的数据包必须比序号靠后的数据包先发送或接收。 -- `timeoutHeight`指示目标链上的一个共识高度,此高度后不再处理数据包,而是计为已超时。 -- `timeoutTimestamp`指示目标链上的一个时间戳,此后将不再处理数据包,而是记为已超时。 -- `sourcePort`标识发送链上的端口。 -- `sourceChannel`标识发送链上的通道端。 -- `destPort`标识接收链上的端口。 -- `destChannel`标识接收链上的通道端。 -- `data`是不透明的值,可以由关联模块的应用程序逻辑定义。 - -请注意,`Packet`绝不会直接序列化。而是在某些函数调用中使用的中间结构,可能需要由调用 IBC 处理程序的模块来创建或处理该中间结构。 - -`OpaquePacket`是一个数据包,但是被主机状态机掩盖为一种模糊的数据类型,因此,除了将其传递给 IBC 处理程序之外,模块无法对其进行任何操作。IBC 处理程序可以将`Packet`转换为`OpaquePacket` ,或反过来。 - -```typescript -type OpaquePacket = object -``` - -### 所需属性 - -#### 效率 - -- 数据包传输和确认的速度应仅受底层链速度的限制。证明应尽可能是批量化的。 - -#### 仅单次传递 - -- 在通道的一端发送的 IBC 数据包应仅一次的传递到另一端。 -- 对于仅一次的安全性,不需要网络同步假设。如果其中一条链或两条链都挂起了,则数据包最多传递不超过一次,并且一旦链恢复,数据包就应该能够再次流转。 - -#### 排序 - -- 在有序通道上,应按相同的顺序发送和接收数据包:如果数据包 *x* 在链`A`通道端的数据包 *y* 之前发送,则数据包 *x* 必须在相应的链`B`通道端的数据包 *y* 之前收到。 -- 在无序通道上,可以以任何顺序发送和接收数据包。像有序数据包一样,无序数据包具有单独的根据目标链高度指定的超时高度。 - -#### 许可 - -- 通道应该在握手期间被通道两端的模块许可,并且此后不可变更(更高级别的逻辑可以通过标记端口的所有权来标记通道所有权)。只有与通道端关联的模块才能在其上发送或接收数据包。 - -## 技术规范 - -### 数据流可视化 - -客户端、连接、通道和数据包的体系结构: - -![Dataflow Visualisation](dataflow.png) - -### 预备知识 - -#### 存储路径 - -通道的结构存储在一个结合了端口标识符和通道标识符的唯一存储路径前缀下: - -```typescript -function channelPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path { - return "ports/{portIdentifier}/channels/{channelIdentifier}" -} -``` - -与通道关联的能力键存储在`channelCapabilityPath` : - -```typescript -function channelCapabilityPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path { - return "{channelPath(portIdentifier, channelIdentifier)}/key" -} -``` - -无符号整数计数器`nextSequenceSend` , `nextSequenceRecv`和`nextSequenceAck`是分别存储的,因此可以单独证明它们: - -```typescript -function nextSequenceSendPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path { - return "{channelPath(portIdentifier, channelIdentifier)}/nextSequenceSend" -} - -function nextSequenceRecvPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path { - return "{channelPath(portIdentifier, channelIdentifier)}/nextSequenceRecv" -} - -function nextSequenceAckPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path { - return "{channelPath(portIdentifier, channelIdentifier)}/nextSequenceAck" -} -``` - -固定大小的加密承诺数据包数据字段存储在数据包序号下: - -```typescript -function packetCommitmentPath(portIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64): Path { - return "{channelPath(portIdentifier, channelIdentifier)}/packets/" + sequence -} -``` - -存储中缺失的路径相当于占用零位。 - -数据包接收数据存储在`packetReceiptPath` - -```typescript -function packetAcknowledgementPath(portIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64): Path { - return "{channelPath(portIdentifier, channelIdentifier)}/acknowledgements/" + sequence -} -``` - -数据包回执数据存储在`packetAcknowledgementPath`下: - -```typescript -function packetAcknowledgementPath(portIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64): Path { - return "acks/ports/{portIdentifier}/channels/{channelIdentifier}/sequences/{sequence}" -} -``` - -### 版本控制 - -在握手过程中,通道的两端在与该通道关联的版本字节串上达成一致。 此版本字节串的内容对于 IBC 核心协议保持不透明。 状态机主机可以利用版本数据来标示其支持的 IBC/APP 协议,确认数据包编码格式,或在协商与 IBC 协议之上自定义逻辑有关的其他通道元数据。 - -状态机主机可以安全的忽略版本数据或指定一个空字符串。 - -### 子协议 - -> 注意:如果主机状态机正在使用对象能力认证(请参阅 [ICS 005](../ics-005-port-allocation) ),则所有使用端口的功能都将带有附加能力参数。 - -#### 标识符验证 - -通道存储在唯一的`(portIdentifier, channelIdentifier)`前缀下。 可以提供验证函数`validatePortIdentifier` 。 - -```typescript -type validateChannelIdentifier = (portIdentifier: Identifier, channelIdentifier: Identifier) => boolean -``` - -如果未提供,默认的`validateChannelIdentifier`函数将始终返回`true` 。 - -#### 通道生命周期管理 - -![Channel State Machine](channel-state-machine.png) - -发起人 | 数据报 | 作用链 | 之前状态 (A, B) | 之后状态 (A, B) ---- | --- | --- | --- | --- -Actor | ChanOpenInit | A | (none, none) | (INIT, none) -中继器 | ChanOpenTry | B | (INIT, none) | (INIT, TRYOPEN) -中继器 | ChanOpenAck | A | (INIT, TRYOPEN) | (OPEN, TRYOPEN) -中继器 | ChanOpenConfirm | B | (OPEN, TRYOPEN) | (OPEN, OPEN) - -发起人 | 数据报 | 作用链 | 之前状态 (A, B) | 之后状态 (A, B) ---- | --- | --- | --- | --- -Actor | ChanCloseInit | A | (OPEN, OPEN) | (CLOSED, OPEN) -中继器 | ChanCloseConfirm | B | (CLOSED, OPEN) | (CLOSED, CLOSED) - -##### 建立握手 - -与另一个链上的模块发起通道建立握手的模块调用`chanOpenInit`函数。 - -建立通道必须提供本地通道标识符、本地端口、远程端口和远程通道的标识符。 - -当建立握手完成后,发起握手的模块将拥有在账本上已创建通道的一端,而对应的另一条链的模块将拥有通道的另一端。创建通道后,所有权就无法更改(尽管更高级别的抽象可以实现并提供此功能)。 - -链必须实现一个函数`generateIdentifier`,它选择一个标识符,例如通过增加一个计数器: - -```typescript -type generateIdentifier = () -> Identifier -``` - -```typescript -function chanOpenInit( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - version: string): CapabilityKey { - abortTransactionUnless(validateChannelIdentifier(portIdentifier, channelIdentifier)) - - abortTransactionUnless(connectionHops.length === 1) // 用于 IBC 协议 v1 - - abortTransactionUnless(provableStore.get(channelPath(portIdentifier, channelIdentifier)) === null) - connection = provableStore.get(connectionPath(connectionHops[0])) - - // 允许Optimistic通道握手 - abortTransactionUnless(connection !== null) - abortTransactionUnless(authenticateCapability(portPath(portIdentifier), portCapability)) - channel = ChannelEnd{INIT, order, counterpartyPortIdentifier, - counterpartyChannelIdentifier, connectionHops, version} - provableStore.set(channelPath(portIdentifier, channelIdentifier), channel) - channelCapability = newCapability(channelCapabilityPath(portIdentifier, channelIdentifier)) - provableStore.set(nextSequenceSendPath(portIdentifier, channelIdentifier), 1) - provableStore.set(nextSequenceRecvPath(portIdentifier, channelIdentifier), 1) - provableStore.set(nextSequenceAckPath(portIdentifier, channelIdentifier), 1) - return channelCapability -} -``` - -模块调用`chanOpenTry`函数,以接受由另一条链上的模块发起的通道建立握手的第一步。 - -```typescript -function chanOpenTry( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - counterpartyChosenChannelIdentifer: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - version: string, // 弃用 - counterpartyVersion: string, - proofInit: CommitmentProof, - proofHeight: Height): CapabilityKey { - channelIdentifier = generateIdentifier() - - abortTransactionUnless(validateChannelIdentifier(portIdentifier, channelIdentifier)) - abortTransactionUnless(connectionHops.length === 1) // 用于 IBC 协议的 v1 - abortTransactionUnless(authenticateCapability(portPath(portIdentifier), portCapability)) - connection = provableStore.get(connectionPath(connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - expected = ChannelEnd{INIT, order, portIdentifier, - "", [connection.counterpartyConnectionIdentifier], counterpartyVersion} - abortTransactionUnless(connection.verifyChannelState( - proofHeight, - proofInit, - counterpartyPortIdentifier, - counterpartyChannelIdentifier, - expected - )) - channel = ChannelEnd{TRYOPEN, order, counterpartyPortIdentifier, - counterpartyChannelIdentifier, connectionHops, version} - provableStore.set(channelPath(portIdentifier, channelIdentifier), channel) - channelCapability = newCapability(channelCapabilityPath(portIdentifier, channelIdentifier)) - - // 初始化通道序列 - provableStore.set(nextSequenceSendPath(portIdentifier, channelIdentifier), 1) - provableStore.set(nextSequenceRecvPath(portIdentifier, channelIdentifier), 1) - provableStore.set(nextSequenceAckPath(portIdentifier, channelIdentifier), 1) - - return channelCapability -} -``` - -握手发起模块调用`chanOpenAck`,以确认收到对方链的模块已接受发起的请求。 - -```typescript -function chanOpenAck( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string, - proofTry: CommitmentProof, - proofHeight: Height) { - channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless(channel.state === INIT || channel.state === TRYOPEN) - abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability)) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - expected = ChannelEnd{TRYOPEN, channel.order, portIdentifier, - channelIdentifier, [connection.counterpartyConnectionIdentifier], counterpartyVersion} - abortTransactionUnless(connection.verifyChannelState( - proofHeight, - proofTry, - channel.counterpartyPortIdentifier, - counterpartyChannelIdentifier, - expected - )) - channel.state = OPEN - channel.version = counterpartyVersion - channel.counterpartyChannelIdentifier = counterpartyChannelIdentifier - provableStore.set(channelPath(portIdentifier, channelIdentifier), channel) -} -``` - -握手接受模块调用 `chanOpenConfirm` 函数以确认收到在另一条链上进行握手发起模块的回执,并完成通道创建握手。 - -```typescript -function chanOpenConfirm( - portIdentifier: Identifier, - channelIdentifier: Identifier, - proofAck: CommitmentProof, - proofHeight: Height) { - channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state === TRYOPEN) - abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability)) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - expected = ChannelEnd{OPEN, channel.order, portIdentifier, - channelIdentifier, [connection.counterpartyConnectionIdentifier], channel.version} - abortTransactionUnless(connection.verifyChannelState( - proofHeight, - proofAck, - channel.counterpartyPortIdentifier, - channel.counterpartyChannelIdentifier, - expected - )) - channel.state = OPEN - provableStore.set(channelPath(portIdentifier, channelIdentifier), channel) -} -``` - -##### 关闭握手 - -两个模块中的任意一个通过调用`chanCloseInit`函数来关闭其通道端。一旦一端关闭,通道将无法重新打开。 - -调用模块可以在调用`chanCloseInit`时原子性的执行适当的应用程序逻辑。 - -通道关闭后,任何传递中的数据包都会超时。 - -```typescript -function chanCloseInit( - portIdentifier: Identifier, - channelIdentifier: Identifier) { - abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability)) - channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state !== CLOSED) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - channel.state = CLOSED - provableStore.set(channelPath(portIdentifier, channelIdentifier), channel) -} -``` - -一旦一端已经关闭,对方模块调用`chanCloseConfirm`函数以关闭其通道端。 - -在调用`chanCloseConfirm`的同时,模块可以原子性的执行其他适当的应用逻辑。 - -关闭通道后,将无法重新打开通道,并且不能重复使用标识符。防止标识符重用是因为我们要防止潜在的重放先前发送的数据包。重放问题类似于直接使用序列号与带签名的消息,而不是用轻客户端算法对消息(IBC 数据包)进行“签名”,防止重放的序列值是端口标识符,通道标识符和数据包序列号的组合-因此我们不允许在序列号重置为零的情况下,再次使用相同的端口标识符和通道标识符,因为这可能允许重放数据包。如果要求并跟踪特定最大高度/时间的超时,则可以安全的重用标识符,将来版本的规范可能包含此功能。 - -```typescript -function chanCloseConfirm( - portIdentifier: Identifier, - channelIdentifier: Identifier, - proofInit: CommitmentProof, - proofHeight: uint64) { - abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability)) - channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state !== CLOSED) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - expected = ChannelEnd{CLOSED, channel.order, portIdentifier, - channelIdentifier, [connection.counterpartyConnectionIdentifier], channel.version} - abortTransactionUnless(connection.verifyChannelState( - proofHeight, - proofInit, - channel.counterpartyPortIdentifier, - channel.counterpartyChannelIdentifier, - expected - )) - channel.state = CLOSED - provableStore.set(channelPath(portIdentifier, channelIdentifier), channel) -} -``` - -#### 数据包流和处理 - -![Packet State Machine](packet-state-machine.png) - -##### 一个数据包的日常工作 - -以下的步骤发生在一个数据包从机器 *A* 上的模块 *1* 发送到机器 *B* 上的模块 *2*,从头开始。 - -该模块可以通过 [ICS 25](../ics-025-handler-interface) 或 [ICS 26](../ics-026-routing-module) 接入 IBC 处理程序。 - -1. 以任何顺序初始客户端和端口设置 - 1. 在 *A* 上为 *B* 创建客户端(请参阅 [ICS 2](../ics-002-client-semantics) ) - 2. 在 *B* 上为 *A* 创建客户端(请参阅 [ICS 2](../ics-002-client-semantics) ) - 3. 模块 *1* 绑定到端口(请参阅 [ICS 5](../ics-005-port-allocation) ) - 4. 模块 *2* 绑定到端口(请参阅 [ICS 5](../ics-005-port-allocation) ),该端口以带外方式(out-of-band)传输到模块 *1* -2. 建立连接和通道,按顺序以optimistic方式发送(optimistic send) - 1. 模块 *1* 自 *A* 向 *B* 创建连接握手(请参见 [ICS 3](../ics-003-connection-semantics) ) - 2. 使用新创建的连接(此 ICS),自 *1* 向 *2* 开始创建通道握手 - 3. 通过新创建的通道自 *1* 向 *2* 发送数据包(此 ICS) -3. 握手成功完成(如果任一握手失败,则连接/通道可以关闭且数据包超时) - 1. 连接握手成功完成(请参阅 [ICS 3](../ics-003-connection-semantics) )(这需要中继器进程参与) - 2. 通道握手成功完成(此 ICS)(这需要中继器进程的参与) -4. 在状态机 *B* 的模块 *2* 上确认数据包(如果超过超时区块高度,则确认数据包超时)(这将需要中继器进程参与) -5. 回执从状态机 *B* 上的模块 *2* 被中继回状态机 *A* 上的模块 *1* - -从空间上表示,两台机器之间的数据包传输可以表示如下: - -![Packet Transit](packet-transit.png) - -##### 发送数据包 - -`sendPacket`函数由模块调用,以便在调用模块的通道端将 IBC 数据包发送到另一条链上的相应模块。 - -在调用 `sendPacket`的同时,调用模块必须同时原子性的执行应用逻辑。 - -IBC 处理程序按顺序执行以下步骤: - -- 检查用于发送数据包的通道和连接是否打开 -- 检查调用模块是否拥有发送端口(参见[ICS 5](../ics-005-port-allocation) ) -- 检查目标链尚未达到指定的超时区块高度 -- 递增通道关联的发送序号 -- 存储对数据包数据和数据包超时信息的固定大小加密承诺 - -请注意,完整的数据包不会存储在链的状态中——仅仅存储数据和超时信息的短长度的哈希加密承诺。数据包数据可以从交易的执行中计算得出,并可能作为中继器可以索引的日志输出出来。 - -```typescript -function sendPacket( - capability: CapabilityKey, - sourcePort: Identifier, - sourceChannel: Identifier, - timeoutHeight: Height, - timeoutTimestamp: uint64, - data: bytes) { - channel = provableStore.get(channelPath(sourcePort, sourceChannel)) - - // 检查通道和连接是否打开以发送数据包; - // 注意:一旦握手开始就允许optimistic发送 - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state !== CLOSED) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - - // 检查调用模块是否拥有发送端口 - abortTransactionUnless(authenticateCapability(channelCapabilityPath(sourcePort, sourceChannel), capability)) - - // 检查超时高度是否超过本地客户端中跟踪的接收链的高度 - latestClientHeight = provableStore.get(clientPath(connection.clientIdentifier)).latestClientHeight() - abortTransactionUnless(packet.timeoutHeight === 0 || latestClientHeight < packet.timeoutHeight) - - // 递增发送序列计数器的序号 - sequence = provableStore.get(nextSequenceSendPath(packet.sourcePort, packet.sourceChannel)) - provableStore.set(nextSequenceSendPath(packet.sourcePort, packet.sourceChannel), sequence+1) - - // 存储对数据包数据和数据包超时的承诺 - provableStore.set( - packetCommitmentPath(sourcePort, sourceChannel, sequence), - hash(data, timeoutHeight, timeoutTimestamp) - ) - - // 记录:一个数据包可以安全发送 - emitLogEntry("sendPacket", {sequence: sequence, data: data, timeoutHeight: timeoutHeight, timeoutTimestamp: timeoutTimestamp}) -} -``` - -#### 接收数据包 - -模块调用`recvPacket`函数以接收和处理在对应的链的通道端发送的 IBC 数据包。 - -在调用`recvPacket`函数的同时,调用模块必须原子性的执行应用逻辑,可能需要事先计算出数据包确认消息的值。 - -IBC 处理程序按顺序执行以下步骤: - -- 检查接收数据包的通道和连接是否打开 -- 检查调用模块是否拥有接收端口 -- 检查数据包元数据与通道及连接信息是否匹配 -- 检查数据包序号是通道端期望接收的(对于有序通道而言) -- 检查尚未达到超时高度 -- 在传出链的状态下检查数据包数据的加密承诺包含证明 -- 在数据包唯一的存储路径上设置一个不透明确认值(如果确认信息为非空或是无序通道) -- 递增与通道端关联的数据包接收序号(仅限有序通道) - -我们传递签署和提交数据包的`relayer`者的地址,以使模块能够选择性地提供一些奖励。这为费用支付提供了基础,但也可用于其他技术(如计算一个排行榜)。 - -```typescript -function recvPacket( - packet: OpaquePacket, - proof: CommitmentProof, - proofHeight: Height, - relayer: string): Packet { - - channel = provableStore.get(channelPath(packet.destPort, packet.destChannel)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state === OPEN) - abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.destPort, packet.destChannel), capability)) - abortTransactionUnless(packet.sourcePort === channel.counterpartyPortIdentifier) - abortTransactionUnless(packet.sourceChannel === channel.counterpartyChannelIdentifier) - - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - - abortTransactionUnless(packet.timeoutHeight === 0 || getConsensusHeight() < packet.timeoutHeight) - abortTransactionUnless(packet.timeoutTimestamp === 0 || currentTimestamp() < packet.timeoutTimestamp) - - abortTransactionUnless(connection.verifyPacketData( - proofHeight, - proof, - packet.sourcePort, - packet.sourceChannel, - packet.sequence, - concat(packet.data, packet.timeoutHeight, packet.timeoutTimestamp) - )) - - // 所有的断言都通过了(除了序列检查),于是我们可以改变状态 - - if (channel.order === ORDERED) { - nextSequenceRecv = provableStore.get(nextSequenceRecvPath(packet.destPort, packet.destChannel)) - abortTransactionUnless(packet.sequence === nextSequenceRecv) - nextSequenceRecv = nextSequenceRecv + 1 - provableStore.set(nextSequenceRecvPath(packet.destPort, packet.destChannel), nextSequenceRecv) - } else { - // 对于无序通道,我们必须设置收据,以便在另一端进行验证 - // 这个收据不包含任何数据,因为数据包还没有被处理 - // 它只是一个单独的存储键,设置为空字符串,表示数据包已被接收 - abortTransactionUnless(provableStore.get(packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence) === null)) - provableStore.set( - packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence), - "1" - ) - } - - // 记录:一个数据包已经被接收 - emitLogEntry("recvPacket", {sequence: packet.sequence, timeoutHeight: packet.timeoutHeight, port: packet.destPort, channel: packet.destChannel, - timeoutTimestamp: packet.timeoutTimestamp, data: packet.data}) - - // 返回透明的数据包 - return packet -} -``` - -#### 编写回执 - -`writeAcknowledgement`函数由模块调用,以写入处理 IBC 数据包产生的数据;此IBC数据包可以由发送链验证,是一种“执行回执”或“RPC 调用响应”。 - -在调用 `writeAcknowledgement`的同时,调用模块必须同时原子性的执行应用逻辑。 - -这是一个异步回执,接收到回执数据时不需要确定其内容,仅在处理完成时确定。在同步情况下, `writeAcknowledgement`可以在与`recvPacket`相同的交易中(原子性地)调用。 - -不需要确认收到数据包;但是,如果有序通道使用回执,则必须确认所有数据包或不确认任何数据包(因为回执是按顺序处理的)。请注意,如果数据包未被确认收到,则无法在源链上删除数据包承诺。 IBC 的未来版本可能包括让模块指定它们是否将确认收到数据包的方法,以允许清理。 - -`writeAcknowledgement`*不*检查是否确实收到了正在确认的数据包,因为这将导致对已确认数据包的证明进行两次验证。验证这方面的正确性是调用模块的责任。调用模块必须仅使用先前从`recvPacket` 中收到的数据包调用`writeAcknowledgement`。 - -IBC 处理程序按顺序执行以下步骤: - -- 检查此数据包的回执是否尚未写入 -- 在数据包唯一的存储路径上写入不透明回执 - -```typescript -function writeAcknowledgement( - packet: Packet, - acknowledgement: bytes) { - - // 如果已经写了回执,流程终止 - abortTransactionUnless(provableStore.get(packetAcknowledgementPath(packet.destPort, packet.destChannel, packet.sequence) === null)) - - // 写回执 - provableStore.set( - packetAcknowledgementPath(packet.destPort, packet.destChannel, packet.sequence), - hash(acknowledgement) - ) - - // 记录:一个数据包已被确认收到 - emitLogEntry("writeAcknowledgement", {sequence: packet.sequence, timeoutHeight: packet.timeoutHeight, port: packet.destPort, channel: packet.destChannel, - timeoutTimestamp: packet.timeoutTimestamp, data: packet.data, acknowledgement}) -} -``` - -#### 处理回执 - -模块调用`acknowledgePacket`函数来处理先前发送到交易对手链上的数据包的回执。 `acknowledgePacket`还清理了数据包承诺,由于数据包已被接收并采取行动,因此可以清理。 - -调用模块可以结合调用`acknowledgePacket`以原子性的执行适当的应用程序回执处理逻辑。 - -我们像在接收数据包中一样传递relayer地址,以便在此处也允许可能的激励。 - -```typescript -function acknowledgePacket( - packet: OpaquePacket, - acknowledgement: bytes, - proof: CommitmentProof, - proofHeight: Height, - relayer: string): Packet { - - // 中止交易,除非:该通道打开;调用模块拥有相关端口,并且数据包字段匹配 - channel = provableStore.get(channelPath(packet.sourcePort, packet.sourceChannel)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state === OPEN) - abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability)) - abortTransactionUnless(packet.destPort === channel.counterpartyPortIdentifier) - abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier) - - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - - // 验证我们发送了数据包并且还没有清除它 - abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) - === hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp)) - - // 除非交易对手链上有正确回执,否则中止交易 - abortTransactionUnless(connection.verifyPacketAcknowledgement( - proofHeight, - proof, - packet.destPort, - packet.destChannel, - packet.sequence, - acknowledgement - )) - - // 除非按顺序处理回执,否则中止交易 - if (channel.order === ORDERED) { - nextSequenceAck = provableStore.get(nextSequenceAckPath(packet.sourcePort, packet.sourceChannel)) - abortTransactionUnless(packet.sequence === nextSequenceAck) - nextSequenceAck = nextSequenceAck + 1 - provableStore.set(nextSequenceAckPath(packet.sourcePort, packet.sourceChannel), nextSequenceAck) - } - - // 所有的断言都通过了,于是我们可以改变状态 - - // 删除我们的承诺,这样我们就不能再次“确认” - provableStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) - - // 返回透明数据包 - return packet -} -``` - -##### 回执数据封包 - -从远端链返回的回执在IBC 协议中被定义为任意字节。该数据可以编码后成功执行或失败(除了超时之外)。没有通用的方法来区分这两种情况,因为这要求每个客户端数据包可视化程序都了解每个特定应用程序的协议,以便区分中继成功或失败的情况。为了减少这个问题,我们提供了一个额外的回执格式的规范,这个范式[应该](https://www.ietf.org/rfc/rfc2119.txt)由每个应用程序特定的协议使用。 - -```proto -message Acknowledgement { - oneof response { - bytes result = 21; - string error = 22; - } -} -``` - -如果应用程序对回执字节使用不同的格式,它不得反序列化这种格式的有效 protobuf 消息。请注意,所有数据包都包含一个非空字段,它必须是正确的结果或错误。明确选择字段编号 21 和 22 是为了避免与用于回执的其他 protobuf 消息格式发生意外冲突。任何具有这种格式的消息的第一个字节将是非 ASCII 值`0xaa` (正确结果)或`0xb2` (错误)。 - -#### 超时 - -应用程序语义可能需要定义超时:超时是链在将一笔交易视作错误之前将等待多长时间的上限。由于这两个链本地时间的不同,因此这是一个明显的双花攻击的方向——攻击者可能延迟发送确认消息或在超时时间后发送数据包——因此应用程序本身无法安全的实现简单的超时逻辑。 - -请注意,为了避免任何可能的“双花”攻击,超时算法要求目标链正在运行并且是可访问的。一个人不能在一个网络完全分区(network parition)的情况下证明任何事情,并且必须等待连接。必须在接收者链上证明已经超时,而不仅仅是以发送链没有收到响应作为判断。 - -##### 发送端 - -`timeoutPacket`函数由最初尝试将数据包发送到对方链的模块在没有提交数据包的情况下对方链达到超时区块高度或超过超时时间戳的情况下调用,以证明该数据包无法再执行,并允许调用模块安全的执行适当的状态转换。 - -在调用`timeoutPacket`的同时,调用模块可以原子性的执行适当的应用超时处理逻辑。 - -在有序通道的情况下, `timeoutPacket`检查接收通道端的`recvSequence` ,如果数据包已超时,则关闭通道。 - -在无序通道的情况下, `timeoutPacket`检查是否存在确认(如果接收到数据包,则该确认将被写入)。面对超时的数据包,无序通道预期会继续工作。 - -如果连续的数据包的超时高度之间是强制的关系,则可以执行所有数据包的安全批量超时而不是使用超时数据包。该规范暂时省略了细节。 - -我们像在接收数据包中一样传递relayer地址,以便在此处也允许可能的激励。 - -```typescript -function timeoutPacket( - packet: OpaquePacket, - proof: CommitmentProof, - proofHeight: Height, - nextSequenceRecv: Maybe, - relayer: string): Packet { - - channel = provableStore.get(channelPath(packet.sourcePort, packet.sourceChannel)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state === OPEN) - - abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability)) - abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier) - - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - // 注意:连接可能已经关闭 - abortTransactionUnless(packet.destPort === channel.counterpartyPortIdentifier) - - // 检查超时高度或超时时间戳是否已传递到另一端 - abortTransactionUnless( - (packet.timeoutHeight > 0 && proofHeight >= packet.timeoutHeight) || - (packet.timeoutTimestamp > 0 && connection.getTimestampAtHeight(proofHeight) > packet.timeoutTimestamp)) - - // 验证我们确实发送了这个数据包,检查存储 - abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) - === hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp)) - - if channel.order === ORDERED { - // 有序通道:检查是否没有收到数据包 - abortTransactionUnless(nextSequenceRecv <= packet.sequence) - // 有序通道:检查 recv 序列是否如声明的那样 - abortTransactionUnless(connection.verifyNextSequenceRecv( - proofHeight, - proof, - packet.destPort, - packet.destChannel, - nextSequenceRecv - )) - } else - // 无序通道:验证在数据包索引处没有收到收据 - abortTransactionUnless(connection.verifyPacketReceiptAbsence( - proofHeight, - proof, - packet.destPort, - packet.destChannel, - packet.sequence - )) - - // 所有的断言都通过了,我们可以改变状态 - - // 删除我们的承诺 - provableStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) - - if channel.order === ORDERED { - // 有序通道:关闭通道 - channel.state = CLOSED - provableStore.set(channelPath(packet.sourcePort, packet.sourceChannel), channel) - } - - // 返回透明数据包 - return packet -} -``` - -##### 关闭时超时 - -`timeoutOnClose`函数由模块调用,以证明未接收到的数据包所发送到的通道已关闭,因此永远不会接收到数据包(即使尚未达到`timeoutHeight`或`timeoutTimestamp` )。 - -在调用`timeoutOnClose`的同时,调用模块可以原子性的执行适当的应用超时处理逻辑。 - -我们像在接收数据包中一样传递relayer地址,以便在此处也允许可能的激励。 - -```typescript -function timeoutOnClose( - packet: Packet, - proof: CommitmentProof, - proofClosed: CommitmentProof, - proofHeight: Height, - nextSequenceRecv: Maybe, - relayer: string): Packet { - - channel = provableStore.get(channelPath(packet.sourcePort, packet.sourceChannel)) - // 注意:连接可能已经关闭 - abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability)) - abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier) - - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - // 注意:连接可能已经关闭 - abortTransactionUnless(packet.destPort === channel.counterpartyPortIdentifier) - - // 验证我们确实发送了这个数据包,检查存储 - abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) - === hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp)) - - // 检查对面通道端是否已关闭 - expected = ChannelEnd{CLOSED, channel.order, channel.portIdentifier, - channel.channelIdentifier, channel.connectionHops.reverse(), channel.version} - abortTransactionUnless(connection.verifyChannelState( - proofHeight, - proofClosed, - channel.counterpartyPortIdentifier, - channel.counterpartyChannelIdentifier, - expected - )) - - if channel.order === ORDERED { - // 有序通道:检查 recv 序列是否如声明的那样 - abortTransactionUnless(connection.verifyNextSequenceRecv( - proofHeight, - proof, - packet.destPort, - packet.destChannel, - nextSequenceRecv - )) - // 有序通道:检查是否没有收到数据包 - abortTransactionUnless(nextSequenceRecv <= packet.sequence) - } else - // 无序通道:验证在数据包索引处没有收到收据 - abortTransactionUnless(connection.verifyPacketReceiptAbsence( - proofHeight, - proof, - packet.destPort, - packet.destChannel, - packet.sequence - )) - - // 所有的断言都通过了,我们可以改变状态 - - // 删除我们的承诺 - provableStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) - - if channel.order === ORDERED { - // 有序通道:关闭通道 - channel.state = CLOSED - provableStore.set(channelPath(packet.sourcePort, packet.sourceChannel), channel) - } - - // 返回透明数据包 - return packet -} -``` - -##### 清理状态 - -必须确认数据包才能进行清理。 - -#### 关于竞态条件的探讨 - -##### 同时发生握手尝试 - -如果两台状态机同时彼此发起通道创建握手,或尝试使用相同的标识符,则两者都会失败,必须使用新的标识符。 - -##### 标识符分配 - -在目标链上分配标识符存在不可避免的竞态条件。最好建议模块使用伪随机,无价值的标识符。设法声明另一个模块希望使用的标识符,但是,尽管令人烦恼,中间人却无法在握手期间攻击,因为接收模块必须已经拥有握手用的目标端口。 - -##### 超时/数据包确认 - -数据包超时和数据包确认之间没有竞态条件,因为数据包只能在接收之前检查超过或没超过超时区块高度。 - -##### 握手期间的中间人攻击 - -跨链状态的验证可防止连接握手和通道握手期间的中间人攻击,因为模块已知道所有信息(源客户端、目标客户端、通道等),该信息将在启动握手之前进行确认完成。 - -##### 数据包传输过程中的连接/通道关闭 - -如果在传输数据包时关闭了连接或通道,则数据包将不再被目标链接收,并且在源链上超时。 - -#### 查询通道 - -可以使用`queryChannel`查询通道: - -```typescript -function queryChannel(connId: Identifier, chanId: Identifier): ChannelEnd | void { - return provableStore.get(channelPath(connId, chanId)) -} -``` - -### 属性与不变性 - -- 通道和端口标识符的唯一组合是先到先服务的:分配了一对标示符后,只有拥有相应端口的模块才能在该通道上发送或接收。 -- 假设链在超时窗口后依然有活性,则数据包只传递一次,并且在超时的情况下,只在发送链上超时一次。 -- 通道握手不能受到区块链上的另一个模块或另一个区块链的 IBC 处理程序的中间人攻击。 - -## 向后兼容性 - -不适用。 - -## 向前兼容性 - -数据结构和编码可以在连接或通道级别进行版本控制。通道逻辑与数据包数据格式完全无关,模块可以随时以任何他们喜欢的方式更改。 - -## 示例实现 - -即将到来。 - -## 其他实现 - -即将到来。 - -## 发布历史 - -2019年6月5日-提交草案 - -2019年7月4日-修改无序通道和确认 - -2019年7月16日-更改“多跳”路由未来的兼容性 - -2019年7月29日-修改以处理连接关闭后的超时 - -2019年8月13日-多处修改 - -2019年8月25日-清理 - -## 版权 - -本规范所有内容均采用 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 许可授权。 diff --git a/other_docs/ibc-protocol/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md b/other_docs/ibc-protocol/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md deleted file mode 100644 index cff408b22..000000000 --- a/other_docs/ibc-protocol/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md +++ /dev/null @@ -1,601 +0,0 @@ -# 升级通道 - -### 概要 - -这个标准文档规定了 IBC 实现必须实现的接口和状态机逻辑,以便现存通道在初始连接握手后能够升级。 - -### 动机 - -随着新功能被添加到 IBC,链可能希望在不放弃现有通道的已积累的状态和网络效应的情况下,同时利用新的通道功能。提议的升级协议将允许链重新协商现有通道,这样可以使用新的功能而无需创建新通道,从而保留当前通道之上的所有的数据包状态。 - -### 所需属性 - -- 两条链都必须认同重新协商后的通道参数。 -- 两条链上的通道状态和逻辑应该或者使用旧参数或者新参数,而不能是一个中间状态,例如,应用程序不能运行 v2 逻辑,而其对手方仍在运行 v1 逻辑。 -- 通道升级协议是原子性的,即 - - 要么不成功,然后通道必须回退到原始通道参数; - - 要么成功,然后通道两端必须采用新的通道参数并妥善地处理数据包。 -- 通道升级协议应该具有改变所有通道相关参数的能力;但是通道升级协议不能改变下层的`ConnectionEnd` 。通道升级协议不得修改通道标识符。 - -## 技术规范 - -### 数据结构 - -`ChannelState`和`ChannelEnd`在[ICS-4](./README.md)中定义。为方便读者,在此转载。 `UPGRADE_INIT` , `UPGRADE_TRY`是新增的附加状态,以启用升级功能。 - -```typescript -enum ChannelState { - INIT, - TRYOPEN, - OPEN, - UPGRADE_INIT, - UPGRADE_TRY, -} -``` - -- 提议升级的链应该将通道状态从`OPEN`修改置为`UPGRADE_INIT` -- 接受升级的对方链应将通道状态从`OPEN`修改置为`UPGRADE_TRY` - -```typescript -interface ChannelEnd { - state: ChannelState - ordering: ChannelOrder - counterpartyPortIdentifier: Identifier - counterpartyChannelIdentifier: Identifier - connectionHops: [Identifier] - version: string -} -``` - -通道升级协议不能修改下层客户端或通道标识符。这个所需要的属性意味着只有`ChannelEnd`的某些字段可以被升级协议升级。 - -- `state` :状态由升级协议的握手步骤指定。 - -可被修改的: - -- `version` :版本可以被升级协议修改。在初始通道握手中发生的相同版本协商可用于升级的握手中。 -- `ordering` :升级协议可以修改排序规则。但是,必须满足先前排序是新排序的有效子集。因此,唯一支持的更改是从更严格的排序规则到不太严格的排序。例如,支持从 ORDERED 切换到 UNORDERED,**不支持**从 UNORDERED 切换到 ORDERED。 -- `connectionHops`: connectionHops 可被升级协议修改. - -不能修改的: - -- `counterpartyChannelIdentifier`: 升级协议不能修改对方链的通道标识符 -- `counterpartyPortIdentifier`端口标识符:升级协议不得修改交易对手端口标识符 - -注意:如果升级将任何字段添加到`ChannelEnd` ,这些是默认可修改的,并且可以被有权限发起升级的 Actor(例如链上治理)所任意选择。 - -```typescript -interface UpgradeTimeout { - timeoutHeight: Height - timeoutTimestamp: uint64 -} -``` - -- `timeoutHeight` :超时高度表示在此区块高度上,对方链不能再进行升级握手。此后,两条链将保留其原始通道,而升级握手被中止。 -- `timeoutTimestamp` :超时时间戳表示对方链不能再进行升级握手的时间。此后,两条链将保留其原始通道,而升级握手被中止。 - -timeoutHeight 或 timeoutTimestamp,至少其中之一必须非零。 - -### 储存路径 - -#### 恢复通道路径 - -链必须存储之前的通道端,以便在升级握手失败时可以恢复它。该数据可以存储在私有存储中。 - -```typescript -function restorePath(portIdentifier: Identifier, channelIdentifier: Identifier): Path { - return "channelUpgrade/ports/{portIdentifier}/channels/{channelIdentifier}/restore" -} -``` - -#### 升级错误路径 - -升级错误路径是可以向对方链发出升级错误信号的公共路径。在成功的情况下它不存储任何内容,但在链不接受提议的升级的情况下,它将存储一个哨兵中止值。 - -```typescript -function errorPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path { - return "channelUpgrade/ports/{portIdentifier}/channels/{channelIdentifier}/upgradeError" - -} -``` - -UpgradeError 必须有一个关联的验证成员和非成员函数添加到连接接口,以便对方链可以验证本链是否在 UpgradeError 路径中存储了该错误。 - -```typescript -// 连接的VerifyChannelUpgradeError的方法 -function verifyChannelUpgradeError( - connection: ConnectionEnd, - height: Height, - proof: CommitmentProof, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - upgradeErrorReceipt: []byte, -) { - client = queryClient(connection.clientIdentifier) - path = applyPrefix(connection.counterpartyPrefix, channelErrorPath(counterpartyPortIdentifier, counterpartyChannelIdentifier)) - client.verifyMembership(height, 0, 0, proof, path, upgradeErrorReceipt) -} -``` - -```typescript -// 连接的VerifyChannelUpgradeErrorAbsence的方法 -function verifyChannelUpgradeErrorAbsence( - connection: ConnectionEnd, - height: Height, - proof: CommitmentProof, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, -) { - client = queryClient(connection.clientIdentifier) - path = applyPrefix(connection.counterpartyPrefix, channelErrorPath(counterpartyPortIdentifier, counterpartyChannelIdentifier)) - client.verifyNonMembership(height, 0, 0, proof, path) -} -``` - -#### 超时路径 - -超时路径是升级发起者设置的公共路径,用于确定 TRY 步骤何时超时。它存储了`timeoutHeight`和`timeoutTimestamp` ,此时对方链必须已进入 TRY 步骤。TRY步骤将证明初始链设置的超时数值并确保没有超时。在超时的情况下,对方链证明已经超过了超时时间,并已经在其链上恢复通道。 - -```typescript -function timeoutPath(portIdentifier: Identifier, channelIdentifier: Identifier) Path { - return "channelUpgrade/ports/{portIdentifier}/channelIdentifier/{channelIdentifier}/upgradeTimeout" -} -``` - -超时路径必须在连接接口上具有关联的验证方法,以便对方链证明该链存储了特定的`UpgradeTimeout` 。 - -```typescript -// 连接的VerifyChannelUpgradeTimeout的方法 -function verifyChannelUpgradeTimeout( - connection: ConnectionEnd, - height: Height, - proof: CommitmentProof, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - upgradeTimeout: UpgradeTimeout, -) { - client = queryClient(connection.clientIdentifier) - path = applyPrefix(connection.counterpartyPrefix, channelTimeoutPath(counterpartyPortIdentifier, counterpartyChannelIdentifier)) - client.verifyMembership(height, 0, 0, proof, path, upgradeTimeout) -} -``` - -## 子协议 - -通道升级过程由三个子协议组成: `UpgradeChannelHandshake` 、 `CancelChannelUpgrade`和`TimeoutChannelUpgrade` 。在两条链都同意提议的升级的情况下,升级握手协议应该成功完成并且ChannelEnd应该成功升级。 - -### 实用函数 - -`restoreChannel()`是一个实用函数,它允许链中止正在进行的升级握手,并将`channelEnd`返回到其原始升级前状态,同时还设置`errorReceipt` 。然后,中继器可以向对方链发送`ChanUpgradeCancelMsg` ,以便它也可以将其`channelEnd`恢复到其升级前状态。一旦两个通道端都恢复到升级前的状态,数据包将继续按照其原始连接参数进行处理。 - -```typescript -function restoreChannel() { - // 取消升级 - // 将错误收据写入错误路径并恢复原始通道 - errorReceipt = []byte{1} - provableStore.set(errorPath(portIdentifier, channelIdentifier), errorReceipt) - originalChannel = privateStore.get(restorePath(portIdentifier, channelIdentifier)) - provableStore.set(channelPath(portIdentifier, channelIdentifier), originalChannel) - provableStore.delete(timeoutPath(portIdentifier, channelIdentifier)) - privateStore.delete(restorePath(portIdentifier, channelIdentifier)) - - // 调用模块 onChanUpgradeRestore的回调 - module = lookupModule(portIdentifier) - // 恢复回调不能返回错误,它必须成功恢复应用到升级前的状态 - module.onChanUpgradeRestore( - portIdentifier, - channelIdentifier - ) - // 调用者也应该返回 -} -``` - -### 升级握手 - -升级握手定义了四个数据报文: *ChanUpgradeInit* 、 *ChanUpgradeTry* 、 *ChanUpgradeAck*和*ChanUpgradeConfirm* - -一个成功的协议执行流程如下(注意,所有调用都是通过 ICS 25 的模块进行的): - -发起者 | 数据报文 | 操作的链 | 先前状态(A,B) | 其后状态(A,B) ---- | --- | --- | --- | --- -Actor | `ChanUpgradeInit` | A | (OPEN, OPEN) | (UPGRADE_INIT, OPEN) -Actor | `ChanUpgradeTry` | B | (UPGRADE_INIT, OPEN) | (UPGRADE_INIT, UPGRADE_TRY) -中继器 | `ChanUpgradeAck` | A | (UPGRADE_INIT, UPGRADE_TRY) | (OPEN, UPGRADE_TRY) -中继器 | `ChanUpgradeConfirm` | B | (OPEN, UPGRADE_TRY) | (OPEN, OPEN) - -在两个实现子协议的链之间的升级握手结束时,以下属性成立: - -- 每条链都在运行它们新升级后的通道端,并根据升级后的参数处理升级后的逻辑和状态。 -- 每条链都知道并同意对方升级的通道参数。 - -如果一个链不同意提议的对方链`UpgradedChannel` ,它可以通过将错误收据写入`errorPath`并恢复原始通道来中止升级握手。错误收据可以有任意字节但必须是非空的。 - -`errorPath(id) => error_receipt` - -之后中继器可以向对方链提交`ChanUpgradeCancelMsg` 。收到此消息后,链必须验证对方链是否在其`UpgradeError`中写入了非空错误收据,如果成功,它将恢复其原始通道,从而取消升级。 - -如果一个升级消息在指定的超时后到达,则消息不能成功执行。中继器可以再次在`ChanUpgradeTimeoutMsg`中提交证明,以便对方链取消升级并恢复其原始通道。 - -```typescript -function chanUpgradeInit( - portIdentifier: Identifier, - channelIdentifier: Identifier, - proposedUpgradeChannel: ChannelEnd, - counterpartyTimeoutHeight: Height, - counterpartyTimeoutTimestamp: uint64, -) { - // 当前通道必须是 OPEN - currentChannel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless(channel.state == OPEN) - - // 如果修改了不可修改的字段,则中止交易 - // 升级后的通道状态必须是 `UPGRADE_INIT` - // 注意:默认情况下,任何添加的字段都是可修改的。 - abortTransactionUnless( - proposedUpgradeChannel.state == UPGRADE_INIT && - proposedUpgradeChannel.counterpartyPortIdentier == currentChannel.counterpartyPortIdentifier && - proposedUpgradeChannel.counterpartyChannelIdentifier == currentChannel.counterpartyChannelIdentifier - ) - - // 当前排序必须是数据包按照建议的合法排序 - // 例如 ORDERED -> UNORDERED, ORDERED -> DAG - abortTransactionUnless( - currentChannel.ordering.subsetOf(proposedUpgradeChannel.ordering) - ) - - // 超时高度或时间戳必须非零 - abortTransactionUnless(counterpartyTimeoutHeight != 0 || counterpartyTimeoutTimestamp != 0) - - upgradeTimeout = UpgradeTimeout{ - timeoutHeight: counterpartyTimeoutHeight, - timeoutTimestamp: counterpartyTimeoutTimestamp, - } - - // 调用模块 onChanUpgradeInit的回调 - module = lookupModule(portIdentifier) - version, err = module.onChanUpgradeInit( - proposedUpgradeChannel.ordering, - proposedUpgradeChannel.connectionHops, - portIdentifier, - channelIdentifer, - proposedUpgradeChannel.counterpartyPortIdentifer, - proposedUpgradeChannel.counterpartyChannelIdentifier, - proposedUpgradeChannel.version - ) - // 如果回调返回错误,则中止交易 - abortTransactionUnless(err != nil) - - // 如果通道版本被修改,将通道版本替换为应用程序返回的版本 - proposedUpgradeChannel.version = version - - provableStore.set(timeoutPath(portIdentifier, channelIdentifier), upgradeTimeout) - provableStore.set(channelPath(portIdentifier, channelIdentifier), proposedUpgradeChannel) - privateStore.set(restorePath(portIdentifier, channelIdentifier), currentChannel) -} -``` - -注意:如何为`ChanUpgradeInit`函数提供访问控制取决于各个实现。例如链上治理、获得许可的 actor、DAO 等。对方链的访问控制应提供超时值的选择,即如果对方链的`UpgradeTry`由链上治理所把关,则超时值应该很大。 - -```typescript -function chanUpgradeTry( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannel: ChannelEnd, - proposedUpgradeChannel: ChannelEnd, - timeoutHeight: Height, - timeoutTimestamp: uint64, - proofChannel: CommitmentProof, - proofUpgradeTimeout: CommitmentProof, - proofHeight: Height -) { - // 当前通道必须是 OPEN 或 UPGRADE_INIT (crossing hellos) - currentChannel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless(currentChannel.state == OPEN || currentChannel.state == UPGRADE_INIT) - - // 如果修改了不可修改的字段,则中止交易 - // 升级后的通道状态必须是 `UPGRADE_TRY` - // 注意:默认情况下,任何添加的字段都是可修改的。 - abortTransactionUnless( - proposedUpgradeChannel.state == UPGRADE_TRY && - proposedUpgradeChannel.counterpartyPortIdentifier == currentChannel.counterpartyPortIdentifier && - proposedUpgradeChannel.counterpartyChannelIdentifier == currentChannel.counterpartyChannelIdentifier - ) - - // 当前排序必须是有效数据包按照建议的排序 - // 例如 ORDERED -> UNORDERED, ORDERED -> DAG - abortTransactionUnless( - currentChannel.ordering.subsetOf(proposedUpgradeChannel.ordering) - ) - - // 构造 upgradeTimeout 以便可以针对交易对手状态进行验证 - upgradeTimeout = UpgradeTimeout{ - timeoutHeight: timeoutHeight, - timeoutTimestamp: timeoutTimestamp, - } - - // 获取底层连接以进行证明验证 - connection = getConnection(currentChannel.connectionIdentifier) - - // 验证交易对手状态的证明 - abortTransactionUnless(verifyChannelState(connection, proofHeight, proofChannel, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) - abortTransactionUnless(verifyChannelUpgradeTimeout(connection, proofHeight, proofUpgradeTimeout, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier, upgradeTimeout)) - - if currentChannel.state == UPGRADE_INIT { - // 如果有交叉 hello,即在两个 channelEnds 上都调用了 UpgradeInit, - // 那么我们要保证对方提出的Upgrade和currentChannel一致 - // 除通道状态外(升级通道将处于 UPGRADE_TRY,当前通道将处于 UPGRADE_INIT) - // 如果双方提议的升级不兼容,那么我们将恢复通道并取消升级。 - currentChannel.state = UPGRADE_TRY - if !currentChannel.IsEqual(proposedUpgradeChannel) { - restoreChannel() - return - } - } else if currentChannel.state == OPEN { - // 这是此链上升级握手的第一条消息,因此我们必须将原始通道存储在恢复路径中 - // 以防我们稍后需要恢复通道。 - privateStore.set(restorePath(portIdentifier, channelIdentifier), currentChannel) - } else { - // 如果当前通道的状态不是:UPGRADE_INIT 或 OPEN,则中止交易 - abortTransactionUnless(false) - } - - // 超时高度或时间戳必须非零 - // 如果升级功能是在 TRY 链上实现的,那么中继器可能会在超时后提交一个 TRY 交易。 - // 这将恢复执行链上的通道,并允许交易对手使用 ChanUpgradeCancelMsg 恢复他们的通道。 - if timeoutHeight == 0 && timeoutTimestamp == 0 { - restoreChannel() - return - } - - - // 不能超过交易对手指定的超时时间 - if (currentHeight() > timeoutHeight && timeoutHeight != 0) || - (currentTimestamp() > timeoutTimestamp && timeoutTimestamp != 0) { - restoreChannel() - return - } - - // 两个通道端必须相互兼容。 - // 此函数未指定,因为它将取决于新通道的特定结构。 - // 实现的责任是确保验证提议两边的新通道都根据选择的新版本正确构造。 - if !IsCompatible(counterpartyChannel, proposedUpgradeChannel) { - restoreChannel() - return - } - - // 调用模块 onChanUpgradeTry的回调 - module = lookupModule(portIdentifier) - version, err = module.onChanUpgradeTry( - proposedUpgradeChannel.ordering, - proposedUpgradeChannel.connectionHops, - portIdentifier, - channelIdentifer, - proposedUpgradeChannel.counterpartyPortIdentifer, - proposedUpgradeChannel.counterpartyChannelIdentifier, - proposedUpgradeChannel.version - ) - // 如果回调返回错误,则恢复通道 - if err != nil { - restoreChannel() - return - } - - // 如果通道版本被修改,将通道版本替换为应用程序返回的版本 - proposedUpgradeChannel.version = version - - provableStore.set(channelPath(portIdentifier, channelIdentifier), proposedUpgradeChannel) -} -``` - -注意:如何为`ChanUpgradeTry`函数提供访问控制取决于各个实现。例如链上治理、许可的 actor、DAO 等。链可以决定是否有许可**或**无许可的`UpgradeTry` 。在许可的情况下,两个链都必须明确同意升级;在无许可的情况下,一条链发起升级,另一条链默认同意升级。在无许可的情况下,中继器可以提交`ChanUpgradeTry`数据报文。 - -```typescript -function chanUpgradeAck( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannel: ChannelEnd, - proofChannel: CommitmentProof, - proofHeight: Height -) { - // 当前通道在 UPGRADE_INIT 或 UPGRADE_TRY 中 (crossing hellos) - currentChannel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless(currentChannel.state == UPGRADE_INIT || currentChannel.state == UPGRADE_TRY) - - // 获取底层连接以进行证明验证 - connection = getConnection(currentChannel.connectionIdentifier) - - // 验证交易对手状态的证明 - abortTransactionUnless(verifyChannelState(connection, proofHeight, proofChannel, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) - - // 交易对手必须处于 TRY 状态 - if counterpartyChannel.State != UPGRADE_TRY { - restoreChannel() - return - } - - // 验证通道是否相互兼容 - // 这也将检查交易对手选择的版本是否有效 - // 此函数未指定,因为它将取决于新通道的特定结构。 - // 实现的责任是确保验证提议两边的新通道都根据选择的新版本正确构造。 - if !IsCompatible(counterpartyChannel, channel) { - restoreChannel() - return - } - - // 调用模块 onChanUpgradeAck的回调 - module = lookupModule(portIdentifier) - err = module.onChanUpgradeAck( - portIdentifier, - channelIdentifier, - counterpartyChannel.channelIdentifier, - counterpartyChannel.version - ) - // 如果回调返回错误,则恢复通道 - if err != nil { - restoreChannel() - return - } - - // 升级完成 - // 将通道设置为 OPEN 并删除不必要的状态 - currentChannel.state = OPEN - provableStore.set(channelPath(portIdentifier, channelIdentifier), currentChannel) - provableStore.delete(timeoutPath(portIdentifier, channelIdentifier)) - privateStore.delete(restorePath(portIdentifier, channelIdentifier)) -} -``` - -```typescript -function chanUpgradeConfirm( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannel: ChannelEnd, - proofChannel: CommitmentProof, - proofUpgradeError: CommitmentProof, - proofHeight: Height, -) { - // 当前通道在 UPGRADE_TRY - currentChannel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless(channel.state == UPGRADE_TRY) - - // 交易对手必须处于 OPEN 状态 - abortTransactionUnless(counterpartyChannel.State == OPEN) - - // 获取底层连接以进行证明验证 - connection = getConnection(currentChannel.connectionIdentifier) - - // 验证交易对手状态的证明 - abortTransactionUnless(verifyChannelState(connection, proofHeight, proofChannel, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) - // 验证交易对手没有通过写入升级错误来中止升级握手 - // upgradeError 路径必须有缺失值 - abortTransactionUnless(verifyUpgradeChannelErrorAbsence(connection, proofHeight, proofUpgradeError, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier)) - - // 调用模块 onChanUpgradeConfirm的回调 - module = lookupModule(portIdentifier) - // 由于交易对手升级成功,确认回调不能返回错误 - module.onChanUpgradeConfirm( - portIdentifer, - channelIdentifier - ) - - // 升级完成 - // 将通道设置为 OPEN 并删除不必要的状态 - currentChannel.state = OPEN - provableStore.set(channelPath(portIdentifier, channelIdentifier), currentChannel) - provableStore.delete(timeoutPath(portIdentifier, channelIdentifier)) - privateStore.delete(restorePath(portIdentifier, channelIdentifier)) -} -``` - -### 取消升级过程 - -在升级握手期间,链可以通过将错误收据写入错误路径并将原始通道恢复到`OPEN`来取消升级。然后,对方链也必须恢复其与`OPEN`的通道。 - -```typescript -function cancelChannelUpgrade( - portIdentifier: Identifier, - channelIdentifier: Identifier, - errorReceipt: []byte, - proofUpgradeError: CommitmentProof, - proofHeight: Height, -) { - // 当前通道在 UPGRADE_INIT 或 UPGRADE_TRY - currentChannel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless(channel.state == UPGRADE_INIT || channel.state == UPGRADE_TRY) - - abortTransactionUnless(!isEmpty(errorReceipt)) - - // 获取底层连接以进行证明验证 - connection = getConnection(currentChannel.connectionIdentifier) - // 验证非空错误回执写入 upgradeError 路径 - abortTransactionUnless(verifyChannelUpgradeError(connection, proofHeight, proofUpgradeError, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier, errorReceipt)) - - // 取消升级 - // 并恢复原始连接 - // 删除不必要的状态 - originalChannel = privateStore.get(restorePath(portIdentifier, channelIdentifier)) - provableStore.set(channelPath(portIdentifier, channelIdentifier), originalChannel) - - // 删除辅助升级状态 - provableStore.delete(timeoutPath(portIdentifier, channelIdentifier)) - privateStore.delete(restorePath(portIdentifier, channelIdentifier)) - - // 调用模块 onChanUpgradeRestore的回调 - module = lookupModule(portIdentifier) - // 由于交易对手升级成功,恢复回调不能返回错误 - module.onChanUpgradeRestore( - portIdentifer, - channelIdentifier - ) -} -``` - -### 超时升级过程 - -如果 UPGRADE_TRY 交易根本无法传递给对方链,则通道升级过程可能会在 UPGRADE_TRY 上无限期停止;例如,对方链上可能未启用升级功能。 - -在这种情况下,我们不希望发起链无限期地停留在`UPGRADE_INIT`步骤中。因此, `UpgradeInit`消息将包含`TimeoutHeight`和`TimeoutTimestamp` 。如果指定的超时时间已经过去,则对方链应拒绝`UpgradeTry`消息。 - -此后,中继器必须向发起链提交`UpgradeTimeout`消息,证明对方链仍处于其原始状态。如果证明成功,则发起链也应恢复其原始通道并取消升级。 - -```typescript -function timeoutChannelUpgrade( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannel: ChannelEnd, - proofChannel: CommitmentProof, - proofHeight: Height, -) { - // 当前通道必须在 UPGRADE_INIT - currentChannel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnles(currentChannel.state == UPGRADE_INIT) - - upgradeTimeout = provableStore.get(timeoutPath(portIdentifier, channelIdentifier)) - - // 证明必须在超时后从某一高度进行。必须定义 timeoutHeight 或 timeoutTimestamp。 - // 如果定义了 timeoutHeight 并且证明来自于 timeout 高度之前,则中止交易 - abortTransactionUnless(upgradeTimeout.timeoutHeight.IsZero() || proofHeight >= upgradeTimeout.timeoutHeight) - // 如果定义了 timeoutTimestamp,那么从证明高度开始的共识时间必须大于 timeout 时间戳 - connection = queryConnection(currentChannel.connectionIdentifier) - abortTransactionUnless(upgradeTimeout.timeoutTimestamp.IsZero() || getTimestampAtHeight(connection, proofHeight) >= upgradeTimeout.timestamp) - - // 获取底层连接以进行证明验证 - connection = getConnection(currentChannel.connectionIdentifier) - - // 必须证明交易对手通道仍处于 OPEN 状态或 UPGRADE_INIT 状态 (crossing hellos) - abortTransactionUnless(counterpartyChannel.State === OPEN || counterpartyChannel.State == UPGRADE_INIT) - abortTransactionUnless(verifyChannelState(connection, proofHeight, proofChannel, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) - - if counterpartyChannel.State == UPGRADE_INIT { - // 如果对方在 UPGRADE_INIT 并且我们已经超时,那么我们应该写错误回执 - // 确保交易对手也中止握手并返回原始状态 - // 将错误收据写入错误路径 - errorReceipt = []byte{1} - provableStore.set(errorPath(portIdentifier, channelIdentifier), errorReceipt) - } - - // 我们必须恢复通道,因为超时验证已经通过 - originalChannel = privateStore.get(restorePath(portIdentifier, channelIdentifier)) - provableStore.set(channelPath(portIdentifier, channelIdentifier), originalChannel) - - // 删除辅助升级状态 - provableStore.delete(timeoutPath(portIdentifier, channelIdentifier)) - privateStore.delete(restorePath(portIdentifier, channelIdentifier)) - - // 调用模块 onChanUpgradeRestore的回调 - module = lookupModule(portIdentifier) - // 由于交易对手升级成功,恢复回调不能返回错误 - module.onChanUpgradeRestore( - portIdentifer, - channelIdentifier - ) -} -``` - -注意,超时逻辑仅适用于 INIT 步骤。这是为了防止升级链在交易对手无法成功执行 TRY 时卡在非 OPEN 状态。一旦 TRY 步骤成功,则保证双方都启用了升级功能。活性不再是一个问题,因为我们可以等到活性恢复后再执行 ACK 步骤,这必将会使通道进入 OPEN 状态(成功升级或回滚)。 - -TRY 链将接收对方链在 INIT 上选择的超时参数,以便它可以拒绝在指定的超时时间后收到的任何 TRY 消息。这可以防止握手进入无效状态,在这种状态下,INIT 链成功处理超时并恢复其与`OPEN`的通道,而 TRY 链将在之后的一个时间点成功写入`TRY`状态。 - -### 迁移 - -链可能必须要去更新其内部状态以与新升级的通道保持一致。在这种情况下,迁移的处理程序应该是升级过程之前链二进制文件的一部分,以便升级成功后链可以正确迁移其状态。如果一个升级需要迁移处理程序但该程序不可用,则执行链必须拒绝升级,以免进入无效状态。这种状态迁移不会被对方链验证,因为它只是假设如果通道升级到特定的通道版本,那么对方链的辅助状态也将被更新以匹配给定通道版本的规范。迁移只能在升级成功完成并且新通道`OPEN` (即在`ACK`和`CONFIRM`上)后运行。 diff --git a/other_docs/ibc-protocol/spec/core/ics-004-channel-and-packet-semantics/channel-state-machine.png b/other_docs/ibc-protocol/spec/core/ics-004-channel-and-packet-semantics/channel-state-machine.png deleted file mode 100644 index e0a1f885e..000000000 Binary files a/other_docs/ibc-protocol/spec/core/ics-004-channel-and-packet-semantics/channel-state-machine.png and /dev/null differ diff --git a/other_docs/ibc-protocol/spec/core/ics-004-channel-and-packet-semantics/dataflow.png b/other_docs/ibc-protocol/spec/core/ics-004-channel-and-packet-semantics/dataflow.png deleted file mode 100644 index 797354ed2..000000000 Binary files a/other_docs/ibc-protocol/spec/core/ics-004-channel-and-packet-semantics/dataflow.png and /dev/null differ diff --git a/other_docs/ibc-protocol/spec/core/ics-004-channel-and-packet-semantics/packet-state-machine.png b/other_docs/ibc-protocol/spec/core/ics-004-channel-and-packet-semantics/packet-state-machine.png deleted file mode 100644 index 5c96164ea..000000000 Binary files a/other_docs/ibc-protocol/spec/core/ics-004-channel-and-packet-semantics/packet-state-machine.png and /dev/null differ diff --git a/other_docs/ibc-protocol/spec/core/ics-004-channel-and-packet-semantics/packet-transit.png b/other_docs/ibc-protocol/spec/core/ics-004-channel-and-packet-semantics/packet-transit.png deleted file mode 100644 index 35cd8ca01..000000000 Binary files a/other_docs/ibc-protocol/spec/core/ics-004-channel-and-packet-semantics/packet-transit.png and /dev/null differ diff --git a/other_docs/ibc-protocol/spec/core/ics-005-port-allocation/README.md b/other_docs/ibc-protocol/spec/core/ics-005-port-allocation/README.md deleted file mode 100644 index 49f40b27f..000000000 --- a/other_docs/ibc-protocol/spec/core/ics-005-port-allocation/README.md +++ /dev/null @@ -1,233 +0,0 @@ ---- -ics: '5' -title: 端口分配 -stage: 草案 -requires: '24' -required-by: '4' -category: IBC/TAO -kind: 接口 -author: Christopher Goes -created: '2019-06-20' -modified: '2019-08-25' ---- - -## 概要 - -该标准指定了端口分配系统,模块可以通过该系统绑定到由 IBC 处理程序分配的唯一命名的端口。 然后可以将端口用于创建通道,并且可以被最初绑定到端口的模块转移或释放。 - -### 动机 - -区块链间通信协议旨在促进模块之间的通信,其中模块是独立的,可能相互不信任,在自治账本上执行的自成一体的代码。为了提供所需的端到端语义,IBC 处理程序必须实现对特定模块许可的通道。 该规范定义了实现该模型的*端口分配和所有权*系统。 - -可能会出现关于哪种模块逻辑可以绑定到特定端口名称的约定,例如“bank”用于处理同质通证,“staking”用于链间抵押的。 这类似于 HTTP 服务器的 80 端口的惯用用法——该协议无法强制将特定的模块逻辑实际上绑定到惯用端口,因此用户必须自己检查。可以创建具有伪随机标识符的临时端口以用于临时协议处理。 - -模块可以绑定到多个端口,并连接到不同的计算机上另一个模块绑定的多个端口。任何数量的(唯一标识的)通道都可以同时使用一个端口。通道是在两个端口之间的端到端的,每个端口必须事先已被模块绑定,然后模块将控制该通道的一端。 - -(可选)主机状态机可以选择将端口绑定通过生成专门用于绑定端口的能力键的方式暴露给特别允许的模块管理器 。然后模块管理器可以使用自定义规则集控制模块可以绑定到哪些端口,和仅被管理器验证后端口名称和模块后才转移端口到其他模块。路由模块可以扮演这个角色(请参阅 [ICS 26](../ics-026-routing-module) )。 - -### 定义 - -`Identifier` , `get` , `set`和`delete`的定义与 [ICS 24](../ics-024-host-requirements) 中的相同。 - -*端口*是一种特殊的标识符,用于许可模块创建和使用通道。 - -*模块*是主机状态机的子组件,独立于 IBC 处理程序。例如以太坊智能合约和 Cosmos SDK 和 Substrate 的模块。 除了主机状态机可以使用对象能力或源身份验证来访问模块的许可端口的能力之外,IBC 规范不对模块功能进行任何假设。 - -### 所需属性 - -- 一个模块绑定到端口后,其他模块将无法使用该端口,直到该模块释放它 -- 一个模块可以选择释放端口或将其转移到另一个模块 -- 单个模块可以一次绑定到多个端口 -- 分配端口时,先到先得,先绑定先服务,链可以在第一次启动时将已知模块绑定“保留”端口。 - -作为一个有帮助的比较,以下 TCP 的类比大致准确: - -IBC 概念 | TCP/IP 概念 | 差异性 ---- | --- | --- -IBC | TCP | 很多,请参阅描述 IBC 的体系结构文档 -端口(例如“bank”) | 端口(例如 80) | 没有低位数字的保留端口,端口为字符串 -模块(例如“bank”) | 应用程序(例如 Nginx) | 特定于应用 -客户端 | - | 没有直接的类比,有点像 L2 路由,也有点像 TLS -连接 | - | 没有直接的类比,合并进了 TCP 的连接 -通道 | 连接 | 可以同时打开或关闭任意数量的通道 - -## 技术规范 - -### 数据结构 - -主机状态机务必支持对象能力引用或模块的源认证。 - -在前一种支持对象能力的情况下,IBC 处理程序必须支持生成唯一的,不透明的*对象能力*引用的能力,它可以传递给某个模块,而其他模块则无法复制。两个示例是 Cosmos SDK( [参考](https://github.com/cosmos/cosmos-sdk/blob/97eac176a5d533838333f7212cbbd79beb0754bc/store/types/store.go#L275) )中使用的存储密钥和 Agoric 的 Javascript 运行时中使用的对象引用( [参考](https://github.com/Agoric/SwingSet) )。 - -```typescript -type CapabilityKey object -``` - -`newCapability`必须使用一个名称生成唯一的能力键,这样该名称将本地映射到能力键,并且以后可以与`getCapability`一起使用。 - -```typescript -function newCapability(name: string): CapabilityKey { - // 由主状态机提供, 例如 Cosmos SDK 的 ADR 3 / ScopedCapabilityKeeper -} -``` - -`authenticateCapability`必须接受一个名称和能力键,并检查名称是否在本地映射到所提供的能力。该名称可以是不受信任的用户输入。 - -```typescript -function authenticateCapability(name: string, capability: CapabilityKey): bool { - // 由主状态机提供, 例如 Cosmos SDK 的 ADR 3 / ScopedCapabilityKeeper -} -``` - -`claimCapability`必须接受一个名称和能力键(由另一个模块提供),并将其本地映射到能力,“声明”该能力以备将来使用。 - -```typescript -function claimCapability(name: string, capability: CapabilityKey) { - // // 由主状态机提供, 例如 ADR 3 / ScopedCapabilityKeeper in Cosmos SDK -} -``` - -`getCapability`必须允许模块查找其先前创建的能力或按名称声明的能力。 - -```typescript -function getCapability(name: string): CapabilityKey { - // 由主状态机提供, 例如 Cosmos SDK 的 ADR 3 / ScopedCapabilityKeeper -} -``` - -`releaseCapability`必须允许模块释放其拥有的能力。 - -```typescript -function releaseCapability(capability: CapabilityKey) { - // 由主状态机提供, 例如 Cosmos SDK 的 ADR 3 / ScopedCapabilityKeeper -} -``` - -在后一种源身份验证的情况下,IBC 处理程序必须具有安全读取调用模块的*源标识符*的能力, 主机状态机中每个模块的唯一字符串,不能由该模块更改或由另一个模块伪造。 一个示例是以太坊( [参考](https://ethereum.github.io/yellowpaper/paper.pdf) )使用的智能合约地址。 - -```typescript -type SourceIdentifier string -``` - -```typescript -function callingModuleIdentifier(): SourceIdentifier { - // 由主状态机提供, 例如 以太坊中的合约地址 -} -``` - -然后按以下方式实现`newCapability` , `authenticateCapability` , `claimCapability` , `getCapability`和`releaseCapability` : - -```typescript -function newCapability(name: string): CapabilityKey { - return callingModuleIdentifier() -} -``` - -```typescript -function authenticateCapability(name: string, capability: CapabilityKey) { - return callingModuleIdentifier() === name -} -``` - -```typescript -function claimCapability(name: string, capability: CapabilityKey) { - // 无操作 -} -``` - -```typescript -function getCapability(name: string): CapabilityKey { - // 实际没有用到 - return nil -} -``` - -```typescript -function releaseCapability(capability: CapabilityKey) { - // 无操作 -} -``` - -#### 储存路径 - -`portPath`接受一个`Identifier`参数并返回存储路径,在该路径下应存储与端口关联的对象能力引用或所有者模块标识符。 - -```typescript -function portPath(id: Identifier): Path { - return "ports/{id}" -} -``` - -### 子协议 - -#### 标识符验证 - -端口的所有者模块标识符存储在唯一的`Identifier`前缀下。 可以提供验证函数`validatePortIdentifier` 。 - -```typescript -type validatePortIdentifier = (id: Identifier) => boolean -``` - -如果未提供,默认的`validatePortIdentifier`函数将始终返回`true` 。 - -#### 绑定到端口 - -IBC 处理程序必须实现`bindPort` 。 `bindPort`绑定到未分配的端口,如果该端口已被分配,则绑定失败。 - -如果主机状态机未实现特殊的模块管理器来控制端口分配,则`bindPort`应该对所有模块都可用。否则`bindPort`应该只能由模块管理器调用。 - -```typescript -function bindPort(id: Identifier): CapabilityKey { - abortTransactionUnless(validatePortIdentifier(id)) - abortTransactionUnless(getCapability(portPath(id)) === null) - capability = newCapability(portPath(id)) - return capability -} -``` - -#### 转让端口所有权 - -如果主机状态机支持对象能力,则不需要增加此协议,因为端口的引用承载了能力。 - -#### 释放端口 - -IBC 处理程序必须实现`releasePort`函数,该函数允许模块释放端口,以便其他模块随后可以绑定到该端口。 - -`releasePort`应对所有模块均可用。 - -> 警告:释放端口将允许其他模块绑定到该端口,并可能拦截传入的通道创建握手请求。仅在安全的情况下,模块才应释放端口。 - -```typescript -function releasePort(capability: CapabilityKey) { - abortTransactionUnless(authenticateCapability(portPath(id), capability)) - releaseCapability(capability) -} -``` - -### 属性与不变性 - -- 默认情况下,端口标识符是遵循“先到先服务”原则的:模块绑定到端口后,只有该模块才能使用该端口,直到模块转移或释放它为止。模块管理器可以实现自定义逻辑,以覆盖此逻辑。 - -## 向后兼容性 - -不适用。 - -## 向前兼容性 - -端口绑定不是线路协议(wire protocol),因此只要所有权语义不受影响,接口就可以在单独的链上独立更改。 - -## 示例实现 - -即将到来。 - -## 其他实现 - -即将到来。 - -## 历史 - -2019年6月29日-初稿 - -## 版权 - -本规范所有内容均采用 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 许可授权。 diff --git a/other_docs/ibc-protocol/spec/core/ics-023-vector-commitments/README.md b/other_docs/ibc-protocol/spec/core/ics-023-vector-commitments/README.md deleted file mode 100644 index cd8ecb5e8..000000000 --- a/other_docs/ibc-protocol/spec/core/ics-023-vector-commitments/README.md +++ /dev/null @@ -1,294 +0,0 @@ ---- -ics: '23' -title: 向量承诺 -stage: 草案 -required-by: 2, 24 -category: IBC/TAO -kind: 接口 -author: Christopher Goes -created: '2019-04-16' -modified: '2019-08-25' ---- - -## 概要 - -*向量承诺*是一种构造,它对向量中任何索引和元素的成员资格/非成员资格的短证明产生恒定大小的绑定承诺。 本规范列举了 IBC 协议中使用的承诺构造所需的函数和特性。特别是,IBC 中使用的承诺必须具有*位置约束力* :它们必须能够证明在特定位置(索引)的值存在或不存在。 - -### 动机 - -为了提供可以在另一条链上验证的一条链上发生的特定状态转换的保证,IBC 需要一种有效的密码构造来证明在状态的特定路径上包含或不包含特定值。 - -### 定义 - -向量承诺的*管理者*是具有在承诺中添加或删除条目的能力和责任的参与者。通常,这将是区块链的状态机。 - -*证明者*是负责生成包含或不包含特定元素的证明的参与者。通常,这将是一个中继器(请参阅 [ICS 18](../../relayer/ics-018-relayer-algorithms) )。 - -*验证者*是检查证明来验证承诺的管理者是否添加了特定元素的参与者。通常,这将是在另一条链上运行的 IBC 处理程序(实现 IBC 的模块)。 - -使用特定的*路径*和*值*类型实例化承诺,它们的类型假定为任意可序列化的数据。 - -一个*可忽略的函数*是一个比每个正多项式的倒数增长更慢的函数,[如此](https://en.wikipedia.org/wiki/Negligible_function)处所定义。 - -### 所需属性 - -本文档仅定义所需的属性,而不是具体的实现——请参见下文中的“属性”。 - -## 技术规范 - -下面我们定义一个行为和数据类型的概述。有关数据类型定义,请查看[conio/ics23](https://github.com/confio/ics23/blob/master/proofs.proto)代码库。 - -### 数据类型 - -承诺构造必须指定以下数据类型,这些数据类型可以是不透明的(不需要外部检视),但必须是可序列化的: - -#### 承诺状态 - -`CommitmentState`是承诺的完整状态,将由管理器存储。 - -```typescript -type CommitmentState = object -``` - -#### 承诺根 - -`CommitmentRoot`确保一个特定的承诺状态,并且应为恒定大小。 - -在状态大小恒定的某些承诺构造中, `CommitmentState`和`CommitmentRoot`可以是同一类型。 - -```typescript -type CommitmentRoot = object -``` - -#### 承诺路径 - -`CommitmentPath`是用于验证承诺证明的路径,该路径可以是任意结构化对象(由承诺类型定义)。它必须由通过`applyPrefix` (定义如下)计算出来。 - -```typescript -type CommitmentPath = object -``` - -#### 前缀 - -`CommitmentPrefix`定义了承诺证明的存储前缀。它在路径传递给证明验证函数之前应用于路径。 - -```typescript -type CommitmentPrefix = object -``` - -函数`applyPrefix`从参数构造一个新的提交路径。它在前缀参数的上下文中解释路径参数。 - -对于两个`(prefix, path)`元组, `applyPrefix(prefix, path)`必须仅在元组元素相等时才返回相同的键。 - -`applyPrefix`必须按`Path`来实现,因为`Path`可以具有不同的具体结构。 `applyPrefix`可以接受多种`CommitmentPrefix`类型。 - -`applyPrefix`返回的`CommitmentPath`不需要是可序列化的(例如,它可能是树节点标识符的列表),但它需要可以被比较是否相等。 - -```typescript -type applyPrefix = (prefix: CommitmentPrefix, path: Path) => CommitmentPath -``` - -#### 证明 - -一个`CommitmentProof`证明一个元素或一组元素的成员资格或非成员资格,可以与已知的承诺根一起验证。证明应是简洁的。 - -```typescript -type CommitmentProof = object -``` - -### 所需函数 - -承诺构造必须提供以下函数,这些函数在路径上定义为可序列化的对象,在值上定义为字节数组: - -```typescript -type Path = string - -type Value = []byte -``` - -#### 初始化 - -`generate`函数从一个路径到值的映射(可能为空)初始化承诺的状态。 - -```typescript -type generate = (initial: Map) => CommitmentState -``` - -#### 根计算 - -`calculateRoot`函数计算承诺状态的恒定大小的承诺,可用于验证证明。 - -```typescript -type calculateRoot = (state: CommitmentState) => CommitmentRoot -``` - -#### 添加和删除元素 - -`set`函数将承诺中的一个路径设置为值。 - -```typescript -type set = (state: CommitmentState, path: Path, value: Value) => CommitmentState -``` - -`remove`函数从承诺中删除路径和其关联值。 - -```typescript -type remove = (state: CommitmentState, path: Path) => CommitmentState -``` - -#### 证明生成 - -`createMembershipProof`函数生成一个证明,证明特定承诺路径已被设置为承诺中的特定值。 - -```typescript -type createMembershipProof = (state: CommitmentState, path: CommitmentPath, value: Value) => CommitmentProof -``` - -`createNonMembershipProof`函数生成一个证明,证明承诺路径尚未设置为任何值。 - -```typescript -type createNonMembershipProof = (state: CommitmentState, path: CommitmentPath) => CommitmentProof -``` - -#### 证明验证 - -`verifyMembership`函数验证在承诺中已将路径设置为特定值的证明。 - -```typescript -type verifyMembership = (root: CommitmentRoot, proof: CommitmentProof, path: CommitmentPath, value: Value) => boolean -``` - -`verifyNonMembership`函数验证在承诺中尚未将路径设置为任何值的证明。 - -```typescript -type verifyNonMembership = (root: CommitmentRoot, proof: CommitmentProof, path: CommitmentPath) => boolean -``` - -### 可选函数 - -承诺构造可以提供以下函数: - -`batchVerifyMembership`函数验证在承诺中已将多个路径设置为特定值的证明。 - -```typescript -type batchVerifyMembership = (root: CommitmentRoot, proof: CommitmentProof, items: Map) => boolean -``` - -`batchVerifyNonMembership`函数可验证证明在承诺中尚未将多个路径设置为任何值的证明。 - -```typescript -type batchVerifyNonMembership = (root: CommitmentRoot, proof: CommitmentProof, paths: Set) => boolean -``` - -如果定义这些函数,必须和使用`verifyMembership`和`verifyNonMembership`联合在一起的结果相同(效率可能有所不同): - -```typescript -batchVerifyMembership(root, proof, items) === - all(items.map((item) => verifyMembership(root, proof, item.path, item.value))) -``` - -```typescript -batchVerifyNonMembership(root, proof, items) === - all(items.map((item) => verifyNonMembership(root, proof, item.path))) -``` - -如果批量验证是可行的并且比单独验证每个元素的证明更有效,则承诺构造应定义批量验证函数。 - -### 属性与不变性 - -承诺必须是*完整的*,*合理的*和*有位置约束的*。这些属性是相对于安全性参数`k`定义的,此安全性参数必须由管理者,证明者和验证者达成一致(并且对于承诺算法通常是恒定的)。 - -#### 完整性 - -承诺证明必须是*完整的* :已添加到承诺中的路径/值映射始终可以被证明已包含在内,未包含的路径始终可以被证明已被排除,除非是`k`定义的可以忽略的概率。 - -对于最后一个设置承诺`acc`中的值`value`的任何前缀`prefix`和任何路径`path`, - -```typescript -root = getRoot(acc) -proof = createMembershipProof(acc, applyPrefix(prefix, path), value) -``` - -``` -Probability(verifyMembership(root, proof, applyPrefix(prefix, path), value) === false) negligible in k -``` - -对于没有在承诺`acc`中设置的任何前缀`prefix`和任何路径`path` ,对于`proof`的所有值和`value`的所有值的 - -```typescript -root = getRoot(acc) -proof = createNonMembershipProof(acc, applyPrefix(prefix, path)) -``` - -``` -Probability(verifyNonMembership(root, proof, applyPrefix(prefix, path)) === false) negligible in k -``` - -#### 合理性 - -承诺证明必须是*合理的* :除非在可配置安全性参数`k`概率下可以忽略不计,否则不能将未添加到承诺中的路径/值映射证明为已包含,或者将已经添加到承诺中的路径证明为已排除。 - -对于最后一个设置值`value`在承诺`acc`中的任何前缀`prefix`和任何路径`path`,对于`proof` 的所有值, - -``` -Probability(verifyNonMembership(root, proof, applyPrefix(prefix, path)) === true) negligible in k -``` - -对于没有在承诺`acc`中设置的任何前缀`prefix`和任何路径`path`,对于`proof`的所有值和`value`的所有值 , - -``` -Probability(verifyMembership(root, proof, applyPrefix(prefix, path), value) === true) negligible in k -``` - -#### 位置绑定 - -承诺证明必须是*有位置约束的* :给定的承诺路径只能映射到一个值,并且承诺证明不能证明同一路径适用于不同的值,除非在概率k下可以被忽略。 - -对于在承诺`acc`设置的任何前缀`prefix`和任何路径`path` ,都有一个`value` : - -```typescript -root = getRoot(acc) -proof = createMembershipProof(acc, applyPrefix(prefix, path), value) -``` - -``` -Probability(verifyMembership(root, proof, applyPrefix(prefix, path), value) === false) negligible in k -``` - -对于所有其他值`otherValue` ,其中`value !== otherValue` ,对于`proof`的所有值, - -``` -Probability(verifyMembership(root, proof, applyPrefix(prefix, path), otherValue) === true) negligible in k -``` - -## 向后兼容性 - -不适用。 - -## 向前兼容性 - -承诺算法将是固定的。可以通过对连接和通道进行版本控制来引入新算法。 - -## 示例实现 - -即将到来。 - -## 其他实现 - -即将到来。 - -## 历史 - -安全性定义主要来自以下文章(并进行了一些简化): - -- [向量承诺及其应用](https://eprint.iacr.org/2011/495.pdf) -- [应用程序对保留匿名撤销的承诺](https://eprint.iacr.org/2017/043.pdf) -- [用于 IOP 和无状态区块链的承诺批处理技术](https://eprint.iacr.org/2018/1188.pdf) - -感谢 Dev Ojha 对这个规范的广泛评论。 - -2019年4月25日-提交的草稿 - -## 版权 - -本规范所有内容均采用 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 许可授权。 diff --git a/other_docs/ibc-protocol/spec/core/ics-024-host-requirements/README.md b/other_docs/ibc-protocol/spec/core/ics-024-host-requirements/README.md deleted file mode 100644 index f20b1e427..000000000 --- a/other_docs/ibc-protocol/spec/core/ics-024-host-requirements/README.md +++ /dev/null @@ -1,322 +0,0 @@ ---- -ics: '24' -title: 主机状态机要求 -stage: 草案 -category: IBC/TAO -kind: 接口 -requires: '23' -required-by: 2, 3, 4, 5, 18 -author: Christopher Goes -created: '2019-04-16' -modified: '2019-08-25' ---- - -## 概要 - -该规范定义了必须提供的最小接口集和满足运行链间链通信协议的状态机必须实现的属性。 - -### 动机 - -IBC 被设计为通用标准,将由各种区块链和状态机运行,并且必须明确定义主机的要求。 - -### 定义 - -### 所需属性 - -IBC 应该要求底层状态机提供尽可能简单的接口,以最大程度的简化正确的实现。 - -## 技术指标 - -### 模块系统 - -主机状态机必须支持一个模块系统,这样,独立的,可能相互不信任的代码包就可以安全的在同一帐本上执行,控制它们如何以及何时允许其他模块与其通信,并由“主模块”或执行环境识别这些代码。 - -IBC/TAO 规范定义了两个模块的实现:核心“ IBC 处理程序”模块和“ IBC 中继器”模块。 IBC/APP 规范还为特定的数据包处理应用程序逻辑定义了其他模块。 IBC 要求可以使用“主模块”或执行环境来授予主机状态机上的其他模块对 IBC 处理程序模块和/或 IBC 路由模块的访问权限,但不对处于同个状态机上的任何其他模块的功能或通信能力施加任何要求。 - -### 路径、标识符、分隔符 - -`Identifier`是一个字节字符串,用作状态存储的对象(例如连接,通道或轻客户端)的键。 - -标识符必须为非空(正整数长度)。 - -标识符只能由以下类别之一中的字符组成: - -- 字母数字 -- `.`, `_`, `+`, `-`, `#` -- `[`, `]`, `<`, `>` - -`Path`是用作状态存储对象的键的字节串。路径必须仅包含标识符,常量字符串和分隔符`"/"` 。 - -标识符并不意图成为有价值的资源,以防止名字抢注,可能需要实现最小长度要求或伪随机生成,但本规范未施加特别限制。 - -分隔符`"/"`用于分隔和连接两个标识符或一个标识符和一个常量字节串。标识符不得包含`"/"`字符,以防止歧义。 - -在整个说明书中,大括号表示的变量插值,用作定义路径格式的简写,例如`client/{clientIdentifier}/consensusState` 。 - -除非另有说明,否则本规范中列出的所有标识符和所有字符串都必须编码为 ASCII。 - -标志符的默认长度规范如下: - -端口标识符 | 客户端标识符 | 连接标识符 | 通道标识符 ---- | --- | --- | --- -2 - 128 | 9 - 64 | 10 - 64 | 10 - 64 - -### 键/值存储 - -主机状态机必须提供键/值存储接口,具有以标准方式运行的三个函数: - -```typescript -type get = (path: Path) => Value | void -``` - -```typescript -type set = (path: Path, value: Value) => void -``` - -```typescript -type delete = (path: Path) => void -``` - -`Path`如上所述。 `Value`是特定数据结构的任意字节串编码。编码细节留给单独的 ICS。 - -这些函数必须仅能由 IBC 处理程序模块(在单独的标准中描述了其实现)使用,因此只有 IBC 处理程序模块才能`set`或`delete` `get`可以读取的路径。这可以实现为整个状态机中的一个大型的键/值存储的子存储(前缀键空间)。 - -主机状态机务必提供此接口的两个实例- 一个`provableStore`用于供其他链读取的存储和主机的本地存储`privateStore`,在这`get` , `set`和`delete`可以被调用,例如`provableStore.set('some/path', 'value')` 。 - -对于`provableStore` : - -- 写入键/值存储的数据必须可以使用 [ICS 23](../ics-023-vector-commitments) 中定义的向量承诺从外部证明。 -- 必须使用这些规范中提供的,如 proto3 文件中的典范数据结构编码。 - -对于`privateStore` : - -- 可以支持外部证明,但不是必须的-IBC 处理程序将永远不会向其写入需要证明的数据。 -- 可以使用典范的 proto3 数据结构,但不是必须的-它可以使用应用程序环境首选的任何格式。 - -> 注意:任何提供这些方法和属性的键/值存储接口都足以满足 IBC 的要求。主机状态机可以使用路径和值映射来实现“代理存储”,这些映射不直接匹配通过存储接口设置和检索的路径和值对-路径可以分组在存储桶结构中,值可以分组存储在页结构中,这样就可以是用一个单独的承诺来证明,可以以某种双射的方式非连续的重新映射路径空间,等等—只要`get` , `set`和`delete`行为符合预期,并且其他机器可以在可证明的存储中验证路径和值对的承诺证明(或它们的不存在)。如果适用,存储必须对外公开此映射,以便客户端(包括中继器)可以确定存储的布局以及如何构造证明。使用这种代理存储的机器的客户端也必须了解映射,因此它将需要新的客户端类型或参数化的客户端。 - -> 注意:此接口不需要任何特定的存储后端或后端数据布局。状态机可以选择使用根据其需求配置的存储后端,只要顶层的存储满足指定的接口并提供承诺证明即可。 - -### 路径空间 - -目前,IBC/TAO 为`provableStore`和`privateStore`建议以下路径前缀。 - -协议的未来版本中可能会使用未来的路径,因此可证明存储中的整个键空间必须为 IBC 处理程序保留。 - -可证明存储中使用的键可以安全的根据每个客户端类型而变化,只要在这里定义的键格式以及在机器实现中实际使用的定义之间存在双向映射 。 - -只要 IBC 处理程序对所需的特定键具有独占访问权,私有存储的某些部分就可以安全的用于其他目的。 只要在此定义的键格式和实际私有存储实现中格式之间存在双向映射,私有存储中使用的键就可以安全的变化。 - -请注意,下面列出的与客户端相关的路径反映了 [ICS 7](../../client/ics-007-tendermint-client) 中定义的 Tendermint 客户端,对于其他客户端类型可能有所不同。 - -存储 | 路径格式 | 值格式 | 定义在 ---- | --- | --- | --- -provableStore | "clients/{identifier}/clientType" | ClientType | [ICS 2](../ics-002-client-semantics) -privateStore | "clients/{identifier}/clientState" | ClientState | [ICS 2](../../client/ics-007-tendermint-client) -provableStore | "clients/{identifier}/consensusState/{height}" | ConsensusState | [ICS 7](../../client/ics-007-tendermint-client) -privateStore | "clients/{identifier}/connections | []Identifier | [ICS 3](../ics-003-connection-semantics) -provableStore | "connections/{identifier}" | ConnectionEnd | [ICS 3](../ics-003-connection-semantics) -privateStore | "ports/{identifier}" | CapabilityKey | [ICS 5](../ics-005-port-allocation) -provableStore | "channelEnds/ports/{identifier}/channels/{identifier}" | ChannelEnd | [ICS 4](../ics-004-channel-and-packet-semantics) -provableStore | "seqSends/ports/{identifier}/channels/{identifier}/nextSequenceSend" | uint64 | [ICS 4](../ics-004-channel-and-packet-semantics) -provableStore | "seqRecvs/ports/{identifier}/channels/{identifier}/nextSequenceRecv" | uint64 | [ICS 4](../ics-004-channel-and-packet-semantics) -provableStore | "seqAcks/ports/{identifier}/channels/{identifier}/nextSequenceAck" | uint64 | [ICS 4](../ics-004-channel-and-packet-semantics) -provableStore | "commitments/ports/{identifier}/channels/{identifier}/sequences/{sequence}" | bytes | [ICS 4](../ics-004-channel-and-packet-semantics) -provableStore | "receipts/ports/{identifier}/channels/{identifier}/sequences/{sequence}" | bytes | [ICS 4](../ics-004-channel-and-packet-semantics) -provableStore | "acks/ports/{identifier}/channels/{identifier}/sequences/{sequence}" | bytes | [ICS 4](../ics-004-channel-and-packet-semantics) - -### 模块布局 - -以空间表示,模块的布局及其在主机状态机上包含的规范如下所示(Aardvark,Betazoid 和 Cephalopod 是任意模块): - -``` -+----------------------------------------------------------------------------------+ -| | -| Host State Machine | -| | -| +-------------------+ +--------------------+ +----------------------+ | -| | Module Aardvark | <--> | IBC Routing Module | | IBC Handler Module | | -| +-------------------+ | | | | | -| | Implements ICS 26. | | Implements ICS 2, 3, | | -| | | | 4, 5 internally. | | -| +-------------------+ | | | | | -| | Module Betazoid | <--> | | --> | Exposes interface | | -| +-------------------+ | | | defined in ICS 25. | | -| | | | | | -| +-------------------+ | | | | | -| | Module Cephalopod | <--> | | | | | -| +-------------------+ +--------------------+ +----------------------+ | -| | -+----------------------------------------------------------------------------------+ -``` - -### 共识状态检视 - -主机状态机必须提供使用`getCurrentHeight`检视其当前高度的功能: - -``` -type getCurrentHeight = () => uint64 -``` - -主机状态机必须使用典范的二进制序列化定义满足 [ICS 2](../ics-002-client-semantics) 要求的一个唯一`ConsensusState`类型。 - -主机状态机必须提供使用`getConsensusState`检视其共识状态的能力: - -```typescript -type getConsensusState = (height: uint64) => ConsensusState -``` - -`getConsensusState`必须至少返回连续`n`个最近的高度的共识状态,其中`n`对于主机状态机是恒定的。大于`n`高度可能会被安全的删除掉(之后对这些高度的调用会失败)。 - -主机状态机必须提供使用`getStoredRecentConsensusStateCount`检视最近`n`个共识状态的能力 : - -```typescript -type getStoredRecentConsensusStateCount = () => uint64 -``` - -### 承诺路径检视 - -主机链必须提供通过`getCommitmentPrefix`检视其承诺路径的能力: - -```typescript -type getCommitmentPrefix = () => CommitmentPrefix -``` - -结果`CommitmentPrefix`是主机状态机的键值存储使用的前缀。 使用主机状态机的`CommitmentRoot root`和`CommitmentState state` ,必须保留以下属性: - -```typescript -if provableStore.get(path) === value { - prefixedPath = applyPrefix(getCommitmentPrefix(), path) - if value !== nil { - proof = createMembershipProof(state, prefixedPath, value) - assert(verifyMembership(root, proof, prefixedPath, value)) - } else { - proof = createNonMembershipProof(state, prefixedPath) - assert(verifyNonMembership(root, proof, prefixedPath)) - } -} -``` - -对于主机状态机, `getCommitmentPrefix`的返回值必须是恒定的。 - -### 时间戳访问 - -主机链必须提供当前的 Unix 时间戳,可通过`currentTimestamp()`访问: - -```typescript -type currentTimestamp = () => uint64 -``` - -为了在超时机制中安全使用时间戳,后续区块头中的时间戳必须不能是递减的。 - -### 端口系统 - -主机状态机必须实现一个端口系统,其中 IBC 处理程序可以允许主机状态机中的不同模块绑定到唯一命名的端口。端口使用`Identifier`标示 。 - -主机状态机必须实现与 IBC 处理程序的权限交互,以便: - -- 模块绑定到端口后,其他模块将无法使用该端口,直到该模块释放它 -- 单个模块可以绑定到多个端口 -- 端口的分配是先到先得的,已知模块的“预留”端口可以在状态机第一次启动的时候绑定。 - -可以通过每个端口的唯一引用(对象能力)(例如 Cosmos SDK),源身份验证(例如以太坊)或某种其他访问控制方法(在任何情况下,由主机状态机实施)来实现此许可。 详细信息,请参见 [ICS 5](../ics-005-port-allocation) 。 - -希望利用特定 IBC 特性的模块可以实现某些处理程序功能,例如,向和另一个状态机上的相关模块的通道握手过程添加其他逻辑。 - -### 数据报提交 - -实现路由模块的主机状态机可以定义一个`submitDatagram`函数来提交数据报1 ,该数据报将包含在交易中,直接发送到路由模块(在[ICS 26](../ics-026-routing-module)中定义): - -```typescript -type submitDatagram = (datagram: Datagram) => void -``` - -`submitDatagram`允许中继器进程将 IBC 数据报直接提交到主机状态机上的路由模块。主机状态机可能要求提交数据报的中继器进程有一个帐户来支付交易费用,在更大的交易结构中对数据报进行签名,等等— `submitDatagram`必须定义并构造任何打包所需的结构。 - -### 异常系统 - -主机状态机务必支持异常系统,借以使交易可以中止执行并回滚以前进行的状态更改(包括同一交易中发生的其他模块中的状态更改),但不包括耗费的 gas 和费用,和违反系统不变性导致状态机挂起的行为。 - -这个异常系统必须暴露两个函数: `abortTransactionUnless`和`abortSystemUnless` ,其中前者回滚交易,后者使状态机挂起。 - -```typescript -type abortTransactionUnless = (bool) => void -``` - -如果传递给`abortTransactionUnless`的布尔值为`true` ,则主机状态机无需执行任何操作。如果传递给`abortTransactionUnless`的布尔值为`false` ,则主机状态机务必中止交易并回滚任何之前进行的状态更改,但不包括消耗的 gas 和费用。 - -```typescript -type abortSystemUnless = (bool) => void -``` - -如果传递给`abortSystemUnless`的布尔值为`true` ,则主机状态机无需执行任何操作。如果传递给`abortSystemUnless`的布尔值为`false` ,则主机状态机务必挂起。 - -### 数据可用性 - -为了达到发送或超时的安全(deliver-or-timeout safety)保证,主机状态机务必具有最终的数据可用性,以便中继器最终可以获取状态中的任何键/值对。而对于仅一次安全(exactly-once safety),不需要数据可用性。 - -对于数据包中继的活性,主机状态机必须具有交易活性(并因此必须具有共识活性),以便在一个高度范围内确认进入的交易(具体就是,小于分配给数据包的超时高度)。 - -IBC 数据包数据,以及未直接存储在状态向量中但中继器依赖的其他数据,必须可供中继器进程使用并高效地计算。 - -具有特定共识算法的轻客户端可能具有不同和/或更严格的数据可用性要求。 - -### 事件日志系统 - -主机状态机必须提供一个事件日志系统,借此可以在交易执行过程中记录任意数据,这些数据可以存储,索引并随后由执行状态机的进程查询。中继器利用这些事件日志读取 IBC 数据包数据和超时,这些数据和超时未直接存储在链上状态中(因为链上存储被认为是昂贵的),而是提交简洁的加密承诺(仅存储该承诺)。 - -该系统期望具有至少一个函数用于发出日志条目,和一个函数用于查询过去的日志,大概如下。 - -状态机可以在交易执行期间调用函数`emitLogEntry`来写入日志条目: - -```typescript -type emitLogEntry = (topic: string, data: []byte) => void -``` - -`queryByTopic`函数可以被外部进程(例如中继器)调用,以检索在给定高度执行的交易写入的与查询主题关联的所有日志条目。 - -```typescript -type queryByTopic = (height: uint64, topic: string) => []byte[] -``` - -也可以支持更复杂的查询功能,并且可以允许更高效的中继器进程查询,但不是必需的。 - -### 升级处理 - -主机可以安全地升级其状态机的一部分,而不会中断 IBC 功能。为了安全地执行此操作,IBC 处理程序逻辑必须保持符合规范,并且所有与 IBC 相关的状态(在可证明和私有存储中)必须在升级过程中保持不变。如果在其他链上存在用于升级链的客户端,并且升级将更改轻客户端验证算法,则必须在升级之前通知这些客户端,以便它们可以安全地原子切换并保持连接和通道的连续性。 - -## 向后兼容性 - -不适用。 - -## 向前兼容性 - -键/值存储功能和共识状态类型在单个主机状态机的操作期间不太可能更改。 - -因为中继器应该能够更新其进程,所以`submitDatagram`会随着时间而变化。 - -## 示例实现 - -即将到来。 - -## 其他实现 - -即将到来。 - -## 历史 - -2019年4月29日-初稿 - -2019年5月11日-将“ RootOfTrust”重命名为“ ConsensusState” - -2019年6月25日-使用“端口”代替模块名称 - -2019年8月18日-修订模块系统,定义 - -## 版权 - -本规范所有内容均采用 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 许可授权。 - ---- - -1 :数据报是通过某个物理网络传输的不透明字节串,由账本状态机中实现的 IBC 路由模块处理。在一些实现中,数据报可以是特定账本的交易的一个字段,或消息数据结构中的一个字段,这样的数据结构还包含其他信息(例如,防止垃圾邮件的费用、防止重放的随机数、路由到 IBC 处理程序的类型标识符等)。 所有 IBC 子协议(例如打开连接、创建通道、发送数据包)都是根据数据报集和协议定义的,用于通过路由模块处理它们。 diff --git a/other_docs/ibc-protocol/spec/core/ics-025-handler-interface/README.md b/other_docs/ibc-protocol/spec/core/ics-025-handler-interface/README.md deleted file mode 100644 index 007a1cf43..000000000 --- a/other_docs/ibc-protocol/spec/core/ics-025-handler-interface/README.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -ics: '25' -title: 处理程序接口 -stage: 草案 -category: IBC/TAO -kind: 实例化 -requires: 2, 3, 4, 23, 24 -author: Christopher Goes -created: '2019-04-23' -modified: '2019-08-25' ---- - -## 概要 - -本文档描述了标准 IBC 实现(称为 IBC 处理程序)向同一状态机内的模块公开的接口,以及 IBC 处理程序对该接口的实现。 - -### 动机 - -IBC 是一种模块间通信协议,旨在促进独立区块链上的模块之间可靠、经过身份验证的消息传递。模块应该能够得知它们与之交互的接口以及它们必须遵守的要求,以便安全地使用接口。 - -### 定义 - -相关定义在参考的先前标准(定义了功能的地方)中适当地定义。 - -### 所需属性 - -- 客户端,连接和通道的创建应尽可能无需许可。 -- 模块集应为动态的:链应该能够添加和删除模块,这些模块本身可以使用持久性 IBC 处理程序任意绑定到端口或从端口取消绑定。 -- 模块应能在 IBC 之上编写自己的更复杂的抽象,以提供附加的语义或保证。 - -## 技术规范 - -> 注意:如果主机状态机正在使用对象能力认证(请参阅 [ICS 005](../ics-005-port-allocation) ),则所有使用端口的函数都将带有附加的能力键参数。 - -### 客户端生命周期管理 - -默认情况下,客户端是没有所有者的:任何模块都可以创建新客户端,查询任何现有客户端,更新任何现有客户端以及删除任何未使用的现有客户端。 - -处理程序接口暴露 [ICS 2](../ics-002-client-semantics) 中定义的`createClient` , `updateClient` , `queryClientConsensusState` , `queryClient`和`submitMisbehaviourToClient` 。 - -### 连接生命周期管理 - -处理程序接口暴露 [ICS 3](../ics-003-connection-semantics) 中定义的`connOpenInit` , `connOpenTry` , `connOpenAck` , `connOpenConfirm`和`queryConnection` 。 - -默认的 IBC 路由模块应允许外部调用`connOpenTry` , `connOpenAck`和`connOpenConfirm` 。 - -### 通道生命周期管理 - -默认情况下,通道归创建端口所有,这意味着只有绑定到该端口的模块才允许在通道上检查、关闭或发送。一个模块可以使用同一端口创建任意数量的通道。 - -处理程序接口暴露了 [ICS 4](../ics-004-channel-and-packet-semantics) 中定义的`chanOpenInit` , `chanOpenTry` , `chanOpenAck` , `chanOpenConfirm` , `chanCloseInit` , `chanCloseConfirm`和`queryChannel` 。 - -默认的 IBC 路由模块应允许外部调用`chanOpenTry` , `chanOpenAck` , `chanOpenConfirm`和`chanCloseConfirm` 。 - -### 数据包中继 - -数据包是需要通道许可的(只有拥有通道的端口可以在其上发送或接收)。 - -该处理程序接口暴露`sendPacket` , `recvPacket` , `acknowledgePacket` , `timeoutPacket` , `timeoutOnClose`和`cleanupPacket`,如 [ICS 4](../ics-004-channel-and-packet-semantics)中定义 。 - -默认 IBC 路由模块应允许外部调用`sendPacket` , `recvPacket` , `acknowledgePacket` , `timeoutPacket` , `timeoutOnClose`和`cleanupPacket` 。 - -### 属性与不变性 - -此处定义的 IBC 处理程序模块接口继承了其关联规范中定义的功能属性。 - -## 向后兼容性 - -不适用。 - -## 向前兼容性 - -只要在语义上相同,在新链上实现(或升级到现有链)时,此接口可以更改。 - -## 示例实现 - -即将到来。 - -## 其他实现 - -即将到来。 - -## 历史 - -2019年6月9日-编写草案 - -2019年8月24日-修订,清理 - -## 版权 - -本规范所有内容均采用 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 许可授权。 diff --git a/other_docs/ibc-protocol/spec/core/ics-026-routing-module/README.md b/other_docs/ibc-protocol/spec/core/ics-026-routing-module/README.md deleted file mode 100644 index 7ef3e1d0b..000000000 --- a/other_docs/ibc-protocol/spec/core/ics-026-routing-module/README.md +++ /dev/null @@ -1,718 +0,0 @@ ---- -ics: '26' -title: 路由模块 -stage: 草案 -category: IBC/TAO -kind: 实例化 -author: Christopher Goes -created: '2019-06-09' -modified: '2019-08-25' ---- - -## 概要 - -路由模块是一个辅助模块的默认实现,该模块将接受外部数据报并调用区块链间通信协议处理程序来处理握手和数据包中继。 路由模块维护一个模块的查找表,当收到数据包时,该表可用于查找和调用模块,因此外部中继器仅需要将数据包中继到路由模块。 - -### 动机 - -默认的 IBC 处理程序使用接收方调用模式,其中模块必须单独调用 IBC 处理程序才能绑定到端口,开始握手,接受握手,发送和接收数据包等。这是灵活而简单的。 但是理解起来有些棘手,中继器进程可能需要额外的工作,中继器进程必须跟踪多个模块的状态。该标准描述了一个 IBC“路由模块”,以自动执行大部分常用功能,路由数据包并简化中继器的任务。 - -路由模块还可以扮演 [ICS 5](../ics-005-port-allocation) 中讨论的模块管理器的角色,并实现确定何时允许模块绑定到端口以及可以命名哪些端口的逻辑。 - -### 定义 - -IBC 处理程序接口提供的所有函数均在 [ICS 25](../ics-025-handler-interface) 中定义。 - -函数 `newCapability` 和 `authenticateCapability` 在 [ICS 5](../ics-005-port-allocation) 中定义。 - -### 所需属性 - -- 模块应该能够通过路由模块绑定到端口和自己的通道。 -- 除了调用中间层外,不应为数据包发送和接收增加任何开销。 -- 当路由模块需要对数据包操作时,路由模块应在模块上调用指定的处理程序函数。 - -## 技术规范 - -> 注意:如果主机状态机正在使用对象能力认证(请参阅 [ICS 005](../ics-005-port-allocation) ),则所有使用端口的函数都需要带有一个附加的能力参数。 - -### 模块回调接口 - -模块必须向路由模块暴露以下函数签名,这些签名在收到各种数据报后即被调用: - -#### **ChanOpenInit** - -`onChanOpenInit`将验证中继器选择的参数是否有效并执行任何自定义的`INIT`逻辑。如果选择的参数无效,则可能会返回错误,在这种情况下握手将被中止。如果提供的版本字符串非空, `onChanOpenInit`应该返回版本字符串,如果提供的版本无效,则返回错误。如果版本字符串为空, `onChanOpenInit`应返回一个默认版本字符串,表示它支持的版本。如果应用程序没有默认的版本字符串,并且提供的版本为空字符串,它应该返回错误。 - -```typescript -function onChanOpenInit( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - version: string) => (version: string, err: Error) { - // 由模块定义 -} -``` - -#### **ChanOpenTry** - -`onChanOpenTry`将验证 INIT 选择的参数以及交易对手选择的版本字符串并执行自定义`TRY`逻辑。如果 INIT 选择的参数无效,回调必须返回错误以中止握手。如果交易对手选择的版本与此模块支持的版本不兼容,回调必须返回错误以中止握手。如果版本兼容,try 回调必须选择最终版本字符串并将其返回给核心 IBC。 `onChanOpenTry`也可以执行自定义初始化逻辑。 - -```typescript -function onChanOpenTry( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string) => (version: string, err: Error) { - // 由模块定义 -} -``` - -#### **OnChanOpenAck** - -如果交易对手选择的版本字符串无效以中止握手, `onChanOpenAck`将出错。它还可以执行自定义 ACK 逻辑。 - -```typescript -function onChanOpenAck( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string) { - // 由模块定义 -} -``` - -#### **OnChanOpenConfirm** - -`onChanOpenConfirm`将执行自定义 CONFIRM 逻辑,并且可能会出错以中止握手。 - -```typescript -function onChanOpenConfirm( - portIdentifier: Identifier, - channelIdentifier: Identifier) { - // 由模块定义 -} -``` - -```typescript -function onChanCloseInit( - portIdentifier: Identifier, - channelIdentifier: Identifier) { - // 由模块定义 -} - -function onChanCloseConfirm( - portIdentifier: Identifier, - channelIdentifier: Identifier): void { - // 由模块定义 -} - -function onRecvPacket(packet: Packet, relayer: string): bytes { - // 由模块定义, 返回回执 -} - -function onTimeoutPacket(packet: Packet, relayer: string) { - // 由模块定义 -} - -function onAcknowledgePacket(packet: Packet, acknowledgement: bytes, relayer: string) { - // 由模块定义 -} - -function onTimeoutPacketClose(packet: Packet, relayer: string) { - // 由模块定义 -} -``` - -如果出现失败,必须抛出异常以拒绝握手和传入的数据包等。 - -它们在`ModuleCallbacks`接口中组合在一起: - -```typescript -interface ModuleCallbacks { - onChanOpenInit: onChanOpenInit, - onChanOpenTry: onChanOpenTry, - onChanOpenAck: onChanOpenAck, - onChanOpenConfirm: onChanOpenConfirm, - onChanCloseConfirm: onChanCloseConfirm - onRecvPacket: onRecvPacket - onTimeoutPacket: onTimeoutPacket - onAcknowledgePacket: onAcknowledgePacket, - onTimeoutPacketClose: onTimeoutPacketClose -} -``` - -当模块绑定到端口时,将提供回调。 - -```typescript -function callbackPath(portIdentifier: Identifier): Path { - return "callbacks/{portIdentifier}" -} -``` - -还将存储调用模块标识符以供将来更改回调时进行身份认证。 - -```typescript -function authenticationPath(portIdentifier: Identifier): Path { - return "authentication/{portIdentifier}" -} -``` - -### 端口绑定作为模块管理器 - -IBC 路由模块位于处理程序模块( [ICS 25](../ics-025-handler-interface) )与主机状态机上的各个模块之间。 - -充当模块管理器的路由模块区分两种端口: - -- “现有名称”端口:例如具有标准化优先含义的“bank”,不应以先到先得的方式使用 -- “新名称”端口:没有先前关系的新身份(可能是智能合约)、新的随机数端口、生成后的端口名称可以通过另一个通道进行通信 - -当主机状态机实例化路由模块时,会分配一组现有名称以及相应的模块。 然后,路由模块允许模块随时分配新端口,但是它们必须使用特定的标准化前缀。 - -模块可以调用函数`bindPort`以便通过路由模块绑定到端口并设置回调。 - -```typescript -function bindPort( - id: Identifier, - callbacks: Callbacks): CapabilityKey { - abortTransactionUnless(privateStore.get(callbackPath(id)) === null) - privateStore.set(callbackPath(id), callbacks) - capability = handler.bindPort(id) - claimCapability(authenticationPath(id), capability) - return capability -} -``` - -模块可以调用函数`updatePort`来更改回调。 - -```typescript -function updatePort( - id: Identifier, - capability: CapabilityKey, - newCallbacks: Callbacks) { - abortTransactionUnless(authenticateCapability(authenticationPath(id), capability)) - privateStore.set(callbackPath(id), newCallbacks) -} -``` - -模块可以调用函数`releasePort`来释放以前使用的端口。 - -> 警告:释放端口将允许其他模块绑定到该端口,并可能拦截传入的通道创建握手请求。只有在安全的情况下,模块才应释放端口。 - -```typescript -function releasePort( - id: Identifier, - capability: CapabilityKey) { - abortTransactionUnless(authenticateCapability(authenticationPath(id), capability)) - handler.releasePort(id) - privateStore.delete(callbackPath(id)) - privateStore.delete(authenticationPath(id)) -} -``` - -路由模块可以使用函数`lookupModule`查找绑定到特定端口的回调。 - -```typescript -function lookupModule(portId: Identifier) { - return privateStore.get(callbackPath(portId)) -} -``` - -### 数据报处理程序(写入) - -*数据报*是路由模块做为交易接受的外部数据 Blob。本部分为每个数据报定义一个*处理函数* , 当关联的数据报在交易中提交给路由模块时执行。 - -所有数据报也可以由其他模块安全的提交给路由模块。 - -除了明确指出,不假定任何消息签名或数据有效性检查。 - -#### 客户端生命周期管理 - -`ClientCreate`使用指定的标识符和共识状态创建一个新的轻客户端。 - -```typescript -interface ClientCreate { - identifier: Identifier - type: ClientType - consensusState: ConsensusState -} -``` - -```typescript -function handleClientCreate(datagram: ClientCreate) { - handler.createClient(datagram.identifier, datagram.type, datagram.consensusState) -} -``` - -`ClientUpdate`使用指定的标识符和新区块头更新现有的轻客户端。 - -```typescript -interface ClientUpdate { - identifier: Identifier - header: Header -} -``` - -```typescript -function handleClientUpdate(datagram: ClientUpdate) { - handler.updateClient(datagram.identifier, datagram.header) -} -``` - -`ClientSubmitMisbehaviour`使用指定的标识符向现有的轻客户端提交不良行为证明。 - -```typescript -interface ClientMisbehaviour { - identifier: Identifier - evidence: bytes -} -``` - -```typescript -function handleClientMisbehaviour(datagram: ClientUpdate) { - handler.submitMisbehaviourToClient(datagram.identifier, datagram.evidence) -} -``` - -#### 连接生命周期管理 - -`ConnOpenInit`数据报开始与另一个链上的 IBC 模块的连接的握手过程。 - -```typescript -interface ConnOpenInit { - identifier: Identifier - desiredCounterpartyIdentifier: Identifier - clientIdentifier: Identifier - counterpartyClientIdentifier: Identifier - version: string -} -``` - -```typescript -function handleConnOpenInit(datagram: ConnOpenInit) { - handler.connOpenInit( - datagram.identifier, - datagram.desiredCounterpartyIdentifier, - datagram.clientIdentifier, - datagram.counterpartyClientIdentifier, - datagram.version - ) -} -``` - -`ConnOpenTry`数据报接受从另一个链上的 IBC 模块发来的握手请求。 - -```typescript -interface ConnOpenTry { - desiredIdentifier: Identifier - counterpartyConnectionIdentifier: Identifier - counterpartyClientIdentifier: Identifier - clientIdentifier: Identifier - version: string - counterpartyVersion: string - proofInit: CommitmentProof - proofConsensus: CommitmentProof - proofHeight: uint64 - consensusHeight: uint64 -} -``` - -```typescript -function handleConnOpenTry(datagram: ConnOpenTry) { - handler.connOpenTry( - datagram.desiredIdentifier, - datagram.counterpartyConnectionIdentifier, - datagram.counterpartyClientIdentifier, - datagram.clientIdentifier, - datagram.version, - datagram.counterpartyVersion, - datagram.proofInit, - datagram.proofConsensus, - datagram.proofHeight, - datagram.consensusHeight - ) -} -``` - -`ConnOpenAck`数据报确认另一条链上的 IBC 模块接受了握手。 - -```typescript -interface ConnOpenAck { - identifier: Identifier - version: string - proofTry: CommitmentProof - proofConsensus: CommitmentProof - proofHeight: uint64 - consensusHeight: uint64 -} -``` - -```typescript -function handleConnOpenAck(datagram: ConnOpenAck) { - handler.connOpenAck( - datagram.identifier, - datagram.version, - datagram.proofTry, - datagram.proofConsensus, - datagram.proofHeight, - datagram.consensusHeight - ) -} -``` - -`ConnOpenConfirm`数据报确认另一个链上的 IBC 模块的握手回执并完成连接。 - -```typescript -interface ConnOpenConfirm { - identifier: Identifier - proofAck: CommitmentProof - proofHeight: uint64 -} -``` - -```typescript -function handleConnOpenConfirm(datagram: ConnOpenConfirm) { - handler.connOpenConfirm( - datagram.identifier, - datagram.proofAck, - datagram.proofHeight - ) -} -``` - -#### 通道生命周期管理 - -```typescript -interface ChanOpenInit { - order: ChannelOrder - connectionHops: [Identifier] - portIdentifier: Identifier - channelIdentifier: Identifier - counterpartyPortIdentifier: Identifier - counterpartyChannelIdentifier: Identifier - version: string -} -``` - -```typescript -function handleChanOpenInit(datagram: ChanOpenInit) { - module = lookupModule(datagram.portIdentifier) - version, err = module.onChanOpenInit( - datagram.order, - datagram.connectionHops, - datagram.portIdentifier, - datagram.channelIdentifier, - datagram.counterpartyPortIdentifier, - datagram.counterpartyChannelIdentifier, - datagram.version - ) - abortTransactionUnless(err === nil) - handler.chanOpenInit( - datagram.order, - datagram.connectionHops, - datagram.portIdentifier, - datagram.channelIdentifier, - datagram.counterpartyPortIdentifier, - datagram.counterpartyChannelIdentifier, - version // pass in version returned from callback - ) -} -``` - -```typescript -interface ChanOpenTry { - order: ChannelOrder - connectionHops: [Identifier] - portIdentifier: Identifier - channelIdentifier: Identifier - counterpartyPortIdentifier: Identifier - counterpartyChannelIdentifier: Identifier - version: string // 弃用 - counterpartyVersion: string - proofInit: CommitmentProof - proofHeight: Height -} -``` - -```typescript -function handleChanOpenTry(datagram: ChanOpenTry) { - module = lookupModule(datagram.portIdentifier) - version, err = module.onChanOpenTry( - datagram.order, - datagram.connectionHops, - datagram.portIdentifier, - datagram.channelIdentifier, - datagram.counterpartyPortIdentifier, - datagram.counterpartyChannelIdentifier, - datagram.counterpartyVersion - ) - abortTransactionUnless(err === nil) - handler.chanOpenTry( - datagram.order, - datagram.connectionHops, - datagram.portIdentifier, - datagram.channelIdentifier, - datagram.counterpartyPortIdentifier, - datagram.counterpartyChannelIdentifier, - version, // 由回调返回的版本号 - datagram.counterpartyVersion, - datagram.proofInit, - datagram.proofHeight - ) -} -``` - -```typescript -interface ChanOpenAck { - portIdentifier: Identifier - channelIdentifier: Identifier - version: string - proofTry: CommitmentProof - proofHeight: uint64 -} -``` - -```typescript -function handleChanOpenAck(datagram: ChanOpenAck) { - module = lookupModule(datagram.portIdentifier) - err = module.onChanOpenAck( - datagram.portIdentifier, - datagram.channelIdentifier, - datagram.counterpartyChannelIdentifier, - datagram.counterpartyVersion - ) - abortTransactionUnless(err === nil) - handler.chanOpenAck( - datagram.portIdentifier, - datagram.channelIdentifier, - datagram.counterpartyChannelIdentifier, - datagram.counterpartyVersion, - datagram.proofTry, - datagram.proofHeight - ) -} -``` - -```typescript -interface ChanOpenConfirm { - portIdentifier: Identifier - channelIdentifier: Identifier - proofAck: CommitmentProof - proofHeight: uint64 -} -``` - -```typescript -function handleChanOpenConfirm(datagram: ChanOpenConfirm) { - module = lookupModule(datagram.portIdentifier) - err = module.onChanOpenConfirm( - datagram.portIdentifier, - datagram.channelIdentifier - ) - abortTransactionUnless(err === nil) - handler.chanOpenConfirm( - datagram.portIdentifier, - datagram.channelIdentifier, - datagram.proofAck, - datagram.proofHeight - ) -} -``` - -```typescript -interface ChanCloseInit { - portIdentifier: Identifier - channelIdentifier: Identifier -} -``` - -```typescript -function handleChanCloseInit(datagram: ChanCloseInit) { - module = lookupModule(datagram.portIdentifier) - err = module.onChanCloseInit( - datagram.portIdentifier, - datagram.channelIdentifier - ) - abortTransactionUnless(err === nil) - handler.chanCloseInit( - datagram.portIdentifier, - datagram.channelIdentifier - ) -} -``` - -```typescript -interface ChanCloseConfirm { - portIdentifier: Identifier - channelIdentifier: Identifier - proofInit: CommitmentProof - proofHeight: uint64 -} -``` - -```typescript -function handleChanCloseConfirm(datagram: ChanCloseConfirm) { - module = lookupModule(datagram.portIdentifier) - err = module.onChanCloseConfirm( - datagram.portIdentifier, - datagram.channelIdentifier - ) - abortTransactionUnless(err === nil) - handler.chanCloseConfirm( - datagram.portIdentifier, - datagram.channelIdentifier, - datagram.proofInit, - datagram.proofHeight - ) -} -``` - -#### 数据包中继 - -数据包直接由模块发送(由模块调用 IBC 处理程序)。 - -```typescript -interface PacketRecv { - packet: Packet - proof: CommitmentProof - proofHeight: uint64 -} -``` - -```typescript -function handlePacketRecv(datagram: PacketRecv) { - module = lookupModule(datagram.packet.sourcePort) - acknowledgement = module.onRecvPacket(datagram.packet) - handler.recvPacket( - datagram.packet, - datagram.proof, - datagram.proofHeight, - acknowledgement - ) -} -``` - -```typescript -interface PacketAcknowledgement { - packet: Packet - acknowledgement: string - proof: CommitmentProof - proofHeight: uint64 -} -``` - -```typescript -function handlePacketAcknowledgement(datagram: PacketAcknowledgement) { - module = lookupModule(datagram.packet.sourcePort) - module.onAcknowledgePacket( - datagram.packet, - datagram.acknowledgement - ) - handler.acknowledgePacket( - datagram.packet, - datagram.acknowledgement, - datagram.proof, - datagram.proofHeight - ) -} -``` - -#### 数据包超时 - -```typescript -interface PacketTimeout { - packet: Packet - proof: CommitmentProof - proofHeight: uint64 - nextSequenceRecv: Maybe -} -``` - -```typescript -function handlePacketTimeout(datagram: PacketTimeout) { - module = lookupModule(datagram.packet.sourcePort) - module.onTimeoutPacket(datagram.packet) - handler.timeoutPacket( - datagram.packet, - datagram.proof, - datagram.proofHeight, - datagram.nextSequenceRecv - ) -} -``` - -```typescript -interface PacketTimeoutOnClose { - packet: Packet - proof: CommitmentProof - proofHeight: uint64 -} -``` - -```typescript -function handlePacketTimeoutOnClose(datagram: PacketTimeoutOnClose) { - module = lookupModule(datagram.packet.sourcePort) - module.onTimeoutPacket(datagram.packet) - handler.timeoutOnClose( - datagram.packet, - datagram.proof, - datagram.proofHeight - ) -} -``` - -#### 超时关闭和数据包清理 - -```typescript -interface PacketCleanup { - packet: Packet - proof: CommitmentProof - proofHeight: uint64 - nextSequenceRecvOrAcknowledgement: Either -} -``` - -### 查询(只读)函数 - -客户端,连接和通道的所有查询函数应直接由 IBC 处理程序模块暴露出来(只读)。 - -### 接口用法示例 - -有关用法示例,请参见 [ICS 20](../../app/ics-020-fungible-token-transfer) 。 - -### 属性与不变性 - -- 代理端口绑定是先到先服务:模块通过 IBC路由模块绑定到端口后,只有该模块才能使用该端口,直到模块释放它为止。 - -## 向后兼容性 - -不适用。 - -## 向前兼容性 - -路由模块与 IBC 处理程序接口紧密相关。 - -## 示例实现 - -即将到来。 - -## 其他实现 - -即将到来。 - -## 历史 - -2019年6月9日-提交的草案 - -2019年7月28日-重大修订 - -2019年8月25日-重大修订 - -## 版权 - -本规范所有内容均采用 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 许可授权。 diff --git a/other_docs/ibc-protocol/spec/core/ics-026-routing-module/UPGRADES.md b/other_docs/ibc-protocol/spec/core/ics-026-routing-module/UPGRADES.md deleted file mode 100644 index 71d843887..000000000 --- a/other_docs/ibc-protocol/spec/core/ics-026-routing-module/UPGRADES.md +++ /dev/null @@ -1,128 +0,0 @@ -# 应用升级回调 - -## 概要 - -这个标准文档规定了 IBC 应用必须实现的接口和状态机逻辑,以便现存通道在初始连接握手后能够升级。 - -### 动机 - -随着新功能被添加到 IBC,链可能希望在不放弃现有通道的已积累的状态和网络效应的情况下,同时利用新的应用功能。提议的升级协议将允许应用重新协商现有通道,这样可以使用新的功能而无需创建新通道,从而在升级应用逻辑时可以保留现有的应用状态。 - -### 所需属性 - -- 两端的应用都必须认同重新协商后的应用的参数。 -- 两条链上的应用的状态和逻辑应该或者使用旧参数或者新参数,而不能是一个中间状态,例如,应用程序不能运行 v2 逻辑,而其对手方仍在运行 v1 逻辑。 -- 应用的升级协议是原子性的,即 - - 要么不成功,然后应用必须回退到原始应用的参数; - - 要么成功,然后两端的应用必须采用新的应用的参数并妥善地处理 数据包。 -- 应用程序必须能够维护几个不同的受支持版本。这样的话,如果一个通道在版本`v1`上,另一个通道在版本`v2`上,应用程序可以根据通道的应用程序版本相应地处理通道状态和逻辑。 - -应用程序升级协议不得修改通道标识符。 - -## 技术规范 - -为了支持通道升级,应用程序必须实现以下接口: - -```typescript -interface ModuleUpgradeCallbacks { - onChanUpgradeInit: onChanUpgradeInit, - onChanUpgradeTry: onChanUpgradeTry, - onChanUpgradeAck: onChanUpgradeAck, - onChanUpgradeConfirm: onChanUpgradeConfirm, - onChanUpgradeRestore: onChanUpgradeRestore -} -``` - -#### **OnChanUpgradeInit** - -`onChanUpgradeInit`将验证升级的参数是否有效并执行任何自定义的`UpgradeInit`逻辑。如果选择的参数无效,则可能会返回错误,在这种情况下握手将被中止。如果提供的版本字符串空, `onChanUpgradeInit`应该返回版本字符串,如果提供的版本无效,则返回错误。如果升级提供了空字符串,这意味着将升级到默认版本,此默认版本可能是一个新的默认版本,与通道创建时的默认版本不一样。如果应用程序没有默认的版本字符串,并且提供的版本为空字符串,它应该返回错误。 - -如果返回错误,则核心 IBC 将撤销`onChanUpgradeInit`所做的任何更改并中止握手。 - -`onChanUpgradeInit`还负责确保应用程序可以恢复到其升级前的状态。应用程序可以将任何新的元数据存储在单独的路径中,或者将以前的元数据存储在不同的路径下以便可以恢复。 - -```typescript -function onChanUpgradeInit( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - version: string) => (version: string, err: Error) { - // 由此模块定义 -} -``` - -#### **OnChanUpgradeTry** - -`onChanUpgradeTry`将验证升级选择的参数并执行自定义`TRY`逻辑。如果升级选择的参数无效,回调必须返回错误以中止握手。如果交易对手选择的版本与此模块支持的版本不兼容,回调必须返回错误以中止握手。如果版本兼容,try 回调必须选择最终版本字符串并将其返回给核心 IBC。 `onChanUpgradeTry`也可以执行自定义初始化逻辑。 - -如果返回错误,则核心 IBC 将撤销`onChanUpgradeTry`所做的任何更改并中止握手。 - -`onChanUpgradeTry`还负责确保应用程序可以恢复到其升级前的状态。应用程序可以将任何新的元数据存储在单独的路径中,或者将以前的元数据存储在不同的路径下以便可以恢复。 - -```typescript -function onChanUpgradeTry( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string) => (version: string, err: Error) { - // 由此模块定义 -} -``` - -#### **OnChanUpgradeAck** - -如果交易对手选择的版本字符串无效, `onChanUpgradeAck`将出错。如果回调返回错误,核心 IBC 将撤销`onChanUpgradeAck`所做的任何更改并中止握手。 - -`onChanUpgradeAck`回调也可以执行自定义 ACK 逻辑。 - -在`onChanUpgradeAck`成功返回后,应用程序升级到此结束,任何为错误恢复而存储的辅助数据都不再需要,可能会被删除。 - -如果回调成功返回,应用程序必须完全迁移其状态以根据新的应用程序参数开始处理数据包。 - -```typescript -function onChanUpgradeAck( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string) { - // 由此模块定义 -} => Error -``` - -#### **OnChanUpgradeConfirm** - -`onChanUpgradeConfirm`将执行自定义 CONFIRM 逻辑。此逻辑不能有错误返回, 因为交易对手已经批准了握手,并已经使用新的升级参数。 - -在`onChanUpgradeConfirm`返回后,应用程序升级到此结束,任何为错误恢复而存储的辅助数据都不再需要,可能会被删除。 - -应用程序必须完全迁移其状态,以便在回调返回时根据新的应用程序参数开始处理数据包。 - -```typescript -function onChanUpgradeConfirm( - portIdentifier: Identifier, - channelIdentifier: Identifier) { - // 由此模块定义 -} -``` - -#### **OnChanUpgradeRestore** - -`onChanUpgradeRestore`将在`cancelChannelUpgrade`和`timeoutChannelUpgrade`被调用以将应用程序恢复到其升级前状态。 - -升级恢复的回调被返回后,应用程序必须将任何应用程序元数据恢复到其升级前状态。为升级而存储的任何临时元数据都可以删除。 - -应用程序必须完全迁移其状态,以便在回调返回时根据初始的应用程序参数开始处理数据包。 - -```typescript -function onChanUpgradeRestore( - portIdentifier: Identifier, - channelIdentifier: Identifier) { - // 由此模块定义 -} -``` diff --git a/other_docs/ibc-protocol/spec/ics-001-ics-standard/README.md b/other_docs/ibc-protocol/spec/ics-001-ics-standard/README.md deleted file mode 100644 index 9e00155cb..000000000 --- a/other_docs/ibc-protocol/spec/ics-001-ics-standard/README.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -ics: '1' -title: ICS 规范标准 -stage: 草案 -category: 元标准 -kind: 元标准 -author: Christopher Goes -created: '2019-02-12' -modified: '2019-08-25' ---- - -## 什么是 ICS? - -链间标准(ICS)是一份描述适用于 Cosmos 生态的特定协议、标准或期望特性的设计文档。ICS 应列出标准所需的属性、解释设计原理,并提供简明、全面的技术规范。ICS 的主要作者负责通过标准化流程推动提案,征求社区的投入和支持,并与利益相关者进行沟通,以确保(社会层面的)共识。 - -链间标准化过程应当是提出生态范围协议、变更与特性的主要载体, 且 ICS 文档应在达成共识后持久保留,以作为设计决策的记录和未来实施者的信息存储库。 - -链间标准*不应*作为针对特定区块链的修改建议(如 Cosmos Hub),指定实现细节(例如特定编程语言的数据结构),或讨论有关现有 Cosmos 区块链的治理建议(尽管 Cosmos 生态中的各个区块链可能会利用其治理流程决定采用或拒绝链间标准)。 - -## 组件 - -ICS 由标题、概要、规范、历史记录和版权声明组成。需包含所有顶层章节。参考文献应作为链接包含在正文中,或在必要时以表格形式列在章节末尾。 - -### 标题 - -ICS 标题应包含与 ICS 相关的元数据。 - -#### 必选项 - -`ics: #` - ICS 编号(顺序分配) - -`题目` - ICS 题目(确保简短) - -`阶段` - 当前 ICS 阶段,请参见 [PROCESS.md](../../meta/PROCESS.md) 获取可能的阶段列表。 - -有关 ICS 可接受阶段的说明,请参见 [README.md](../../README.md)。 - -`类别` - ICS 类别,应为以下之一: - -- `元标准` - 有关 ICS 流程的标准 -- `IBC/TAO` - 有关区块链间通信系统核心传输、身份认证和排序层协议的标准。 -- `IBC/APP` - 关于区块链间通信系统应用层协议的标准。 - -`作者` - ICS 作者和联系信息(优先顺序:电子邮件、GitHub、Twitter、其他可能得到回应的联系方法)。第一作者是 ICS 的主要“所有者”,并负责通过标准化过程进行推进。随后的作者应按贡献度排序。 - -`创建` - 首次创建 ICS 的日期(YYYY-MM-DD) - -`修改` - ICS 上次修改日期(YYYY-MM-DD ) - -#### 可选项 - -`依赖` - 此标准依赖的其他 ICS 标准(使用编号引用)。 - -`依赖于` - 依赖此标准的其他 ICS 标准(使用编号引用)。 - -`替换` - 被此标准替代的另一个 ICS 标准(如果适用)。 - -`替换为` - 替代此标准的另一个 ICS 标准(如果适用)。 - -### 概要 - -ICS 应在标题之后编写简短的概要(约 200 字),提供规范的高级描述和基本原理。 - -### 规范 - -「规范」是 ICS 的主要组成部分,应包含协议文档、设计原理、必需的参考以及适当的技术细节。 - -#### 子组件 - -规范可以包含任何或所有以下子组件,具体根据特定 ICS 类型确定。包含的子组件应按此处指定的顺序列出。 - -- *动机* - 提议特性或对现有特性的修改的根本依据。 -- *定义* - 此 ICS 中使用的或理解该 ICS 所需的新术语或概念的列表。未在顶级“ docs”文件夹中定义的术语必须在此处定义。 -- *所需属性* - 此协议所需的属性或特性,以及违反这些属性时的预期结果或错误的列表。 -- *技术规范* - 所提议协议的所有技术细节,包括语法、语义、子协议、数据结构、算法和相应的伪代码。技术规范应足够详细,使独立的正确实现可以在不参阅其他规范的情况下彼此兼容。 -- *向后兼容性* - 讨论与以前的功能或协议版本的兼容性(或缺乏兼容性)。 -- *向前兼容性* - 讨论与未来可能或预期的功能或协议版本的兼容性(或缺乏兼容性)。 -- *示例实现* - 具体的示例实现或对预期实现的描述,是实现者的主要参考。 -- *其他实现* - 候选或最终实现的列表(外部引用,无需以正文形式出现)。 - -### 历史 - -ICS 应该包括一个“历史记录”部分,列出所有启发性的文档以及重大更改的纯文本日志。 - -请参见[下方](#history-1)的示例历史记录 。 - -### 版权 - -ICS 应该包含版权部分,按照 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 的要求放弃权利。 - -## 格式 - -### 总体格式 - -ICS 规范必须使用符合 GitHub 风格的 Markdown 格式编写。 - -有关 GitHub 风格的 Markdown 速查表,请参见[此处](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet)。有关本地 Markdown 渲染器,请参见[此处](https://github.com/joeyespo/grip)。 - -### 用语 - -ICS 规范应使用简单英语编写,避免使用晦涩的术语和不必要的行话。有关简单英语的最佳示例,请参见 [Simple English Wikipedia](https://simple.wikipedia.org/wiki/Main_Page)。 - -规范中的关键词“必须”、“禁止”、“必选”、“应该”、“不应该”、“应当”、“不应当”、“建议”、“可”,以及“可选”需遵照 [RFC 2119](https://tools.ietf.org/html/rfc2119) 中的说明进行解释。 - -### 伪代码 - -规范中的伪代码应与语言无关,并以简单的命令式标准进行格式化,并带有行号、变量、简单的条件块,for 循环和需要解释更多功能的英语片段,如调度超时。应避免使用 LaTeX 图片,因为这类图片很难以 diff 形式进行查看。 - -用于结构体的伪代码应以简单的 Typescript 作为接口编写。 - -伪代码结构示例: - -```typescript -interface Connection { - state: ConnectionState - version: Version - counterpartyIdentifier: Identifier - consensusState: ConsensusState -} -``` - -用于算法的伪代码应以简单的 Typescript 作为函数编写。 - -伪代码算法示例: - -```typescript -function startRound(round) { - round_p = round - step_p = PROPOSE - if (proposer(h_p, round_p) === p) { - if (validValue_p !== nil) - proposal = validValue_p - else - proposal = getValue() - broadcast( {PROPOSAL, h_p, round_p, proposal, validRound} ) - } else - schedule(onTimeoutPropose(h_p, round_p), timeoutPropose(round_p)) -} -``` - -## 历史 - -该规范的灵感来自以太坊的 [EIP 1](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1.md),分别源自比特币的 BIP 流程和 Python 的 PEP 流程。前任作者不对本 ICS 规范或 ICS 流程的不足负责。请将所有评论定向到 ICS 仓库维护者。 - -2019年3月4日 - 初稿已完成并提交 PR - -2019年3月7日 - 草案合并 - -2019年4月11日 - 更新伪代码格式,添加定义小节 - -2019年8月17日 - 类别澄清 - -## 版权 - -本规范所有内容均采用 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 许可授权。 diff --git a/other_docs/ibc-protocol/spec/relayer/ics-018-relayer-algorithms/README.md b/other_docs/ibc-protocol/spec/relayer/ics-018-relayer-algorithms/README.md deleted file mode 100644 index b03e3ad87..000000000 --- a/other_docs/ibc-protocol/spec/relayer/ics-018-relayer-algorithms/README.md +++ /dev/null @@ -1,266 +0,0 @@ ---- -ics: '18' -title: 中继器算法 -stage: 草案 -category: IBC/TAO -kind: 接口 -requires: 24, 25, 26 -author: Christopher Goes -created: '2019-03-07' -modified: '2019-08-25' ---- - -## 概要 - -中继器算法是 IBC 的“物理”连接层——链下进程通过扫描链的状态,构造适当的数据报,并按照 IBC 规定在对方链上执行,从而在运行 IBC 协议的两条链之间中继数据。 - -### 动机 - -在 IBC 协议中,区块链只能记录将特定数据发送到另一条链的*意图*——它不能直接访问网络传输层。物理数据报中继必须由能够访问传输层(例如 TCP/IP)的链下基础设施执行。该标准定义了*中继*算法的概念,该算法可由具有查询链状态的链下进程执行,以执行此中继。 - -### 定义 - -*中继器*是一种链下进程,能够使用 IBC 协议读取状态并将交易提交到某些账本集。 - -### 所需属性 - -- IBC 的仅一次传递或超时安全属性都不应依赖中继器的行为(假设中继器可以有拜占庭行为)。 -- IBC 的中继活性仅应依赖于至少一个正确的,活跃的中继器存在。 -- 中继应该是不需许可的,所有必要的验证都应在链上执行。 -- 应该最小化 IBC 用户和中继器之间的必要通信。 -- 应能在应用层提供中继器激励措施。 - -## 技术指标 - -### 基础中继器算法 - -中继器算法是在一个实现了 IBC 协议的链集`C`上定义的。每个中继器不一定需要访问链间网络中所有链的状态来读取数据报或将数据报写入链间网络中的所有链(尤其是在许可链或私有链的情况下),不同的中继器可以在不同子集之间中继。 - -`pendingDatagrams`根据两条链的状态计算要从一个链中继到另一个链的所有有效数据报的集合。中继器必须具有为其中继的集合中的区块链实现了哪些 IBC 协议的子集的先验知识(例如,通过阅读源代码)。下面定义了一个示例。 - -`submitDatagram`是链自己定义的过程(提交某个交易)。数据报可以每个当作单独的交易提交,也可以在链支持的情况下作为一整个交易原子性提交。 - -`relay`每隔一段时间就会调用一次 - 不高于任一链的出块速度,并且可能根据中继器期望的中继频率而降低一些。 - -不同的中继器可以在不同的链之间进行中继——只要每对链具有至少一个正确且活跃的中继器,同时这些链保持活性,网络中链之间流动的所有数据包最终都将被中继。 - -```typescript -function relay(C: Set) { - for (const chain of C) - for (const counterparty of C) - if (counterparty !== chain) { - const datagrams = chain.pendingDatagrams(counterparty) - for (const localDatagram of datagrams[0]) - chain.submitDatagram(localDatagram) - for (const counterpartyDatagram of datagrams[1]) - counterparty.submitDatagram(counterpartyDatagram) - } -} -``` - -### 数据包、回执、超时 - -#### 在有序通道中中继数据包 - -可以基于事件的方式或基于查询的方式中继有序通道中的数据包。对于前者,中继器应监视源链,每当发送数据包发出事件时,使用事件日志中的数据来组成数据包。对于后者,中继器应定期查询源链上的发送序列号,并保持中继的最后一个序列号,两者之间的任何序列号都是需要查询然后中继的数据包。无论哪种情况,中继器进程都应通过检查接收序列号来检查目的链是否尚未接收到这个数据包,然后才进行中继。 - -#### 在无序通道中中继数据包 - -可以基于事件的方式中继无序通道中的数据包。中继器应监视源链中每个发送数据包发出的事件,然后使用事件日志中的数据来组成数据包。随后,中继器应通过查询数据包的序列号是否存在对应的回执来检查目的链是否已接收到过该数据包,如果尚未出现,中继器才中继该数据包。 - -#### 中继回执 - -回执可以基于事件的方式进行中继。中继器应该监视目标链,每当接收数据包并写入回执时,使用事件日志中的数据组成回执数据包,检查数据包承诺在源链上是否存在(一旦回执被中继,它将被删除),如果是,则将回执中继到源链。 - -#### 中继超时 - -超时中继稍微复杂一些,因为当数据包超时时没有特定事件发出,这是简单的情况,由于目标链已经超过超时高度或时间戳,因此无法再中继数据包。中继器进程必须选择跟踪一组数据包(可以通过扫描事件日志来构造),并且一旦目的链的高度或时间戳超过跟踪的数据包的高度或时间戳,就检查数据包承诺是否仍存在于源链(一旦超时被中继,它将被删除),如果是,则将超时中继到源链。 - -### 待处理的数据报 - -`pendingDatagrams`整理要从一台机器发送到另一台机器的数据报。此功能的实现将取决于两台机器支持的 IBC 协议子集和源机器的状态布局。特定的中继器可能还希望实现他们自己的过滤器功能,以便仅中继可能被中继的数据报的子集(例如,他们已支付费用以某种链外方式中继的子集)。 - -下面概述了在两个链之间执行单向中继的示例实现。通过交换`chain`和`counterparty` ,可以更改为执行双向中继。 哪个中继器进程负责哪个数据报是一个灵活的选择——在此示例中,中继器进程中继在`chain`上开始的所有握手(将数据报发送到两个链),中继从`chain`发送的所有数据包到`counterparty` ,并中继所有数据包的回执从`counterparty`发送到`chain` 。 - -```typescript -function pendingDatagrams(chain: Chain, counterparty: Chain): List> { - const localDatagrams = [] - const counterpartyDatagrams = [] - - // ICS2 : 客户端 - // - 确定轻客户端是否需要更新(本地和对方) - height = chain.latestHeight() - client = counterparty.queryClientConsensusState(chain) - if client.height < height { - header = chain.latestHeader() - counterpartyDatagrams.push(ClientUpdate{chain, header}) - } - counterpartyHeight = counterparty.latestHeight() - client = chain.queryClientConsensusState(counterparty) - if client.height < counterpartyHeight { - header = counterparty.latestHeader() - localDatagrams.push(ClientUpdate{counterparty, header}) - } - - // ICS3 : 连接 - // - 确定是否正在进行任何连接握手 - connections = chain.getConnectionsUsingClient(counterparty) - for (const localEnd of connections) { - remoteEnd = counterparty.getConnection(localEnd.counterpartyIdentifier) - if (localEnd.state === INIT && - (remoteEnd === null || remoteEnd.state === INIT)) - // 握手已在本地开始(完成 1 步),将 `connOpenTry` 中继到远程端 - counterpartyDatagrams.push(ConnOpenTry{ - desiredIdentifier: localEnd.counterpartyConnectionIdentifier, - counterpartyConnectionIdentifier: localEnd.identifier, - counterpartyClientIdentifier: localEnd.clientIdentifier, - counterpartyPrefix: localEnd.commitmentPrefix, - clientIdentifier: localEnd.counterpartyClientIdentifier, - version: localEnd.version, - counterpartyVersion: localEnd.version, - proofInit: localEnd.proof(), - proofConsensus: localEnd.client.consensusState.proof(), - proofHeight: height, - consensusHeight: localEnd.client.height, - }) - else if (localEnd.state === INIT && remoteEnd.state === TRYOPEN) - // 另一端已开始握手(完成 2 步),将 `connOpenAck` 中继到本地端 - localDatagrams.push(ConnOpenAck{ - identifier: localEnd.identifier, - version: remoteEnd.version, - proofTry: remoteEnd.proof(), - proofConsensus: remoteEnd.client.consensusState.proof(), - proofHeight: remoteEnd.client.height, - consensusHeight: remoteEnd.client.height, - }) - else if (localEnd.state === OPEN && remoteEnd.state === TRYOPEN) - // 握手已在本地确认(完成 3 步),将 `connOpenConfirm` 中继到远程端 - counterpartyDatagrams.push(ConnOpenConfirm{ - identifier: remoteEnd.identifier, - proofAck: localEnd.proof(), - proofHeight: height, - }) - } - - // ICS4:通道和数据包 - // - 确定是否正在进行任何通道握手 - // - 确定是否需要中继任何数据包、回执或超时 - channels = chain.getChannelsUsingConnections(connections) - for (const localEnd of channels) { - remoteEnd = counterparty.getConnection(localEnd.counterpartyIdentifier) - // 处理正在进行的握手 - if (localEnd.state === INIT && - (remoteEnd === null || remoteEnd.state === INIT)) - // 握手已在本地开始(完成 1 步),将 `chanOpenTry` 中继到远程端 - counterpartyDatagrams.push(ChanOpenTry{ - order: localEnd.order, - connectionHops: localEnd.connectionHops.reverse(), - portIdentifier: localEnd.counterpartyPortIdentifier, - channelIdentifier: localEnd.counterpartyChannelIdentifier, - counterpartyPortIdentifier: localEnd.portIdentifier, - counterpartyChannelIdentifier: localEnd.channelIdentifier, - version: localEnd.version, - counterpartyVersion: localEnd.version, - proofInit: localEnd.proof(), - proofHeight: height, - }) - else if (localEnd.state === INIT && remoteEnd.state === TRYOPEN) - // 另一端已开始握手(已完成 2 步),将 `chanOpenAck` 中继到本地端 - localDatagrams.push(ChanOpenAck{ - portIdentifier: localEnd.portIdentifier, - channelIdentifier: localEnd.channelIdentifier, - version: remoteEnd.version, - proofTry: remoteEnd.proof(), - proofHeight: localEnd.client.height, - }) - else if (localEnd.state === OPEN && remoteEnd.state === TRYOPEN) - // 本地握手已确认(完成 3 步),将 `chanOpenConfirm` 中继到远程端 - counterpartyDatagrams.push(ChanOpenConfirm{ - portIdentifier: remoteEnd.portIdentifier, - channelIdentifier: remoteEnd.channelIdentifier, - proofAck: localEnd.proof(), - proofHeight: height - }) - - // 处理数据包 - // 首先,扫描发送数据包的日志并中继所有数据包 - sentPacketLogs = queryByTopic(height, "sendPacket") - for (const logEntry of sentPacketLogs) { - // 用这个序列号中继数据包 - packetData = Packet{logEntry.sequence, logEntry.timeoutHeight, logEntry.timeoutTimestamp, - localEnd.portIdentifier, localEnd.channelIdentifier, - remoteEnd.portIdentifier, remoteEnd.channelIdentifier, logEntry.data} - counterpartyDatagrams.push(PacketRecv{ - packet: packetData, - proof: packet.proof(), - proofHeight: height, - }) - } - - // 然后,扫描日志以获取回执,中继回发送链 - recvPacketLogs = queryByTopic(height, "writeAcknowledgement") - for (const logEntry of recvPacketLogs) { - // 使用此序列号中继数据包回执 - packetData = Packet{logEntry.sequence, logEntry.timeoutHeight, logEntry.timeoutTimestamp, - localEnd.portIdentifier, localEnd.channelIdentifier, - remoteEnd.portIdentifier, remoteEnd.channelIdentifier, logEntry.data} - counterpartyDatagrams.push(PacketAcknowledgement{ - packet: packetData, - acknowledgement: logEntry.acknowledgement, - proof: packet.proof(), - proofHeight: height, - }) - } - } - - return [localDatagrams, counterpartyDatagrams] -} -``` - -中继器可以选择过滤这些数据报,或许会根据费用支付模型,来中继特定的客户端、特定的连接、特定的通道,甚至特定类型的数据包(本文档未指定,因为它可能会有所不同)。 - -### 排序约束 - -在中继器进程上存在隐式排序约束,以确定必须以什么顺序提交哪些数据报。例如,必须先提交区块头才能最终确定存储在轻客户端中特定高度的共识状态和承诺根,然后才能转发数据包。两条链直接的中继器进程负责频繁查询两条链的状态,以确定何时必须中继什么。 - -### 捆绑 - -如果主机状态机支持,则中继器进程可以将许多数据报捆绑到一个交易中,这将导致它们按顺序执行,并平摊所有开销成本(例如,签名检查费用)。 - -### 竞态条件 - -在同一对模块和链之间中继的多个中继器可能会尝试同时中继相同的数据包(或提交相同的区块头)。如果两个中继器这样做,第一个交易将成功,第二个交易将失败。中继器之间或发送原始数据包的参与者与中继器之间的带外协调对于缓解这种情况是必要的。进一步的讨论超出了本标准的范围。 - -### 激励措施 - -中继进程必须能够访问两条链上的账户,并具有足够的余额来支付交易费用。中继器可以使用应用程序级别的方法来收回这些费用,例如通过在数据包数据中包含对自己的小额费用——中继器费用支付协议将在此 ICS 的未来版本或单独的 ICS 中描述。 - -可以安全的并行运行任意数量的中继器进程(实际上,预计单独的中继器会服务于链间的单独子集)。但是,如果他们多次提交相同的证明,则可能会花费不必要的费用,因此一些最小的协调可能是理想的(例如,将特定的中继器分配给特定的数据包或扫描内存池以查找未处理的交易)。 - -## 向后兼容性 - -不适用。中继器进程是链下的,可以根据需要进行升级或降级。 - -## 向前兼容性 - -不适用。中继器进程是链下的,可以根据需要进行升级或降级。 - -## 示例实现 - -即将到来。 - -## 其他实现 - -即将到来。 - -## 历史 - -2019年3月30日-提交初稿 - -2019年4月15日-修订格式和清晰度 - -2019年4月23日-注释修订;草案合并 - -## 版权 - -本规范所有内容均采用 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 许可授权。 diff --git a/other_docs/solana/README.md b/other_docs/solana/README.md deleted file mode 100644 index c6f3272a5..000000000 --- a/other_docs/solana/README.md +++ /dev/null @@ -1,135 +0,0 @@ -# Solana Co-learn Guide (On Working) - -## 前置环境准备 - -1. [安装 Rust](https://www.rust-lang.org/tools/install) -2. [安装 Solana](https://docs.solana.com/cli/install-solana-cli-tools) -3. [安装 Node.js](https://nodejs.org/en/download/) -4. [安装 Yarn](https://classic.yarnpkg.com/en/docs/install/#mac-stable) -5. [配置第三方RPC服务: QuickNode](https://www.quicknode.com/) - - 这里我们选择使用 QuickNode 作为我们的 RPC 服务,因为它提供了免费的 RPC 服务,而且它的服务节点分布在全球各地,可以为我们提供更好的网络体验。 - - 注意我们使用的是DevNet网络,而不是MainNet网络,因为我们在开发过程中需要使用测试币,而不是真正的币。 -6. [安装 Solana 浏览器插件](https://phantom.app/) - - Solana 浏览器插件是一个浏览器插件,它可以让我们在浏览器中连接到 Solana 网络,并且可以让我们在浏览器中查看我们的钱包地址和余额等信息。 - - 安装完成后,我们需要在浏览器中登录我们的钱包,这里我们选择使用 Sollet 钱包,因为它是一个网页钱包,我们可以直接在浏览器中使用它,而不需要下载安装任何软件。 -7. [文件编辑器] - - [Vscode](https://code.visualstudio.com/) - - [Zed](https://zed.dev/download) - -8. Solan 前端模版配置 - -> summary或者小的作业, 使用github classroom打分 - - -## 区块链原理及Solana原理介绍 - -- 区块链原理介绍 -- Solana原理介绍 - - -## Module1 - -### Solana客户端开发 - -1. [从Solana网络读取数据](./module1/read-data-from-the-solana-network/README.md) -2. [将数据写入 Solana 网络](./module1/write-data-to-the-blockchain/README.md) -3. [构建交互脚本](./module1/build-an-interaction-script/README.md) - - -### 钱包和前端 - -4. [连接到钱包](./module1/connecting-to-wallet/README.md) -5. [与程序交互](./module1/interact-with-a-program/README.md) - - -### instructions说明 - -6. 使用量具instructions说明 -7. [instructions说明](./module1/custom-instructions/README.md) -8. [构建一个电影评论应用程序](./module1/build-a-movie-review-app/README.md) -9. [运行回来——反序列化](./module1/run-it-back-deserialization/README.md) - -### 开始您自己的定制项目 - -10. [构建 NFT 铸币者前端](./module1/build-an-nft-minter-front-end/README.md) -11. [部署到 Vercel](./module1/deploy-to-vercel/README.md) - -## Module 2 - -### SPL 代币 - -### NFT + 使用 Metaplex 铸造 - -### 在 UI 中显示 NFT - -### 赚取神奇的互联网货币并出售 jpeg - - -## Module 3 - -### Rust 简介 - -### 原生 Solana 开发 - -### 安全性和验证 - -### NFT 质押 - - -## Module 4 - -### 本地环境设置和 PDA - -### 跨程序调用 - -### 测试 - -### 发送质押应用程序 - - -## Module5 - -### Anchor 锚简介 - -### Anchor 中的程序 - -### Anchor 在前端 - -### 全栈Anchor应用程序 - -## Module6 - -### 船舶周 - -> "Ship Week" 在技术开发和产品发布领域通常指的是一个特定的时间段,产品团队在这一周内专注于将新的功能或产品进行最后的调试和优化,准备好将这些新的功能或产品发布(或者说"上线")到生产环境中去。 -> -> 这个词“Ship”在这里是一个行业术语,源自于“shipping a product”,意为“发布一个产品”。因此,“Ship Week”就是“产品发布周”,在这周内,团队的主要目标是确保新的功能或产品可以顺利地发布出去。 - -### 随机性 - -### 收尾工作 - - -## Reference - -- [buildspace](https://buildspace.so/) -- [Intro Solana](https://www.soldev.app/course) -- [Solana CookBook](https://solanacookbook.com/) -- [Solana Docs](https://docs.solana.com/) -- [QuickNode Solana Rpc Docs](https://www.quicknode.com/docs/solana) -- [QuickNode Solana Developer Docs](https://www.quicknode.com/guides/solana-development/getting-started/solana-fundamentals-reference-guide) -- [alchemy Solana docs](https://docs.alchemy.com/reference/solana-api-quickstart) -- [Alchemy Doc](https://docs.alchemy.com/) -- [Figment Learn](https://learn.figment.io/protocols/solana) - -## 不错的Solana文章 -- [Squads blog](https://squads.so/blog) -- [Solana Dev](https://www.soldev.app/) - - -## 希望后面要做的事情 - -- [Solana 手机App开发](https://solanamobile.com/zh/developers) - - [Solana Mobile Stack SDK](https://github.com/solana-mobile/solana-mobile-stack-sdk#solana-mobile-stack-sdk) - - [Mobile Wallet Adapter](https://github.com/solana-mobile/mobile-wallet-adapter) -- [backpack app开发](https://docs.xnfts.dev/getting-started/introduction) diff --git a/other_docs/solana/module1/build-a-movie-review-app/README.md b/other_docs/solana/module1/build-a-movie-review-app/README.md deleted file mode 100644 index a39877a66..000000000 --- a/other_docs/solana/module1/build-a-movie-review-app/README.md +++ /dev/null @@ -1,276 +0,0 @@ ---- -sidebar_position: 8 -sidebar_label: 🎥 构建一个电影评论应用程序 -sidebar_class_name: green ---- - -# 🎥 构建一个电影评论应用程序 - -现在我们已经完成了钱包连接,让我们的 ping 按钮真正执行一些操作!为了将所有这些结合在一起,我们将构建一个链上电影评论应用程序 - 它可以让任何人提交对他们最喜欢的电影的评论,有点像烂番茄。 - -在 Solana 工作区中设置起始代码: - -```bash -git clone https://github.com/buildspace/solana-movie-frontend/ -cd solana-movie-frontend -git checkout starter -npm i -``` - -如果您运行 `npm run dev` 您应该在 `localhost:3000` 上看到以下内容: - -![](./img/upload_1.png) - -这是一个普通的 Next.js 应用程序,安装了一些模板组件和一些 Solana 依赖项,以帮助您节省时间。那里有一些模拟评论,请查看各个组件以感受该应用程序。 - -您会注意到我们已将钱包上下文提供程序从 `_app.tsx` 移至其自己的组件。它的工作原理是一样的,只是将其与更大的应用程序分开,性能更高。应用程序现在所做的就是将您的评论记录到控制台中,我们将在 `Form.tsx` 中设置 `handleTransactionSubmit` 函数。我们走吧呜呜呜呜 - -## 🗺 定义架构 - - -序列化的第一步是为我们想要序列化的数据创建模式/映射。我们需要告诉 Borsh 数据的名称以及每个项目的大小。 - -首先安装 `borsh` ,在终端中运行: - -```bash -npm install @project-serum/borsh --force -``` - -接下来前往 `Movie.ts` 导入 borsh 并在 Movie 类中添加架构(不要复制粘贴此内容): - -```ts -// We're importing borsh -import * as borsh from '@project-serum/borsh' - -export class Movie { - title: string; - rating: number; - description: string; - - // The constructor and the mocks will remain the same - constructor(title: string, rating: number, description: string) {} - static mocks: Movie[] = [] - - // Here's our schema! - borshInstructionSchema = borsh.struct([ - borsh.u8('variant'), - borsh.str('title'), - borsh.u8('rating'), - borsh.str('description'), - ]) - -} -``` - -电影评论程序期望指令数据包含: - -1. `variant` 作为无符号的 8 位整数,表示应执行哪条指令(换句话说,应调用程序上的哪个函数)。 -2. `title` 作为表示您正在查看的电影标题的字符串。 -3. `rating` 作为无符号 8 位整数,表示您对正在评论的电影的评分(满分 5 分)。 -4. `description` 作为一个字符串,表示您为电影留下的评论的书面部分。 - -模式需要匹配程序的期望 - 包括结构中项目的顺序。当程序读取你的数据时,它会按照定义的顺序反序列化,如果你的顺序不同,它创建的数据将无效。由于我们正在使用一个已经部署的程序,因此我已经为您提供了架构。通常,您会自己阅读文档或查看程序代码! - -## 🌭 创建序列化方法 - -现在我们知道数据是什么样子,我们需要编写将其序列化的方法。将其添加到 Movie 类中架构的正下方: - -```ts -serialize(): Buffer { - const buffer = Buffer.alloc(1000) - this.borshInstructionSchema.encode({ ...this, variant: 0 }, buffer) - return buffer.slice(0, this.borshInstructionSchema.getSpan(buffer)) -} -``` - -首先,我们创建一个超大缓冲区 - 这个缓冲区为 1000 字节。为什么是 1000 字节?因为我知道它足以容纳我想要的所有东西,并在最后留下额外的空间。 - -接下来,我们使用创建的模式对数据进行编码。 `encode` 接受两个值 - 我们想要编码的数据以及我们想要存储它的位置。 `this` 指的是我们所在的当前对象 - 因此我们解构电影对象并将其与 `...this` 一起传递,就像传递 `{ title, rating, description, variant }` 一样。 - -最后 - 我们删除缓冲区中的额外空间。 `getSpan` 有点像 `array.length` - 它根据模式为我们提供缓冲区中最后使用的项目的索引,因此我们的缓冲区只包含我们需要的数据,而不包含其他数据。 - -这是我的最终 `Movie.ts` 的样子: - -```ts -import * as borsh from '@project-serum/borsh' - -export class Movie { - title: string; - rating: number; - description: string; - - constructor(title: string, rating: number, description: string) { - this.title = title; - this.rating = rating; - this.description = description; - } - - static mocks: Movie[] = [ - new Movie('The Shawshank Redemption', 5, `For a movie shot entirely in prison where there is no hope at all, Shawshank redemption's main message and purpose is to remind us of hope, that even in the darkest places hope exists, and only needs someone to find it. Combine this message with a brilliant screenplay, lovely characters, and Martin freeman, and you get a movie that can teach you a lesson every time you watch it. An all-time Classic!!!`), - new Movie('The Godfather', 5, `One of Hollywood's greatest critical and commercial successes, The Godfather gets everything right; not only did the movie transcend expectations, it established new benchmarks for American cinema.`), - new Movie('The Godfather: Part II', 4, `The Godfather: Part II is a continuation of the saga of the late Italian-American crime boss, Francis Ford Coppola, and his son, Vito Corleone. The story follows the continuing saga of the Corleone family as they attempt to successfully start a new life for themselves after years of crime and corruption.`), - new Movie('The Dark Knight', 5, `The Dark Knight is a 2008 superhero film directed, produced, and co-written by Christopher Nolan. Batman, in his darkest hour, faces his greatest challenge yet: he must become the symbol of the opposite of the Batmanian order, the League of Shadows.`), - ] - - borshInstructionSchema = borsh.struct([ - borsh.u8('variant'), - borsh.str('title'), - borsh.u8('rating'), - borsh.str('description'), - ]) - - serialize(): Buffer { - const buffer = Buffer.alloc(1000) - this.borshInstructionSchema.encode({ ...this, variant: 0 }, buffer) - return buffer.slice(0, this.borshInstructionSchema.getSpan(buffer)) - } -} -``` - -就是这样!我们已经完成了序列化部分。来回顾一下几部电影吧🍿 - -## 🤝 用数据创建交易 - -难题的最后一部分是获取用户的数据,用我们刚刚创建的方法将其序列化,并用它创建一个事务。 - - -首先更新 Form.tsx 中的导入: - -```tsx -import { FC } from 'react' -import { Movie } from '../models/Movie' -import { useState } from 'react' -import { Box, Button, FormControl, FormLabel, Input, NumberDecrementStepper, NumberIncrementStepper, NumberInput, NumberInputField, NumberInputStepper, Textarea } from '@chakra-ui/react' -import * as web3 from '@solana/web3.js' -import { useConnection, useWallet } from '@solana/wallet-adapter-react' -``` - -我们需要在 `handleSubmit` 函数之前建立 RPC 连接并获取钱包详细信息: - -```tsx -const { connection } = useConnection(); -const { publicKey, sendTransaction } = useWallet(); -``` - -现在是重点, `handleTransactionSubmit` 函数。除了序列化位之外,这对于您之前的交易看起来非常熟悉:进行交易、制定指令、提交交易。 - -前半部分如下所示: - -```tsx -const handleTransactionSubmit = async (movie: Movie) => { - if (!publicKey) { - alert('Please connect your wallet!') - return - } - - const buffer = movie.serialize() - const transaction = new web3.Transaction() - - const [pda] = await web3.PublicKey.findProgramAddress( - [publicKey.toBuffer(), new TextEncoder().encode(movie.title)], - new web3.PublicKey(MOVIE_REVIEW_PROGRAM_ID) - ) -} -``` - -除了 PDA 之外,您应该认识到所有这些。回想一下指令的要求。它需要与之交互的程序 ID、可选数据以及将读取或写入的帐户列表。由于我们要提交数据以在网络上存储,因此将创建一个新帐户来存储它(记住海绵宝宝中的帕特​​里克 - 程序是无状态的,一切都在帐户中)。 - -Patrick 指的是 PDA(程序派生地址)!这是一个用于存储我们的电影评论的帐户。你可能会开始注意到我们遇到了经典的“先有鸡还是先有蛋”的情况...... - -![](./img/upload_2.png) - -我们需要知道帐户地址才能进行有效交易,并且需要处理交易才能创建帐户。解决方案?一个理论蛋。如果交易创建者和程序都使用相同的过程来选择地址,我们可以在交易处理之前导出地址。 - -这就是 `web3.PublicKey.findProgramAddress` 方法正在做的事情。它接受两个变量:种子和生成种子的程序(电影评论程序)。在我们的例子中,种子是发件人的地址和电影的标题。通过这个应用程序,我告诉您种子要求,通常您要么阅读文档,查看程序代码,要么对其进行逆向工程。 - -要完成 handleTransactionSubmit 功能,您所需要做的就是创建一条指令并发送它,以下是完整代码: - -```tsx -const handleTransactionSubmit = async (movie: Movie) => { - if (!publicKey) { - alert('Please connect your wallet!') - return - } - - const buffer = movie.serialize() - const transaction = new web3.Transaction() - - const [pda] = await web3.PublicKey.findProgramAddress( - [publicKey.toBuffer(), new TextEncoder().encode(movie.title)], - new web3.PublicKey(MOVIE_REVIEW_PROGRAM_ID) - ) - - const instruction = new web3.TransactionInstruction({ - keys: [ - { - // Your account will pay the fees, so it's writing to the network - pubkey: publicKey, - isSigner: true, - isWritable: false, - }, - { - // The PDA will store the movie review - pubkey: pda, - isSigner: false, - isWritable: true - }, - { - // The system program will be used for creating the PDA - pubkey: web3.SystemProgram.programId, - isSigner: false, - isWritable: false - } - ], - // Here's the most important part! - data: buffer, - programId: new web3.PublicKey(MOVIE_REVIEW_PROGRAM_ID) - }) - - transaction.add(instruction) - - try { - let txid = await sendTransaction(transaction, connection) - console.log(`Transaction submitted: https://explorer.solana.com/tx/${txid}?cluster=devnet`) - } catch (e) { - alert(JSON.stringify(e)) - } - } -``` -这就是一个包装!确保您的钱包位于 devnet 上并且您拥有 devnet SOL 并前往 localhost:3000 。提交评论并访问控制台中记录的资源管理器链接。一直向下滚动,您会看到您的电影名称以及一堆其他内容: - -![](./img/upload_3.png) - -哇。您刚刚将自定义数据写入 Solana 网络。 - -拍拍自己的背,这不是简单的事情!此时,有些人可能已经退出该计划,给他们一些动力并向他们展示您所构建的内容!如果你已经走到这一步了,我毫不怀疑你会一直走到最后:) - - -## 🚢 船舶挑战 - -是时候给大脑多一些皱纹了🧠 - - -继续创建一个应用程序,让 Solana Core 中的构建者进行自我介绍!我们将在这个地址 `HdE95RSVsdb315jfJtaykXhXY478h53X6okDupVfY9yf` 处使用 Solana 程序。它最终看起来与电影评论应用程序类似: - -![](./img/upload_4.png) - -起始代码 -您可以设置使用 - -```bash -git clone https://github.com/buildspace/solana-student-intros-frontend.git -cd solana-student-intros-frontend -git checkout starter -npm i -``` - -提示: -程序期望指令数据按顺序包含以下内容: - -1. `variant` 作为无符号 8 位整数,表示要调用的指令(在本例中应为 0) -2. `name` 作为字符串 -3. `message` 作为字符串 - -请注意,该程序使用所连接钱包的公钥(仅此而已)派生每个学生介绍帐户。这意味着每个PublicKey只能初始化一个Student Intro账户,如果使用同一个PublicKey提交两次,交易将会失败。 - -与往常一样,首先尝试独立执行此操作,但如果您陷入困境或只是想将您的解决方案与我们的解决方案进行比较,请查看[此存储库](https://github.com/buildspace/solana-student-intros-frontend/tree/solution-serialize-instruction-data?utm_source=buildspace.so&utm_medium=buildspace_project)中的 solution-serialize-instruction-data 分支。 diff --git a/other_docs/solana/module1/build-a-movie-review-app/img/upload_1.png b/other_docs/solana/module1/build-a-movie-review-app/img/upload_1.png deleted file mode 100644 index 5b3912192..000000000 Binary files a/other_docs/solana/module1/build-a-movie-review-app/img/upload_1.png and /dev/null differ diff --git a/other_docs/solana/module1/build-a-movie-review-app/img/upload_2.png b/other_docs/solana/module1/build-a-movie-review-app/img/upload_2.png deleted file mode 100644 index d5ca64a52..000000000 Binary files a/other_docs/solana/module1/build-a-movie-review-app/img/upload_2.png and /dev/null differ diff --git a/other_docs/solana/module1/build-a-movie-review-app/img/upload_3.png b/other_docs/solana/module1/build-a-movie-review-app/img/upload_3.png deleted file mode 100644 index 7fc8bf5d4..000000000 Binary files a/other_docs/solana/module1/build-a-movie-review-app/img/upload_3.png and /dev/null differ diff --git a/other_docs/solana/module1/build-a-movie-review-app/img/upload_4.png b/other_docs/solana/module1/build-a-movie-review-app/img/upload_4.png deleted file mode 100644 index 8eb623248..000000000 Binary files a/other_docs/solana/module1/build-a-movie-review-app/img/upload_4.png and /dev/null differ diff --git a/other_docs/solana/module1/build-an-interaction-script/README.md b/other_docs/solana/module1/build-an-interaction-script/README.md deleted file mode 100644 index 210af3707..000000000 --- a/other_docs/solana/module1/build-an-interaction-script/README.md +++ /dev/null @@ -1,272 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: 📝 构建交互脚本 -sidebar_class_name: green ---- - -# 📝 构建交互脚本 - -准备好探索 Solana 网络了吗?我们将编写一个脚本来生成密钥对,使用 devnet SOL 为其提供资金,并与 Solana 网络上的现有程序进行交互。 - -该程序是一个简单的“ping”计数器:我们点击它,它会记录我们对其的 ping 并递增计数器。稍后我们将讨论 Rust 和我们自己的程序,现在我们将使用 JS/TS。 - -## 🚧 设置本地 Solana 客户端 - -让我们换个方式——我们将在这里放弃 `React/Next.js`,只用 `Typescript` 构建一个本地客户端。这比设置前端和构建一堆 UI 快得多。您可以处理单个 TS 文件并异步运行它以与网络交互。 - -在 Solana 工作区中创建一个新文件夹,并使用这个方便的命令来设置本地客户端: - -```bash -npx create-solana-client solana-intro-client -``` - -如果它询问您是否要安装 `create-solana-client` 软件包,请说“是”。 - - -现在只需导航到目录并在 VS Code 中启动它 - -```bash -cd solana-intro-client -code . -``` - -## ⚙ 设置客户端脚本 - -`create-solana-client` 的美妙之处在于我们可以立即开始编写客户端代码!跳转到 `index.ts` 并导入我们的依赖项并添加此 `initializeKeypair` 函数: - -```ts -// We're adding these -import * as Web3 from '@solana/web3.js'; -import * as fs from 'fs'; -import dotenv from 'dotenv'; -dotenv.config(); - -async function main() { -} - -main() - .then(() => { - console.log('Finished successfully'); - process.exit(0); - }) - .catch((error) => { - console.log(error); - process.exit(1); - }); -``` - -如果您在终端中运行 `npm start` ,您将看到脚本已运行!只需一个命令即可设置 Solana 客户端。 - -让我们添加一个 `initializeKeypair` 函数,如果我们没有密钥对,它将自动为我们创建一个密钥对。在导入之后添加以下内容: - -```ts -async function initializeKeypair(connection: Web3.Connection): Promise { - if (!process.env.PRIVATE_KEY) { - console.log('Generating new keypair... 🗝️'); - const signer = Web3.Keypair.generate(); - - console.log('Creating .env file'); - fs.writeFileSync('.env', `PRIVATE_KEY=[${signer.secretKey.toString()}]`); - - return signer; - } - - const secret = JSON.parse(process.env.PRIVATE_KEY ?? '') as number[]; - const secretKey = Uint8Array.from(secret); - const keypairFromSecret = Web3.Keypair.fromSecretKey(secretKey); - return keypairFromSecret; -} -``` - -这是一个非常智能的函数 - 它会检查您的 `.env` 文件中是否有私钥,如果没有,它就会创建一个! - -您已经熟悉这里发生的一切 - 我们调用 `Web3.Keypair.generate()` 函数并将结果写入本地 `dotenv` 文件。创建后,我们将返回密钥对,以便我们可以在脚本的其余部分中使用它。 - -更新您的 `main` 函数并使用 `npm start` 运行脚本来测试它: - -```ts -async function main() { - const connection = new Web3.Connection(Web3.clusterApiUrl('devnet')); - const signer = await initializeKeypair(connection); - - console.log("Public key:", signer.publicKey.toBase58()); -} -``` - -您应该在终端中看到类似这样的内容: - -```bash -> solana-course-client@1.0.0 start -> ts-node src/index.ts - -Generating new keypair... 🗝️ -Creating .env file -Public key: jTAsqBrjsYp4uEJNmED5R66gHPnFW4wvQrbmFG3c4QS -Finished successfully -``` - -好的!如果您检查 `.env` 文件,您将看到一个字节格式的私钥!该密钥与文件一样保密。如果您将此文件推送到公共 GitHub 存储库,任何人都可以访问其中的资金,因此请确保您不要将其用于真正的货币用途,哈哈。 - -再次运行 `npm start` 将使用它而不是创建一个新的。 - -保持测试帐户独立非常重要,这就是为什么这个脚本特别酷的原因 - 它消除了创建和管理测试钱包的麻烦。 - -现在,如果我们也能自动获取 `devnet SOL` 就好了。哦等等,我们可以! - -看看这个恶心的空投功能—— - -```ts -async function airdropSolIfNeeded( - signer: Web3.Keypair, - connection: Web3.Connection -) { - const balance = await connection.getBalance(signer.publicKey); - console.log('Current balance is', balance / Web3.LAMPORTS_PER_SOL, 'SOL'); - - // 1 SOL should be enough for almost anything you wanna do - if (balance / Web3.LAMPORTS_PER_SOL < 1) { - // You can only get up to 2 SOL per request - console.log('Airdropping 1 SOL'); - const airdropSignature = await connection.requestAirdrop( - signer.publicKey, - Web3.LAMPORTS_PER_SOL - ); - - const latestBlockhash = await connection.getLatestBlockhash(); - - await connection.confirmTransaction({ - blockhash: latestBlockhash.blockhash, - lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, - signature: airdropSignature, - }); - - const newBalance = await connection.getBalance(signer.publicKey); - console.log('New balance is', newBalance / Web3.LAMPORTS_PER_SOL, 'SOL'); - } -} -``` - -这可能看起来令人难以承受,但您实际上知道这里发生的一切!我们使用我们的老朋友 `getBalance` 来检查我们是否破产了,如果破产了,我们使用 `requestAidrop` 函数来下雨。 - -区块哈希和区块高度是区块标识符,用于向网络传达我们是最新的并且不会发送过时的交易。 - -不过,不要尝试循环运行它 - 水龙头有一个冷却时间,如果你继续向它发送垃圾邮件,请求将会失败,哈哈。 - -确保在创建/获取密钥对后更新 `initializeKeypair` 函数以调用空投。 - -```ts -// When generating a keypair - await airdropSolIfNeeded(signer, connection); - - // When creating it from the secret key - await airdropSolIfNeeded(keypairFromSecret, connection); -``` - - -现在,如果您 npm run start ,您将看到空投: - -```bash -Current balance is 0 SOL -Airdropping 1 SOL -New balance is 1 SOL -Public key: 7Fw3bXskk5eonycvET6BSufxAsuNudvuxF7MMnS8KMqX -``` - -我们准备好了 rrrrrrrrrrrrumble 🥊 - -## 🖱 调用链上程序 - -是时候使用我们的客户端了。我们要将数据写入 Solana 网络上的现有程序。人们认为 Solana 开发就是用 Rust 编写程序。不!大多数区块链开发都是与现有程序交互。 - - -您可以构建数百个仅与现有所有程序交互的应用程序。这就是乐趣的开始!我们将保持简单 - 我们的客户端将 ping 一个计数器程序,这将增加一个计数器。您将告诉网络上的每个人您是一名建设者。 - - -我们需要告诉客户它将与哪些程序交互。首先在顶部、导入正下方添加这些地址: - -```ts -const PROGRAM_ID = new Web3.PublicKey("ChT1B39WKLS8qUrkLvFDXMhEJ4F1XZzwUNHUt4AU9aVa") -const PROGRAM_DATA_PUBLIC_KEY = new Web3.PublicKey("Ah9K7dQ8EHaZqcAsgBW8w37yN2eAy3koFmUn4x3CJtod") -``` - - -`PROGRAM_ID` 是ping程序本身的地址。 `PROGRAM_DATA_PUBLIC_KEY` 是存储程序数据的帐户的地址。请记住 - 可执行代码和状态数据单独存储在 Solana 上! - -然后添加此函数以在任何地方 ping 程序: - -```ts -async function pingProgram(connection: Web3.Connection, payer: Web3.Keypair) { - const transaction = new Web3.Transaction() - const instruction = new Web3.TransactionInstruction({ - // Instructions need 3 things - - // 1. The public keys of all the accounts the instruction will read/write - keys: [ - { - pubkey: PROGRAM_DATA_PUBLIC_KEY, - isSigner: false, - isWritable: true - } - ], - - // 2. The ID of the program this instruction will be sent to - programId: PROGRAM_ID - - // 3. Data - in this case, there's none! - }) - - transaction.add(instruction) - const transactionSignature = await Web3.sendAndConfirmTransaction(connection, transaction, [payer]) - - console.log( - `Transaction https://explorer.solana.com/tx/${transactionSignature}?cluster=devnet` - ) -} -``` - -这并不像看起来那么复杂!你已经知道了这一部分 - -- 我们进行交易 -- 我们发出指示 -- 我们将指令添加到交易中 -- 我们将交易发送到网络! - -查看上面的代码注释 - 我回顾一下指令的三个部分。 - -这里最重要的是 keys 值 - 它是该指令将读取或写入的每个帐户的帐户元数据数组。在我们的例子中,我告诉您该指令将处理哪些帐户。 - -您需要知道这是什么 - 您可以通过阅读程序本身或它的文档来找到答案。如果您不知道这一点,则无法与程序交互,因为指令将无效。您将发送一个将触及数据帐户的交易,但您不会告诉运行时哪个帐户,因此它将被删除。 - -可以将其想象为尝试开车前往没有 GPS 的地址。您知道自己想去哪里,但不知道到达那里的路线。 - -由于此写入不需要数据帐户的签名,因此我们将 `isSigner` 设置为 `false`。 `isWritable` 为 `true`,因为该帐户正在被写入! - -通过告诉网络我们需要与哪些帐户交互以及我们是否正在向它们写入数据,Solana 运行时就知道可以并行运行哪些事务。这就是 Solana 如此之快的部分原因! - -将此函数调用 `await pingProgram(connection, signer)` 添加到 `main()` 并使用 `npm start` 运行脚本。访问记录的资源管理器链接,您将在页面底部看到您编写的数据(您可以忽略其他所有内容)- - -![](./img/ping-solana.png) - -您刚刚将数据写入区块链。那有多容易? - -这可能看起来很简单,但您确实已经取得了成功。当推特上的每个人都在大喊猴子图片时,你正在建造GGGGGGGGGGGGGGGGGG。您在本节中学到的内容 - 从 Solana 网络读取和写入数据,足以制作价值 1 万美元的产品。想象一下在这个项目结束时你能做什么 🤘 - -## 🚢 船舶挑战 - SOL 传输脚本 - -现在我们已经共同将交易发送到网络,现在轮到您独立尝试了。 - -按照与上一步类似的过程,从头开始创建一个脚本,让您可以将 SOL 从 Devnet 上的一个帐户转移到另一个帐户。请务必打印出交易签名,以便您可以在 Solana Explorer 上查看它。 - - -想想到目前为止你学到了什么 - - -- 通过事务将数据写入网络 -- 交易需要指令 -- 指令告诉网络它们接触哪些程序以及它们做什么 -- 使用系统程序传输 SOL(嗯,我想知道它叫什么。🤔 传输?) - -您在这里所需要做的就是找出确切的函数名称是什么以及指令应该是什么样子。我会从谷歌开始:P - -附:如果您确定已经弄清楚了,但转账仍然失败,则可能是您转账太少 - 尝试至少转账 0.1 SOL。 - -像往常一样,在引用解决方案代码之前尝试自己执行此操作。当您确实需要参考解决方案时,[请查看此处](https://github.com/buildspace/solana-send-sol-client/tree/main)。 👀 diff --git a/other_docs/solana/module1/build-an-interaction-script/img/ping-solana.png b/other_docs/solana/module1/build-an-interaction-script/img/ping-solana.png deleted file mode 100644 index 102d0167b..000000000 Binary files a/other_docs/solana/module1/build-an-interaction-script/img/ping-solana.png and /dev/null differ diff --git a/other_docs/solana/module1/build-an-nft-minter-front-end/README.md b/other_docs/solana/module1/build-an-nft-minter-front-end/README.md deleted file mode 100644 index 08d645b8a..000000000 --- a/other_docs/solana/module1/build-an-nft-minter-front-end/README.md +++ /dev/null @@ -1,484 +0,0 @@ ---- -sidebar_position: 10 -sidebar_label: 💻 构建 NFT 铸币者前端 -sidebar_class_name: green ---- - -# 💻 构建 NFT 铸币者前端 - -欢迎来到运输的第一周。每周您都会有一个完整的部分专门用于将您所学到的内容构建到带有战利品盒的自定义 NFT 质押应用程序中! - - -这些部分的重点是让您脱离本地主机并构建其他人可以使用的真实内容。在您之前的所有建设者都通过将他们的工作公开并进行建设而获得了巨大的成功。这是你一直在准备的时刻——让我们做这件事🤘。 - - -今天我们将从前端开始制作这些光滑的登陆和薄荷页面。 - -![](./img/upload_1.png) - - -第一个屏幕上的唯一功能是连接到用户的钱包。您可以使用屏幕顶部的按钮以及中间的按钮来执行此操作。 - -![](./img/upload_2.png) - -第二个屏幕功能将在下一个核心项目中实现,因此无需为“mint buildoor”按钮实现任何内容。 - -## 🕸 设置项目 - -我们从头开始,这次没有模板!设置一个新的 Next.js 应用程序并向其中添加 Chakra UI: - -```bash -npx create-next-app --typescript -cd -npm i @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^6 @chakra-ui/icons -npm i @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-react-ui @solana/wallet-adapter-wallets @solana/web3.js -``` - - -注意:在整个项目中,我们将使用 Typescript!如果您愿意,我们非常欢迎您使用普通的 Javascript :)。 - -如果要求安装 create-next-app ,请说“是”。您可以为您的应用程序命名任何您想要的名称,我将我的应用程序命名为构建器,哈哈。 - -接下来您想要添加一些资产。您可以在[这里](https://cdn.disco.co/media%2FAssets_a68f5cab-20c9-45c7-b25c-43bc9dcd9e7d.zip?utm_source=buildspace.so&utm_medium=buildspace_project)购买,也可以自己制作。您将看到五个“头像”文件和一个背景 svg。将它们放入公共文件夹中。 - -## ✨ 设置 Chakra UI - -第一个任务是设置 Chakra UI,这样我们就不必手动编写大量 CSS。我们将在 `pages/_app.tsx` 中执行此操作: - -```ts -import type { AppProps } from "next/app" -import { ChakraProvider } from "@chakra-ui/react" - -import { extendTheme } from "@chakra-ui/react" - -const colors = { - background: "#1F1F1F", - accent: "#833BBE", - bodyText: "rgba(255, 255, 255, 0.75)", -} - -const theme = extendTheme({ colors }) - -function MyApp({ Component, pageProps }: AppProps) { - return ( - - - - ) -} - -export default MyApp -``` - -我要为我的一些定制颜色,请确保您按照自己的喜好来调味! - -## 🌶 添加一些样式 - -打开 styles/Home.module.css 并使其看起来像这样: - -```css -.container { - background: #1F1F1F; -} -.wallet-adapter-button-trigger { - background-color: #833BBE; -} -``` - -如果样式文件夹中有 globals.css 文件,请将其删除。我们不会需要它! - -接下来我们有 index.tsx ,我们将更新导入以使用 Chakra UI 并渲染(单个
{ - - return ( -
- - Buildoors - - - - - - - { /* NavBar */ } - - -
- { /* If connected, the second view, otherwise the first */ } -
- - -
- - - built with @_buildspace - - -
-
-
-
- ) -} - -export default Home -``` - -## 🎫 添加导航栏 - -现在让我们构建 NavBar 。创建 components 文件夹并添加新文件 NavBar.tsx 。我们将其构建为带有垫片和用于连接钱包的按钮的水平堆栈: - - - -```ts -import { HStack, Spacer } from "@chakra-ui/react" -import { FC } from "react" -import styles from "../styles/Home.module.css" -import dynamic from "next/dynamic"; - -const WalletMultiButtonDynamic = dynamic( - async () => - (await import("@solana/wallet-adapter-react-ui")).WalletMultiButton, - { ssr: false } -); - -const NavBar: FC = () => { - return ( - - - - - ) -} - -export default NavBar -``` - -我们有 import dynamic from "next/dynamic" 从 @solana/wallet-adapter-react-ui 动态导入 WalletMultiButton 并将其分配给 WalletMultiButtonDynamic ,如下所示: - -```ts -const WalletMultiButtonDynamic = dynamic( - async () => - (await import("@solana/wallet-adapter-react-ui")).WalletMultiButton, - { ssr: false } -); -``` - -这是因为 NextJS 是服务器端渲染,在加载到客户端之前无法访问依赖于浏览器 API(如 window )的外部依赖项或组件。这意味着 NextJS 无法与只能在浏览器上使用的钱包进行交互。 { ssr: false } 禁用导入的服务器渲染。如果您的模块不使用动态导入,您很可能会遇到 Hydration failed because the initial UI does not match what was rendered on the server 。您可以在[这里](https://nextjs.org/docs/advanced-features/dynamic-import?utm_source=buildspace.so&utm_medium=buildspace_project)阅读有关动态导入的更多信息! - -返回到 index.tsx ,导入 NavBar 并将其放在堆栈顶部(我留下了关于它应该在哪里的评论): - -```ts -// Existing imports -import NavBar from "../components/NavBar" - -const Home: NextPage = () => { - - return ( -
- - - - - { /* NavBar */ } - - -// Rest of the file remains the same -``` - -此时,除了“Connect Wallet”之外,您在 localhost:3000 上仍然没有任何内容。让我们解决这个问题。 - -## 🏠 创建登陆页面 - -在 components 文件夹中创建 Disconnected.tsx 文件并添加以下内容: - -```ts -import { FC, MouseEventHandler, useCallback } from "react" -import { - Button, - Container, - Heading, - HStack, - Text, - VStack, -} from "@chakra-ui/react" -import { ArrowForwardIcon } from "@chakra-ui/icons" - -const Disconnected: FC = () => { - - const handleClick: MouseEventHandler = useCallback( - (event) => { - if (event.defaultPrevented) { - return - } - }, - [] - ) - - return ( - - - - Mint your buildoor. Earn $BLD. Level up. - - - - - ) -} - -export default Disconnected -``` - -这将是我们的登陆页面 - 用户访问该网站时看到的第一个视图。您需要将其导入 index.tsx 并将其放置在渲染组件的中间(再次查找注释): - -```ts -// Existing imports -import Disconnected from '../components/Disconnected' - -const Home: NextPage = () => { - - return ( -
- - - - - { /* NavBar */ } - - - -
- -
- - -// Rest of the file remains the same -``` - -现在,如果您查看 localhost:3000 ,您应该会看到带有“成为 buildoor”按钮的登录页面。如果你点击它,什么也不会发生。我们不喜欢什么都没有发生,让我们解决这个问题! - -## 🔌 连接到用户的钱包 - -我们这里需要很多钩子。让我们把它们带进来: - -```bash -npm i @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-react-ui @solana/wallet-adapter-wallets @solana/web3.js -``` - -如果您正在为特定的钱包进行构建,那么您可以在此处进行更改,我只是坚持使用默认值:D - -在 components 中创建一个 WalletContextProvider.tsx ,这样我们就可以将所有这些样板文件放入其中: - -```ts -import { FC, ReactNode } from "react" -import { - ConnectionProvider, - WalletProvider, -} from "@solana/wallet-adapter-react" -import { WalletModalProvider } from "@solana/wallet-adapter-react-ui" -import { clusterApiUrl } from "@solana/web3.js" -import { PhantomWalletAdapter } from "@solana/wallet-adapter-wallets" -import { useMemo } from "react" -require("@solana/wallet-adapter-react-ui/styles.css") - -const WalletContextProvider: FC<{ children: ReactNode }> = ({ children }) => { - const url = useMemo(() => clusterApiUrl("devnet"), []) - const phantom = new PhantomWalletAdapter() - - return ( - - - {children} - - - ) -} - -export default WalletContextProvider -``` - -我们需要将其导入 _app.tsx : - -```ts -import WalletContextProvider from '../components/WalletContextProvider' - - - - - - -``` - -现在我们还希望“成为建造者”按钮也能与您联系。在 Disconnected.tsx 中,添加这些导入 - -```ts -import { useWalletModal } from "@solana/wallet-adapter-react-ui" -import { useWallet } from "@solana/wallet-adapter-react" -``` - -然后在渲染之前将 Disconnected 的主体更新为以下内容: - -```ts -const modalState = useWalletModal() - const { wallet, connect } = useWallet() - - const handleClick: MouseEventHandler = useCallback( - (event) => { - if (event.defaultPrevented) { - return - } - - if (!wallet) { - modalState.setVisible(true) - } else { - connect().catch(() => {}) - } - }, - [wallet, connect, modalState] - ) -``` - -瞧,您应该能够连接了! - -## 🎇 创建连接视图 - -现在我们可以连接了,我们需要更新视图以显示连接时的样子。让我们在 components 目录中创建一个 Connected.tsx 文件 - -```ts -import { FC } from "react" -import { - Button, - Container, - Heading, - HStack, - Text, - VStack, - Image, -} from "@chakra-ui/react" -import { ArrowForwardIcon } from "@chakra-ui/icons" - -const Connected: FC = () => { - return ( - - - - - Welcome Buildoor. - - - - Each buildoor is randomly generated and can be staked to receive - $BLD Use your $BLD to - upgrade your buildoor and receive perks within the community! - - - - - - - - - - - - - - - ) -} - -export default Connected -``` - -现在我们必须找到一种方法将其显示在屏幕上。回到 index.tsx ,让我们添加两个导入: - -```ts -import { useWallet } from "@solana/wallet-adapter-react" -import Connected from "../components/Connected" -``` - -现在我们可以使用 useWallet 钩子来访问一个变量,告诉我们是否已连接。我们可以使用它来有条件地渲染 Connected 与 Disconnected 视图。 - - -```ts -const Home: NextPage = () => { - const { connected } = useWallet() - - return ( -
- - Buildoors - - - - - - - - - -
{connected ? : }
- -``` - - -我们开始吧!我们已经设置了前端,并且正在全力打造 buildoors diff --git a/other_docs/solana/module1/build-an-nft-minter-front-end/img/upload_1.png b/other_docs/solana/module1/build-an-nft-minter-front-end/img/upload_1.png deleted file mode 100644 index d796d5e68..000000000 Binary files a/other_docs/solana/module1/build-an-nft-minter-front-end/img/upload_1.png and /dev/null differ diff --git a/other_docs/solana/module1/build-an-nft-minter-front-end/img/upload_2.png b/other_docs/solana/module1/build-an-nft-minter-front-end/img/upload_2.png deleted file mode 100644 index 3699a2144..000000000 Binary files a/other_docs/solana/module1/build-an-nft-minter-front-end/img/upload_2.png and /dev/null differ diff --git a/other_docs/solana/module1/connecting-to-wallet/README.md b/other_docs/solana/module1/connecting-to-wallet/README.md deleted file mode 100644 index 269680cd5..000000000 --- a/other_docs/solana/module1/connecting-to-wallet/README.md +++ /dev/null @@ -1,151 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: 🔌 连接钱包 -sidebar_class_name: green ---- - - -# 🔌 连接钱包 - -我们现在了解了很多有关通过代码与网络交互的知识。为了进行交易,我们使用私钥。这对用户不起作用,哈哈。为了让人们用真钱从我们这里购买 jpeg,我们需要与钱包合作。 - -“钱包”是一个奇怪的名字,因为它们的作用不仅仅是存放东西。钱包是安全存储密钥并允许用户签署交易的任何东西。它们有多种形式,最常见的是浏览器扩展,它们为您(开发人员)提供 API 来向用户建议交易。钱包使您可以安全地执行以下操作: - -![](./img/upload_1.png) - -我们将使用 Phantom 浏览器扩展程序,因为它是最受欢迎的,如果您愿意,您可以使用另一个:) - -让我们将我们的网络应用程序与钱包连接起来,让它为用户提供交易报价! - -## 🛠 Solana 钱包适配器 - -那里有几十个钱包。他们每个人都以自己的方式做事。想象一下,如果您必须为每个单独的钱包 API 进行构建,那将是一场噩梦。值得庆幸的是,我们有 [Solana Wallet-Adapter](https://github.com/solana-labs/wallet-adapter?utm_source=buildspace.so&utm_medium=buildspace_project) - 它是一套库,为您提供几乎通用的 API,可与大量钱包一起使用([完整列表见此处](https://github.com/solana-labs/wallet-adapter#wallets?utm_source=buildspace.so&utm_medium=buildspace_project))。 - - -您将主要使用 wallet-adapter-base 和 wallet-adapter-react 库。您可以选择您想要支持的特定钱包,或者只支持所有钱包。这里的区别在于您要使用哪些库 - 特定的钱包库或 wallet-adapter-wallets 。由于我们要使用 Phantom,因此我们可以只使用 Phantom 库! - -这是我们需要安装的内容(您现在不需要运行它): - -```bash -npm install @solana/wallet-adapter-base \ - @solana/wallet-adapter-react \ - @solana/wallet-adapter-phantom \ - @solana/wallet-adapter-react-ui -``` - -wallet-adapter-react-ui 为我们处理整个 UI - 连接、选择钱包、断开连接,一切都已排序! - -![](./img/wallets.png) - -感谢所有这些病态的库,我们再也不需要在 Solana 上构建钱包连接的东西了!借此机会感谢维护人员节省了您的时间和头发。 - -## 👜 构建钱包连接按钮 - -让我们来看看这些库吧!在您的工作区中设置一个新项目: - -```bash -git clone https://github.com/buildspace/solana-ping-frontend.git -cd solana-ping-frontend -git checkout starter -npm i -``` - -该模板继承了我们上次构建的内容 - 我们为 ping 客户端提供了一个前端,用于将数据写入区块链。使用 npm run dev 你会在本地主机上看到这个: - -![](./img/upload_2.png) - -这是一个准系统 UI - 让我们将其连接到 wallet-adapter-react 库。 - -拉起 _app.tsx 并使其看起来像这样: - -```ts -import React, { useMemo } from "react"; -import { WalletAdapterNetwork } from "@solana/wallet-adapter-base"; -import { WalletModalProvider } from "@solana/wallet-adapter-react-ui"; -import { ConnectionProvider, WalletProvider } from "@solana/wallet-adapter-react"; -import { - GlowWalletAdapter, - PhantomWalletAdapter -} from "@solana/wallet-adapter-wallets"; -import { clusterApiUrl } from "@solana/web3.js"; - -require("@solana/wallet-adapter-react-ui/styles.css"); -require("../styles/globals.css"); -require ("../styles/Home.module.css"); - -const App = ({ Component, pageProps }) => { - // Can be set to 'devnet', 'testnet', or 'mainnet-beta' - const network = WalletAdapterNetwork.Devnet; - - // You can provide a custom RPC endpoint here - const endpoint = useMemo(() => clusterApiUrl(network), [network]); - - // @solana/wallet-adapter-wallets includes all the adapters but supports tree shaking and lazy loading -- - // Only the wallets you configure here will be compiled into your application, and only the dependencies - // of wallets that your users connect to will be loaded - const wallets = useMemo( - () => [ - new PhantomWalletAdapter(), - new GlowWalletAdapter() - ], - [network] - ); - - return ( - - - - - - - - ); -}; - -export default App; -``` - -这是大量进口。不用担心,您只需要知道它们各自的用途即可,无需深入了解它们如何工作。这是每个部分的快速摘要。 - -我们从 React 开始。 useMemo() 是一个钩子,仅当依赖项之一发生更改时才加载内容。在我们的例子中,只有当用户连接的网络发生变化时, clusterApiUrl 的值才会发生变化。 - -我们的第一个 Solana 导入是来自 @solana/wallet-adapter-base 的 wallet-adapter-network 。这只是可用网络的可枚举对象。 - -WalletModalProvider 就是这样,哈哈 - 它是一个奇特的 React 组件,会提示用户选择他们的钱包。埃兹普兹。 - -ConnectionProvider 采用 RPC 端点,让我们直接与 Solana 区块链上的节点对话。我们将在整个应用程序中使用它来发送交易。 - -WalletProvider 为我们提供了一个连接各种钱包的标准接口,因此我们不必费心阅读每个钱包的文档呵呵。 - -接下来您将看到来自 wallet-adapter-wallets 的一堆钱包适配器。我们将使用从中导入的内容来创建我们将提供给 WalletProvider 的钱包列表。还有许多其他钱包适配器可用,甚至有些是为其他区块链制作的!在这里查看它们。我刚刚选择了 Phantom 和 Glow。 - -最后,我们有 clusterApiURL ,它只是一个根据我们提供的网络为我们生成 RPC 端点的函数。 - -对于 React App 组件内的 return 语句,我们用一些上下文提供程序包装子组件(应用程序的其余部分)。 - -总结一下:这个文件是我们网络应用程序的 start 。我们在这里提供的任何内容都可以通过我们应用程序的其余部分访问。我们在这里提供所有钱包和网络工具,因此我们不需要在每个子组件中重新初始化它们。 - -我从官方 wallet-adapter Next.js 模板复制了所有这些代码,所以不要对复制/粘贴(这次)感到难过。 - -## 🧞‍♂️ 使用提供商连接钱包 - -唷,那是一堆设置!现在您可以看到与钱包交互是多么容易。我们所要做的就是在 components/AppBar.tsx 中设置一个 React hook: - -```ts -import { FC } from 'react' -import styles from '../styles/Home.module.css' -import Image from 'next/image' -import { WalletMultiButton } from '@solana/wallet-adapter-react-ui' - -export const AppBar: FC = () => { - return ( -
- - Wallet-Adapter Example - -
- ) -} -``` - -很容易,嗯? WalletMultiButton 为我们提供了很多魔力并处理所有连接位。如果您现在硬刷新您的应用程序,您应该会在右上角看到一个漂亮的紫色按钮! diff --git a/other_docs/solana/module1/connecting-to-wallet/img/upload_1.png b/other_docs/solana/module1/connecting-to-wallet/img/upload_1.png deleted file mode 100644 index aa02660fc..000000000 Binary files a/other_docs/solana/module1/connecting-to-wallet/img/upload_1.png and /dev/null differ diff --git a/other_docs/solana/module1/connecting-to-wallet/img/upload_2.png b/other_docs/solana/module1/connecting-to-wallet/img/upload_2.png deleted file mode 100644 index 7778c46b3..000000000 Binary files a/other_docs/solana/module1/connecting-to-wallet/img/upload_2.png and /dev/null differ diff --git a/other_docs/solana/module1/connecting-to-wallet/img/wallets.png b/other_docs/solana/module1/connecting-to-wallet/img/wallets.png deleted file mode 100644 index e2a002342..000000000 Binary files a/other_docs/solana/module1/connecting-to-wallet/img/wallets.png and /dev/null differ diff --git a/other_docs/solana/module1/custom-instructions/README.md b/other_docs/solana/module1/custom-instructions/README.md deleted file mode 100644 index f606d76f4..000000000 --- a/other_docs/solana/module1/custom-instructions/README.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -sidebar_position: 7 -sidebar_label: 🤔 Instructions 说明 -sidebar_class_name: green ---- - -# 🤔 Instructions 说明 - -现在我们已经设置了钱包连接,让我们的 ping 按钮实际执行一些操作!您现在知道如何通过简单的交易读取数据并写入网络。几乎立刻,您就会发现自己希望通过交易发送数据。那么让我们看看如何向 Solana 区块链讲述您的故事。 - -Solana 中数据的棘手之处在于程序是无状态的。与以太坊等其他区块链中的智能合约不同,程序不存储任何数据,仅存储逻辑。 - -![](./img/upload_1.png) - -图为:Solana 创始人 Anatoly Yakovenko 正在制作 Solana。 - -Solana 程序中绝对不存储任何内容。它不知道所有者是谁,甚至不知道是谁部署了它。一切都存储在帐户内。 - -## 📧 指令数据 - -我们要深入了解一下。在实践中,我们在本节中要做的很多事情将由像 Anchor 这样的库为我们处理,但了解原子指令级别发生的事情很重要。 - -让我们退后一步看看指令数据所在的位置。 - -![](./img/upload_2.png) - - -事务可以有一条或多条指令,每条指令可以有数据。 - -指令数据最重要的是格式——它是 8 位数据。 “位”表示它是机器代码:1 和 0。 8 只是指大小,例如 32 位或 64 位。如果您的指令数据不是这种格式,Solana 运行时将无法识别它。 - -这就是 Solana 速度如此之快的原因!您无需让网络转换您的数据,而是向其提供转换后的数据,它只会处理它。想象一下,如果您在开始烹饪之前准备好一道菜的所有原料 - 您将能够更快地烹饪,因为您不必切东西。 - -您不需要知道机器代码如何工作。您需要记住的是,指令数据具有某种类型,并且当您想要将其包含在指令中时,您需要将数据转换为该类型。 - -> 这段话在解释 Solana 网络如何处理事务和指令数据的。在 Solana 中,一个事务可以包含一条或多条指令,每条指令都可以携带一些数据。 -> -> 重点是,这些指令数据需要以特定格式提供,即 8 位数据。这里的 “8位” 不是指数据的大小,而是指数据的格式,这种格式是机器代码格式,用 1 和 0 表示。如果你提供的指令数据不是这种格式,Solana 运行时就无法识别和处理它。 -> -> 这种处理方式是 Solana 能够高速运行的一个原因。你不需要让网络转换你的数据,而是自己转换数据并提供给网络,网络只负责处理它。这就像在开始烹饪前就准备好所有食材,这样你就能更快地烹饪,因为你不需要在烹饪过程中去切东西。 -> -> 作者强调的是,你并不需要了解机器代码是如何工作的。你需要记住的是,当你想要在指令中包含一些数据时,这些数据需要是特定类型的,你需要把你的数据转换为这种类型。这就意味着在你编写和提交给 Solana 网络的代码中,你需要负责把你的数据转换为适当的格式。 -> -> 这是低级别编程的一个常见特性。虽然很多高级编程语言(比如 Python 或 JavaScript)会自动处理这些类型转换,但在低级语言(比如 Rust,这也是 Solana 主要使用的语言)中,你需要自己处理这些转换。然而,有些库,如 Anchor,可以帮助你处理这些转换,让编程更简单。 - -## 🔨 序列化和borsh - - -这就是序列化的用武之地——它将常规代码或数据转换为字节数组(机器代码:1 和 0)的过程。 - -我们将在我们的项目中使用 [Borsh](https://borsh.io/) 序列化格式,因为它有一个方便我们使用的库。 - -让我们通过一个例子来看看它是如何工作的——目标是装备一个链上游戏物品。为此,我们需要三个数据 - -- variant - 我们要调用的命令的名称(即装备或删除) -- playerId - 装备该物品的玩家的ID -- itemId - 我们想要装备的物品 - -序列化此数据有四个步骤: - -1. 为您的数据创建模式/映射 -2. 为比需要大得多的数据分配缓冲区 -3. 对我们的数据进行编码并将其添加到缓冲区中 -4. 砍掉缓冲区末尾的多余空间 - -作为网络开发人员,我们永远不需要处理这样的低级内容,所以我这样做是为了让它感觉不那么抽象: - -![](./img/upload_3.png) - -我希望这是有道理的,哈哈。让我们看一些代码以了解其实际情况。 - -```ts -import * as Borsh from "@project-serum/borsh" - -const equipPlayerSchema = Borsh.struct([ - Borsh.u8("variant"), - Borsh.u8("playerId"), - Borsh.u8("itemId"), -]) -``` - -我们将从为装备物品指令创建一个模式开始。我们正在使用三段数据创建一个 borsh 结构,所有数据都是无符号整数,但大小不同 - 8、16 和 256 位。 - -由于我们的数据将成为一长串 1 和 0 的列表,因此我们需要知道每个数据项的开始和结束位置。这就是为什么我们为每件商品赋予特定的尺寸。当程序需要读取这些数据时,它会知道 variant 在哪里结束, playerId 在哪里开始。 - -想象一下蒙着眼睛试图从链接上切香肠。只有知道每根香肠的长度,才能在正确的位置切。 - -![](./img/upload_4.png) - -在我们的例子中,第二根和第三根香肠会长很多,但我想你明白了,哈哈。 - - -```ts -import * as Borsh from "@project-serum/borsh" - -const equipPlayerSchema = Borsh.struct([ - Borsh.u8("variant"), - Borsh.u8("playerId"), - Borsh.u8("itemId"), -]) - -const buffer = Buffer.alloc(1000) -equipPlayerSchems.encode({ variant: 2, playerId: 1435, itemId: 737498}, buffer) - -const instructBuffer = buffer.slice(0, equipPlayerSchems.getSpan(buffer)) -``` - -这里发生第二步、第三步和第四步。我们创建一个 1000 字节长的缓冲区。我们对数据进行编码并将其添加到缓冲区中。然后我们将末端切成薄片,使其长度达到需要的长度。 - -```ts -const endpoint = clusterApiUrl("devnet") -const connection = new Connection(endpoint) - -const transaction = new Transaction().add({ - key: [ - { - pubkey: player.Publickey, - isSigner: true, - isWritable: false, - }, - { - pubkey: playerInfoAccount, - isSigner: false, - isWritable: true, - }, - { - pubkey: SystemProgram.programId, - isSigner: false, - isWritable: false, - }, - ], - data: instructBuffer, - programId: PROGRAM_ID, -}) - -sendAndConfirmTransaction(connection, transaction, [player]) -``` - -一旦我们获得了正确格式的数据,剩下的就是 ezpz!这笔交易看起来应该很熟悉。这里唯一的“新”东西是我们以前没有的可选 data 项。 - -我在这里对您的知识做了一些假设 - 您粗略地知道什么是机器代码以及内存分配如何发挥作用。你不需要知道所有这些东西,我当然不需要。只需在 YouTube 上观看一两个视频,直到您对正在发生的事情有一个大概的了解。 - - -没有多少现代开发人员定期处理字节缓冲区 - 它被认为是低级别的,所以如果这感觉不熟悉或新鲜,请不要担心。接下来我们将使用它进行构建,这样您就可以称自己为软件工程师😎 diff --git a/other_docs/solana/module1/custom-instructions/img/upload_1.png b/other_docs/solana/module1/custom-instructions/img/upload_1.png deleted file mode 100644 index f4019cdca..000000000 Binary files a/other_docs/solana/module1/custom-instructions/img/upload_1.png and /dev/null differ diff --git a/other_docs/solana/module1/custom-instructions/img/upload_2.png b/other_docs/solana/module1/custom-instructions/img/upload_2.png deleted file mode 100644 index 5a351d175..000000000 Binary files a/other_docs/solana/module1/custom-instructions/img/upload_2.png and /dev/null differ diff --git a/other_docs/solana/module1/custom-instructions/img/upload_3.png b/other_docs/solana/module1/custom-instructions/img/upload_3.png deleted file mode 100644 index 8d2c04675..000000000 Binary files a/other_docs/solana/module1/custom-instructions/img/upload_3.png and /dev/null differ diff --git a/other_docs/solana/module1/custom-instructions/img/upload_4.png b/other_docs/solana/module1/custom-instructions/img/upload_4.png deleted file mode 100644 index 3b7c4129f..000000000 Binary files a/other_docs/solana/module1/custom-instructions/img/upload_4.png and /dev/null differ diff --git a/other_docs/solana/module1/custom-instructions/img/upload_5.png b/other_docs/solana/module1/custom-instructions/img/upload_5.png deleted file mode 100644 index fcdfdf85f..000000000 Binary files a/other_docs/solana/module1/custom-instructions/img/upload_5.png and /dev/null differ diff --git a/other_docs/solana/module1/deploy-to-vercel/README.md b/other_docs/solana/module1/deploy-to-vercel/README.md deleted file mode 100644 index 5be873ab5..000000000 --- a/other_docs/solana/module1/deploy-to-vercel/README.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -sidebar_position: 11 -sidebar_label: 🌐 部署到 Vercel -sidebar_class_name: green ---- - -# 🌐 部署到 Vercel - -这是你本周所做的一切中最重要的部分。离开本地主机。 - -我们将把前端部署到 Vercel。 Vercel 是一个托管平台,可让您轻松部署应用程序。最好的部分?免费! - -首先,您需要将项目推送到 Github。这应该需要大约 5 分钟,如果您不确定如何执行此操作,只需 Google 即可! - -完成后,前往 [Vercel](https://vercel.com/) 并连接您的 Github 帐户。它应该自动检测到这是一个 Next.js 项目,并且部署应该很简单。构建完成后,它会输出一个链接。 - -您现在已经离开本地主机了! 🎉 diff --git a/other_docs/solana/module1/interact-with-a-program/README.md b/other_docs/solana/module1/interact-with-a-program/README.md deleted file mode 100644 index 67117b2a5..000000000 --- a/other_docs/solana/module1/interact-with-a-program/README.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -sidebar_position: 5 -sidebar_label: 🦺 与程序交互 -sidebar_class_name: green ---- - -# 🦺 与程序交互 - -现在我们已经设置了钱包连接,让我们的 ping 按钮实际执行一些操作! - -“PingButton.tsx”应如下所示: - -```ts -import { useConnection, useWallet } from '@solana/wallet-adapter-react'; -import * as Web3 from '@solana/web3.js' -import { FC } from 'react' -import styles from '../styles/PingButton.module.css' - -const PROGRAM_ID = new Web3.PublicKey("ChT1B39WKLS8qUrkLvFDXMhEJ4F1XZzwUNHUt4AU9aVa") -const PROGRAM_DATA_PUBLIC_KEY = new Web3.PublicKey("Ah9K7dQ8EHaZqcAsgBW8w37yN2eAy3koFmUn4x3CJtod") - -export const PingButton: FC = () => { - const { connection } = useConnection(); - const { publicKey, sendTransaction } = useWallet(); - - const onClick = () => { - if (!connection || !publicKey) { - alert("Please connect your wallet first lol") - return - } - - const transaction = new Web3.Transaction() - - const instruction = new Web3.TransactionInstruction({ - keys: [ - { - pubkey: PROGRAM_DATA_PUBLIC_KEY, - isSigner: false, - isWritable: true - }, - ], - programId: PROGRAM_ID, - }); - - transaction.add(instruction) - sendTransaction(transaction, connection).then(sig => { - console.log(`Explorer URL: https://explorer.solana.com/tx/${sig}?cluster=devnet`) - }) - } - - return ( -
- -
- ) -} -``` - -其中的一大堆内容对您来说应该非常熟悉 - 我们正在做与我们在本地客户端上所做的完全相同的事情,只是使用了 React hooks! - - -是时候测试一下了。确保您的钱包位于开发网络上 - 设置 -> 开发者设置 -> 更改网络。连接您的钱包并点击 ping 按钮,您将看到以下内容: - -![](./img/upload_1.png) - -如果您点击确认,您的控制台将打印出交易链接。就像以前一样,滚动到底部,您会看到数字上升了🚀 - -您现在可以让用户与应用程序交互!您上一节制作的价值 1 万美元的产品?现在它是价值百万美元的产品。想想现有的所有程序 - Metaplex、Serum、Solana 程序库中的任何程序 - 您现在拥有将它们连接到 UI 并让人们使用它们的技能。我的朋友,你可以创造未来。 - -## 🚢 船舶挑战 - SOL 发送者 - -是时候锻炼一下肌肉了。 - -在此挑战中,使用[此起始代码](https://github.com/buildspace/solana-send-sol-frontend/tree/starter?utm_source=buildspace.so&utm_medium=buildspace_project)创建一个应用程序,让用户连接其 Phantom 钱包并将 SOL 发送到另一个帐户。确保克隆后使用 git checkout starter 切换到起始分支。 - -通过两个关键步骤来做到这一点: -- 将启动应用程序包装在适当的上下文提供程序中。 -- 在表单组件中,设置交易并将其发送到用户的钱包以供批准。 -最后它应该看起来像这样! - -![](./img/upload_2.png) - -不要忘记验证地址! - -完成后,将您的解决方案与此处的[解决方案代码](https://github.com/buildspace/solana-send-sol-frontend/tree/main?utm_source=buildspace.so&utm_medium=buildspace_project)进行比较。 diff --git a/other_docs/solana/module1/interact-with-a-program/img/upload_1.png b/other_docs/solana/module1/interact-with-a-program/img/upload_1.png deleted file mode 100644 index ad3270b34..000000000 Binary files a/other_docs/solana/module1/interact-with-a-program/img/upload_1.png and /dev/null differ diff --git a/other_docs/solana/module1/interact-with-a-program/img/upload_2.png b/other_docs/solana/module1/interact-with-a-program/img/upload_2.png deleted file mode 100644 index b70afeebd..000000000 Binary files a/other_docs/solana/module1/interact-with-a-program/img/upload_2.png and /dev/null differ diff --git a/other_docs/solana/module1/read-data-from-the-solana-network/README.md b/other_docs/solana/module1/read-data-from-the-solana-network/README.md deleted file mode 100644 index 3ebf7f32f..000000000 --- a/other_docs/solana/module1/read-data-from-the-solana-network/README.md +++ /dev/null @@ -1,318 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: 从 Solana 🤓 区块链读取数据 -sidebar_class_name: green ---- - -# 从 Solana 🤓 区块链读取数据 - -是时候回去了。一路回到幼儿园。还记得你学到的第一件事是什么吗?字母表。一旦你征服了全部 26 个,你就学会了如何阅读。这就是您成为 Solana 开发人员的旅程开始的地方。阅读英语的独特技能使您成为现在的咀嚼玻璃的超级大脑。 - -是时候再做一次了。我们将继续您好心的老师应该停下来的地方 - 从区块链中读取数据。 - -TL; DR - -- 帐户(Accounts)就像 Solana 网络分类帐中的文件。**所有状态数据都存储在帐户中**。帐户可用于很多用途,但现在我们将重点关注存储 SOL 的帐户方面。 -- SOL 是 Solana 原生代币的名称。 -- Lamports 是分数 SOL,以 [Leslie Lamport](https://en.wikipedia.org/wiki/Leslie_Lamport) 命名。 -- 公钥(通常称为地址)指向 Solana 网络上的帐户。虽然您必须拥有特定的密钥才能在帐户内执行某些功能,但任何人都可以使用公钥读取帐户数据。 -- JSON RPC API:与 Solana 网络的所有交互都通过 [JSON RPC API](https://docs.solana.com/api/http) 进行。这实际上是一个带有 JSON 正文的 HTTP POST,表示您要调用的方法。 -- @solana/web3.js 是 JSON RPC API 之上的抽象。它可以与 npm 一起安装,并允许您将 Solana 方法作为 JavaScript 函数调用。例如,您可以使用它来查询任何帐户的SOL余额: - -```typescript -async function getBalanceUsingWeb3(address: PublicKey): Promise { - const connection = new Connection(clusterApiUrl('devnet')); - return connection.getBalance(address); -} - -const publicKey = new PublicKey('7C4jsPZpht42Tw6MjXWF56Q5RQUocjBBmciEjDa8HRtp') -getBalanceUsingWeb3(publicKey).then(balance => { - console.log(balance) -}) -``` - -## 概述 - -### Accounts - -从 Solana 字母表开始,A 代表账户。我们从帐户开始,因为 Solana 上的智能合约(称为“程序”)是无状态的 - 这意味着它们除了代码之外不存储任何内容。一切都发生在账户中,因此它们是 Solana 的核心,它们用于存储、合约和本机区块链程序。 - -在本课程中,除了存储 SOL(Solana 的原生代币 - 稍后详细介绍)的能力之外,我们不会过多考虑帐户。但是,帐户还用于存储自定义数据结构和可以作为程序运行的可执行代码。您使用 Solana 所做的一切都会涉及到账户。 - -Solana 上有三种类型的账户 -- 数据帐户 - 这些存储数据, LOL -- 程序帐户 - 这些存储可执行程序(又称智能合约) -- 原生账户 - 这些用于核心区块链功能,例如权益、投票 - -原生账户是区块链运行所需的,我们稍后将深入探讨。目前,我们将仅使用数据和程序帐户。 - -在数据帐户中,您还有两种类型 - -- 系统拥有的帐户 -- PDA(程序派生地址)帐户 - -我们很快就会知道这些到底是什么™️,现在就继续吧。 - - -每个帐户都带有许多您应该了解的字段: - -| FIELD | 描述 | -| --- | --- | -| lamports | 该账户拥有的lamports数量 | -| 所有者 | 该帐户的程序所有者 | -| 可执行文件 | 该账户是否可以处理指令(可执行) | -| 数据 | 该账户存储的原始数据字节数组 | -| 租金纪元 | 该帐户将欠租金的下一个纪元 | - -我们将只关注我们现在需要了解的内容,因此,如果某些内容没有意义,请继续前进 - 我们将在前进过程中开始填补空白。 - -Lamports 是 Solana 的最小单位。如果您熟悉以太坊生态系统,这有点像 Gwei。 1 lamport = 0.000000001 SOL,所以这个字段只是告诉我们该账户有多少 SOL。 - -每个帐户都有一个公钥 - 它就像帐户的地址一样。你知道你的钱包里有一个用来接收那些辛辣的 NFT 的地址吗?一样! Solana 地址只是 base58 编码的字符串。 - -executable 是一个布尔字段,告诉我们该帐户是否包含可执行数据。数据是存储在帐户中的内容,租金我们稍后会支付! - -现在让我们继续简单的事情:) - -### Public Key - -公钥通常称为地址。这些地址指向 Solana 网络上的帐户。如果您想运行特定程序或传输 SOL,则需要提供必要的公钥(或多个密钥)来执行此操作。 - -公钥为 256 位,通常显示为 base-58 编码字符串,例如 `7C4jsPZpht42Tw6MjXWF56Q5RQUocjBBmciEjDa8HRtp`。 - -### Solana JSON RPC API - -好吧,我们知道什么是账户,我们如何阅读它们呢?我们将使用 JSON RPC 端点!查看此图,您将是这里的客户端,尝试从 Solana 网络读取内容。 - -![](./img/json-rpc-illustration.png) - -您可以使用您想要的东西对 JSON RPC 进行 API 调用,它会与网络进行通信并为您提供好处。 - -与 Solana 网络的所有客户端交互都通过 Solana 的 [JSON RPC API](https://docs.solana.com/api/http) 进行。 - -根据 [JSON-RPC 2.0 规范](https://www.jsonrpc.org/specification) - -> JSON-RPC 是一种无状态、轻量级远程过程调用 (RPC) 协议。该规范主要定义了几种数据结构及其处理规则。它与传输无关,因为这些概念可以在同一进程中、通过套接字、通过 http 或在许多不同的消息传递环境中使用。它使用 [JSON](https://www.json.org/) ([RFC 4627](https://www.ietf.org/rfc/rfc4627.txt)) 作为数据格式。 - -这里正在发生很多事情。我们正在发出一个 post 请求,其中主体具有告诉 RPC 做什么的特定参数。我们需要指定 RPC 的版本、id、方法(在本例中为 getBalance)以及该方法所需的参数(在本例中为地址)。 - - -实际上,此规范仅涉及发送表示您要调用的方法的 JSON 对象。您可以使用套接字、http 等来实现此目的。 - -该 JSON 对象需要四个成员: - -- jsonrpc - JSON RPC 版本号。这需要恰好是“2.0”。 -- id - 您选择用于识别呼叫的标识符。这可以是字符串或整数。 -- method - 您要调用的方法的名称。 -- params - 包含方法调用期间要使用的参数的数组。 - -因此,如果您想在 Solana 网络上调用 getBalance 方法,您可以向 Solana 集群发送 HTTP 调用,如下所示: - -```typescript -async function getBalanceUsingJSONRPC(address: string): Promise { - const url = clusterApiUrl('devnet') - console.log(url); - return fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - "jsonrpc": "2.0", - "id": 1, - "method": "getBalance", - "params": [ - address - ] - }) - }).then(response => response.json()) - .then(json => { - if (json.error) { - throw json.error - } - - return json['result']['value'] as number; - }) - .catch(error => { - throw error - }) -} -``` - -我们有一堆非常简单方法的样板,因此我们可以使用 Solana 的 Web3.js SDK。需要做的是: - -```typescript -async function getBalanceUsingWeb3(address: PublicKey): Promise { - const connection = new Connection(clusterApiUrl('devnet')); - return connection.getBalance(address); -} -``` - -那不是很漂亮吗?要获取某人的 Solana 余额,我们需要做的就是这三行。想象一下,获取任何人的银行余额是否都那么容易。 - -现在您知道如何从 Solana 上的帐户读取数据了!我知道这可能看起来微不足道,但仅使用这一功能,您就可以获取 Solana 上任何帐户的余额。想象一下,能够获得地球上任何银行账户的银行余额,这就是多么强大。 - - -## Solana 的 Web3.js SDK - -虽然 JSON-RPC API 足够简单,但它涉及大量繁琐的样板文件。为了简化通信过程,Solana Labs 创建了 @solana/web3.js SDK 作为 JSON-RPC API 之上的抽象。 - -Web3.js 允许您使用 JavaScript 函数调用 JSON-RPC API 方法。 SDK 提供了一套辅助函数和对象。我们将在本课程中逐步介绍很多 SDK,但我们不会深入讨论所有内容,因此请务必在某个时候查看[文档](https://docs.solana.com/developing/clients/javascript-reference)。 - - -### 安装 - -在本课程中,我们将主要使用 npm。如何使用 npm 超出了本课程的范围,我们假设它是您经常使用的工具。如果情况并非如此,[请检查一下](https://nodesource.com/blog/an-absolute-beginners-guide-to-using-npm/)。 - -要安装 @solana/web3.js,请按照通常使用的方式设置项目: - -```bash -npm install @solana/web3.js。 -``` - - -### 连接到网络 - -使用 @solana/web3.js 与 Solana 网络的每次交互都将通过 Connection 对象进行。该对象与 Solana 集群建立 JSON-RPC 连接(稍后将详细介绍集群)。现在,我们将使用 Devnet 集群的 url,而不是 Mainnet。顾名思义,该集群是为开发人员使用和测试而设计的。 - -```typescript -const connection = new Connection(clusterApiUrl('devnet')); -``` - -### 从网络读取 - -一旦有了 Connection 对象,查询网络就像调用适当的方法一样简单。例如,要获取特定地址的余额,您可以执行以下操作: - -```typescript -async function getBalanceUsingWeb3(address: PublicKey): Promise { - const connection = new Connection(clusterApiUrl('devnet')); - return connection.getBalance(address); -} -``` - -返回的余额采用小数 SOL 形式,称为“lamports”。单个 lamport 代表 0.000000001 SOL。大多数时候,在处理 SOL 时,系统会使用 lamports 而不是 SOL。 Web3.js 提供了常量 LAMPORTS_PER_SOL 来进行快速转换。 - -...就像这样,现在您知道如何从 Solana 区块链读取数据了!一旦我们进入自定义数据,事情就会变得更加复杂。但现在,让我们练习一下到目前为止所学到的知识。 - - -## 🤑 构建一个余额获取器 - -是时候构建一个通用平衡获取器了(假设整个宇宙都在 Solana 上)。这将是一个简单但功能强大的应用程序,可以获取 Solana 上任何帐户的余额。 - - -在工作区的某个位置创建一个文件夹。我把我的放在桌面上。克隆起始存储库并进行设置: - -```bash -git clone https://github.com/buildspace/solana-intro-frontend.git -cd solana-intro-frontend -git checkout starter -npm i -``` - -这是一个简单的 Next.js 应用程序,因此一旦安装了所有依赖项,您就可以在终端中使用 npm run dev 启动它。您应该在本地主机上看到此内容: - -![](./img/intro-frontend-demo.png) - -我们为您提供了一个带有一些样式的普通 Next.js 应用程序,如果您在地址字段中输入一些内容并点击“检查 SOL 余额”按钮,您将看到余额为 1,000 SOL。是时候让它发挥作用了。 - -为了保持主题不变,我们不会完全从头开始工作。您可以在此处找到起始代码。入门项目使用 Next.js 和 Typescript。如果您习惯了不同的堆栈,请不要担心!您将在这些课程中学到的 web3 和 Solana 原则适用于您最熟悉的任何前端堆栈。 - -### 1. 确定方向 - -获得起始代码后,请四处查看。使用 `npm install` 安装依赖项,然后使用 `npm run dev` 运行应用程序。请注意,无论您在地址字段中输入什么内容,当您单击“检查 SOL 余额”时,余额都将是占位符值 1000。 - -从结构上讲,该应用程序由`index.tsx`和`AddressForm.tsx`组成。当用户提交表单时,`index.tsx` 中的 `addressSubscribedHandler` 被调用。这就是我们将添加逻辑来更新 UI 其余部分的地方。 - -### 2.安装依赖 - - -使用 `npm install @solana/web3.js` 安装对 Solana Web3 库的依赖项。 - -### 3.设置地址余额 - -首先,在index.tsx顶部导入@solana/web3.js。 - -现在该库已可用,让我们进入 addressSubscribedHandler 并使用表单输入中的地址值创建 PublicKey 的实例。接下来,创建 Connection 的实例并使用它来调用 getBalance。传入您刚刚创建的公钥的值。最后调用setBalance,传入getBalance的结果。如果您愿意,请独立尝试,而不是从下面的代码片段中复制。 - -```typescript -import type { NextPage } from 'next' -import { useState } from 'react' -import styles from '../styles/Home.module.css' -import AddressForm from '../components/AddressForm' -import * as Web3 from '@solana/web3.js' - -const Home: NextPage = () => { - const [balance, setBalance] = useState(0) - const [address, setAddress] = useState('') - - const addressSubmittedHandler = (address: string) => { - const key = new Web3.PublicKey(address) - setAddress(key.toBase58()) - - const connection = new Web3.Connection(Web3.clusterApiUrl('devnet')) - connection.getBalance(key).then(balance => { - setBalance(balance / Web3.LAMPORTS_PER_SOL) - }) - } - -... - -} -``` - -这里有一些新东西 - -- 我们使用 key.toBase58 设置地址。这是 Solana 地址作为字符串的编码。 -- 我们正在连接到 devnet 网络。共有三个网络 - 主网、测试网和开发网。我们将使用 devnet 来处理所有事情。 -- 我们将余额从 Lamports 转换为 SOL - 余额返回到 Lamports,而不是 SOL。 - -我们就完成了!如果您在此处粘贴地址,您将看到余额。确保您的帐户上有 devnet SOL!如果没有,您可以使用我的帐户来测试您的应用 - B1aLAAe4vW8nSQCetXnYqJfRxzTjnbooczwkUJAr7yMS 。 - - -请注意,我们将 Solana 返回的余额除以 LAMPORTS_PER_SOL。 Lamport 是分数 SOL (0.000000001 SOL)。大多数时候,在处理 SOL 时,系统会使用 lamports 而不是 SOL。在这种情况下,网络返回的余额以 lamports 为单位。在将其设置为我们的状态之前,我们使用 LAMPORTS_PER_SOL 常量将其转换为 SOL。 - -此时,您应该能够在表单字段中输入有效地址,然后单击“检查 SOL 余额”以查看下面填充的地址和余额。 - -### 4. 处理无效地址 - -我们即将完成。唯一剩下的问题是,使用无效地址不会显示任何错误消息或更改显示的余额。如果打开开发者控制台,您将看到错误:无效的公钥输入。使用 PublicKey 构造函数时,需要传入有效的地址,否则会出现此错误。 - - -为了解决这个问题,让我们将所有内容包装在 try-catch 块中,并在用户输入无效时提醒用户。 - -```typescript -const addressSubmittedHandler = (address: string) => { - try { - setAddress(address) - const key = new Web3.PublicKey(address) - const connection = new Web3.Connection(Web3.clusterApiUrl('devnet')) - connection.getBalance(key).then(balance => { - setBalance(balance / Web3.LAMPORTS_PER_SOL) - }) - } catch (error) { - setAddress('') - setBalance(0) - alert(error) - } -} -``` - -请注意,在 catch 块中,我们还清除了地址和余额以避免混淆。 - -我们做到了!我们有一个正常运行的站点,可以从 Solana 网络读取 SOL 余额。您正在 Solana 上实现您的宏伟抱负。如果您需要花更多时间查看此代码以更好地理解它,请查看[完整的解决方案代码](https://github.com/Unboxed-Software/solana-intro-frontend)。坚持住,这些课程会很快增加。 - -## 挑战 - -由于这是第一个挑战,我们将保持简单。继续添加到我们已经创建的前端,在“余额”之后添加一个行项目。让行项目显示该帐户是否是可执行帐户。提示:有一个 getAccountInfo 方法。 - -![](./img/intro-frontend-challenge.png) - -要查明帐户是否可执行,您将: -- 使用方法 getAccountInfo 获取包含帐户信息的 JSON 对象 -- 检查其属性以查明其是否可执行 -- 添加另一个对 useState 的调用,让您可以根据帐户信息设置可执行属性值并将其显示在 UI 中 - - -您的标准钱包地址将无法执行,因此如果您想要一个可执行测试的地址,请使用 CenYq6bDRB7p73EjsPEpiYN7uveyPUTdXkDkgUduboaN。 - -在您自己做出可靠的尝试之前,不要查看解决方案!这是 ezpz 柠檬汁。 - -如果您遇到困难,请随时查看[解决方案代码](https://github.com/Unboxed-Software/solana-intro-frontend/tree/challenge-solution)。 diff --git a/other_docs/solana/module1/read-data-from-the-solana-network/img/intro-frontend-challenge.png b/other_docs/solana/module1/read-data-from-the-solana-network/img/intro-frontend-challenge.png deleted file mode 100644 index 499acb433..000000000 Binary files a/other_docs/solana/module1/read-data-from-the-solana-network/img/intro-frontend-challenge.png and /dev/null differ diff --git a/other_docs/solana/module1/read-data-from-the-solana-network/img/intro-frontend-demo.png b/other_docs/solana/module1/read-data-from-the-solana-network/img/intro-frontend-demo.png deleted file mode 100644 index dc97c3b9b..000000000 Binary files a/other_docs/solana/module1/read-data-from-the-solana-network/img/intro-frontend-demo.png and /dev/null differ diff --git a/other_docs/solana/module1/read-data-from-the-solana-network/img/json-rpc-illustration.png b/other_docs/solana/module1/read-data-from-the-solana-network/img/json-rpc-illustration.png deleted file mode 100644 index d78a64d16..000000000 Binary files a/other_docs/solana/module1/read-data-from-the-solana-network/img/json-rpc-illustration.png and /dev/null differ diff --git a/other_docs/solana/module1/run-it-back-deserialization/README.md b/other_docs/solana/module1/run-it-back-deserialization/README.md deleted file mode 100644 index 7b6056df7..000000000 --- a/other_docs/solana/module1/run-it-back-deserialization/README.md +++ /dev/null @@ -1,251 +0,0 @@ ---- -sidebar_position: 9 -sidebar_label: 📡 运行回来 - 反序列化 -sidebar_class_name: green ---- - -# 📡 运行回来 - 反序列化 - -现在我们已经设置了钱包连接,让我们的 ping 按钮实际执行一些操作!将数据写入网络帐户只是成功的一半,另一半是读取数据。在第一部分中,我们使用 Web3.js 库中内置的函数来读取内容。这仅适用于绝对重要的数据,例如余额和交易详细信息。正如我们在上一节中看到的,所有好东西都在 PDA 中。 - -## 🧾 程序派生地址 - -账目是 Solana 的热门话题。如果您听说过“帐户”这个词,您可能听说过有人在谈论 PDA。 PDA 是 Solana 上用于存储数据的特殊类型帐户。但它不是一个帐户 - 它们实际上是通过地址而不是帐户进行的,因为它们没有私钥。它们只能由创建它们的程序控制。 - -![](./img/upload_1.png) - -常规 Solana 帐户是使用 [Ed25519](https://ed25519.cr.yp.to/?utm_source=buildspace.so&utm_medium=buildspace_project) 签名系统创建的 - 该系统为我们提供了公钥和私钥。由于 PDA 是由程序控制的,因此它们不需要私钥。因此,我们使用不在 Ed25519 曲线上的地址来制作 PDA。 - -![](./img/upload_2.png) - -有时, findProgramAddress 给我们一个位于曲线上的密钥(意味着它也有一个私钥),因此我们添加一个可选的“bump”参数以将其移出曲线。 - -就是这样。您不需要了解 Ed25519,甚至不需要了解数字签名算法是什么。您只需要知道 PDA 看起来就像常规 Solana 地址并且由程序控制。 - -您需要了解 PDA 工作原理的原因是,它们是链上和链下程序定位数据的确定性方式。把它想象成一个键值存储。 seeds 、 programId 和 bump 组合起来形成密钥,以及网络在该地址存储的值。如果我们知道密钥是什么,这使我们能够可靠且一致地查找存储在网络上的数据。 - -借助 PDA,我们拥有了 Solana 上所有程序都可以访问的通用数据库。回想一下我们交互的第一个程序 - 我们对它执行 ping 操作,它增加了一个数字。您可以通过以下方式查找与程序交互的所有帐户共享的数据: - -```ts -const [pda, bump] = await PublicKey.findProgramAddress( - [Buffer.from("GLOBAL_STATE")], - programId -); -``` - -这有点像 Javascript 中的全局变量。 - -如果您想为每个用户存储一个单独的计数器怎么办?使用他们的公钥作为种子: - -```ts -const [pda, bump] = await PublicKey.findProgramAddress( - [ - publickey.toBuffer() - ], - programId -); -``` - -也许您想制作一个链上笔记系统,每个用户都可以存储自己的笔记?将公钥与标识符结合起来: -```ts -const [pda, bump] = await PublicKey.findProgramAddress( - [ - publickey.toBuffer(), - Buffer.from("First Note") - ], - programId -); -``` - -请记住,您或调用者必须付费才能存储内容,并且每个帐户有 10 MB 的限制,因此您需要谨慎选择将内容放在链上。 - -## 🎢 反序列化 - - -找到要读取的帐户后,您需要反序列化数据,以便您的应用程序可以使用它。回想一下我们在这个程序中学到的第一件事——帐户及其包含的内容。回顾一下: - -| FIELD | 描述 | -| --- | --- | -| lamports | 该账户拥有的lamports数量 | -| 所有者 | 该帐户的程序所有者 | -| 可执行文件 | 该账户是否可以处理指令(可执行) | -| 数据 | 该账户存储的原始数据字节数组 | -| 租金纪元 | 该帐户将欠租金的下一个纪元 | - -数据字段包含大量字节数组。就像我们如何将可读数据转换为指令字节一样,我们将在这里做相反的事情:将字节数组转换为我们的应用程序可以使用的数据。这是真正的魔法开始的时候,你真的感觉就像在玻璃上冲浪😎 - -我们在这里见到了我们最好的新老朋友 Borsh 先生: - -```ts -impot * as borsh from '@project-serum/borsh'; - -borshAccountSchema = borsh.struct({ - borsh.bool('initialized'), - borsh.u16('playerId'), - borsh.str('name') -}); - -const { playerId, name } = borshAccountSchema.decode(buffer) -``` - -这些步骤与我们对序列化所做的类似: -1.创建字节数组中存储内容的模式/映射 -2.使用模式来解码数据 -3.提取我们想要的项目 - -这应该感觉很熟悉,但如果不熟悉,当我们付诸行动时就会有意义! - -## 构建一个解串器 - -有没有想过您会构建一个解串器?好吧——我们将继续使用我们的电影评论应用程序。您可以继续上一节(推荐)的项目,也可以使用完成的版本进行设置: - -```bash -git clone https://github.com/buildspace/solana-movie-frontend.git -cd solana-movie-frontend -git checkout solution-serialize-instruction-data -npm i -``` - -当您运行 npm run dev 时,您将看到一堆模拟数据。与假 yeezy 不同,假数据是蹩脚的。让我们在 Movie.ts 中保持真实(仅复制/粘贴新内容): - -```ts -import * as borsh from '@project-serum/borsh' - -export class Movie { - title: string; - rating: number; - description: string; -... - - static borshAccountSchema = borsh.struct([ - borsh.bool('initialized'), - borsh.u8('rating'), - borsh.str('title'), - borsh.str('description'), - ]) - - static deserialize(buffer?: Buffer): Movie|null { - if (!buffer) { - return null - } - - try { - const { title, rating, description } = this.borshAccountSchema.decode(buffer) - return new Movie(title, rating, description) - } catch(error) { - console.log('Deserialization error:', error) - return null - } - } -} -``` - -就像序列化一样,我们有一个模式和一个方法。该架构具有: - -1. initialized 作为一个布尔值,表示帐户是否已初始化。 -2. rating 作为无符号 8 位整数,表示评论者对电影的评分(满分 5 分)。 -3. title 作为表示所评论电影的标题的字符串。 -4. description 作为表示评论的书面部分的字符串. - -看起来很熟悉!好东西在 deserialize 中。这里的返回类型可以是 Movie 或 null ,因为帐户可能根本没有任何数据。 - -最后,我们需要在页面加载时使用此方法从 PDA 获取数据。我们在 MovieList.tsx 中执行此操作: - -```ts -import { Card } from './Card' -import { FC, useEffect, useState } from 'react' -import { Movie } from '../models/Movie' -import * as web3 from '@solana/web3.js' - -const MOVIE_REVIEW_PROGRAM_ID = 'CenYq6bDRB7p73EjsPEpiYN7uveyPUTdXkDkgUduboaN' - -export const MovieList: FC = () => { - const connection = new web3.Connection(web3.clusterApiUrl('devnet')) - const [movies, setMovies] = useState([]) - - useEffect(() => { - connection.getProgramAccounts(new web3.PublicKey(MOVIE_REVIEW_PROGRAM_ID)) - .then(async (accounts) => { - const movies: Movie[] = accounts.reduce((accum: Movie[], { pubkey, account }) => { - const movie = Movie.deserialize(account.data) - if (!movie) { - return accum - } - - return [...accum, movie] - }, []) - setMovies(movies) - }) - }, []) - - return ( -
- { - movies.map((movie, i) => ) - } -
- ) -} -``` - -就像以前一样,我们设置了导入和连接。主要更改在 useEffect 中。 - - -```ts -connection.getProgramAccounts(new web3.PublicKey(MOVIE_REVIEW_PROGRAM_ID)) -``` - -在获取电影评论之前,我们需要获取包含它们的帐户。我们可以通过使用 getProgramAccounts 端点获取电影评论程序的所有程序帐户来实现这一点。 - -**这是一个相当繁重的端点 - 如果您在像 Magic Eden 程序这样的大型程序上尝试它,您将得到数十万到数百万的结果。如果你不小心的话,这会破坏东西。在现实世界中,您很少[需要同时获得多个帐户](https://twitter.com/redacted_noah/status/1593831571014012929?utm_source=buildspace.so&utm_medium=buildspace_project),所以现在不用担心。只需知道您不应该对数据进行建模,使得 getProgramAccounts 是必要的。** - -```ts -.then(async (accounts) => { - const movies: Movie[] = accounts.reduce((accum: Movie[], { pubkey, account }) => { - // Try to extract movie item from account data - const movie = Movie.deserialize(account.data) - - // If the account does not have a review, movie will be null - if (!movie) { - return accum - } - - return [...accum, movie] - }, []) - setMovies(movies) -}) -``` - -为了存储我们的电影评论,我们将创建一个 Movie 类型的数组。为了填充它,我们将使用 reduce 反序列化每个帐户并尝试解构 movie 项。如果该帐户中有电影数据,那么这将起作用!如果没有,电影将为空,我们可以返回累积的电影列表。 - - -如果这看起来令人困惑,请逐行浏览代码并确保您知道 reduce 方法是如何工作的。 - -确保您正在运行 npm run dev 并转到 localhost:3000 ,您应该会看到其他构建者添加的一堆随机评论:D - -## 🚢 船舶挑战 - -我们现在可以序列化和反序列化数据。好的。让我们切换到我们在序列化部分开始的 Student Intros 应用程序。 - -目标:更新应用程序以获取并反序列化程序的帐户数据。支持此功能的 Solana 程序位于: HdE95RSVsdb315jfJtaykXhXY478h53X6okDupVfY9yf - -您可以从上次挑战中离开的位置开始,也可以从此存储库中获取代码。确保您从 solution-serialize-instruction-data 分支开始。 - -### Hints: - -在 StudentIntro.ts 中创建帐户缓冲区布局。账户数据包含: -1. initialized 作为一个布尔值,表示帐户是否已初始化 -2. name 作为表示学生姓名的字符串 -3. message 作为表示学生分享的有关 Solana 旅程的消息的字符串 - -在 StudentIntro.ts 中创建一个静态方法,该方法将使用缓冲区布局将帐户数据缓冲区反序列化为 StudentIntro 对象。 - - -在 StudentIntroList 组件的 useEffect 中,获取程序的帐户并将其数据反序列化到 StudentIntro 对象列表中。 - -### Solution code: - -解决方案代码: -与往常一样,首先尝试独立执行此操作,但如果您陷入困境或只是想将您的解决方案与我们的解决方案进行比较,请查看此存储库中的 solution-deserialize-account-data [分支](https://github.com/buildspace/solana-student-intros-frontend/tree/solution-deserialize-account-data?utm_source=buildspace.so&utm_medium=buildspace_project)。 - -祝你好运! diff --git a/other_docs/solana/module1/run-it-back-deserialization/img/upload_1.png b/other_docs/solana/module1/run-it-back-deserialization/img/upload_1.png deleted file mode 100644 index 9a116e68c..000000000 Binary files a/other_docs/solana/module1/run-it-back-deserialization/img/upload_1.png and /dev/null differ diff --git a/other_docs/solana/module1/run-it-back-deserialization/img/upload_2.png b/other_docs/solana/module1/run-it-back-deserialization/img/upload_2.png deleted file mode 100644 index d77783215..000000000 Binary files a/other_docs/solana/module1/run-it-back-deserialization/img/upload_2.png and /dev/null differ diff --git a/other_docs/solana/module1/write-data-to-the-blockchain/README.md b/other_docs/solana/module1/write-data-to-the-blockchain/README.md deleted file mode 100644 index ff8681df6..000000000 --- a/other_docs/solana/module1/write-data-to-the-blockchain/README.md +++ /dev/null @@ -1,465 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: ✍ 将数据写入区块链 -sidebar_class_name: green ---- - -# ✍ 将数据写入区块链 - -幼儿园毕业的时间到了。我们了解有关阅读的一切 - 您只需对 JSON RPC 端点进行 API 调用即可。让我们写入区块链吧! - -TL;DR - -- KeyPair是指公钥和私钥的配对。公钥用作指向 Solana 网络上帐户的“地址”。密钥用于验证身份或权限。顾名思义,您应该始终将密钥保密。 @solana/web3.js 提供了用于创建全新密钥对或使用现有密钥构建密钥对的辅助函数。 -- Transactions 实际上是调用 Solana 程序的一组指令。每个事务的结果取决于被调用的程序。对链上数据的所有修改都是通过交易发生的。 - - - -## 🔐 密钥对 - -顾名思义,密钥对是一对密钥:公钥和秘密密钥。 - -- 公钥用作指向 Solana 网络上帐户的“地址”。 -- 密钥用于验证身份或权限。顾名思义,您应该始终将密钥保密。 - - -要将数据写入区块链,需要提交交易。可以将其视为数据写入命令,如果不满足某些条件,则可以拒绝该命令。 - -为了理解交易及其工作原理,您需要知道什么是密钥对。顾名思义,这是一对密钥 - 一个是公共的,另一个是私有的。公钥指向网络上帐户的地址,每个公钥都有一个相应的私钥/秘密密钥。 - -Web3.js 库有几个用于处理密钥对的辅助函数。您可以生成密钥对并使用它们来获取公钥或私钥。 - -```typescript -// Create a new keypair -const ownerKeypair = Keypair.generate() - -// Get the public key (address) -const publicKey = ownerKeypair.publicKey - -// Get the secret key -const secretKey = ownerKeypair.secretKey -``` - -密钥可以有几种不同的格式 - -1. 助记词——这是最常见的 - -``` -pill tomorrow foster begin walnut borrow virtual kick shift mutual shoe scatter -``` - -2. bs58 字符串 - 钱包有时会导出该字符串 - -``` -5MaiiCavjCmn9Hs1o3eznqDEhRwxo7pXiAYez7keQUviUkauRiTMD8DrESdrNjN8zd9mTmVhRvBJeg5vhyvgrAhG -``` - -3. 字节 - 编写代码时,我们通常将原始字节作为数字数组处理 - -``` -[ 174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56, 222, 53, 138, 189, 224, 216, 117,173, 10, 149, 53, 45, 73, 251, 237, 246, 15, 185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 121, 121, 35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135, ] -``` - -如果您已经有想要使用的密钥对,则可以使用 `Keypair.fromSecretKey()` 函数从密钥创建 `Keypair` 对象。 - -当涉及到主网时,您将面临真实的金钱和真实的后果。花时间研究管理秘密的各种方法是值得的。您可能不想使用 `.env` 变量注入密钥。这里有[一篇很好的读物](https://security.stackexchange.com/questions/197784/is-it-unsafe-to-use-environmental-variables-for-secret-data?utm_source=buildspace.so&utm_medium=buildspace_project)。 - -```typescript -//private key as an array of bytes -const secret = JSON.parse(process.env.PRIVATE_KEY ?? "") as number[] -const secretKey = Uint8Array.from(secret) -const keypairFromSecretKey = Keypair.fromSecretKey(secretKey) -``` - -我们在这里所做的是以字节格式获取私钥并将其解析为数字数组,然后将其转换为 uint 数组。我们使用这个 uint 数组来创建密钥对。您不需要知道它是如何工作的,但您可以在[此处](https://solanacookbook.com/references/keypairs-and-wallets.html)和[此处](https://mattmazur.com/2021/11/19/splitting-a-solana-keypair-into-a-public-and-private-keys/)阅读更多相关信息。 - - -好吧。现在您对 Solana 密钥对的了解比 98% 的 Solana 开发人员还要多 🕶️ - -回到交易镇。 - -Solana 网络上数据的所有修改都是通过交易进行的。所有事务都与网络上的程序交互 - 这些程序可以是系统程序或用户构建的程序。事务告诉程序他们想要用一堆指令做什么,如果它们有效,程序就会执行这些操作! - -这些说明是什么样子的?他们包含: - -1. 您要调用的程序的标识符 -2. 将读取和/或写入的帐户数组 -3. 数据结构为字节数组,指定给正在调用的程序 - -如果感觉很多,别担心,随着我们的进展,一切都会顺利! - -## 🚆 进行并发送交易 - -我们来做一笔交易吧。我们将调用系统程序来传输一些SOL。由于我们正在与系统程序进行交互,因此 web3.js 库中的辅助函数使这变得非常简单! - -```typescript -const transaction = new Transaction() - -const sendSolInstruction = SystemProgram.transfer({ - fromPubkey: sender, - toPubkey: recipient, - lamports: LAMPORTS_PER_SOL * amount -}) - -transaction.add(sendSolInstruction) -``` - -这就是创建转账交易所需的全部!您可以向一笔交易添加多条指令,它们将按顺序执行。我们稍后会尝试这个😈 - -web3.js 库还提供了发送交易的功能。以下是我们发送交易的方式: - -```typescript -const signature = sendAndConfirmTransaction( - connection, - transaction, - [senderKeypair] -) -``` - -您知道这里的一切 - 连接是我们通过 JSON RPC 与网络通信的方式。交易就是我们刚刚通过转账指令进行的事情。最后一个参数是签名者数组。这些是“签署”交易的密钥对,以便 Solana 运行时和您发送的程序知道谁授权了该交易。某些交易需要多方签名,因此这里并不总是一个地址。 - -签名是必要的,因此我们只能进行授权的更改。由于此交易将 SOL 从一个帐户转移到另一个帐户,因此我们需要证明我们控制着尝试发送的帐户。 - -现在您已经了解了有关交易的所有信息以及我提到的“条件”是什么:) - -## ✍ 指令 - - -我们上次交易采取了简单的路线。当使用非本机程序或未内置到 web3 库中的程序时,我们需要非常具体地了解我们正在创建的指令。这是我们需要传递到构造函数以创建指令的类型。一探究竟 - - -```typescript -export type TransactionInstructionCtorFields = { - keys: Array; - programId: PublicKey; - data?: Buffer; -}; -``` - -本质上,指令包含: -- AccountMeta 类型的键数组 -- 您调用的程序的公钥/地址 -- 可选 - 包含要传递给程序的数据的 Buffer - -从Keys开始-该数组中的每个对象代表一个将在事务执行期间读取或写入的帐户。这样节点就知道哪些账户将参与交易,从而加快速度!这意味着您需要了解正在调用的程序的行为,并确保提供数组中所有必要的帐户。 - - -Keys数组中的每个对象必须包含以下内容: -- pubkey - 账户的公钥 -- isSigner - 一个布尔值,表示该帐户是否是交易的签名者 -- isWritable - 一个布尔值,表示在交易执行期间是否写入帐户 - -programId 字段是相当不言自明的:它是与您想要交互的程序关联的公钥。一定要知道你想和谁说话! - -我们现在将忽略数据字段,并将在将来重新审视它。 - -下面是实际操作中的示例: - -```typescript -async function callProgram( - connection: web3.Connection, - payer: web3.Keypair, - programId: web3.PublicKey, - programDataAccount: web3.PublicKey -) { - const instruction = new web3.TransactionInstruction({ - // We only have one key here - keys: [ - { - pubkey: programDataAccount, - isSigner: false, - isWritable: true - }, - ], - - // The program we're interacting with - programId - - // We don't have any data here! - }) - - const sig = await web3.sendAndConfirmTransaction( - connection, - new web3.Transaction().add(instruction), - [payer] - ) -} -``` - -没那么难!我们得到了这个:P - -## ⛽ 交易费用 - -我们唯一没有讨论过的事情是:费用。 Solana 费用如此之低,您不妨忽略它们!可悲的是,作为开发者,我们必须关注他们,哈哈。 Solana 上的费用行为与以太坊等 EVM 链类似。每次您提交交易时,网络上的某人都会提供空间和处理能力来实现交易。费用激励人们提供空间和处理能力。 - -主要需要注意的是,交易签名者数组中的第一个签名者始终是负责支付交易费用的人。如果您没有足够的 SOL 会发生什么?交易被取消! - -当您在 devnet 或 localhost 上时,您可以使用 CLI 中的 solana airdrop 来获取 devnet SOL。您还可以使用 [SPL 代币水龙头](https://spl-token-faucet.com/)来获取 SPL 代币(稍后我们将了解它们是什么:P)。 - - -## Demo - -我们将创建一个脚本来 ping 一个简单的程序,每次 ping 时都会增加一个计数器。该程序存在于 Solana Devnet 上,地址为 `ChT1B39WKLS8qUrkLvFDXMhEJ4F1XZzwUNHUt4AU9aVa`。该程序将计数数据存储在地址为 `Ah9K7dQ8EHaZqcAsgBW8w37yN2eAy3koFmUn4x3CJtod` 的特定帐户中。 - -### 1. 基础脚手架 - -让我们从一些基本的脚手架开始。欢迎您以最合适的方式设置您的项目,但我们将使用一个简单的 Typescript 项目,并依赖于 `@solana/web3.js` 包。如果你想使用我们的脚手架,可以在命令行中使用以下命令: - -```bash -mkdir -p solana-ping-client/src && \ - cd solana-ping-client && \ - touch src/index.ts && \ - git init && touch .gitignore && \ - npm init -y && \ - npm install --save-dev typescript && \ -npm install --save-dev ts-node && \ - npx tsc --init && \ - npm install @solana/web3.js && \ - npm install dotenv && \ - touch .env -``` - -这会: -1.为项目创建一个新目录,并包含子目录 src -2.将命令行提示符移动到项目目录中 -3.在 src 内创建一个 index.ts 文件 -4.使用 .gitignore 文件初始化 git 存储库 -5.创建一个新的 npm 包 -6.添加开发人员对 typescript 的依赖 -7.添加对 ts-node 的开发人员依赖 -8.创建 .tsconfig 文件 -9.安装 @solana/web3.js 依赖项 -10.安装 .dotenv 依赖项 -11.创建一个 .env 文件 - -如果您想完全匹配我们的代码,请将 `tsconfig.json` 的内容替换为以下内容: - -```json -{ - "compilerOptions": { - "target": "es5", - "module": "commonjs", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "outDir": "dist" - }, - "include": [ "./src/**/*" ] -} -``` - -将以下内容添加到 `.gitignore`: - -```bash -node_modules/ -dist/ -.env -``` - -最后,将以下内容添加到 `package.json` 中的脚本对象中: - -```json -"start": "ts-node src/index.ts" -``` - -### 2. 生成新的密钥对 - -在您执行任何操作之前,您需要一个密钥对。让我们跳转到 `index.ts` 文件并生成一个: - -```typescript -import web3 = require('@solana/web3.js') -import Dotenv from 'dotenv' -Dotenv.config() - -async function main() { - const newKeypair = web3.Keypair.generate() - console.log(newKeypair.secretKey.toString()) -} - -main().then(() => { - console.log("Finished successfully") -}).catch((error) => { - console.error(error) -}) -``` - -大部分代码只是正确运行文件的样板。 main() 函数内部的行生成一个新的密钥对并将密钥记录到控制台。 - -保存此文件后运行 `npm start`,您应该会看到控制台打印出一组数字。该数组代表新密钥对的秘密密钥。请勿使用此密钥对进行主网操作。仅使用此密钥对进行测试。 - -从控制台日志复制密钥数组并将其作为名为 `PRIVATE_KEY` 的环境变量粘贴到 `.env` 文件中。这样我们就可以在未来的开发中重用这个密钥对,而不是每次运行某些东西时都生成一个新的密钥对。它应该看起来像这样,但数字不同: - -```json -PRIVATE_KEY=[56,83,31,62,66,154,33,74,106,59,111,224,176,237,89,224,10,220,28,222,128,36,138,89,30,252,100,209,206,155,154,65,98,194,97,182,98,162,107,238,61,183,163,215,44,6,10,49,218,156,5,131,125,253,247,190,181,196,0,249,40,149,119,246] -``` - -### 3.从secret初始化密钥对 - -现在我们已经成功生成了密钥对并将其复制到 `.env` 文件中,我们可以删除 main() 函数内部的代码。 - -我们很快就会返回到 main() 函数,但现在让我们在 main() 之外创建一个名为`initializeKeypair()` 的新函数。这个新函数的内部: - -1. 将 `PRIVATE_KEY` 环境变量解析为 `number[]` -2. 用它来初始化 `Uint8Array` -3. 使用该 `Uint8Array` 初始化并返回密钥对。 - -```typescript -function initializeKeypair(): web3.Keypair { - const secret = JSON.parse(process.env.PRIVATE_KEY ?? "") as number[] - const secretKey = Uint8Array.from(secret) - const keypairFromSecretKey = web3.Keypair.fromSecretKey(secretKey) - return keypairFromSecretKey -} -``` - - -### 4. Ping 程序 - -现在我们有了初始化密钥对的方法,我们需要与 Solana 的 Devnet 建立连接。在`main()`中,我们调用`initializeKeypair()`并创建一个连接: - -```typescript -async function main() { - const payer = initializeKeypair() - const connection = new web3.Connection(web3.clusterApiUrl('devnet')) -} -``` - -现在在 main() 之外创建一个名为 `pingProgram()` 的异步函数,其中有两个需要连接的参数和付款人的密钥对作为参数: - -```typescript -async function pingProgram(connection: web3.Connection, payer: web3.Keypair) { } -``` - -在这个函数中,我们需要: - -1.创建交易 -2.创建指令 -3.将指令添加到交易中 -4.发送交易 - -请记住,这里最具挑战性的部分是在说明中包含正确的信息。我们知道我们正在调用的程序的地址。我们还知道该程序将数据写入一个单独的帐户,我们也知道该帐户的地址。让我们将这两个版本的字符串版本作为常量添加到 `index.ts` 文件的顶部: - -```typescript -const PROGRAM_ADDRESS = 'ChT1B39WKLS8qUrkLvFDXMhEJ4F1XZzwUNHUt4AU9aVa' -const PROGRAM_DATA_ADDRESS = 'Ah9K7dQ8EHaZqcAsgBW8w37yN2eAy3koFmUn4x3CJtod' -``` - -现在,在 `pingProgram()` 函数中,我们创建一个新交易,然后为程序帐户初始化一个 `PublicKey`,为数据帐户初始化另一个 `PublicKey`。 - -```typescript -async function pingProgram(connection: web3.Connection, payer: web3.Keypair) { - const transaction = new web3.Transaction() - - const programId = new web3.PublicKey(PROGRAM_ADDRESS) - const programDataPubkey = new web3.PublicKey(PROGRAM_DATA_ADDRESS) -} -``` - -接下来,让我们创建指令。请记住,该指令需要包含程序的公钥,还需要包含一个数组,其中包含将读取或写入的所有帐户。在此示例程序中,仅需要上面引用的数据帐户。 - -```typescript -async function pingProgram(connection: web3.Connection, payer: web3.Keypair) { - const transaction = new web3.Transaction() - - const programId = new web3.PublicKey(PROGRAM_ADDRESS) - const programDataPubkey = new web3.PublicKey(PROGRAM_DATA_ADDRESS) - - const instruction = new web3.TransactionInstruction({ - keys: [ - { - pubkey: programDataPubkey, - isSigner: false, - isWritable: true - }, - ], - programId - }) -} -``` - -接下来,让我们将指令添加到我们在函数开始时创建的事务中。然后,通过传入连接、交易和付款人来调用 `sendAndConfirmTransaction()`。最后,让我们记录该函数调用的结果,以便我们可以在 Solana Explorer 上查找它。 - -```typescript -async function pingProgram(connection: web3.Connection, payer: web3.Keypair) { - const transaction = new web3.Transaction() - - const programId = new web3.PublicKey(PROGRAM_ADDRESS) - const programDataPubkey = new web3.PublicKey(PROGRAM_DATA_ADDRESS) - - const instruction = new web3.TransactionInstruction({ - keys: [ - { - pubkey: programDataPubkey, - isSigner: false, - isWritable: true - }, - ], - programId - }) - - transaction.add(instruction) - - const signature = await web3.sendAndConfirmTransaction( - connection, - transaction, - [payer] - ) - - console.log(signature) -} -``` - -最后,让我们使用连接和付款人在 main() 中调用 `pingProgram()`: - -```typescript -async function main() { - const payer = initializeKeypair() - const connection = new web3.Connection(web3.clusterApiUrl('devnet')) - await pingProgram(connection, payer) -} -``` - -### 5. 空投 - -现在使用 `npm start` 运行代码并查看它是否有效。您最终可能会在控制台中看到以下错误: - -> Transaction simulation failed: Attempt to debit an account but found no record of a prior credit. - -如果您收到此错误,那是因为您的密钥对是全新的,并且没有任何 SOL 来支付交易费用。让我们通过在 main() 中调用 `pingProgram()` 之前添加以下行来解决此问题: - -```typescript -await connection.requestAirdrop(payer.publicKey, web3.LAMPORTS_PER_SOL*1) -``` - -这会将 1 SOL 存入您的帐户,您可以将其用于测试。这在主网上行不通,因为它实际上具有价值。但对于本地和 Devnet 上的测试来说,它非常方便。 - -### 6.检查Solana浏览器 - -现在再次运行代码。这可能需要一两分钟,但现在代码应该可以运行,并且您应该看到一个长字符串打印到控制台,如下所示: - -``` -55S47uwMJprFMLhRSewkoUuzUs5V6BpNfRx21MpngRUQG3AswCzCSxvQmS3WEPWDJM7bhHm3bYBrqRshj672cUSG -``` - -复制此确认签名。打开浏览器并访问 `https://explorer.solana.com/?cluster=devnet`(URL 末尾的查询参数将确保您在 Devnet 而不是主网上探索交易)。将签名粘贴到 `Solana Devnet` 浏览器顶部的搜索栏中,然后按 Enter 键。您应该看到有关交易的所有详细信息。如果一直滚动到底部,您将看到程序日志,其中显示程序已被 ping 的次数,包括您的 ping。 - -如果您希望将来更轻松地查看 Solana Explorer 中的事务,只需将 pingProgram() 中的 console.log 更改为以下内容: - -```ts -console.log(`You can view your transaction on the Solana Explorer at:\nhttps://explorer.solana.com/tx/${sig}?cluster=devnet`) -``` - -就像这样,您可以调用 Solana 网络上的程序并将数据写入链! - - -在接下来的几节课程中,您将学习如何 - -1.从浏览器安全地执行此操作,而不是运行脚本 -2.将自定义数据添加到您的说明中 -3.从链上反序列化数据 - -## 挑战 - -继续从头开始创建一个脚本,该脚本将允许您将 SOL 从 Devnet 上的一个帐户转移到另一个帐户。请务必打印出交易签名,以便您可以在 Solana Explorer 上查看它。 - -这个在仓库的`src/main.ts`其实已经实现,但是还是希望您能跟着这个教程的Demo的操作,自己实现一遍。 diff --git a/other_docs/zk-learn/README.md b/other_docs/zk-learn/README.md deleted file mode 100644 index 3a464701c..000000000 --- a/other_docs/zk-learn/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# Zero knowledge proof tutorial - - -## Main tutorial - -- [MIT Zk Course](https://zkiap.com/) - - -## zk co learn tutorial - -- [zk-co-learn](https://github.com/Antalpha-Labs/zkp-co-learn) - - -## Other tutorial - -- [learn-zkp](https://github.com/sec-bit/learning-zkp) -- [Mooc Zk Learning](https://zk-learning.org/) -- [ZKP】 Resource list](https://github.com/Antalpha-Labs/zkp-co-learn/discussions/18) -- [awesome-zero-knowledge-proofs](https://github.com/sCrypt-Inc/awesome-zero-knowledge-proofs#plonk) -- [moonmath-manual](https://github.com/LeastAuthority/moonmath-manual) - - -- [用程序员听得懂的方式介绍零知识证明](https://mirror.xyz/0xE44081Ee2D0D4cbaCd10b44e769A14Def065eD4D/nKtuByYTvPri75xHQoA7f8vNyJ6NQPvCL_YH8KVp31Q) -- [AA钱包与Dapp的连接:BatchCall](https://medium.com/@George_QXQ/%E5%8F%AF%E8%83%BD%E9%A2%A0%E8%A6%86aa%E7%9A%84%E6%8A%80%E6%9C%AF-batchcall-75d71c38d47f) -- [【OH问题点小结】Week1-Session1&Session3](https://github.com/Antalpha-Labs/zkp-co-learn/discussions/33) - -- [zk-co-learn disscurssion](https://github.com/Antalpha-Labs/zkp-co-learn/discussions) - -- [【session 2】有推荐或者规范的circom写法么 ](https://github.com/Antalpha-Labs/zkp-co-learn/discussions/21) - -- [【2023-03-08】半小时过一下”多项式承诺到 KZG 构建](https://github.com/Antalpha-Labs/zkp-co-learn/discussions/52) -- [session 5】KZG10多项式承诺相关疑问](https://github.com/Antalpha-Labs/zkp-co-learn/discussions/53) -- [[Lecture 8] Plonk 阅读资料](https://github.com/Antalpha-Labs/zkp-co-learn/discussions/66) -- [Efficient Multi-Exponentiation](https://jbootle.github.io/Misc/pippenger.pdf) -- [Learning Crypto via Python3](https://docs.google.com/presentation/d/1HgJaONmF1Yd-FMxXihwLvnT4oa61tAeAA_qeZjgmhHY/edit?pli=1#slide=id.p) -- [research](https://github.com/qizhou/research/tree/main) -- [作业提交】Python Plonk](https://github.com/Antalpha-Labs/zkp-co-learn/discussions/83) -- [理解 Plonk 协议](https://github.com/sec-bit/learning-zkp/tree/develop/plonk-intro-cn) -- [理解 PLONK - 2】 by 郭宇老师@安比实验室](https://github.com/Antalpha-Labs/zkp-co-learn/discussions/89) -- [理解 PLONK(四):算术约束与拷贝约束](https://github.com/sec-bit/learning-zkp/blob/develop/plonk-intro-cn/plonk-constraints.md) -- [Simple guide to fast linear combinations (aka multiexponentiations)](https://ethresear.ch/t/simple-guide-to-fast-linear-combinations-aka-multiexponentiations/7238) -- [快速傅里叶变换(FFT)——有史以来最巧妙的算法?](https://www.bilibili.com/video/BV1za411F76U/) -- [理解 PLONK - 3 - lookup】 by 郭宇老师@安比实验室](https://github.com/Antalpha-Labs/zkp-co-learn/discussions/95) -- [cq: Cached quotients for fast lookups](https://eprint.iacr.org/2022/1763)