diff --git a/Cargo.lock b/Cargo.lock index a331a4d46e..b53a56d40e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,60 @@ dependencies = [ "generic-array", ] +[[package]] +name = "aggregator-common" +version = "0.1.0" +dependencies = [ + "ckb-hash 0.116.1", + "ckb-types 0.116.1", + "secp256k1", + "thiserror", +] + +[[package]] +name = "aggregator-main" +version = "0.1.0" +dependencies = [ + "aggregator-common", + "aggregator-rgbpp-tx", + "ckb-app-config", + "ckb-channel 0.116.1", + "ckb-gen-types 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-hash 0.116.1", + "ckb-jsonrpc-types 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-logger 0.116.1", + "ckb-sdk 3.2.1", + "ckb-types 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "crossbeam-channel", + "hex", + "jsonrpc-core", + "molecule", + "reqwest 0.12.5", + "secp256k1", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "aggregator-rgbpp-tx" +version = "0.1.0" +dependencies = [ + "aggregator-common", + "ckb-app-config", + "ckb-gen-types 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ckb-hash 0.116.1", + "ckb-jsonrpc-types 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ckb-logger 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ckb-sdk 3.2.0", + "ckb-stop-handler", + "ckb-types 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", + "molecule", + "secp256k1", + "serde", + "serde_json", +] + [[package]] name = "ahash" version = "0.3.8" @@ -395,31 +449,6 @@ dependencies = [ "rand 0.8.5", ] -[[package]] -name = "branch-chain-aggregator" -version = "0.1.0" -dependencies = [ - "ckb-app-config", - "ckb-async-runtime", - "ckb-channel 0.116.1", - "ckb-gen-types 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", - "ckb-hash 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", - "ckb-jsonrpc-types 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", - "ckb-logger 0.116.1", - "ckb-sdk", - "ckb-stop-handler", - "ckb-types 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-channel", - "hex", - "jsonrpc-core", - "molecule", - "reqwest 0.12.5", - "secp256k1", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "branch-chain-producer" version = "0.1.0" @@ -717,8 +746,8 @@ dependencies = [ name = "ckb-bin" version = "0.116.1" dependencies = [ + "aggregator-main", "base64 0.21.7", - "branch-chain-aggregator", "ckb-app-config", "ckb-async-runtime", "ckb-build-info", @@ -860,6 +889,28 @@ dependencies = [ "toml", ] +[[package]] +name = "ckb-chain-spec" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "cacache", + "ckb-constant 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-crypto 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-dao-utils 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-error 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-hash 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-jsonrpc-types 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-logger 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-pow 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-rational 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-resource 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-traits 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-types 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "serde", + "toml", +] + [[package]] name = "ckb-channel" version = "0.116.1" @@ -876,6 +927,14 @@ dependencies = [ "crossbeam-channel", ] +[[package]] +name = "ckb-channel" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "crossbeam-channel", +] + [[package]] name = "ckb-constant" version = "0.116.1" @@ -886,6 +945,11 @@ version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baf91b16a3b8360c85211dfdff3d2adc0a1f3ae571ea6b1637d55d6b227e312" +[[package]] +name = "ckb-constant" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" + [[package]] name = "ckb-crypto" version = "0.116.1" @@ -912,6 +976,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ckb-crypto" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "ckb-fixed-hash 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "faster-hex", + "lazy_static", + "rand 0.7.3", + "secp256k1", + "thiserror", +] + [[package]] name = "ckb-dao" version = "0.116.1" @@ -947,6 +1024,16 @@ dependencies = [ "ckb-types 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ckb-dao-utils" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "byteorder", + "ckb-error 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-types 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", +] + [[package]] name = "ckb-db" version = "0.116.1" @@ -1003,6 +1090,17 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ckb-error" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "anyhow", + "ckb-occupied-capacity 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "derive_more", + "thiserror", +] + [[package]] name = "ckb-fixed-hash" version = "0.116.1" @@ -1021,6 +1119,15 @@ dependencies = [ "ckb-fixed-hash-macros 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ckb-fixed-hash" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "ckb-fixed-hash-core 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-fixed-hash-macros 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", +] + [[package]] name = "ckb-fixed-hash-core" version = "0.116.1" @@ -1044,6 +1151,17 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ckb-fixed-hash-core" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "ckb_schemars", + "faster-hex", + "serde", + "thiserror", +] + [[package]] name = "ckb-fixed-hash-macros" version = "0.116.1" @@ -1066,6 +1184,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ckb-fixed-hash-macros" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "ckb-fixed-hash-core 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "ckb-freezer" version = "0.116.1" @@ -1110,6 +1239,20 @@ dependencies = [ "numext-fixed-uint", ] +[[package]] +name = "ckb-gen-types" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "cfg-if 1.0.0", + "ckb-error 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-fixed-hash 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-hash 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-occupied-capacity 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "molecule", + "numext-fixed-uint", +] + [[package]] name = "ckb-hash" version = "0.116.1" @@ -1128,6 +1271,15 @@ dependencies = [ "blake2b-rs", ] +[[package]] +name = "ckb-hash" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "blake2b-ref", + "blake2b-rs", +] + [[package]] name = "ckb-indexer" version = "0.116.1" @@ -1205,6 +1357,18 @@ dependencies = [ "serde_json", ] +[[package]] +name = "ckb-jsonrpc-types" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "ckb-types 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb_schemars", + "faster-hex", + "serde", + "serde_json", +] + [[package]] name = "ckb-launcher" version = "0.116.1" @@ -1285,6 +1449,14 @@ dependencies = [ "log", ] +[[package]] +name = "ckb-logger" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "log", +] + [[package]] name = "ckb-logger-config" version = "0.116.1" @@ -1432,6 +1604,17 @@ dependencies = [ "serde", ] +[[package]] +name = "ckb-mock-tx-types" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb-standalone-debugger?branch=v0.116.1-branch-chain#b6039036d269a1ae36118a491341ec84792971fa" +dependencies = [ + "ckb-jsonrpc-types 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-traits 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-types 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "serde", +] + [[package]] name = "ckb-multisig" version = "0.116.1" @@ -1533,6 +1716,15 @@ dependencies = [ "ckb-occupied-capacity-macros 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ckb-occupied-capacity" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "ckb-occupied-capacity-core 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-occupied-capacity-macros 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", +] + [[package]] name = "ckb-occupied-capacity-core" version = "0.116.1" @@ -1549,6 +1741,14 @@ dependencies = [ "serde", ] +[[package]] +name = "ckb-occupied-capacity-core" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "serde", +] + [[package]] name = "ckb-occupied-capacity-macros" version = "0.116.1" @@ -1569,6 +1769,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ckb-occupied-capacity-macros" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "ckb-occupied-capacity-core 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "quote", + "syn 1.0.109", +] + [[package]] name = "ckb-pow" version = "0.116.1" @@ -1595,6 +1805,19 @@ dependencies = [ "serde", ] +[[package]] +name = "ckb-pow" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "byteorder", + "ckb-hash 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-types 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "eaglesong", + "log", + "serde", +] + [[package]] name = "ckb-proposal-table" version = "0.116.1" @@ -1623,6 +1846,15 @@ dependencies = [ "serde", ] +[[package]] +name = "ckb-rational" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "numext-fixed-uint", + "serde", +] + [[package]] name = "ckb-resource" version = "0.116.1" @@ -1652,6 +1884,20 @@ dependencies = [ "walkdir", ] +[[package]] +name = "ckb-resource" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "ckb-system-scripts", + "ckb-types 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "includedir", + "includedir_codegen", + "phf 0.8.0", + "serde", + "walkdir", +] + [[package]] name = "ckb-reward-calculator" version = "0.116.1" @@ -1812,6 +2058,23 @@ dependencies = [ "serde", ] +[[package]] +name = "ckb-script" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "byteorder", + "ckb-chain-spec 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-error 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-hash 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-logger 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-traits 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-types 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-vm", + "faster-hex", + "serde", +] + [[package]] name = "ckb-sdk" version = "3.2.0" @@ -1827,7 +2090,7 @@ dependencies = [ "ckb-dao-utils 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", "ckb-hash 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", "ckb-jsonrpc-types 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", - "ckb-mock-tx-types", + "ckb-mock-tx-types 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", "ckb-resource 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", "ckb-script 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", "ckb-traits 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1854,6 +2117,47 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "ckb-sdk" +version = "3.2.1" +source = "git+https://github.com/ethanyuan/ckb-sdk-rust?branch=v3.2.1-branch-chain#16dbe10bfce936ef1ec14b086694ae0933e388b6" +dependencies = [ + "anyhow", + "bech32", + "bitflags 1.3.2", + "bytes", + "ckb-chain-spec 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-crypto 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-dao-utils 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-hash 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-jsonrpc-types 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-mock-tx-types 0.116.1 (git+https://github.com/ethanyuan/ckb-standalone-debugger?branch=v0.116.1-branch-chain)", + "ckb-resource 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-script 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-traits 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-types 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "dashmap 5.5.3", + "derive-getters", + "dyn-clone", + "enum-repr-derive", + "futures", + "jsonrpc-core", + "lazy_static", + "log", + "lru", + "parking_lot 0.12.1", + "reqwest 0.11.20", + "secp256k1", + "serde", + "serde_derive", + "serde_json", + "sha3", + "sparse-merkle-tree", + "thiserror", + "tokio", + "tokio-util", +] + [[package]] name = "ckb-shared" version = "0.116.1" @@ -2033,6 +2337,14 @@ dependencies = [ "ckb-types 0.116.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ckb-traits" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "ckb-types 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", +] + [[package]] name = "ckb-tx-pool" version = "0.116.1" @@ -2122,6 +2434,31 @@ dependencies = [ "paste", ] +[[package]] +name = "ckb-types" +version = "0.116.1" +source = "git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain#58be3ea7aec2c25d9f53a12360a38f6151629bbe" +dependencies = [ + "bit-vec", + "bytes", + "ckb-channel 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-constant 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-error 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-fixed-hash 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-gen-types 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-hash 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-merkle-mountain-range", + "ckb-occupied-capacity 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "ckb-rational 0.116.1 (git+https://github.com/ethanyuan/ckb?branch=v0.116.1-branch-chain)", + "derive_more", + "golomb-coded-set", + "merkle-cbt", + "molecule", + "numext-fixed-uint", + "once_cell", + "paste", +] + [[package]] name = "ckb-util" version = "0.116.1" diff --git a/Cargo.toml b/Cargo.toml index c5087f8be8..5b1cdaff10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,8 +94,10 @@ members = [ "util/launcher", "devtools/doc/rpc-gen", "ckb-bin", - "branch-chain-producer" -, "branch-chain-aggregator"] + "branch-chain-producer", + "aggregator/util/common", + "aggregator/util/rgbpp-tx", + "aggregator/aggregator-main"] [workspace.dependencies] tempfile = "3" diff --git a/aggregator/aggregator-main/Cargo.toml b/aggregator/aggregator-main/Cargo.toml new file mode 100644 index 0000000000..161bd1c687 --- /dev/null +++ b/aggregator/aggregator-main/Cargo.toml @@ -0,0 +1,32 @@ + +[package] +name = "aggregator-main" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +aggregator-common = { path = "../util/common", version = "0.1.0" } +aggregator-rgbpp-tx = { path = "../util/rgbpp-tx", version = "0.1.0" } +ckb-app-config = { path = "../../util/app-config", version = "= 0.116.1" } +ckb-channel = { path = "../../util/channel", version = "= 0.116.1" } +ckb-hash = { path = "../../util/hash", version = "= 0.116.1" } +ckb-logger = { path = "../../util/logger", version = "= 0.116.1" } + +ckb-sdk = { git = "https://github.com/ethanyuan/ckb-sdk-rust", branch = "v3.2.1-branch-chain", features = ["native-tls-vendored"] } +ckb-gen-types = { package = "ckb-gen-types", git = "https://github.com/ethanyuan/ckb", branch = "v0.116.1-branch-chain" } +ckb-jsonrpc-types = { package = "ckb-jsonrpc-types", git = "https://github.com/ethanyuan/ckb", branch = "v0.116.1-branch-chain" } +ckb-types = { package = "ckb-types", git = "https://github.com/ethanyuan/ckb", branch = "v0.116.1-branch-chain" } + +crossbeam-channel = "0.5.1" +jsonrpc-core = "18.0" +molecule = { version = "0.7.5", default-features = false } +reqwest = { version = "0.12.4", features = ["json"] } +secp256k1 = { version = "0.24", features = ["recovery"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +thiserror = "1.0" + +[dev-dependencies] +hex = "0.4" \ No newline at end of file diff --git a/aggregator/aggregator-main/src/lib.rs b/aggregator/aggregator-main/src/lib.rs new file mode 100644 index 0000000000..6aa5f934ef --- /dev/null +++ b/aggregator/aggregator-main/src/lib.rs @@ -0,0 +1,269 @@ +//! Branch Chain Aggregator + +pub(crate) mod schemas; +pub(crate) mod transaction; + +use crate::schemas::leap::Request; + +use aggregator_common::{error::Error, utils::encode_udt_amount}; +use aggregator_rgbpp_tx::RgbppTxBuilder; +use ckb_app_config::{AggregatorConfig, AssetConfig, LockConfig, ScriptConfig}; +use ckb_channel::Receiver; +use ckb_logger::{error, info}; +use ckb_sdk::rpc::CkbRpcClient as RpcClient; +use ckb_types::{ + packed::{CellDep, OutPoint, Script}, + prelude::*, + H256, +}; + +use std::collections::HashMap; +use std::str::FromStr; +use std::thread::{self, sleep}; +use std::time::Duration; + +/// +#[derive(Clone)] +pub struct Aggregator { + config: AggregatorConfig, + poll_interval: Duration, + rgbpp_rpc_client: RpcClient, + branch_rpc_client: RpcClient, + branch_scripts: HashMap, + rgbpp_assets: HashMap, + rgbpp_locks: HashMap, + + rgbpp_tx_builder: RgbppTxBuilder, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct ScriptInfo { + pub script: Script, + pub cell_dep: CellDep, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct AssetInfo { + pub script: Script, + pub is_capacity: bool, + pub script_name: String, +} + +impl Aggregator { + /// Create an Aggregator + pub fn new(config: AggregatorConfig, poll_interval: Duration, branch_chain_id: String) -> Self { + let rgbpp_rpc_client = RpcClient::new(&config.rgbpp_uri); + let branch_rpc_client = RpcClient::new(&config.branch_uri); + let rgbpp_tx_builder = RgbppTxBuilder::new( + branch_chain_id.clone(), + config.rgbpp_uri.clone(), + config.rgbpp_scripts.clone(), + config.rgbpp_custodian_lock_key_path.clone(), + config.rgbpp_queue_lock_key_path.clone(), + config.rgbpp_ckb_provider_key_path.clone(), + ); + Aggregator { + config: config.clone(), + poll_interval, + rgbpp_rpc_client, + branch_rpc_client, + branch_scripts: get_script_map(config.branch_scripts), + rgbpp_assets: get_asset_map(config.rgbpp_assets), + rgbpp_locks: get_rgbpp_locks(config.rgbpp_asset_locks), + rgbpp_tx_builder, + } + } + + /// Run the Aggregator + pub fn run(&self, stop_rx: Receiver<()>) { + let poll_interval = self.poll_interval; + let poll_service: Aggregator = self.clone(); + + loop { + match stop_rx.try_recv() { + Ok(_) => { + info!("Aggregator received exit signal, stopped"); + break; + } + Err(crossbeam_channel::TryRecvError::Empty) => { + // No exit signal, continue execution + } + Err(_) => { + info!("Error receiving exit signal"); + break; + } + } + + // get queue data + let rgbpp_requests = poll_service.rgbpp_tx_builder.get_rgbpp_queue_requests(); + let (rgbpp_requests, queue_cell) = match rgbpp_requests { + Ok((rgbpp_requests, queue_cell)) => { + let rgbpp_requests: Vec<_> = rgbpp_requests + .into_iter() + .map(|r| Request::new_unchecked(r.as_bytes())) + .collect(); + let queue_cell = OutPoint::new_unchecked(queue_cell.as_bytes()); + (rgbpp_requests, queue_cell) + } + Err(e) => { + error!("get RGB++ queue data error: {}", e.to_string()); + continue; + } + }; + + let leap_tx = poll_service.create_leap_tx(rgbpp_requests.clone(), queue_cell); + let leap_tx = match leap_tx { + Ok(leap_tx) => leap_tx, + Err(e) => { + error!("create leap transaction error: {}", e.to_string()); + continue; + } + }; + match wait_for_tx_confirmation( + poll_service.rgbpp_rpc_client.clone(), + leap_tx, + Duration::from_secs(600), + ) { + Ok(()) => {} + Err(e) => error!("{}", e.to_string()), + } + + if !rgbpp_requests.is_empty() { + let update_queue_tx = poll_service.rgbpp_tx_builder.create_clear_queue_tx(); + let update_queue_tx = match update_queue_tx { + Ok(update_queue_tx) => update_queue_tx, + Err(e) => { + error!("{}", e.to_string()); + continue; + } + }; + match wait_for_tx_confirmation( + poll_service.rgbpp_rpc_client.clone(), + H256(update_queue_tx.0), + Duration::from_secs(600), + ) { + Ok(()) => {} + Err(e) => error!("{}", e.to_string()), + } + } + + if let Err(e) = poll_service.rgbpp_tx_builder.collect_rgbpp_request() { + info!("Aggregator: {:?}", e); + } + + thread::sleep(poll_interval); + } + } +} + +fn get_script_map(scripts: Vec) -> HashMap { + scripts + .iter() + .map(|s| { + ( + s.script_name.clone(), + ScriptInfo { + script: serde_json::from_str::(&s.script) + .expect("config string to script") + .into(), + cell_dep: serde_json::from_str::(&s.cell_dep) + .expect("config string to cell dep") + .into(), + }, + ) + }) + .collect() +} + +fn get_asset_map(asset_configs: Vec) -> HashMap { + let mut is_capacity_found = false; + + asset_configs + .into_iter() + .map(|asset_config| { + let script = serde_json::from_str::(&asset_config.script) + .expect("config string to script") + .into(); + let script_name = asset_config.asset_name.clone(); + let is_capacity = asset_config.is_capacity && !is_capacity_found; + if is_capacity { + is_capacity_found = true; + } + let asset_id = asset_config.asset_id.clone(); + ( + H256::from_str(&asset_id).expect("asset id to h256"), + AssetInfo { + script, + is_capacity, + script_name, + }, + ) + }) + .collect() +} + +fn get_rgbpp_locks(lock_configs: Vec) -> HashMap { + lock_configs + .iter() + .map(|lock_config| { + let lock_hash = H256::from_str(&lock_config.lock_hash).expect("lock hash to h256"); + let script = serde_json::from_str::(&lock_config.script) + .expect("config string to script") + .into(); + (lock_hash, script) + }) + .collect() +} + +fn wait_for_tx_confirmation( + _client: RpcClient, + _tx_hash: H256, + timeout: Duration, +) -> Result<(), Error> { + let start = std::time::Instant::now(); + + loop { + if true { + sleep(Duration::from_secs(8)); + return Ok(()); + } + + if start.elapsed() > timeout { + return Err(Error::TimedOut( + "Transaction confirmation timed out".to_string(), + )); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use ckb_types::{bytes::Bytes, core::ScriptHashType}; + + use std::str::FromStr; + + #[test] + fn calc_script() { + let code_hash = "00000000000000000000000000000000000000000000000000545950455f4944"; + let args = "57fdfd0617dcb74d1287bb78a7368a3a4bf9a790cfdcf5c1a105fd7cb406de0d"; + let script_hash = "6283a479a3cf5d4276cd93594de9f1827ab9b55c7b05b3d28e4c2e0a696cfefd"; + + let code_hash = H256::from_str(code_hash).unwrap(); + let args = Bytes::from(hex::decode(args).unwrap()); + + let script = Script::new_builder() + .code_hash(code_hash.pack()) + .hash_type(ScriptHashType::Type.into()) + .args(args.pack()) + .build(); + + println!("{:?}", script.calc_script_hash()); + + assert_eq!( + script.calc_script_hash().as_bytes(), + Bytes::from(hex::decode(script_hash).unwrap()) + ); + } +} diff --git a/branch-chain-aggregator/src/schemas/leap.rs b/aggregator/aggregator-main/src/schemas/leap.rs similarity index 100% rename from branch-chain-aggregator/src/schemas/leap.rs rename to aggregator/aggregator-main/src/schemas/leap.rs diff --git a/branch-chain-aggregator/src/schemas/mod.rs b/aggregator/aggregator-main/src/schemas/mod.rs similarity index 100% rename from branch-chain-aggregator/src/schemas/mod.rs rename to aggregator/aggregator-main/src/schemas/mod.rs diff --git a/branch-chain-aggregator/src/transaction/leap.rs b/aggregator/aggregator-main/src/transaction/leap.rs similarity index 92% rename from branch-chain-aggregator/src/transaction/leap.rs rename to aggregator/aggregator-main/src/transaction/leap.rs index a3c7b5fcb0..ca2f5100f4 100644 --- a/branch-chain-aggregator/src/transaction/leap.rs +++ b/aggregator/aggregator-main/src/transaction/leap.rs @@ -1,8 +1,11 @@ -use crate::error::Error; use crate::schemas::leap::{MessageUnion, Request}; -use crate::utils::{get_sighash_script_from_privkey, SECP256K1, XUDT}; +use crate::transaction::SIGHASH_TYPE_HASH; use crate::{encode_udt_amount, Aggregator}; +use aggregator_common::{ + error::Error, + utils::{privkey::get_sighash_lock_args_from_privkey, SECP256K1, XUDT}, +}; use ckb_jsonrpc_types::TransactionView; use ckb_logger::info; use ckb_sdk::CkbRpcClient; @@ -24,6 +27,7 @@ use ckb_sdk::{ }; use ckb_types::{ bytes::Bytes, + core::ScriptHashType, packed::{ Byte32, Bytes as PackedBytes, CellInput, CellOutput, OutPoint, Script, Transaction, WitnessArgs, @@ -59,6 +63,7 @@ impl Aggregator { } // build inputs + info!("Search Token Manager Cell ..."); let token_manager_cell = self.get_token_manager_cell()?; let inputs: Vec = std::iter::once(token_manager_cell.out_point.clone()) .map(|out_point| { @@ -132,8 +137,8 @@ impl Aggregator { } // cell deps - let secp256k1_cell_dep = self._get_branch_cell_dep(SECP256K1)?; - let xudt_cell_dep = self._get_branch_cell_dep(XUDT)?; + let secp256k1_cell_dep = self.get_branch_cell_dep(SECP256K1)?; + let xudt_cell_dep = self.get_branch_cell_dep(XUDT)?; // build transaction let mut tx_builder = TransactionBuilder::default(); @@ -183,9 +188,15 @@ impl Aggregator { config.fee_rate = fee_rate; config }; - let (capacity_provider_script, capacity_provider_key) = get_sighash_script_from_privkey( - self.config.branch_chain_capacity_provider_key_path.clone(), - )?; + let (capacity_provider_script_args, capacity_provider_key) = + get_sighash_lock_args_from_privkey( + self.config.branch_chain_capacity_provider_key_path.clone(), + )?; + let capacity_provider_script = Script::new_builder() + .code_hash(SIGHASH_TYPE_HASH.pack()) + .hash_type(ScriptHashType::Type.into()) + .args(capacity_provider_script_args.pack()) + .build(); let mut change_builder = DefaultChangeBuilder::new(&configuration, capacity_provider_script.clone(), Vec::new()); change_builder.init(&mut tx_builder); @@ -235,7 +246,7 @@ impl Aggregator { })?; // sign - let (_, token_manager_key) = get_sighash_script_from_privkey( + let (_, token_manager_key) = get_sighash_lock_args_from_privkey( self.config.branch_chain_token_manager_lock_key_path.clone(), )?; TransactionSigner::new(&network_info) @@ -307,8 +318,6 @@ impl Aggregator { } fn get_token_manager_cell(&self) -> Result { - info!("Search Token Manager Cell ..."); - let token_manager_cell_search_option = self.build_token_manager_cell_search_option()?; let token_manager_cell = self .branch_rpc_client @@ -335,9 +344,14 @@ impl Aggregator { } fn build_token_manager_cell_search_option(&self) -> Result { - let (token_manager_lock, _) = get_sighash_script_from_privkey( + let (token_manager_lock_args, _) = get_sighash_lock_args_from_privkey( self.config.branch_chain_token_manager_lock_key_path.clone(), )?; + let token_manager_lock = Script::new_builder() + .code_hash(SIGHASH_TYPE_HASH.pack()) + .hash_type(ScriptHashType::Type.into()) + .args(token_manager_lock_args.pack()) + .build(); let cell_query_option = CellQueryOptions { primary_script: token_manager_lock, diff --git a/aggregator/aggregator-main/src/transaction/mod.rs b/aggregator/aggregator-main/src/transaction/mod.rs new file mode 100644 index 0000000000..1471e23235 --- /dev/null +++ b/aggregator/aggregator-main/src/transaction/mod.rs @@ -0,0 +1,58 @@ +mod leap; + +use crate::Aggregator; + +use aggregator_common::error::Error; +use ckb_logger::{info, warn}; +use ckb_types::{ + core::FeeRate, + h256, + packed::{CellDep, Script}, + H256, +}; + +pub const SIGHASH_TYPE_HASH: H256 = + h256!("0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"); +const CKB_FEE_RATE_LIMIT: u64 = 5000; + +impl Aggregator { + fn fee_rate(&self) -> Result { + let value = { + let dynamic = self + .rgbpp_rpc_client + .get_fee_rate_statistics(None) + .map_err(|e| Error::RpcError(format!("get dynamic fee rate error: {}", e)))? + .ok_or_else(|| Error::RpcError("get dynamic fee rate error: None".to_string())) + .map(|resp| resp.median) + .map(Into::into) + .map_err(|e| Error::RpcError(format!("get dynamic fee rate error: {}", e)))?; + info!("CKB fee rate: {} (dynamic)", FeeRate(dynamic)); + if dynamic > CKB_FEE_RATE_LIMIT { + warn!( + "dynamic CKB fee rate {} is too large, it seems unreasonable;\ + so the upper limit {} will be used", + FeeRate(dynamic), + FeeRate(CKB_FEE_RATE_LIMIT) + ); + CKB_FEE_RATE_LIMIT + } else { + dynamic + } + }; + Ok(value) + } + + fn get_branch_cell_dep(&self, script_name: &str) -> Result { + self.branch_scripts + .get(script_name) + .map(|script_info| script_info.cell_dep.clone()) + .ok_or_else(|| Error::MissingScriptInfo(script_name.to_string())) + } + + fn _get_branch_script(&self, script_name: &str) -> Result { + self.branch_scripts + .get(script_name) + .map(|script_info| script_info.script.clone()) + .ok_or_else(|| Error::MissingScriptInfo(script_name.to_string())) + } +} diff --git a/aggregator/util/common/Cargo.toml b/aggregator/util/common/Cargo.toml new file mode 100644 index 0000000000..de13f5cc5f --- /dev/null +++ b/aggregator/util/common/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "aggregator-common" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ckb-hash = { path = "../../../util/hash", version = "= 0.116.1" } +ckb-types = { path = "../../../util/types", version = "= 0.116.1" } + +secp256k1 = { version = "0.24", features = ["recovery"] } +thiserror = "1.0" \ No newline at end of file diff --git a/branch-chain-aggregator/src/error.rs b/aggregator/util/common/src/error.rs similarity index 100% rename from branch-chain-aggregator/src/error.rs rename to aggregator/util/common/src/error.rs diff --git a/aggregator/util/common/src/lib.rs b/aggregator/util/common/src/lib.rs new file mode 100644 index 0000000000..cb45d0e1e5 --- /dev/null +++ b/aggregator/util/common/src/lib.rs @@ -0,0 +1,5 @@ +#![allow(missing_docs)] + +pub mod error; +pub mod types; +pub mod utils; diff --git a/aggregator/util/common/src/types.rs b/aggregator/util/common/src/types.rs new file mode 100644 index 0000000000..1349106f7c --- /dev/null +++ b/aggregator/util/common/src/types.rs @@ -0,0 +1,5 @@ +pub enum RequestType { + CkbToBranch = 1, + BranchToCkb = 2, + BranchToBranch = 3, +} diff --git a/branch-chain-aggregator/src/utils/mod.rs b/aggregator/util/common/src/utils/mod.rs similarity index 94% rename from branch-chain-aggregator/src/utils/mod.rs rename to aggregator/util/common/src/utils/mod.rs index 6088894b3c..4bbec7875b 100644 --- a/branch-chain-aggregator/src/utils/mod.rs +++ b/aggregator/util/common/src/utils/mod.rs @@ -1,6 +1,4 @@ -mod key; - -pub(crate) use key::*; +pub mod privkey; pub const SECP256K1: &str = "secp256k1_blake160"; pub const XUDT: &str = "xudt"; diff --git a/branch-chain-aggregator/src/utils/key.rs b/aggregator/util/common/src/utils/privkey.rs similarity index 63% rename from branch-chain-aggregator/src/utils/key.rs rename to aggregator/util/common/src/utils/privkey.rs index 8ffe27fa30..ed4feb3cd2 100644 --- a/branch-chain-aggregator/src/utils/key.rs +++ b/aggregator/util/common/src/utils/privkey.rs @@ -1,27 +1,20 @@ -//! Utility functions for branch-chain-aggregator +//! Utility functions for aggregator use crate::error::Error; use ckb_hash::blake2b_256; -use ckb_types::{ - bytes::Bytes, - core::ScriptHashType, - h256, - packed::Script, - prelude::{Builder, Entity, Pack}, - H256, -}; +use ckb_types::{bytes::Bytes, h256, H256}; use std::fs::File; use std::io::Read; use std::path::PathBuf; -const SIGHASH_TYPE_HASH: H256 = +pub const SIGHASH_TYPE_HASH: H256 = h256!("0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"); -pub(crate) fn get_sighash_script_from_privkey( +pub fn get_sighash_lock_args_from_privkey( key_path: PathBuf, -) -> Result<(Script, secp256k1::SecretKey), Error> { +) -> Result<(Bytes, secp256k1::SecretKey), Error> { let pk = parse_file_to_h256(key_path).map_err(|e| Error::BinaryFileReadError(e.to_string()))?; let secret_key = secp256k1::SecretKey::from_slice(&pk).expect("impossible: fail to build secret key"); @@ -31,15 +24,10 @@ pub(crate) fn get_sighash_script_from_privkey( let pubkey_hash = blake2b_256(pubkey_compressed); let pubkey_hash = &pubkey_hash[0..20]; let args = Bytes::from(pubkey_hash.to_vec()); - let script = Script::new_builder() - .code_hash(SIGHASH_TYPE_HASH.pack()) - .hash_type(ScriptHashType::Type.into()) - .args(args.pack()) - .build(); - Ok((script, secret_key)) + Ok((args, secret_key)) } -fn parse_file_to_h256(path: PathBuf) -> Result, Error> { +pub fn parse_file_to_h256(path: PathBuf) -> Result, Error> { let mut file = File::open(path).map_err(|e| Error::BinaryFileReadError(e.to_string()))?; let mut data = vec![]; file.read_to_end(&mut data) diff --git a/aggregator/util/rgbpp-tx/Cargo.toml b/aggregator/util/rgbpp-tx/Cargo.toml new file mode 100644 index 0000000000..7a3b06b697 --- /dev/null +++ b/aggregator/util/rgbpp-tx/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "aggregator-rgbpp-tx" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +aggregator-common = { path = "../common", version = "0.1.0" } +ckb-app-config = { path = "../../../util/app-config", version = "= 0.116.1" } +ckb-hash = { path = "../../../util/hash", version = "= 0.116.1" } +ckb-stop-handler = { path = "../../../util/stop-handler", version = "= 0.116.1" } + +ckb-sdk = "=3.2.0" +ckb-gen-types = { version = "=0.116.1", default-features = false } +ckb-logger = "=0.116.1" +ckb-jsonrpc-types = "=0.116.1" +ckb-types = "=0.116.1" + +molecule = { version = "0.7.5", default-features = false } +secp256k1 = { version = "0.24", features = ["recovery"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/branch-chain-aggregator/src/transaction/clear.rs b/aggregator/util/rgbpp-tx/src/clear.rs similarity index 85% rename from branch-chain-aggregator/src/transaction/clear.rs rename to aggregator/util/rgbpp-tx/src/clear.rs index 1f032a5f35..5435c21e73 100644 --- a/branch-chain-aggregator/src/transaction/clear.rs +++ b/aggregator/util/rgbpp-tx/src/clear.rs @@ -1,8 +1,9 @@ -use crate::error::Error; -use crate::schemas::leap::Request; -use crate::utils::{get_sighash_script_from_privkey, QUEUE_TYPE, SECP256K1}; -use crate::Aggregator; +use crate::RgbppTxBuilder; +use aggregator_common::{ + error::Error, + utils::{privkey::get_sighash_lock_args_from_privkey, QUEUE_TYPE, SECP256K1}, +}; use ckb_jsonrpc_types::TransactionView; use ckb_logger::{debug, info}; use ckb_sdk::{ @@ -18,7 +19,9 @@ use ckb_sdk::{ ScriptGroup, }; use ckb_types::{ - packed::{Byte32, Bytes as PackedBytes, CellInput, OutPoint, Script}, + core::ScriptHashType, + h256, + packed::{Byte32, Bytes as PackedBytes, CellInput, Script}, prelude::*, H256, }; @@ -26,19 +29,20 @@ use molecule::prelude::Entity; use std::collections::HashMap; -impl Aggregator { - pub(crate) fn create_clear_queue_tx( - &self, - rgbpp_queue_cells: Vec, - _queue_cell: OutPoint, - ) -> Result { - if rgbpp_queue_cells.is_empty() { - info!("no queue requests to clear"); - return Ok(H256::default()); - } +pub const SIGHASH_TYPE_HASH: H256 = + h256!("0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"); +impl RgbppTxBuilder { + pub fn create_clear_queue_tx(&self) -> Result { // get queue cell let (queue_cell, queue_cell_data) = self.get_rgbpp_queue_cell()?; + info!( + "The queue contains {} items that need to be cleared.", + queue_cell_data.outbox().len() + ); + if queue_cell_data.outbox().is_empty() { + return Ok(H256::default()); + } // build new queue let request_ids = vec![]; @@ -99,8 +103,7 @@ impl Aggregator { } // balance transaction - let network_info = - NetworkInfo::new(NetworkType::Testnet, self.config.rgbpp_uri.clone()); + let network_info = NetworkInfo::new(NetworkType::Testnet, self.rgbpp_uri.clone()); let fee_rate = self.fee_rate()?; let configuration = { let mut config = @@ -109,8 +112,13 @@ impl Aggregator { config.fee_rate = fee_rate; config }; - let (capacity_provider_script, capacity_provider_key) = - get_sighash_script_from_privkey(self.config.rgbpp_ckb_provider_key_path.clone())?; + let (capacity_provider_script_args, capacity_provider_key) = + get_sighash_lock_args_from_privkey(self.rgbpp_ckb_provider_key_path.clone())?; + let capacity_provider_script = Script::new_builder() + .code_hash(SIGHASH_TYPE_HASH.pack()) + .hash_type(ScriptHashType::Type.into()) + .args(capacity_provider_script_args.pack()) + .build(); let mut change_builder = DefaultChangeBuilder::new( &configuration, capacity_provider_script.clone(), @@ -183,7 +191,7 @@ impl Aggregator { // sign let (_, message_queue_key) = - get_sighash_script_from_privkey(self.config.rgbpp_queue_lock_key_path.clone())?; + get_sighash_lock_args_from_privkey(self.rgbpp_queue_lock_key_path.clone())?; TransactionSigner::new(&network_info) .sign_transaction( &mut tx_with_groups, diff --git a/branch-chain-aggregator/src/transaction/custodian.rs b/aggregator/util/rgbpp-tx/src/custodian.rs similarity index 55% rename from branch-chain-aggregator/src/transaction/custodian.rs rename to aggregator/util/rgbpp-tx/src/custodian.rs index 8a01d76a40..250c1bc93a 100644 --- a/branch-chain-aggregator/src/transaction/custodian.rs +++ b/aggregator/util/rgbpp-tx/src/custodian.rs @@ -1,16 +1,23 @@ -use crate::schemas::leap::{Message, MessageUnion, Request, RequestContent, Requests, Transfer}; -use crate::Aggregator; -use crate::Error; -use crate::{ - encode_udt_amount, get_sighash_script_from_privkey, RequestType, QUEUE_TYPE, REQUEST_LOCK, - SECP256K1, XUDT, +use crate::schemas::leap::{ + Message, MessageUnion, Request, RequestContent, RequestLockArgs, Requests, Transfer, }; +use crate::{RgbppTxBuilder, CONFIRMATION_THRESHOLD, SIGHASH_TYPE_HASH}; +use aggregator_common::{ + error::Error, + types::RequestType, + utils::{ + decode_udt_amount, encode_udt_amount, privkey::get_sighash_lock_args_from_privkey, + QUEUE_TYPE, REQUEST_LOCK, SECP256K1, XUDT, + }, +}; use ckb_jsonrpc_types::TransactionView; use ckb_logger::{debug, info}; use ckb_sdk::{ core::TransactionBuilder, - rpc::ckb_indexer::Cell, + rpc::ckb_indexer::{Cell, Order}, + rpc::CkbRpcClient as RpcClient, + traits::{CellQueryOptions, LiveCell}, transaction::{ builder::{ChangeBuilder, DefaultChangeBuilder}, handler::HandlerContexts, @@ -19,10 +26,12 @@ use ckb_sdk::{ TransactionBuilderConfiguration, }, types::{NetworkInfo, NetworkType, TransactionWithScriptGroups}, - ScriptGroup, + ScriptGroup, Since, SinceType, }; +use ckb_stop_handler::{new_tokio_exit_rx, CancellationToken}; use ckb_types::{ bytes::Bytes, + core::ScriptHashType, packed::{Byte32, Bytes as PackedBytes, CellInput, CellOutput, Script, WitnessArgs}, prelude::*, H256, @@ -30,12 +39,68 @@ use ckb_types::{ use molecule::prelude::Byte; use std::collections::HashMap; +use std::thread::sleep; +use std::time::Duration; + +impl RgbppTxBuilder { + pub fn collect_rgbpp_request(&self) -> Result<(), Error> { + info!("Scan RGB++ Request ..."); + + let stop: CancellationToken = new_tokio_exit_rx(); + + let request_search_option = self.build_request_cell_search_option()?; + let mut cursor = None; + let limit = 10; + + loop { + if stop.is_cancelled() { + info!("Aggregator scan_rgbpp_request received exit signal, exiting now"); + return Ok(()); + } + + let request_cells = self + .rgbpp_rpc_client + .get_cells( + request_search_option.clone().into(), + Order::Asc, + limit.into(), + cursor, + ) + .map_err(|e| Error::LiveCellNotFound(e.to_string()))?; + + if request_cells.objects.is_empty() { + info!("No more request cells found"); + break; + } + cursor = Some(request_cells.last_cursor); -impl Aggregator { - pub(crate) fn create_custodian_tx( - &self, - request_cells: Vec<(Cell, Transfer)>, - ) -> Result { + info!("Found {} request cells", request_cells.objects.len()); + let tip = self + .rgbpp_rpc_client + .get_tip_block_number() + .map_err(|e| Error::RpcError(format!("get tip block number error: {}", e)))? + .value(); + let cells_with_messge = self.check_request(request_cells.objects.clone(), tip); + info!("Found {} valid request cells", cells_with_messge.len()); + if cells_with_messge.is_empty() { + break; + } + + let custodian_tx = self.create_custodian_tx(cells_with_messge)?; + match wait_for_tx_confirmation( + self.rgbpp_rpc_client.clone(), + custodian_tx, + Duration::from_secs(15), + ) { + Ok(()) => info!("Transaction confirmed"), + Err(e) => info!("{}", e.to_string()), + } + } + + Ok(()) + } + + pub fn create_custodian_tx(&self, request_cells: Vec<(Cell, Transfer)>) -> Result { // get queue cell let (queue_cell, queue_cell_data) = self.get_rgbpp_queue_cell()?; @@ -73,8 +138,13 @@ impl Aggregator { let queue_witness = Requests::new_builder().set(requests).build(); // build custodian lock - let (custodian_lock, _) = - get_sighash_script_from_privkey(self.config.rgbpp_custodian_lock_key_path.clone())?; + let (custodian_lock_args, _) = + get_sighash_lock_args_from_privkey(self.rgbpp_custodian_lock_key_path.clone())?; + let custodian_lock = Script::new_builder() + .code_hash(SIGHASH_TYPE_HASH.pack()) + .hash_type(ScriptHashType::Type.into()) + .args(custodian_lock_args.pack()) + .build(); // build inputs let inputs: Vec = std::iter::once(queue_cell.out_point.clone()) @@ -157,7 +227,7 @@ impl Aggregator { } // balance transaction - let network_info = NetworkInfo::new(NetworkType::Testnet, self.config.rgbpp_uri.clone()); + let network_info = NetworkInfo::new(NetworkType::Testnet, self.rgbpp_uri.clone()); let fee_rate = self.fee_rate()?; let configuration = { let mut config = @@ -166,8 +236,14 @@ impl Aggregator { config.fee_rate = fee_rate; config }; - let (capacity_provider_script, capacity_provider_key) = - get_sighash_script_from_privkey(self.config.rgbpp_ckb_provider_key_path.clone())?; + let (capacity_provider_script_args, capacity_provider_key) = + get_sighash_lock_args_from_privkey(self.rgbpp_ckb_provider_key_path.clone())?; + let capacity_provider_script = Script::new_builder() + .code_hash(SIGHASH_TYPE_HASH.pack()) + .hash_type(ScriptHashType::Type.into()) + .args(capacity_provider_script_args.pack()) + .build(); + let mut change_builder = DefaultChangeBuilder::new(&configuration, capacity_provider_script.clone(), Vec::new()); change_builder.init(&mut tx_builder); @@ -242,7 +318,7 @@ impl Aggregator { // sign let (_, message_queue_key) = - get_sighash_script_from_privkey(self.config.rgbpp_queue_lock_key_path.clone())?; + get_sighash_lock_args_from_privkey(self.rgbpp_queue_lock_key_path.clone())?; TransactionSigner::new(&network_info) .sign_transaction( &mut tx_with_groups, @@ -264,4 +340,105 @@ impl Aggregator { Ok(tx_hash) } + + fn build_request_cell_search_option(&self) -> Result { + let request_script = self.get_rgbpp_script(REQUEST_LOCK)?; + Ok(CellQueryOptions::new_lock(request_script)) + } + + fn check_request(&self, cells: Vec, tip: u64) -> Vec<(Cell, Transfer)> { + cells + .into_iter() + .filter_map(|cell| { + let live_cell: LiveCell = cell.clone().into(); + RequestLockArgs::from_slice(&live_cell.output.lock().args().raw_data()) + .ok() + .and_then(|args| { + let target_request_type_hash = args.request_type_hash(); + info!("target_request_type_hash: {:?}", target_request_type_hash); + + let timeout: u64 = args.timeout().unpack(); + let since = Since::from_raw_value(timeout); + let since_check = + since.extract_metric().map_or(false, |(since_type, value)| { + match since_type { + SinceType::BlockNumber => { + let threshold = if since.is_absolute() { + value + } else { + cell.block_number.value() + value + }; + tip + CONFIRMATION_THRESHOLD < threshold + } + _ => false, + } + }); + + let content = args.content(); + let target_chain_id: Bytes = content.target_chain_id().raw_data(); + info!("target_chain_id: {:?}", target_chain_id); + let request_type = content.request_type(); + + let (check_message, transfer) = { + let message = content.message(); + let message_union = message.to_enum(); + match message_union { + MessageUnion::Transfer(transfer) => { + let transfer_amount: u128 = transfer.amount().unpack(); + let check_message = cell + .clone() + .output_data + .and_then(|data| decode_udt_amount(data.as_bytes())) + .map_or(false, |amount| { + info!( + "original amount: {:?}, transfer amount: {:?}", + amount, transfer_amount + ); + transfer_amount <= amount + }); + (check_message, transfer) + } + } + }; + + let request_type_hash = self + .rgbpp_scripts + .get(QUEUE_TYPE) + .map(|script_info| script_info.script.calc_script_hash()); + + if Some(target_request_type_hash) == request_type_hash + && self.chain_id.clone() == target_chain_id + && request_type == Byte::new(RequestType::CkbToBranch as u8) + && check_message + && since_check + { + Some((cell, transfer)) + } else { + None + } + }) + }) + .collect() + } +} + +fn wait_for_tx_confirmation( + _client: RpcClient, + _tx_hash: H256, + timeout: Duration, +) -> Result<(), Error> { + let start = std::time::Instant::now(); + + loop { + if true { + sleep(Duration::from_secs(8)); + return Ok(()); + } + + if start.elapsed() > timeout { + return Err(Error::TimedOut( + "Transaction confirmation timed out".to_string(), + )); + } + } } diff --git a/aggregator/util/rgbpp-tx/src/lib.rs b/aggregator/util/rgbpp-tx/src/lib.rs new file mode 100644 index 0000000000..fa8a274967 --- /dev/null +++ b/aggregator/util/rgbpp-tx/src/lib.rs @@ -0,0 +1,254 @@ +#![allow(missing_docs)] + +mod clear; +mod custodian; +mod schemas; + +pub use crate::schemas::leap::{self, CrossChainQueue, Request, Requests}; + +use aggregator_common::{ + error::Error, + utils::{privkey::get_sighash_lock_args_from_privkey, QUEUE_TYPE}, +}; +use ckb_app_config::ScriptConfig; +use ckb_logger::{info, warn}; +use ckb_sdk::{ + rpc::ckb_indexer::{Cell, Order}, + rpc::CkbRpcClient as RpcClient, + rpc::ResponseFormatGetter, + traits::{CellQueryOptions, LiveCell, MaturityOption, PrimaryScriptType, QueryOrder}, +}; +use ckb_types::{ + core::{FeeRate, ScriptHashType}, + h256, + packed::{Byte32, Bytes as PackedBytes, CellDep, OutPoint, Script, Transaction, WitnessArgs}, + prelude::*, + H256, +}; + +use std::collections::{HashMap, HashSet}; +use std::path::PathBuf; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ScriptInfo { + pub script: Script, + pub cell_dep: CellDep, +} + +/// Sighash type hash +pub const SIGHASH_TYPE_HASH: H256 = + h256!("0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"); +/// Confirmation threshold +const CONFIRMATION_THRESHOLD: u64 = 24; +/// CKB fee rate limit +const CKB_FEE_RATE_LIMIT: u64 = 5000; + +#[derive(Clone)] +pub struct RgbppTxBuilder { + chain_id: String, + rgbpp_uri: String, + rgbpp_rpc_client: RpcClient, + rgbpp_scripts: HashMap, + rgbpp_custodian_lock_key_path: PathBuf, + rgbpp_queue_lock_key_path: PathBuf, + rgbpp_ckb_provider_key_path: PathBuf, +} + +impl RgbppTxBuilder { + pub fn new( + chain_id: String, + rgbpp_uri: String, + rgbpp_script_config: Vec, + rgbpp_custodian_lock_key_path: PathBuf, + rgbpp_queue_lock_key_path: PathBuf, + rgbpp_ckb_provider_key_path: PathBuf, + ) -> Self { + let rgbpp_rpc_client = RpcClient::new(&rgbpp_uri); + Self { + chain_id, + rgbpp_uri, + rgbpp_rpc_client, + rgbpp_scripts: get_script_map(rgbpp_script_config), + rgbpp_custodian_lock_key_path, + rgbpp_queue_lock_key_path, + rgbpp_ckb_provider_key_path, + } + } + + fn fee_rate(&self) -> Result { + let value = { + let dynamic = self + .rgbpp_rpc_client + .get_fee_rate_statistics(None) + .map_err(|e| Error::RpcError(format!("get dynamic fee rate error: {}", e)))? + .ok_or_else(|| Error::RpcError("get dynamic fee rate error: None".to_string())) + .map(|resp| resp.median) + .map(Into::into) + .map_err(|e| Error::RpcError(format!("get dynamic fee rate error: {}", e)))?; + info!("CKB fee rate: {} (dynamic)", FeeRate(dynamic)); + if dynamic > CKB_FEE_RATE_LIMIT { + warn!( + "dynamic CKB fee rate {} is too large, it seems unreasonable;\ + so the upper limit {} will be used", + FeeRate(dynamic), + FeeRate(CKB_FEE_RATE_LIMIT) + ); + CKB_FEE_RATE_LIMIT + } else { + dynamic + } + }; + Ok(value) + } + + pub(crate) fn get_rgbpp_queue_cell(&self) -> Result<(Cell, CrossChainQueue), Error> { + info!("Scan RGB++ Message Queue ..."); + + let queue_cell_search_option = self.build_message_queue_cell_search_option()?; + let queue_cell = self + .rgbpp_rpc_client + .get_cells(queue_cell_search_option.into(), Order::Asc, 1.into(), None) + .map_err(|e| Error::LiveCellNotFound(e.to_string()))?; + if queue_cell.objects.len() != 1 { + return Err(Error::LiveCellNotFound(format!( + "Queue cell found: {}", + queue_cell.objects.len() + ))); + } + info!("Found {} queue cell", queue_cell.objects.len()); + let queue_cell = queue_cell.objects[0].clone(); + + let queue_live_cell: LiveCell = queue_cell.clone().into(); + let queue_data = queue_live_cell.output_data; + let queue = CrossChainQueue::from_slice(&queue_data) + .map_err(|e| Error::QueueCellDataDecodeError(e.to_string()))?; + + Ok((queue_cell, queue)) + } + + pub fn get_rgbpp_queue_requests(&self) -> Result<(Vec, OutPoint), Error> { + let (queue_cell, queue_cell_data) = self.get_rgbpp_queue_cell()?; + if queue_cell_data.outbox().is_empty() { + info!("No requests in queue"); + return Ok((vec![], OutPoint::default())); + } + let request_ids: Vec = queue_cell_data.outbox().into_iter().collect(); + + let queue_out_point = queue_cell.out_point.clone(); + let (_, witness_input_type) = + self.get_tx_witness_input_type(queue_cell.out_point, self.rgbpp_rpc_client.clone())?; + let requests = Requests::from_slice(&witness_input_type.raw_data()).map_err(|e| { + Error::TransactionParseError(format!("get requests from witness error: {}", e)) + })?; + info!("Found {} requests in witness", requests.len()); + + // check requests + let request_set: HashSet = requests + .clone() + .into_iter() + .map(|request| request.as_bytes().pack().calc_raw_data_hash()) + .collect(); + let all_ids_present = request_ids.iter().all(|id| request_set.contains(id)); + if all_ids_present { + Ok((requests.into_iter().collect(), queue_out_point.into())) + } else { + Err(Error::QueueCellDataError( + "Request IDs in queue cell data do not match witness".to_string(), + )) + } + } + + fn get_tx_witness_input_type( + &self, + out_point: ckb_jsonrpc_types::OutPoint, + rpc_client: RpcClient, + ) -> Result<(H256, PackedBytes), Error> { + let tx_hash = out_point.tx_hash; + let index: u32 = out_point.index.into(); + let tx = rpc_client + .get_transaction(tx_hash.clone()) + .map_err(|e| Error::RpcError(format!("get transaction error: {}", e)))? + .ok_or(Error::RpcError("get transaction error: None".to_string()))? + .transaction + .ok_or(Error::RpcError("get transaction error: None".to_string()))? + .get_value() + .map_err(|e| Error::RpcError(format!("get transaction error: {}", e)))? + .inner; + let tx: Transaction = tx.into(); + let witness = tx + .witnesses() + .get(index as usize) + .ok_or(Error::TransactionParseError( + "get witness error: None".to_string(), + ))?; + let witness_input_type = WitnessArgs::from_slice(&witness.raw_data()) + .map_err(|e| Error::TransactionParseError(format!("get witness error: {}", e)))? + .input_type() + .to_opt() + .ok_or(Error::TransactionParseError( + "get witness input type error: None".to_string(), + ))?; + Ok((tx.calc_tx_hash().unpack(), witness_input_type)) + } + + pub(crate) fn get_rgbpp_cell_dep(&self, script_name: &str) -> Result { + self.rgbpp_scripts + .get(script_name) + .map(|script_info| script_info.cell_dep.clone()) + .ok_or_else(|| Error::MissingScriptInfo(script_name.to_string())) + } + + fn get_rgbpp_script(&self, script_name: &str) -> Result { + self.rgbpp_scripts + .get(script_name) + .map(|script_info| script_info.script.clone()) + .ok_or_else(|| Error::MissingScriptInfo(script_name.to_string())) + } + + fn build_message_queue_cell_search_option(&self) -> Result { + let message_queue_type = self.get_rgbpp_script(QUEUE_TYPE)?; + let (message_queue_lock_args, _) = + get_sighash_lock_args_from_privkey(self.rgbpp_queue_lock_key_path.clone())?; + let message_queue_lock = Script::new_builder() + .code_hash(SIGHASH_TYPE_HASH.pack()) + .hash_type(ScriptHashType::Type.into()) + .args(message_queue_lock_args.pack()) + .build(); + + let cell_query_option = CellQueryOptions { + primary_script: message_queue_type, + primary_type: PrimaryScriptType::Type, + with_data: Some(true), + secondary_script: Some(message_queue_lock), + secondary_script_len_range: None, + data_len_range: None, + capacity_range: None, + block_range: None, + order: QueryOrder::Asc, + limit: Some(1), + maturity: MaturityOption::Mature, + min_total_capacity: 1, + script_search_mode: None, + }; + Ok(cell_query_option) + } +} + +fn get_script_map(scripts: Vec) -> HashMap { + scripts + .iter() + .map(|s| { + ( + s.script_name.clone(), + ScriptInfo { + script: serde_json::from_str::(&s.script) + .expect("config string to script") + .into(), + cell_dep: serde_json::from_str::(&s.cell_dep) + .expect("config string to cell dep") + .into(), + }, + ) + }) + .collect() +} diff --git a/aggregator/util/rgbpp-tx/src/schemas/leap.rs b/aggregator/util/rgbpp-tx/src/schemas/leap.rs new file mode 100644 index 0000000000..2f72438277 --- /dev/null +++ b/aggregator/util/rgbpp-tx/src/schemas/leap.rs @@ -0,0 +1,1983 @@ +// Generated by Molecule 0.7.5 + +use super::blockchain::*; +use molecule::prelude::*; +#[derive(Clone)] +pub struct RequestLockArgs(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for RequestLockArgs { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for RequestLockArgs { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for RequestLockArgs { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "request_type_hash", self.request_type_hash())?; + write!(f, ", {}: {}", "owner_lock_hash", self.owner_lock_hash())?; + write!(f, ", {}: {}", "timeout", self.timeout())?; + write!(f, ", {}: {}", "content", self.content())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl ::core::default::Default for RequestLockArgs { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + RequestLockArgs::new_unchecked(v) + } +} +impl RequestLockArgs { + const DEFAULT_VALUE: [u8; 205] = [ + 205, 0, 0, 0, 20, 0, 0, 0, 52, 0, 0, 0, 84, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 113, 0, 0, 0, 20, 0, 0, 0, 21, 0, 0, 0, 25, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + pub const FIELD_COUNT: usize = 4; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn request_type_hash(&self) -> Byte32 { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + Byte32::new_unchecked(self.0.slice(start..end)) + } + pub fn owner_lock_hash(&self) -> Byte32 { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + Byte32::new_unchecked(self.0.slice(start..end)) + } + pub fn timeout(&self) -> Uint64 { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + let end = molecule::unpack_number(&slice[16..]) as usize; + Uint64::new_unchecked(self.0.slice(start..end)) + } + pub fn content(&self) -> RequestContent { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[16..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[20..]) as usize; + RequestContent::new_unchecked(self.0.slice(start..end)) + } else { + RequestContent::new_unchecked(self.0.slice(start..)) + } + } + pub fn as_reader<'r>(&'r self) -> RequestLockArgsReader<'r> { + RequestLockArgsReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for RequestLockArgs { + type Builder = RequestLockArgsBuilder; + const NAME: &'static str = "RequestLockArgs"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + RequestLockArgs(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + RequestLockArgsReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + RequestLockArgsReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder() + .request_type_hash(self.request_type_hash()) + .owner_lock_hash(self.owner_lock_hash()) + .timeout(self.timeout()) + .content(self.content()) + } +} +#[derive(Clone, Copy)] +pub struct RequestLockArgsReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for RequestLockArgsReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for RequestLockArgsReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for RequestLockArgsReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "request_type_hash", self.request_type_hash())?; + write!(f, ", {}: {}", "owner_lock_hash", self.owner_lock_hash())?; + write!(f, ", {}: {}", "timeout", self.timeout())?; + write!(f, ", {}: {}", "content", self.content())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl<'r> RequestLockArgsReader<'r> { + pub const FIELD_COUNT: usize = 4; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn request_type_hash(&self) -> Byte32Reader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + Byte32Reader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn owner_lock_hash(&self) -> Byte32Reader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + Byte32Reader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn timeout(&self) -> Uint64Reader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + let end = molecule::unpack_number(&slice[16..]) as usize; + Uint64Reader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn content(&self) -> RequestContentReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[16..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[20..]) as usize; + RequestContentReader::new_unchecked(&self.as_slice()[start..end]) + } else { + RequestContentReader::new_unchecked(&self.as_slice()[start..]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for RequestLockArgsReader<'r> { + type Entity = RequestLockArgs; + const NAME: &'static str = "RequestLockArgsReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + RequestLockArgsReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let field_count = offset_first / molecule::NUMBER_SIZE - 1; + if field_count < Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + } else if !compatible && field_count > Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + }; + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + Byte32Reader::verify(&slice[offsets[0]..offsets[1]], compatible)?; + Byte32Reader::verify(&slice[offsets[1]..offsets[2]], compatible)?; + Uint64Reader::verify(&slice[offsets[2]..offsets[3]], compatible)?; + RequestContentReader::verify(&slice[offsets[3]..offsets[4]], compatible)?; + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct RequestLockArgsBuilder { + pub(crate) request_type_hash: Byte32, + pub(crate) owner_lock_hash: Byte32, + pub(crate) timeout: Uint64, + pub(crate) content: RequestContent, +} +impl RequestLockArgsBuilder { + pub const FIELD_COUNT: usize = 4; + pub fn request_type_hash(mut self, v: Byte32) -> Self { + self.request_type_hash = v; + self + } + pub fn owner_lock_hash(mut self, v: Byte32) -> Self { + self.owner_lock_hash = v; + self + } + pub fn timeout(mut self, v: Uint64) -> Self { + self.timeout = v; + self + } + pub fn content(mut self, v: RequestContent) -> Self { + self.content = v; + self + } +} +impl molecule::prelude::Builder for RequestLockArgsBuilder { + type Entity = RequestLockArgs; + const NAME: &'static str = "RequestLockArgsBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1) + + self.request_type_hash.as_slice().len() + + self.owner_lock_hash.as_slice().len() + + self.timeout.as_slice().len() + + self.content.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1); + let mut offsets = Vec::with_capacity(Self::FIELD_COUNT); + offsets.push(total_size); + total_size += self.request_type_hash.as_slice().len(); + offsets.push(total_size); + total_size += self.owner_lock_hash.as_slice().len(); + offsets.push(total_size); + total_size += self.timeout.as_slice().len(); + offsets.push(total_size); + total_size += self.content.as_slice().len(); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + writer.write_all(self.request_type_hash.as_slice())?; + writer.write_all(self.owner_lock_hash.as_slice())?; + writer.write_all(self.timeout.as_slice())?; + writer.write_all(self.content.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + RequestLockArgs::new_unchecked(inner.into()) + } +} +#[derive(Clone)] +pub struct CrossChainQueue(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for CrossChainQueue { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for CrossChainQueue { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for CrossChainQueue { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "locked_assets", self.locked_assets())?; + write!(f, ", {}: {}", "outbox", self.outbox())?; + write!(f, ", {}: {}", "inbox", self.inbox())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl ::core::default::Default for CrossChainQueue { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + CrossChainQueue::new_unchecked(v) + } +} +impl CrossChainQueue { + const DEFAULT_VALUE: [u8; 56] = [ + 56, 0, 0, 0, 16, 0, 0, 0, 48, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + pub const FIELD_COUNT: usize = 3; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn locked_assets(&self) -> Byte32 { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + Byte32::new_unchecked(self.0.slice(start..end)) + } + pub fn outbox(&self) -> Byte32Vec { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + Byte32Vec::new_unchecked(self.0.slice(start..end)) + } + pub fn inbox(&self) -> Byte32Vec { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[16..]) as usize; + Byte32Vec::new_unchecked(self.0.slice(start..end)) + } else { + Byte32Vec::new_unchecked(self.0.slice(start..)) + } + } + pub fn as_reader<'r>(&'r self) -> CrossChainQueueReader<'r> { + CrossChainQueueReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for CrossChainQueue { + type Builder = CrossChainQueueBuilder; + const NAME: &'static str = "CrossChainQueue"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + CrossChainQueue(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + CrossChainQueueReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + CrossChainQueueReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder() + .locked_assets(self.locked_assets()) + .outbox(self.outbox()) + .inbox(self.inbox()) + } +} +#[derive(Clone, Copy)] +pub struct CrossChainQueueReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for CrossChainQueueReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for CrossChainQueueReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for CrossChainQueueReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "locked_assets", self.locked_assets())?; + write!(f, ", {}: {}", "outbox", self.outbox())?; + write!(f, ", {}: {}", "inbox", self.inbox())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl<'r> CrossChainQueueReader<'r> { + pub const FIELD_COUNT: usize = 3; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn locked_assets(&self) -> Byte32Reader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + Byte32Reader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn outbox(&self) -> Byte32VecReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + Byte32VecReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn inbox(&self) -> Byte32VecReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[16..]) as usize; + Byte32VecReader::new_unchecked(&self.as_slice()[start..end]) + } else { + Byte32VecReader::new_unchecked(&self.as_slice()[start..]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for CrossChainQueueReader<'r> { + type Entity = CrossChainQueue; + const NAME: &'static str = "CrossChainQueueReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + CrossChainQueueReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let field_count = offset_first / molecule::NUMBER_SIZE - 1; + if field_count < Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + } else if !compatible && field_count > Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + }; + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + Byte32Reader::verify(&slice[offsets[0]..offsets[1]], compatible)?; + Byte32VecReader::verify(&slice[offsets[1]..offsets[2]], compatible)?; + Byte32VecReader::verify(&slice[offsets[2]..offsets[3]], compatible)?; + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct CrossChainQueueBuilder { + pub(crate) locked_assets: Byte32, + pub(crate) outbox: Byte32Vec, + pub(crate) inbox: Byte32Vec, +} +impl CrossChainQueueBuilder { + pub const FIELD_COUNT: usize = 3; + pub fn locked_assets(mut self, v: Byte32) -> Self { + self.locked_assets = v; + self + } + pub fn outbox(mut self, v: Byte32Vec) -> Self { + self.outbox = v; + self + } + pub fn inbox(mut self, v: Byte32Vec) -> Self { + self.inbox = v; + self + } +} +impl molecule::prelude::Builder for CrossChainQueueBuilder { + type Entity = CrossChainQueue; + const NAME: &'static str = "CrossChainQueueBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1) + + self.locked_assets.as_slice().len() + + self.outbox.as_slice().len() + + self.inbox.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1); + let mut offsets = Vec::with_capacity(Self::FIELD_COUNT); + offsets.push(total_size); + total_size += self.locked_assets.as_slice().len(); + offsets.push(total_size); + total_size += self.outbox.as_slice().len(); + offsets.push(total_size); + total_size += self.inbox.as_slice().len(); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + writer.write_all(self.locked_assets.as_slice())?; + writer.write_all(self.outbox.as_slice())?; + writer.write_all(self.inbox.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + CrossChainQueue::new_unchecked(inner.into()) + } +} +#[derive(Clone)] +pub struct Requests(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for Requests { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for Requests { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for Requests { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} [", Self::NAME)?; + for i in 0..self.len() { + if i == 0 { + write!(f, "{}", self.get_unchecked(i))?; + } else { + write!(f, ", {}", self.get_unchecked(i))?; + } + } + write!(f, "]") + } +} +impl ::core::default::Default for Requests { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + Requests::new_unchecked(v) + } +} +impl Requests { + const DEFAULT_VALUE: [u8; 4] = [4, 0, 0, 0]; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn item_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> Request { + let slice = self.as_slice(); + let start_idx = molecule::NUMBER_SIZE * (1 + idx); + let start = molecule::unpack_number(&slice[start_idx..]) as usize; + if idx == self.len() - 1 { + Request::new_unchecked(self.0.slice(start..)) + } else { + let end_idx = start_idx + molecule::NUMBER_SIZE; + let end = molecule::unpack_number(&slice[end_idx..]) as usize; + Request::new_unchecked(self.0.slice(start..end)) + } + } + pub fn as_reader<'r>(&'r self) -> RequestsReader<'r> { + RequestsReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for Requests { + type Builder = RequestsBuilder; + const NAME: &'static str = "Requests"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + Requests(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + RequestsReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + RequestsReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().extend(self.into_iter()) + } +} +#[derive(Clone, Copy)] +pub struct RequestsReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for RequestsReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for RequestsReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for RequestsReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} [", Self::NAME)?; + for i in 0..self.len() { + if i == 0 { + write!(f, "{}", self.get_unchecked(i))?; + } else { + write!(f, ", {}", self.get_unchecked(i))?; + } + } + write!(f, "]") + } +} +impl<'r> RequestsReader<'r> { + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn item_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option> { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> RequestReader<'r> { + let slice = self.as_slice(); + let start_idx = molecule::NUMBER_SIZE * (1 + idx); + let start = molecule::unpack_number(&slice[start_idx..]) as usize; + if idx == self.len() - 1 { + RequestReader::new_unchecked(&self.as_slice()[start..]) + } else { + let end_idx = start_idx + molecule::NUMBER_SIZE; + let end = molecule::unpack_number(&slice[end_idx..]) as usize; + RequestReader::new_unchecked(&self.as_slice()[start..end]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for RequestsReader<'r> { + type Entity = Requests; + const NAME: &'static str = "RequestsReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + RequestsReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len == molecule::NUMBER_SIZE { + return Ok(()); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!( + Self, + TotalSizeNotMatch, + molecule::NUMBER_SIZE * 2, + slice_len + ); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + for pair in offsets.windows(2) { + let start = pair[0]; + let end = pair[1]; + RequestReader::verify(&slice[start..end], compatible)?; + } + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct RequestsBuilder(pub(crate) Vec); +impl RequestsBuilder { + pub fn set(mut self, v: Vec) -> Self { + self.0 = v; + self + } + pub fn push(mut self, v: Request) -> Self { + self.0.push(v); + self + } + pub fn extend>(mut self, iter: T) -> Self { + for elem in iter { + self.0.push(elem); + } + self + } + pub fn replace(&mut self, index: usize, v: Request) -> Option { + self.0 + .get_mut(index) + .map(|item| ::core::mem::replace(item, v)) + } +} +impl molecule::prelude::Builder for RequestsBuilder { + type Entity = Requests; + const NAME: &'static str = "RequestsBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (self.0.len() + 1) + + self + .0 + .iter() + .map(|inner| inner.as_slice().len()) + .sum::() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let item_count = self.0.len(); + if item_count == 0 { + writer.write_all(&molecule::pack_number( + molecule::NUMBER_SIZE as molecule::Number, + ))?; + } else { + let (total_size, offsets) = self.0.iter().fold( + ( + molecule::NUMBER_SIZE * (item_count + 1), + Vec::with_capacity(item_count), + ), + |(start, mut offsets), inner| { + offsets.push(start); + (start + inner.as_slice().len(), offsets) + }, + ); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + for inner in self.0.iter() { + writer.write_all(inner.as_slice())?; + } + } + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + Requests::new_unchecked(inner.into()) + } +} +pub struct RequestsIterator(Requests, usize, usize); +impl ::core::iter::Iterator for RequestsIterator { + type Item = Request; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl ::core::iter::ExactSizeIterator for RequestsIterator { + fn len(&self) -> usize { + self.2 - self.1 + } +} +impl ::core::iter::IntoIterator for Requests { + type Item = Request; + type IntoIter = RequestsIterator; + fn into_iter(self) -> Self::IntoIter { + let len = self.len(); + RequestsIterator(self, 0, len) + } +} +impl<'r> RequestsReader<'r> { + pub fn iter<'t>(&'t self) -> RequestsReaderIterator<'t, 'r> { + RequestsReaderIterator(&self, 0, self.len()) + } +} +pub struct RequestsReaderIterator<'t, 'r>(&'t RequestsReader<'r>, usize, usize); +impl<'t: 'r, 'r> ::core::iter::Iterator for RequestsReaderIterator<'t, 'r> { + type Item = RequestReader<'t>; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl<'t: 'r, 'r> ::core::iter::ExactSizeIterator for RequestsReaderIterator<'t, 'r> { + fn len(&self) -> usize { + self.2 - self.1 + } +} +#[derive(Clone)] +pub struct Request(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for Request { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for Request { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for Request { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "request_cell", self.request_cell())?; + write!(f, ", {}: {}", "request_content", self.request_content())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl ::core::default::Default for Request { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + Request::new_unchecked(v) + } +} +impl Request { + const DEFAULT_VALUE: [u8; 161] = [ + 161, 0, 0, 0, 12, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 113, 0, 0, 0, 20, 0, 0, 0, 21, 0, + 0, 0, 25, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + pub const FIELD_COUNT: usize = 2; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn request_cell(&self) -> OutPoint { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + OutPoint::new_unchecked(self.0.slice(start..end)) + } + pub fn request_content(&self) -> RequestContent { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[12..]) as usize; + RequestContent::new_unchecked(self.0.slice(start..end)) + } else { + RequestContent::new_unchecked(self.0.slice(start..)) + } + } + pub fn as_reader<'r>(&'r self) -> RequestReader<'r> { + RequestReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for Request { + type Builder = RequestBuilder; + const NAME: &'static str = "Request"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + Request(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + RequestReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + RequestReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder() + .request_cell(self.request_cell()) + .request_content(self.request_content()) + } +} +#[derive(Clone, Copy)] +pub struct RequestReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for RequestReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for RequestReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for RequestReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "request_cell", self.request_cell())?; + write!(f, ", {}: {}", "request_content", self.request_content())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl<'r> RequestReader<'r> { + pub const FIELD_COUNT: usize = 2; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn request_cell(&self) -> OutPointReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + OutPointReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn request_content(&self) -> RequestContentReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[12..]) as usize; + RequestContentReader::new_unchecked(&self.as_slice()[start..end]) + } else { + RequestContentReader::new_unchecked(&self.as_slice()[start..]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for RequestReader<'r> { + type Entity = Request; + const NAME: &'static str = "RequestReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + RequestReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let field_count = offset_first / molecule::NUMBER_SIZE - 1; + if field_count < Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + } else if !compatible && field_count > Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + }; + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + OutPointReader::verify(&slice[offsets[0]..offsets[1]], compatible)?; + RequestContentReader::verify(&slice[offsets[1]..offsets[2]], compatible)?; + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct RequestBuilder { + pub(crate) request_cell: OutPoint, + pub(crate) request_content: RequestContent, +} +impl RequestBuilder { + pub const FIELD_COUNT: usize = 2; + pub fn request_cell(mut self, v: OutPoint) -> Self { + self.request_cell = v; + self + } + pub fn request_content(mut self, v: RequestContent) -> Self { + self.request_content = v; + self + } +} +impl molecule::prelude::Builder for RequestBuilder { + type Entity = Request; + const NAME: &'static str = "RequestBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1) + + self.request_cell.as_slice().len() + + self.request_content.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1); + let mut offsets = Vec::with_capacity(Self::FIELD_COUNT); + offsets.push(total_size); + total_size += self.request_cell.as_slice().len(); + offsets.push(total_size); + total_size += self.request_content.as_slice().len(); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + writer.write_all(self.request_cell.as_slice())?; + writer.write_all(self.request_content.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + Request::new_unchecked(inner.into()) + } +} +#[derive(Clone)] +pub struct RequestContent(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for RequestContent { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for RequestContent { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for RequestContent { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "request_type", self.request_type())?; + write!(f, ", {}: {}", "initial_chain_id", self.initial_chain_id())?; + write!(f, ", {}: {}", "target_chain_id", self.target_chain_id())?; + write!(f, ", {}: {}", "message", self.message())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl ::core::default::Default for RequestContent { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + RequestContent::new_unchecked(v) + } +} +impl RequestContent { + const DEFAULT_VALUE: [u8; 113] = [ + 113, 0, 0, 0, 20, 0, 0, 0, 21, 0, 0, 0, 25, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + pub const FIELD_COUNT: usize = 4; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn request_type(&self) -> Byte { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + Byte::new_unchecked(self.0.slice(start..end)) + } + pub fn initial_chain_id(&self) -> Bytes { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + Bytes::new_unchecked(self.0.slice(start..end)) + } + pub fn target_chain_id(&self) -> Bytes { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + let end = molecule::unpack_number(&slice[16..]) as usize; + Bytes::new_unchecked(self.0.slice(start..end)) + } + pub fn message(&self) -> Message { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[16..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[20..]) as usize; + Message::new_unchecked(self.0.slice(start..end)) + } else { + Message::new_unchecked(self.0.slice(start..)) + } + } + pub fn as_reader<'r>(&'r self) -> RequestContentReader<'r> { + RequestContentReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for RequestContent { + type Builder = RequestContentBuilder; + const NAME: &'static str = "RequestContent"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + RequestContent(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + RequestContentReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + RequestContentReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder() + .request_type(self.request_type()) + .initial_chain_id(self.initial_chain_id()) + .target_chain_id(self.target_chain_id()) + .message(self.message()) + } +} +#[derive(Clone, Copy)] +pub struct RequestContentReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for RequestContentReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for RequestContentReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for RequestContentReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "request_type", self.request_type())?; + write!(f, ", {}: {}", "initial_chain_id", self.initial_chain_id())?; + write!(f, ", {}: {}", "target_chain_id", self.target_chain_id())?; + write!(f, ", {}: {}", "message", self.message())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl<'r> RequestContentReader<'r> { + pub const FIELD_COUNT: usize = 4; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn request_type(&self) -> ByteReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + ByteReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn initial_chain_id(&self) -> BytesReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + BytesReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn target_chain_id(&self) -> BytesReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + let end = molecule::unpack_number(&slice[16..]) as usize; + BytesReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn message(&self) -> MessageReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[16..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[20..]) as usize; + MessageReader::new_unchecked(&self.as_slice()[start..end]) + } else { + MessageReader::new_unchecked(&self.as_slice()[start..]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for RequestContentReader<'r> { + type Entity = RequestContent; + const NAME: &'static str = "RequestContentReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + RequestContentReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let field_count = offset_first / molecule::NUMBER_SIZE - 1; + if field_count < Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + } else if !compatible && field_count > Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + }; + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + ByteReader::verify(&slice[offsets[0]..offsets[1]], compatible)?; + BytesReader::verify(&slice[offsets[1]..offsets[2]], compatible)?; + BytesReader::verify(&slice[offsets[2]..offsets[3]], compatible)?; + MessageReader::verify(&slice[offsets[3]..offsets[4]], compatible)?; + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct RequestContentBuilder { + pub(crate) request_type: Byte, + pub(crate) initial_chain_id: Bytes, + pub(crate) target_chain_id: Bytes, + pub(crate) message: Message, +} +impl RequestContentBuilder { + pub const FIELD_COUNT: usize = 4; + pub fn request_type(mut self, v: Byte) -> Self { + self.request_type = v; + self + } + pub fn initial_chain_id(mut self, v: Bytes) -> Self { + self.initial_chain_id = v; + self + } + pub fn target_chain_id(mut self, v: Bytes) -> Self { + self.target_chain_id = v; + self + } + pub fn message(mut self, v: Message) -> Self { + self.message = v; + self + } +} +impl molecule::prelude::Builder for RequestContentBuilder { + type Entity = RequestContent; + const NAME: &'static str = "RequestContentBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1) + + self.request_type.as_slice().len() + + self.initial_chain_id.as_slice().len() + + self.target_chain_id.as_slice().len() + + self.message.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1); + let mut offsets = Vec::with_capacity(Self::FIELD_COUNT); + offsets.push(total_size); + total_size += self.request_type.as_slice().len(); + offsets.push(total_size); + total_size += self.initial_chain_id.as_slice().len(); + offsets.push(total_size); + total_size += self.target_chain_id.as_slice().len(); + offsets.push(total_size); + total_size += self.message.as_slice().len(); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + writer.write_all(self.request_type.as_slice())?; + writer.write_all(self.initial_chain_id.as_slice())?; + writer.write_all(self.target_chain_id.as_slice())?; + writer.write_all(self.message.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + RequestContent::new_unchecked(inner.into()) + } +} +#[derive(Clone)] +pub struct Message(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for Message { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for Message { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for Message { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}(", Self::NAME)?; + self.to_enum().display_inner(f)?; + write!(f, ")") + } +} +impl ::core::default::Default for Message { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + Message::new_unchecked(v) + } +} +impl Message { + const DEFAULT_VALUE: [u8; 84] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + pub const ITEMS_COUNT: usize = 1; + pub fn item_id(&self) -> molecule::Number { + molecule::unpack_number(self.as_slice()) + } + pub fn to_enum(&self) -> MessageUnion { + let inner = self.0.slice(molecule::NUMBER_SIZE..); + match self.item_id() { + 0 => Transfer::new_unchecked(inner).into(), + _ => panic!("{}: invalid data", Self::NAME), + } + } + pub fn as_reader<'r>(&'r self) -> MessageReader<'r> { + MessageReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for Message { + type Builder = MessageBuilder; + const NAME: &'static str = "Message"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + Message(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + MessageReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + MessageReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().set(self.to_enum()) + } +} +#[derive(Clone, Copy)] +pub struct MessageReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for MessageReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for MessageReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for MessageReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}(", Self::NAME)?; + self.to_enum().display_inner(f)?; + write!(f, ")") + } +} +impl<'r> MessageReader<'r> { + pub const ITEMS_COUNT: usize = 1; + pub fn item_id(&self) -> molecule::Number { + molecule::unpack_number(self.as_slice()) + } + pub fn to_enum(&self) -> MessageUnionReader<'r> { + let inner = &self.as_slice()[molecule::NUMBER_SIZE..]; + match self.item_id() { + 0 => TransferReader::new_unchecked(inner).into(), + _ => panic!("{}: invalid data", Self::NAME), + } + } +} +impl<'r> molecule::prelude::Reader<'r> for MessageReader<'r> { + type Entity = Message; + const NAME: &'static str = "MessageReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + MessageReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let item_id = molecule::unpack_number(slice); + let inner_slice = &slice[molecule::NUMBER_SIZE..]; + match item_id { + 0 => TransferReader::verify(inner_slice, compatible), + _ => ve!(Self, UnknownItem, Self::ITEMS_COUNT, item_id), + }?; + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct MessageBuilder(pub(crate) MessageUnion); +impl MessageBuilder { + pub const ITEMS_COUNT: usize = 1; + pub fn set(mut self, v: I) -> Self + where + I: ::core::convert::Into, + { + self.0 = v.into(); + self + } +} +impl molecule::prelude::Builder for MessageBuilder { + type Entity = Message; + const NAME: &'static str = "MessageBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE + self.0.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + writer.write_all(&molecule::pack_number(self.0.item_id()))?; + writer.write_all(self.0.as_slice()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + Message::new_unchecked(inner.into()) + } +} +#[derive(Debug, Clone)] +pub enum MessageUnion { + Transfer(Transfer), +} +#[derive(Debug, Clone, Copy)] +pub enum MessageUnionReader<'r> { + Transfer(TransferReader<'r>), +} +impl ::core::default::Default for MessageUnion { + fn default() -> Self { + MessageUnion::Transfer(::core::default::Default::default()) + } +} +impl ::core::fmt::Display for MessageUnion { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + MessageUnion::Transfer(ref item) => { + write!(f, "{}::{}({})", Self::NAME, Transfer::NAME, item) + } + } + } +} +impl<'r> ::core::fmt::Display for MessageUnionReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + MessageUnionReader::Transfer(ref item) => { + write!(f, "{}::{}({})", Self::NAME, Transfer::NAME, item) + } + } + } +} +impl MessageUnion { + pub(crate) fn display_inner(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + MessageUnion::Transfer(ref item) => write!(f, "{}", item), + } + } +} +impl<'r> MessageUnionReader<'r> { + pub(crate) fn display_inner(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + MessageUnionReader::Transfer(ref item) => write!(f, "{}", item), + } + } +} +impl ::core::convert::From for MessageUnion { + fn from(item: Transfer) -> Self { + MessageUnion::Transfer(item) + } +} +impl<'r> ::core::convert::From> for MessageUnionReader<'r> { + fn from(item: TransferReader<'r>) -> Self { + MessageUnionReader::Transfer(item) + } +} +impl MessageUnion { + pub const NAME: &'static str = "MessageUnion"; + pub fn as_bytes(&self) -> molecule::bytes::Bytes { + match self { + MessageUnion::Transfer(item) => item.as_bytes(), + } + } + pub fn as_slice(&self) -> &[u8] { + match self { + MessageUnion::Transfer(item) => item.as_slice(), + } + } + pub fn item_id(&self) -> molecule::Number { + match self { + MessageUnion::Transfer(_) => 0, + } + } + pub fn item_name(&self) -> &str { + match self { + MessageUnion::Transfer(_) => "Transfer", + } + } + pub fn as_reader<'r>(&'r self) -> MessageUnionReader<'r> { + match self { + MessageUnion::Transfer(item) => item.as_reader().into(), + } + } +} +impl<'r> MessageUnionReader<'r> { + pub const NAME: &'r str = "MessageUnionReader"; + pub fn as_slice(&self) -> &'r [u8] { + match self { + MessageUnionReader::Transfer(item) => item.as_slice(), + } + } + pub fn item_id(&self) -> molecule::Number { + match self { + MessageUnionReader::Transfer(_) => 0, + } + } + pub fn item_name(&self) -> &str { + match self { + MessageUnionReader::Transfer(_) => "Transfer", + } + } +} +#[derive(Clone)] +pub struct Transfer(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for Transfer { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for Transfer { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for Transfer { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "owner_lock_hash", self.owner_lock_hash())?; + write!(f, ", {}: {}", "amount", self.amount())?; + write!(f, ", {}: {}", "asset_type", self.asset_type())?; + write!(f, " }}") + } +} +impl ::core::default::Default for Transfer { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + Transfer::new_unchecked(v) + } +} +impl Transfer { + const DEFAULT_VALUE: [u8; 80] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + pub const TOTAL_SIZE: usize = 80; + pub const FIELD_SIZES: [usize; 3] = [32, 16, 32]; + pub const FIELD_COUNT: usize = 3; + pub fn owner_lock_hash(&self) -> Byte32 { + Byte32::new_unchecked(self.0.slice(0..32)) + } + pub fn amount(&self) -> Uint128 { + Uint128::new_unchecked(self.0.slice(32..48)) + } + pub fn asset_type(&self) -> Byte32 { + Byte32::new_unchecked(self.0.slice(48..80)) + } + pub fn as_reader<'r>(&'r self) -> TransferReader<'r> { + TransferReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for Transfer { + type Builder = TransferBuilder; + const NAME: &'static str = "Transfer"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + Transfer(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + TransferReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + TransferReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder() + .owner_lock_hash(self.owner_lock_hash()) + .amount(self.amount()) + .asset_type(self.asset_type()) + } +} +#[derive(Clone, Copy)] +pub struct TransferReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for TransferReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for TransferReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for TransferReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "owner_lock_hash", self.owner_lock_hash())?; + write!(f, ", {}: {}", "amount", self.amount())?; + write!(f, ", {}: {}", "asset_type", self.asset_type())?; + write!(f, " }}") + } +} +impl<'r> TransferReader<'r> { + pub const TOTAL_SIZE: usize = 80; + pub const FIELD_SIZES: [usize; 3] = [32, 16, 32]; + pub const FIELD_COUNT: usize = 3; + pub fn owner_lock_hash(&self) -> Byte32Reader<'r> { + Byte32Reader::new_unchecked(&self.as_slice()[0..32]) + } + pub fn amount(&self) -> Uint128Reader<'r> { + Uint128Reader::new_unchecked(&self.as_slice()[32..48]) + } + pub fn asset_type(&self) -> Byte32Reader<'r> { + Byte32Reader::new_unchecked(&self.as_slice()[48..80]) + } +} +impl<'r> molecule::prelude::Reader<'r> for TransferReader<'r> { + type Entity = Transfer; + const NAME: &'static str = "TransferReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + TransferReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len != Self::TOTAL_SIZE { + return ve!(Self, TotalSizeNotMatch, Self::TOTAL_SIZE, slice_len); + } + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct TransferBuilder { + pub(crate) owner_lock_hash: Byte32, + pub(crate) amount: Uint128, + pub(crate) asset_type: Byte32, +} +impl TransferBuilder { + pub const TOTAL_SIZE: usize = 80; + pub const FIELD_SIZES: [usize; 3] = [32, 16, 32]; + pub const FIELD_COUNT: usize = 3; + pub fn owner_lock_hash(mut self, v: Byte32) -> Self { + self.owner_lock_hash = v; + self + } + pub fn amount(mut self, v: Uint128) -> Self { + self.amount = v; + self + } + pub fn asset_type(mut self, v: Byte32) -> Self { + self.asset_type = v; + self + } +} +impl molecule::prelude::Builder for TransferBuilder { + type Entity = Transfer; + const NAME: &'static str = "TransferBuilder"; + fn expected_length(&self) -> usize { + Self::TOTAL_SIZE + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + writer.write_all(self.owner_lock_hash.as_slice())?; + writer.write_all(self.amount.as_slice())?; + writer.write_all(self.asset_type.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + Transfer::new_unchecked(inner.into()) + } +} diff --git a/aggregator/util/rgbpp-tx/src/schemas/mod.rs b/aggregator/util/rgbpp-tx/src/schemas/mod.rs new file mode 100644 index 0000000000..c41fda299f --- /dev/null +++ b/aggregator/util/rgbpp-tx/src/schemas/mod.rs @@ -0,0 +1,12 @@ +#![allow(clippy::needless_lifetimes)] +#![allow(clippy::write_literal)] +#![allow(clippy::if_same_then_else)] +#![allow(clippy::needless_borrow)] +#![allow(clippy::derivable_impls)] +#![allow(clippy::useless_conversion)] +#![allow(clippy::redundant_slicing)] +#![allow(clippy::wrong_self_convention)] +#![allow(dead_code)] + +pub mod leap; +pub use ckb_gen_types::packed as blockchain; diff --git a/aggregator/util/rgbpp-tx/tests/send_request.rs b/aggregator/util/rgbpp-tx/tests/send_request.rs new file mode 100644 index 0000000000..56a8ca04ee --- /dev/null +++ b/aggregator/util/rgbpp-tx/tests/send_request.rs @@ -0,0 +1,392 @@ +use aggregator_common::types::RequestType; +use aggregator_rgbpp_tx::{ + leap::{self, MessageUnion, RequestContent, Transfer}, + ScriptInfo, SIGHASH_TYPE_HASH, +}; +use ckb_app_config::ScriptConfig; +use ckb_hash::blake2b_256; +use ckb_jsonrpc_types::TransactionView; +use ckb_sdk::{ + core::TransactionBuilder, + rpc::CkbRpcClient as RpcClient, + traits::LiveCell, + transaction::{ + builder::{ChangeBuilder, DefaultChangeBuilder}, + input::{InputIterator, TransactionInput}, + signer::{SignContexts, TransactionSigner}, + TransactionBuilderConfiguration, + }, + types::{NetworkInfo, NetworkType, TransactionWithScriptGroups}, + ScriptGroup, +}; +use ckb_types::{ + bytes::Bytes, + core::ScriptHashType, + h160, h256, + packed::{ + Byte, Byte32, Bytes as PackedBytes, CellDep, CellInput, CellOutput, OutPoint, Script, + }, + prelude::*, + H256, +}; + +use std::collections::HashMap; + +#[test] +#[ignore] +fn send_request() { + // rgbpp CKB provider + // address: + // mainnet: ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdqculgfkp8hpc88g4quezgjgv8p5g56qs4hh5xp + // testnet: ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdqculgfkp8hpc88g4quezgjgv8p5g56qsm9umve + // address(deprecated): + // mainnet: ckb1qyq2p3e7snvz0wrsww32pejy3yscwrg3f5pqrrkuvg + // testnet: ckt1qyq2p3e7snvz0wrsww32pejy3yscwrg3f5pq7xgrq5 + // lock_arg: 0xa0c73e84d827b87073a2a0e6448921870d114d02 + // lock_hash: 0xdb66c34c8eff03fbeb03a22441d9506cda8c7ae5b431b4fccd5682dca67818a5 + // private key: 6b726167f8d0b2b8722b9a3f0cfdc67d43f1622666a6fda6b32da76a3824e52e + + let ckb_provider_lock_privkey = + h256!("0x6b726167f8d0b2b8722b9a3f0cfdc67d43f1622666a6fda6b32da76a3824e52e"); + let ckb_provider_lock_args = h160!("0xa0c73e84d827b87073a2a0e6448921870d114d02").as_bytes(); + + let rgbpp_uri = "https://testnet.ckb.dev"; + + let message_queue_type_id = + h256!("0xd9911b00409a9f443ae7ed6b00d59dd1e33979e4c986478cf7863fcb7f62941b"); + let request_lock_code_hash = + h256!("0x2fca96b423bd2b4d0d4b5098bf7a3e74ea42c3f2e1bb6f973f7c1c68adfa3d9c"); + let xudt_code_hash = + h256!("0x25c29dc317811a6f6f3985a7a9ebc4838bd388d19d0feeecf0bcd60f6c0975bb"); + + // input + println!("send request"); + let out_point = OutPoint::new( + h256!("0xd87b596d4d24dc6ba7b99318153414b621c96ddee3c88410159f8e75b974f428").pack(), + 0, + ); + let input = CellInput::new_builder() + .previous_output(out_point.clone().clone()) + .build(); + let rgbpp_rpc_client = RpcClient::new(rgbpp_uri); + let input_cell = rgbpp_rpc_client + .get_live_cell(out_point.clone().into(), true) + .unwrap(); + let input_cell = input_cell.cell.unwrap(); + let tx_input = TransactionInput { + live_cell: LiveCell { + output: input_cell.output.clone().into(), + output_data: input_cell.data.unwrap().content.into_bytes(), + out_point: input.previous_output(), + block_number: 14318072, + tx_index: 2, + }, + since: 0, + }; + + let user_lock = Script::new_builder() + .code_hash(SIGHASH_TYPE_HASH.pack()) + .hash_type(ScriptHashType::Type.into()) + .args(Bytes::from(ckb_provider_lock_args).pack()) + .build(); + let token_script = Script::new_builder() + .code_hash(xudt_code_hash.pack()) + .hash_type(ScriptHashType::Type.into()) + .args(user_lock.calc_script_hash().as_bytes().pack()) + .build(); + + // output: create request + let branch_chain_id = Bytes::from(b"mocked-branch-chain-id".to_vec()); + let message = leap::Message::new_builder() + .set(MessageUnion::Transfer( + Transfer::new_builder() + .owner_lock_hash(user_lock.calc_script_hash()) + .amount(1000u128.pack()) + .asset_type(token_script.calc_script_hash()) + .build(), + )) + .build(); + let request_content = RequestContent::new_builder() + .request_type(Byte::new(RequestType::CkbToBranch as u8)) + .target_chain_id(branch_chain_id.pack()) + .message(message) + .build(); + let (request_cell, request_cell_data) = build_request( + request_lock_code_hash.pack(), + message_queue_type_id.pack(), + user_lock.calc_script_hash(), + request_content.clone(), + token_script.clone(), + 1000, + ); + + // cell deps + let scripts = prepare_scripts(); + let secp256k1_cell_dep = get_rgbpp_cell_dep("secp256k1_blake160", &scripts); + let xudt_cell_dep = get_rgbpp_cell_dep("xudt", &scripts); + let request_cell_dep = get_rgbpp_cell_dep("request_lock", &scripts); + let queue_type_cell_dep = get_rgbpp_cell_dep("queue_type", &scripts); + + // create transaction + let mut tx_builder = TransactionBuilder::default(); + tx_builder + .cell_deps(vec![ + secp256k1_cell_dep, + xudt_cell_dep, + request_cell_dep, + queue_type_cell_dep, + ]) + .input(input) + .output(request_cell) + .output_data(request_cell_data.pack()); + + // group + let mut lock_groups: HashMap = HashMap::default(); + let mut type_groups: HashMap = HashMap::default(); + { + lock_groups + .entry(user_lock.calc_script_hash()) + .or_insert_with(|| ScriptGroup::from_lock_script(&user_lock)) + .input_indices + .push(0); + } + for (output_idx, output) in tx_builder.get_outputs().clone().iter().enumerate() { + if let Some(type_script) = &output.type_().to_opt() { + type_groups + .entry(type_script.calc_script_hash()) + .or_insert_with(|| ScriptGroup::from_type_script(type_script)) + .output_indices + .push(output_idx); + } + } + + // balance transaction + let network_info = NetworkInfo::new(NetworkType::Testnet, rgbpp_uri.to_string()); + let fee_rate = 3000; + let configuration = { + let mut config = + TransactionBuilderConfiguration::new_with_network(network_info.clone()).unwrap(); + config.fee_rate = fee_rate; + config + }; + let (capacity_provider_script_args, capacity_provider_key) = + get_sighash_lock_args_from_privkey(ckb_provider_lock_privkey); + let capacity_provider_script = Script::new_builder() + .code_hash(SIGHASH_TYPE_HASH.pack()) + .hash_type(ScriptHashType::Type.into()) + .args(capacity_provider_script_args.pack()) + .build(); + let mut change_builder = + DefaultChangeBuilder::new(&configuration, capacity_provider_script.clone(), Vec::new()); + change_builder.init(&mut tx_builder); + { + let _ = change_builder.check_balance(tx_input, &mut tx_builder); + }; + let iterator = InputIterator::new(vec![user_lock], &network_info); + let mut tx_with_groups = { + let mut check_result = None; + for (mut input_index, input) in iterator.enumerate() { + input_index += 1; + let input = input.unwrap(); + tx_builder.input(input.cell_input()); + tx_builder.witness(PackedBytes::default()); + + let previous_output = input.previous_output(); + let lock_script = previous_output.lock(); + lock_groups + .entry(lock_script.calc_script_hash()) + .or_insert_with(|| ScriptGroup::from_lock_script(&lock_script)) + .input_indices + .push(input_index); + + if change_builder.check_balance(input, &mut tx_builder) { + let script_groups: Vec = lock_groups + .into_values() + .chain(type_groups.into_values()) + .collect(); + + let tx_view = change_builder.finalize(tx_builder); + + check_result = Some(TransactionWithScriptGroups::new(tx_view, script_groups)); + break; + } + } + check_result + } + .unwrap(); + + // sign + TransactionSigner::new(&network_info) + .sign_transaction( + &mut tx_with_groups, + &SignContexts::new_sighash(vec![capacity_provider_key]), + ) + .unwrap(); + + // send tx + let tx_json = TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!( + "request tx: {}", + serde_json::to_string_pretty(&tx_json).unwrap() + ); + let tx_hash = rgbpp_rpc_client + .send_transaction(tx_json.inner, None) + .unwrap(); + println!("request tx send: {:?}", tx_hash.pack()); +} + +fn build_request( + request_lock_code_hash: Byte32, + request_type_hash: Byte32, + owner_lock_hash: Byte32, + content: RequestContent, + token_script: Script, + amount: u128, +) -> (CellOutput, Bytes) { + let lock_args = leap::RequestLockArgs::new_builder() + .request_type_hash(request_type_hash) + .owner_lock_hash(owner_lock_hash) + .content(content) + .build(); + let request_lock = Script::new_builder() + .code_hash(request_lock_code_hash) + .hash_type(ScriptHashType::Type.into()) + .args(lock_args.as_bytes().pack()) + .build(); + let cell = CellOutput::new_builder() + .lock(request_lock) + .type_(Some(token_script).pack()) + .capacity(500_0000_0000.pack()) + .build(); + let buf: [u8; 16] = amount.to_le_bytes(); + let data = buf.to_vec().into(); + (cell, data) +} + +fn prepare_scripts() -> HashMap { + let mut rgbpp_script_config: Vec = Vec::new(); + let xudt_script = r#" + { + "args": "0x", + "code_hash": "0x25c29dc317811a6f6f3985a7a9ebc4838bd388d19d0feeecf0bcd60f6c0975bb", + "hash_type": "type" + } + "#; + let xudt_out_point = r#" + { + "dep_type": "code", + "out_point": { + "index": "0x0", + "tx_hash": "0xbf6fb538763efec2a70a6a3dcb7242787087e1030c4e7d86585bc63a9d337f5f" + } + } + "#; + rgbpp_script_config.push(ScriptConfig { + script_name: "xudt".to_string(), + script: xudt_script.to_string(), + cell_dep: xudt_out_point.to_string(), + }); + let request_lock = r#" + { + "args": "0x", + "code_hash": "0x2fca96b423bd2b4d0d4b5098bf7a3e74ea42c3f2e1bb6f973f7c1c68adfa3d9c", + "hash_type": "type" + } + "#; + let request_out_point = r#" + { + "dep_type": "code", + "out_point": { + "index": "0x0", + "tx_hash": "0x79e7a69cf175cde1d8f4fd1f7f5c9792cf07b4099a4a75946393ac6616b7aa0b" + } + } + "#; + rgbpp_script_config.push(ScriptConfig { + script_name: "request_lock".to_string(), + script: request_lock.to_string(), + cell_dep: request_out_point.to_string(), + }); + let queue_type_script = r#" + { + "args": "0x4242", + "code_hash": "0x2da1e80cec3e553a76e22d826b63ce5f65d77622de48caa5a2fe724b0f9a18f2", + "hash_type": "type" + } + "#; + let queue_type_out_point = r#" + { + "dep_type": "code", + "out_point": { + "index": "0x0", + "tx_hash": "0xeb4614bc1d8b2aadb928758c77a07720f1794418d0257a61bac94240d4c21905" + } + } + "#; + rgbpp_script_config.push(ScriptConfig { + script_name: "queue_type".to_string(), + script: queue_type_script.to_string(), + cell_dep: queue_type_out_point.to_string(), + }); + let secp256k1_blake160_script = r#" + { + "args": "0x", + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type" + } + "#; + let secp256k1_blake160_out_point = r#" + { + "dep_type": "dep_group", + "out_point": { + "index": "0x0", + "tx_hash": "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37" + } + } + "#; + rgbpp_script_config.push(ScriptConfig { + script_name: "secp256k1_blake160".to_string(), + script: secp256k1_blake160_script.to_string(), + cell_dep: secp256k1_blake160_out_point.to_string(), + }); + + get_script_map(rgbpp_script_config) +} + +fn get_rgbpp_cell_dep(script_name: &str, rgbpp_scripts: &HashMap) -> CellDep { + rgbpp_scripts + .get(script_name) + .map(|script_info| script_info.cell_dep.clone()) + .unwrap() +} + +fn get_script_map(scripts: Vec) -> HashMap { + scripts + .iter() + .map(|s| { + ( + s.script_name.clone(), + ScriptInfo { + script: serde_json::from_str::(&s.script) + .expect("config string to script") + .into(), + cell_dep: serde_json::from_str::(&s.cell_dep) + .expect("config string to cell dep") + .into(), + }, + ) + }) + .collect() +} + +pub fn get_sighash_lock_args_from_privkey(key: H256) -> (Bytes, secp256k1::SecretKey) { + let secret_key = secp256k1::SecretKey::from_slice(key.as_bytes()) + .expect("impossible: fail to build secret key"); + let secp256k1: secp256k1::Secp256k1 = secp256k1::Secp256k1::new(); + let pubkey = secp256k1::PublicKey::from_secret_key(&secp256k1, &secret_key); + let pubkey_compressed = &pubkey.serialize()[..]; + let pubkey_hash = blake2b_256(pubkey_compressed); + let pubkey_hash = &pubkey_hash[0..20]; + let args = Bytes::from(pubkey_hash.to_vec()); + (args, secret_key) +} diff --git a/branch-chain-aggregator/Cargo.toml b/branch-chain-aggregator/Cargo.toml deleted file mode 100644 index d7753907c4..0000000000 --- a/branch-chain-aggregator/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ - -[package] -name = "branch-chain-aggregator" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -ckb-app-config = { path = "../util/app-config", version = "= 0.116.1" } -ckb-async-runtime = { path = "../util/runtime", version = "= 0.116.1" } -ckb-channel = { path = "../util/channel", version = "= 0.116.1" } -ckb-logger = { path = "../util/logger", version = "= 0.116.1" } -ckb-stop-handler = { path = "../util/stop-handler", version = "= 0.116.1" } - -ckb-hash = "=0.116.1" -ckb-gen-types = { version = "=0.116.1", default-features = false } -ckb-jsonrpc-types = "=0.116.1" -ckb-types = "=0.116.1" - -ckb-sdk = "=3.2.0" -crossbeam-channel = "0.5.1" -jsonrpc-core = "18.0" -molecule = { version = "0.7.5", default-features = false } -reqwest = { version = "0.12.4", features = ["json"] } -secp256k1 = { version = "0.24", features = ["recovery"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -thiserror = "1.0" - -[dev-dependencies] -hex = "0.4" \ No newline at end of file diff --git a/branch-chain-aggregator/src/lib.rs b/branch-chain-aggregator/src/lib.rs deleted file mode 100644 index 5eb27bf93f..0000000000 --- a/branch-chain-aggregator/src/lib.rs +++ /dev/null @@ -1,551 +0,0 @@ -//! Branch Chain Aggregator - -pub(crate) mod error; -pub(crate) mod schemas; -pub(crate) mod transaction; -pub(crate) mod utils; - -use crate::error::Error; -use crate::schemas::leap::{ - CrossChainQueue, MessageUnion, Request, RequestLockArgs, Requests, Transfer, -}; -use crate::utils::QUEUE_TYPE; -use crate::utils::{ - decode_udt_amount, encode_udt_amount, get_sighash_script_from_privkey, REQUEST_LOCK, SECP256K1, - XUDT, -}; - -use ckb_app_config::{AggregatorConfig, AssetConfig, LockConfig, ScriptConfig}; -use ckb_channel::Receiver; -use ckb_logger::{error, info, warn}; -use ckb_sdk::traits::LiveCell; -use ckb_sdk::{ - rpc::ckb_indexer::{Cell, Order}, - rpc::CkbRpcClient as RpcClient, - traits::{CellQueryOptions, MaturityOption, PrimaryScriptType, QueryOrder}, - Since, SinceType, -}; -use ckb_stop_handler::{new_tokio_exit_rx, CancellationToken}; -use ckb_types::H256; -use ckb_types::{ - bytes::Bytes, - core::FeeRate, - packed::{Byte32, CellDep, OutPoint, Script}, - prelude::*, -}; -use molecule::prelude::Byte; - -use std::collections::{HashMap, HashSet}; -use std::str::FromStr; -use std::thread; -use std::thread::sleep; -use std::time::Duration; - -const CKB_FEE_RATE_LIMIT: u64 = 5000; -const CONFIRMATION_THRESHOLD: u64 = 24; -/// -#[derive(Clone)] -pub struct Aggregator { - chain_id: String, - config: AggregatorConfig, - poll_interval: Duration, - rgbpp_rpc_client: RpcClient, - branch_rpc_client: RpcClient, - rgbpp_scripts: HashMap, - branch_scripts: HashMap, - rgbpp_assets: HashMap, - rgbpp_locks: HashMap, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -struct ScriptInfo { - pub script: Script, - pub cell_dep: CellDep, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -struct AssetInfo { - pub script: Script, - pub is_capacity: bool, - pub script_name: String, -} - -#[allow(dead_code)] -pub(crate) enum RequestType { - CkbToBranch = 1, - BranchToCkb = 2, - BranchToBranch = 3, -} - -impl Aggregator { - /// Create an Aggregator - pub fn new(config: AggregatorConfig, poll_interval: Duration, chain_id: String) -> Self { - let rgbpp_rpc_client = RpcClient::new(&config.rgbpp_uri); - let branch_rpc_client = RpcClient::new(&config.branch_uri); - Aggregator { - chain_id, - config: config.clone(), - poll_interval, - rgbpp_rpc_client, - branch_rpc_client, - rgbpp_scripts: get_script_map(config.rgbpp_scripts), - branch_scripts: get_script_map(config.branch_scripts), - rgbpp_assets: get_asset_map(config.rgbpp_assets), - rgbpp_locks: get_rgbpp_locks(config.rgbpp_asset_locks), - } - } - - /// Run the Aggregator - pub fn run(&self, stop_rx: Receiver<()>) { - let poll_interval = self.poll_interval; - let poll_service: Aggregator = self.clone(); - - loop { - match stop_rx.try_recv() { - Ok(_) => { - info!("Aggregator received exit signal, stopped"); - break; - } - Err(crossbeam_channel::TryRecvError::Empty) => { - // No exit signal, continue execution - } - Err(_) => { - info!("Error receiving exit signal"); - break; - } - } - - // get queue data - let rgbpp_requests = poll_service.get_rgbpp_queue_requests(); - let (rgbpp_requests, queue_cell) = match rgbpp_requests { - Ok((rgbpp_requests, queue_cell)) => (rgbpp_requests, queue_cell), - Err(e) => { - error!("get RGB++ queue data error: {}", e.to_string()); - continue; - } - }; - - let leap_tx = poll_service.create_leap_tx(rgbpp_requests.clone(), queue_cell.clone()); - let leap_tx = match leap_tx { - Ok(leap_tx) => leap_tx, - Err(e) => { - error!("create leap transaction error: {}", e.to_string()); - continue; - } - }; - match wait_for_tx_confirmation( - poll_service.rgbpp_rpc_client.clone(), - leap_tx, - Duration::from_secs(600), - ) { - Ok(()) => {} - Err(e) => error!("{}", e.to_string()), - } - - let update_queue_tx = poll_service.create_clear_queue_tx(rgbpp_requests, queue_cell); - let update_queue_tx = match update_queue_tx { - Ok(update_queue_tx) => update_queue_tx, - Err(e) => { - error!("{}", e.to_string()); - continue; - } - }; - match wait_for_tx_confirmation( - poll_service.rgbpp_rpc_client.clone(), - update_queue_tx, - Duration::from_secs(600), - ) { - Ok(()) => {} - Err(e) => error!("{}", e.to_string()), - } - - if let Err(e) = poll_service.scan_rgbpp_request() { - info!("Aggregator: {:?}", e); - } - thread::sleep(poll_interval); - } - } - - fn scan_rgbpp_request(&self) -> Result<(), Error> { - info!("Scan RGB++ Request ..."); - - let stop: CancellationToken = new_tokio_exit_rx(); - - let request_search_option = self.build_request_cell_search_option()?; - let mut cursor = None; - let limit = 10; - - loop { - if stop.is_cancelled() { - info!("Aggregator scan_rgbpp_request received exit signal, exiting now"); - return Ok(()); - } - - let request_cells = self - .rgbpp_rpc_client - .get_cells( - request_search_option.clone().into(), - Order::Asc, - limit.into(), - cursor, - ) - .map_err(|e| Error::LiveCellNotFound(e.to_string()))?; - - if request_cells.objects.is_empty() { - info!("No more request cells found"); - break; - } - cursor = Some(request_cells.last_cursor); - - info!("Found {} request cells", request_cells.objects.len()); - let tip = self - .rgbpp_rpc_client - .get_tip_block_number() - .map_err(|e| Error::RpcError(format!("get tip block number error: {}", e)))? - .value(); - let cells_with_messge = self.check_request(request_cells.objects.clone(), tip); - info!("Found {} valid request cells", cells_with_messge.len()); - if cells_with_messge.is_empty() { - break; - } - - let custodian_tx = self.create_custodian_tx(cells_with_messge)?; - match wait_for_tx_confirmation( - self.rgbpp_rpc_client.clone(), - custodian_tx, - Duration::from_secs(15), - ) { - Ok(()) => info!("Transaction confirmed"), - Err(e) => info!("{}", e.to_string()), - } - } - - Ok(()) - } - - pub(crate) fn build_request_cell_search_option(&self) -> Result { - let request_script = self.get_rgbpp_script(REQUEST_LOCK)?; - Ok(CellQueryOptions::new_lock(request_script)) - } - - pub(crate) fn build_message_queue_cell_search_option(&self) -> Result { - let message_queue_type = self.get_rgbpp_script(QUEUE_TYPE)?; - let (message_queue_lock, _) = - get_sighash_script_from_privkey(self.config.rgbpp_queue_lock_key_path.clone())?; - - let cell_query_option = CellQueryOptions { - primary_script: message_queue_type, - primary_type: PrimaryScriptType::Type, - with_data: Some(true), - secondary_script: Some(message_queue_lock), - secondary_script_len_range: None, - data_len_range: None, - capacity_range: None, - block_range: None, - order: QueryOrder::Asc, - limit: Some(1), - maturity: MaturityOption::Mature, - min_total_capacity: 1, - script_search_mode: None, - }; - Ok(cell_query_option) - } - - fn get_rgbpp_queue_requests(&self) -> Result<(Vec, OutPoint), Error> { - let (queue_cell, queue_cell_data) = self.get_rgbpp_queue_cell()?; - if queue_cell_data.outbox().is_empty() { - info!("No requests in queue"); - return Ok((vec![], OutPoint::default())); - } - let request_ids: Vec = queue_cell_data.outbox().into_iter().collect(); - - let queue_out_point = queue_cell.out_point.clone(); - let (_, witness_input_type) = - self.get_tx_witness_input_type(queue_cell.out_point, self.rgbpp_rpc_client.clone())?; - let requests = Requests::from_slice(&witness_input_type.raw_data()).map_err(|e| { - Error::TransactionParseError(format!("get requests from witness error: {}", e)) - })?; - info!("Found {} requests in witness", requests.len()); - - // check requests - let request_set: HashSet = requests - .clone() - .into_iter() - .map(|request| request.as_bytes().pack().calc_raw_data_hash()) - .collect(); - let all_ids_present = request_ids.iter().all(|id| request_set.contains(id)); - if all_ids_present { - Ok((requests.into_iter().collect(), queue_out_point.into())) - } else { - Err(Error::QueueCellDataError( - "Request IDs in queue cell data do not match witness".to_string(), - )) - } - } - - fn get_rgbpp_queue_cell(&self) -> Result<(Cell, CrossChainQueue), Error> { - info!("Scan RGB++ Message Queue ..."); - - let queue_cell_search_option = self.build_message_queue_cell_search_option()?; - let queue_cell = self - .rgbpp_rpc_client - .get_cells(queue_cell_search_option.into(), Order::Asc, 1.into(), None) - .map_err(|e| Error::LiveCellNotFound(e.to_string()))?; - if queue_cell.objects.len() != 1 { - return Err(Error::LiveCellNotFound(format!( - "Queue cell found: {}", - queue_cell.objects.len() - ))); - } - info!("Found {} queue cell", queue_cell.objects.len()); - let queue_cell = queue_cell.objects[0].clone(); - - let queue_live_cell: LiveCell = queue_cell.clone().into(); - let queue_data = queue_live_cell.output_data; - let queue = CrossChainQueue::from_slice(&queue_data) - .map_err(|e| Error::QueueCellDataDecodeError(e.to_string()))?; - - Ok((queue_cell, queue)) - } - - fn check_request(&self, cells: Vec, tip: u64) -> Vec<(Cell, Transfer)> { - cells - .into_iter() - .filter_map(|cell| { - let live_cell: LiveCell = cell.clone().into(); - RequestLockArgs::from_slice(&live_cell.output.lock().args().raw_data()) - .ok() - .and_then(|args| { - let target_request_type_hash = args.request_type_hash(); - info!("target_request_type_hash: {:?}", target_request_type_hash); - - let timeout: u64 = args.timeout().unpack(); - let since = Since::from_raw_value(timeout); - let since_check = - since.extract_metric().map_or(false, |(since_type, value)| { - match since_type { - SinceType::BlockNumber => { - let threshold = if since.is_absolute() { - value - } else { - cell.block_number.value() + value - }; - tip + CONFIRMATION_THRESHOLD < threshold - } - _ => false, - } - }); - - let content = args.content(); - let target_chain_id: Bytes = content.target_chain_id().raw_data(); - info!("target_chain_id: {:?}", target_chain_id); - let request_type = content.request_type(); - - let (check_message, transfer) = { - let message = content.message(); - let message_union = message.to_enum(); - match message_union { - MessageUnion::Transfer(transfer) => { - let transfer_amount: u128 = transfer.amount().unpack(); - let check_message = cell - .clone() - .output_data - .and_then(|data| decode_udt_amount(data.as_bytes())) - .map_or(false, |amount| { - info!( - "original amount: {:?}, transfer amount: {:?}", - amount, transfer_amount - ); - transfer_amount <= amount - }); - (check_message, transfer) - } - } - }; - - let request_type_hash = self - .rgbpp_scripts - .get(QUEUE_TYPE) - .map(|script_info| script_info.script.calc_script_hash()); - - if Some(target_request_type_hash) == request_type_hash - && self.chain_id.clone() == target_chain_id - && request_type == Byte::new(RequestType::CkbToBranch as u8) - && check_message - && since_check - { - Some((cell, transfer)) - } else { - None - } - }) - }) - .collect() - } - - fn get_rgbpp_cell_dep(&self, script_name: &str) -> Result { - self.rgbpp_scripts - .get(script_name) - .map(|script_info| script_info.cell_dep.clone()) - .ok_or_else(|| Error::MissingScriptInfo(script_name.to_string())) - } - - fn _get_branch_cell_dep(&self, script_name: &str) -> Result { - self.branch_scripts - .get(script_name) - .map(|script_info| script_info.cell_dep.clone()) - .ok_or_else(|| Error::MissingScriptInfo(script_name.to_string())) - } - - fn get_rgbpp_script(&self, script_name: &str) -> Result { - self.rgbpp_scripts - .get(script_name) - .map(|script_info| script_info.script.clone()) - .ok_or_else(|| Error::MissingScriptInfo(script_name.to_string())) - } - - fn _get_branch_script(&self, script_name: &str) -> Result { - self.branch_scripts - .get(script_name) - .map(|script_info| script_info.script.clone()) - .ok_or_else(|| Error::MissingScriptInfo(script_name.to_string())) - } - - fn fee_rate(&self) -> Result { - let value = { - let dynamic = self - .rgbpp_rpc_client - .get_fee_rate_statistics(None) - .map_err(|e| Error::RpcError(format!("get dynamic fee rate error: {}", e)))? - .ok_or_else(|| Error::RpcError("get dynamic fee rate error: None".to_string())) - .map(|resp| resp.median) - .map(Into::into) - .map_err(|e| Error::RpcError(format!("get dynamic fee rate error: {}", e)))?; - info!("CKB fee rate: {} (dynamic)", FeeRate(dynamic)); - if dynamic > CKB_FEE_RATE_LIMIT { - warn!( - "dynamic CKB fee rate {} is too large, it seems unreasonable;\ - so the upper limit {} will be used", - FeeRate(dynamic), - FeeRate(CKB_FEE_RATE_LIMIT) - ); - CKB_FEE_RATE_LIMIT - } else { - dynamic - } - }; - Ok(value) - } -} - -fn get_script_map(scripts: Vec) -> HashMap { - scripts - .iter() - .map(|s| { - ( - s.script_name.clone(), - ScriptInfo { - script: serde_json::from_str::(&s.script) - .expect("config string to script") - .into(), - cell_dep: serde_json::from_str::(&s.cell_dep) - .expect("config string to cell dep") - .into(), - }, - ) - }) - .collect() -} - -fn get_asset_map(asset_configs: Vec) -> HashMap { - let mut is_capacity_found = false; - - asset_configs - .into_iter() - .map(|asset_config| { - let script = serde_json::from_str::(&asset_config.script) - .expect("config string to script") - .into(); - let script_name = asset_config.asset_name.clone(); - let is_capacity = asset_config.is_capacity && !is_capacity_found; - if is_capacity { - is_capacity_found = true; - } - let asset_id = asset_config.asset_id.clone(); - ( - H256::from_str(&asset_id).expect("asset id to h256"), - AssetInfo { - script, - is_capacity, - script_name, - }, - ) - }) - .collect() -} - -fn get_rgbpp_locks(lock_configs: Vec) -> HashMap { - lock_configs - .iter() - .map(|lock_config| { - let lock_hash = H256::from_str(&lock_config.lock_hash).expect("lock hash to h256"); - let script = serde_json::from_str::(&lock_config.script) - .expect("config string to script") - .into(); - (lock_hash, script) - }) - .collect() -} - -fn wait_for_tx_confirmation( - _client: RpcClient, - _tx_hash: H256, - timeout: Duration, -) -> Result<(), Error> { - let start = std::time::Instant::now(); - - loop { - if true { - sleep(Duration::from_secs(8)); - return Ok(()); - } - - if start.elapsed() > timeout { - return Err(Error::TimedOut( - "Transaction confirmation timed out".to_string(), - )); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use ckb_types::{bytes::Bytes, core::ScriptHashType}; - - use std::str::FromStr; - - #[test] - fn calc_script() { - let code_hash = "00000000000000000000000000000000000000000000000000545950455f4944"; - let args = "57fdfd0617dcb74d1287bb78a7368a3a4bf9a790cfdcf5c1a105fd7cb406de0d"; - let script_hash = "6283a479a3cf5d4276cd93594de9f1827ab9b55c7b05b3d28e4c2e0a696cfefd"; - - let code_hash = H256::from_str(code_hash).unwrap(); - let args = Bytes::from(hex::decode(args).unwrap()); - - let script = Script::new_builder() - .code_hash(code_hash.pack()) - .hash_type(ScriptHashType::Type.into()) - .args(args.pack()) - .build(); - - println!("{:?}", script.calc_script_hash()); - - assert_eq!( - script.calc_script_hash().as_bytes(), - Bytes::from(hex::decode(script_hash).unwrap()) - ); - } -} diff --git a/branch-chain-aggregator/src/transaction/mod.rs b/branch-chain-aggregator/src/transaction/mod.rs deleted file mode 100644 index de170c4734..0000000000 --- a/branch-chain-aggregator/src/transaction/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod clear; -mod custodian; -mod leap; diff --git a/ckb-bin/Cargo.toml b/ckb-bin/Cargo.toml index 3892e4aa60..bd5a5e6aac 100644 --- a/ckb-bin/Cargo.toml +++ b/ckb-bin/Cargo.toml @@ -38,7 +38,7 @@ ckb-verification-traits = { path = "../verification/traits", version = "= 0.116. ckb-async-runtime = { path = "../util/runtime", version = "= 0.116.1" } ckb-migrate = { path = "../util/migrate", version = "= 0.116.1" } ckb-launcher = { path = "../util/launcher", version = "= 0.116.1" } -branch-chain-aggregator = { path = "../branch-chain-aggregator", version = "= 0.1.0" } +aggregator-main = { path = "../aggregator/aggregator-main", version = "= 0.1.0" } base64 = "0.21.0" tempfile.workspace = true rayon = "1.0" diff --git a/ckb-bin/src/subcommand/aggregator.rs b/ckb-bin/src/subcommand/aggregator.rs index 3236965716..9a5674d304 100644 --- a/ckb-bin/src/subcommand/aggregator.rs +++ b/ckb-bin/src/subcommand/aggregator.rs @@ -1,4 +1,4 @@ -use branch_chain_aggregator::Aggregator; +use aggregator_main::Aggregator; use ckb_app_config::{AggregatorArgs, ExitCode}; use ckb_logger::info; use ckb_stop_handler::{ diff --git a/resource/ckb-aggregator.toml b/resource/ckb-aggregator.toml index 650893fb50..004c008436 100644 --- a/resource/ckb-aggregator.toml +++ b/resource/ckb-aggregator.toml @@ -172,7 +172,7 @@ is_capacity = true asset_id = "29b0b1a449b0e7fb08881e1d810a6abbedb119e9c4ffc76eebbc757fb214f091" script = ''' { - "args": "0x562e4e8a2f64a3e9c24beb4b7dd002d0ad3b842d0cc77924328e36ad114e3ebe3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333", + "args": "0x562e4e8a2f64a3e9c24beb4b7dd002d0ad3b842d0cc77924328e36ad114e3ebe", "code_hash": "0x25c29dc317811a6f6f3985a7a9ebc4838bd388d19d0feeecf0bcd60f6c0975bb", "hash_type": "type" }