diff --git a/bridges/pangolin-pangolinparachain/Cargo.lock b/bridges/pangolin-pangolinparachain/Cargo.lock index e447db7d1..0ef0abb7f 100644 --- a/bridges/pangolin-pangolinparachain/Cargo.lock +++ b/bridges/pangolin-pangolinparachain/Cargo.lock @@ -12,6 +12,26 @@ dependencies = [ "regex", ] +[[package]] +name = "abstract-bridge-s2s" +version = "0.5.7" +dependencies = [ + "array-bytes", + "async-trait", + "bp-header-chain", + "bp-messages", + "bp-runtime", + "bridge-runtime-common", + "jsonrpsee-core", + "parity-scale-codec", + "scale-info", + "serde 1.0.137", + "sp-core", + "sp-runtime", + "subxt", + "thiserror", +] + [[package]] name = "addr2line" version = "0.17.0" @@ -1011,12 +1031,15 @@ dependencies = [ name = "client-pangolin" version = "0.5.7" dependencies = [ + "abstract-bridge-s2s", "array-bytes", - "bp-header-chain", + "async-trait", + "bp-pangolin", "finality-grandpa", "parity-scale-codec", "serde 1.0.137", "serde_json", + "sp-core", "sp-finality-grandpa", "sp-runtime", "subxt", @@ -6782,6 +6805,7 @@ dependencies = [ name = "support-toolkit" version = "0.5.7" dependencies = [ + "parity-scale-codec", "thiserror", ] diff --git a/bridges/pangolin-pangolinparachain/bridge/Cargo.toml b/bridges/pangolin-pangolinparachain/bridge/Cargo.toml index d0a4fbc77..fcb787f9f 100644 --- a/bridges/pangolin-pangolinparachain/bridge/Cargo.toml +++ b/bridges/pangolin-pangolinparachain/bridge/Cargo.toml @@ -64,7 +64,7 @@ relay-utils = { git = "https://github.com/darwinia-network/d substrate-relay-helper = { git = "https://github.com/darwinia-network/darwinia-bridges-substrate.git", branch = "darwinia-v0.12.2" } subxt = { git = "https://github.com/darwinia-network/subxt.git", branch = "darwinia-v0.12.2" } ## component -client-pangolin = { path = "../../../frame/assistants/client-pangolin", features = ["s2s"] } +client-pangolin = { path = "../../../frame/assistants/client-pangolin", features = ["bridge-s2s"] } client-pangolin-parachain = { path = "../../../frame/assistants/client-pangolin-parachain" } client-rococo = { path = "../../../frame/assistants/client-rococo" } component-subscan = { path = "../../../frame/components/subscan" } diff --git a/bridges/pangolin-pangoro/Cargo.lock b/bridges/pangolin-pangoro/Cargo.lock index 7968d5a3a..afdff4d5e 100644 --- a/bridges/pangolin-pangoro/Cargo.lock +++ b/bridges/pangolin-pangoro/Cargo.lock @@ -12,6 +12,38 @@ dependencies = [ "regex", ] +[[package]] +name = "abstract-bridge-s2s" +version = "0.5.7" +dependencies = [ + "array-bytes", + "async-trait", + "bp-header-chain", + "bp-messages", + "bp-runtime", + "bridge-runtime-common", + "jsonrpsee-core", + "parity-scale-codec", + "scale-info", + "serde 1.0.136", + "sp-core", + "sp-runtime", + "subxt", + "thiserror", +] + +[[package]] +name = "abstract-feemarket-s2s" +version = "0.5.7" +dependencies = [ + "async-trait", + "bp-runtime", + "pallet-fee-market", + "parity-scale-codec", + "subxt", + "thiserror", +] + [[package]] name = "addr2line" version = "0.17.0" @@ -62,6 +94,15 @@ version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits 0.2.14", +] + [[package]] name = "array-bytes" version = "1.5.2" @@ -241,10 +282,26 @@ dependencies = [ "byte-tools", ] +[[package]] +name = "bp-darwinia-core" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bp-messages", + "bp-runtime", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-std", +] + [[package]] name = "bp-header-chain" version = "0.1.0" -source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#734a60313dd32e77574d8c72298660c291440151" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" dependencies = [ "finality-grandpa", "frame-support", @@ -257,33 +314,179 @@ dependencies = [ "sp-std", ] +[[package]] +name = "bp-message-dispatch" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bp-runtime", + "frame-support", + "parity-scale-codec", + "scale-info", + "sp-std", +] + +[[package]] +name = "bp-messages" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bitvec", + "bp-runtime", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "serde 1.0.136", + "sp-core", + "sp-std", +] + +[[package]] +name = "bp-pangolin" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bp-darwinia-core", + "bp-messages", + "bp-runtime", + "frame-support", + "sp-api", + "sp-runtime", + "sp-std", + "sp-version", +] + +[[package]] +name = "bp-pangoro" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bp-darwinia-core", + "bp-messages", + "bp-runtime", + "frame-support", + "sp-api", + "sp-runtime", + "sp-std", + "sp-version", +] + +[[package]] +name = "bp-parachains" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bp-polkadot-core", + "bp-runtime", + "frame-support", + "parity-scale-codec", + "scale-info", + "sp-core", +] + +[[package]] +name = "bp-polkadot-core" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bp-messages", + "bp-runtime", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-core", + "sp-runtime", + "sp-std", + "sp-version", +] + +[[package]] +name = "bp-runtime" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "frame-support", + "hash-db", + "num-traits 0.2.14", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-trie", +] + +[[package]] +name = "bp-test-utils" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bp-header-chain", + "ed25519-dalek", + "finality-grandpa", + "parity-scale-codec", + "sp-application-crypto", + "sp-finality-grandpa", + "sp-runtime", + "sp-std", +] + [[package]] name = "bridge-pangolin-pangoro" version = "0.5.7" dependencies = [ + "abstract-bridge-s2s", "array-bytes", + "async-trait", "client-pangolin", "client-pangoro", "color-eyre", - "component-http-client", - "futures", + "feemarket-ns2s", "lifeline", - "once_cell", - "parity-scale-codec", "postage", + "relay-s2s", "serde 1.0.136", - "sp-core", "structopt", "strum", "subquery-s2s", - "subxt", "support-common", "support-lifeline", - "support-terminal", + "support-toolkit", "tokio", "tracing", ] +[[package]] +name = "bridge-runtime-common" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bp-message-dispatch", + "bp-messages", + "bp-polkadot-core", + "bp-runtime", + "frame-support", + "hash-db", + "pallet-bridge-dispatch", + "pallet-bridge-grandpa", + "pallet-bridge-messages", + "pallet-bridge-parachains", + "pallet-transaction-payment", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-trie", +] + [[package]] name = "bumpalo" version = "3.9.1" @@ -370,12 +573,16 @@ dependencies = [ name = "client-pangolin" version = "0.5.7" dependencies = [ + "abstract-bridge-s2s", + "abstract-feemarket-s2s", "array-bytes", - "bp-header-chain", + "async-trait", + "bp-pangolin", "finality-grandpa", "parity-scale-codec", "serde 1.0.136", "serde_json", + "sp-core", "sp-finality-grandpa", "sp-runtime", "subxt", @@ -388,12 +595,16 @@ dependencies = [ name = "client-pangoro" version = "0.5.7" dependencies = [ + "abstract-bridge-s2s", + "abstract-feemarket-s2s", "array-bytes", - "bp-header-chain", + "async-trait", + "bp-pangoro", "finality-grandpa", "parity-scale-codec", "serde 1.0.136", "serde_json", + "sp-core", "sp-finality-grandpa", "sp-runtime", "subxt", @@ -439,23 +650,15 @@ dependencies = [ ] [[package]] -name = "colored" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" -dependencies = [ - "atty", - "lazy_static", - "winapi", -] - -[[package]] -name = "component-http-client" +name = "component-subscan" version = "0.5.7" dependencies = [ - "color-eyre", "reqwest", "serde 1.0.136", + "serde-aux", + "serde-hex", + "serde_json", + "thiserror", "tracing", ] @@ -792,6 +995,20 @@ dependencies = [ "instant", ] +[[package]] +name = "feemarket-ns2s" +version = "0.5.7" +dependencies = [ + "abstract-bridge-s2s", + "abstract-feemarket-s2s", + "async-trait", + "component-subscan", + "serde 1.0.136", + "support-toolkit", + "thiserror", + "tracing", +] + [[package]] name = "finality-grandpa" version = "0.14.4" @@ -851,6 +1068,26 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "frame-benchmarking" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "frame-support", + "frame-system", + "linregress", + "log", + "parity-scale-codec", + "paste", + "scale-info", + "sp-api", + "sp-io", + "sp-runtime", + "sp-runtime-interface", + "sp-std", + "sp-storage", +] + [[package]] name = "frame-metadata" version = "14.2.0" @@ -924,6 +1161,23 @@ dependencies = [ "syn", ] +[[package]] +name = "frame-system" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "frame-support", + "log", + "parity-scale-codec", + "scale-info", + "serde 1.0.136", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-version", +] + [[package]] name = "funty" version = "1.1.0" @@ -1487,6 +1741,12 @@ version = "0.2.124" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50" +[[package]] +name = "libm" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db" + [[package]] name = "libsecp256k1" version = "0.6.0" @@ -1567,6 +1827,16 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +[[package]] +name = "linregress" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c601a85f5ecd1aba625247bca0031585fb1c446461b142878a16f8245ddeb8" +dependencies = [ + "nalgebra", + "statrs", +] + [[package]] name = "lock_api" version = "0.3.4" @@ -1610,6 +1880,15 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +[[package]] +name = "matrixmultiply" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84" +dependencies = [ + "rawpointer", +] + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -1689,6 +1968,35 @@ dependencies = [ "winapi", ] +[[package]] +name = "nalgebra" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462fffe4002f4f2e1f6a9dcf12cc1a6fc0e15989014efc02a941d3e0f5dc2120" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational 0.4.0", + "num-traits 0.2.14", + "rand 0.8.5", + "rand_distr", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "native-tls" version = "0.2.10" @@ -1744,6 +2052,15 @@ dependencies = [ "num-traits 0.2.14", ] +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits 0.2.14", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -1766,6 +2083,17 @@ dependencies = [ "num-traits 0.2.14", ] +[[package]] +name = "num-rational" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +dependencies = [ + "autocfg", + "num-integer", + "num-traits 0.2.14", +] + [[package]] name = "num-traits" version = "0.1.43" @@ -1782,6 +2110,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1860,6 +2189,149 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" +[[package]] +name = "pad" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "pallet-bridge-dispatch" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bp-message-dispatch", + "bp-runtime", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-bridge-grandpa" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bp-header-chain", + "bp-runtime", + "bp-test-utils", + "finality-grandpa", + "frame-support", + "frame-system", + "log", + "num-traits 0.2.14", + "parity-scale-codec", + "scale-info", + "serde 1.0.136", + "sp-finality-grandpa", + "sp-runtime", + "sp-std", + "sp-trie", +] + +[[package]] +name = "pallet-bridge-messages" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bitvec", + "bp-message-dispatch", + "bp-messages", + "bp-runtime", + "frame-support", + "frame-system", + "log", + "num-traits 0.2.14", + "parity-scale-codec", + "scale-info", + "serde 1.0.136", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-bridge-parachains" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bp-parachains", + "bp-polkadot-core", + "bp-runtime", + "frame-support", + "frame-system", + "log", + "pallet-bridge-grandpa", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-std", + "sp-trie", +] + +[[package]] +name = "pallet-fee-market" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bitvec", + "bp-messages", + "bp-runtime", + "frame-support", + "frame-system", + "pallet-bridge-messages", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-timestamp" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-inherents", + "sp-runtime", + "sp-std", + "sp-timestamp", +] + +[[package]] +name = "pallet-transaction-payment" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serde 1.0.136", + "smallvec 1.8.0", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "parity-scale-codec" version = "2.3.1" @@ -2248,6 +2720,16 @@ dependencies = [ "getrandom 0.2.6", ] +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits 0.2.14", + "rand 0.8.5", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -2266,6 +2748,12 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "redox_syscall" version = "0.1.57" @@ -2338,6 +2826,23 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "relay-s2s" +version = "0.5.7" +dependencies = [ + "abstract-bridge-s2s", + "array-bytes", + "once_cell", + "sp-core", + "sp-runtime", + "subquery-parachain", + "subquery-s2s", + "support-toolkit", + "thiserror", + "tokio", + "tracing", +] + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -2760,6 +3265,18 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" +[[package]] +name = "simba" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e82063457853d00243beda9952e910b82593e4b07ae9f721b9278a99a0d3d5c" +dependencies = [ + "approx", + "num-complex", + "num-traits 0.2.14", + "paste", +] + [[package]] name = "slab" version = "0.4.6" @@ -3121,6 +3638,22 @@ dependencies = [ "sp-std", ] +[[package]] +name = "sp-timestamp" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "async-trait", + "futures-timer", + "log", + "parity-scale-codec", + "sp-api", + "sp-inherents", + "sp-runtime", + "sp-std", + "thiserror", +] + [[package]] name = "sp-tracing" version = "4.0.0-dev" @@ -3204,6 +3737,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "statrs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05bdbb8e4e78216a85785a85d3ec3183144f98d0097b9281802c019bb07a6f05" +dependencies = [ + "approx", + "lazy_static", + "nalgebra", + "num-traits 0.2.14", + "rand 0.8.5", +] + [[package]] name = "strsim" version = "0.8.0" @@ -3261,6 +3807,22 @@ dependencies = [ "syn", ] +[[package]] +name = "subquery-parachain" +version = "0.5.7" +dependencies = [ + "async-trait", + "gql_client", + "hex", + "include_dir", + "serde 1.0.136", + "serde-aux", + "serde-hex", + "strum", + "thiserror", + "tracing", +] + [[package]] name = "subquery-s2s" version = "0.5.7" @@ -3387,20 +3949,13 @@ dependencies = [ "lifeline", ] -[[package]] -name = "support-terminal" -version = "0.5.7" -dependencies = [ - "colored", - "serde 1.0.136", - "structopt", - "strum", -] - [[package]] name = "support-toolkit" version = "0.5.7" dependencies = [ + "once_cell", + "pad", + "parity-scale-codec", "thiserror", ] @@ -3956,7 +4511,7 @@ dependencies = [ "downcast-rs", "libc", "memory_units", - "num-rational", + "num-rational 0.2.4", "num-traits 0.2.14", "parity-wasm", "wasmi-validation", diff --git a/bridges/pangolin-pangoro/bridge/Cargo.toml b/bridges/pangolin-pangoro/bridge/Cargo.toml index 3a342bf29..b8ac9114e 100644 --- a/bridges/pangolin-pangoro/bridge/Cargo.toml +++ b/bridges/pangolin-pangoro/bridge/Cargo.toml @@ -13,8 +13,9 @@ repository = "https://github.com/darwinia-network/bridger" version = "0.5.7" [dependencies] -tracing = "0.1" -color-eyre = "0.5" +tracing = "0.1" +color-eyre = "0.5" +async-trait = "0.1" array-bytes = "1.4" tokio = { version = "1", features = ["full"] } @@ -22,24 +23,17 @@ tokio = { version = "1", features = ["full"] } structopt = "0.3" strum = { version = "0.21", features = ["derive"] } serde = { version = "1", features = ["derive"] } -codec = { package = "parity-scale-codec", version = "2", default-features = false } -once_cell = "1" -futures = "0.3" lifeline = { git = "https://github.com/fewensa/lifeline-rs.git", branch = "threads-safely" } postage = "0.4" -sp-core = { git = "https://github.com/darwinia-network/substrate.git", branch = "darwinia-v0.12.2" } - -subxt = { git = "https://github.com/darwinia-network/subxt.git", branch = "darwinia-v0.12.2" } - support-common = { path = "../../../frame/supports/support-common" } support-lifeline = { path = "../../../frame/supports/support-lifeline" } -support-terminal = { path = "../../../frame/supports/support-terminal" } - -component-http-client = { path = "../../../frame/components/http-client" } - -client-pangolin = { path = "../../../frame/assistants/client-pangolin", features = [ "s2s-pangoro" ]} -client-pangoro = { path = "../../../frame/assistants/client-pangoro", features = [ "s2s-pangolin" ]} - -subquery-s2s = { path = "../../../frame/assistants/subquery-s2s" } +support-toolkit = { path = "../../../frame/supports/support-toolkit" } + +abstract-bridge-s2s = { path = "../../../frame/abstract/bridge-s2s" } +client-pangolin = { path = "../../../frame/assistants/client-pangolin", features = [ "bridge-s2s-pangoro", "feemarket-s2s-pangoro" ]} +client-pangoro = { path = "../../../frame/assistants/client-pangoro", features = [ "bridge-s2s-pangolin", "feemarket-s2s-pangolin" ]} +subquery-s2s = { path = "../../../frame/assistants/subquery-s2s" } +relay-s2s = { path = "../../../frame/assistants/relay-s2s" } +feemarket-ns2s = { path = "../../../frame/assistants/feemarket-ns2s" } diff --git a/bridges/pangolin-pangoro/bridge/src/bridge/config.rs b/bridges/pangolin-pangoro/bridge/src/bridge/config.rs index d3e1d20dd..cfefadbad 100644 --- a/bridges/pangolin-pangoro/bridge/src/bridge/config.rs +++ b/bridges/pangolin-pangoro/bridge/src/bridge/config.rs @@ -1,5 +1,10 @@ +use client_pangolin::client::PangolinClient; +use client_pangolin::component::PangolinClientComponent; +use client_pangoro::client::PangoroClient; +use client_pangoro::component::PangoroClientComponent; use serde::{Deserialize, Serialize}; -use subquery_s2s::SubqueryConfig; +use subquery_s2s::types::BridgeName; +use subquery_s2s::{Subquery, SubqueryComponent, SubqueryConfig}; use crate::types::HexLaneId; @@ -58,23 +63,43 @@ impl From for client_pangoro::config::ClientConfig { } impl ChainInfoConfig { - pub fn to_pangolin_client_config( - &self, - ) -> color_eyre::Result { - Ok(client_pangolin::config::ClientConfig { + pub async fn to_pangolin_client(&self) -> color_eyre::Result { + let config = client_pangolin::config::ClientConfig { endpoint: self.endpoint.clone(), relayer_private_key: self.signer.clone(), relayer_real_account: None, - }) + }; + Ok(PangolinClientComponent::component(config).await?) } - pub fn to_pangoro_client_config( - &self, - ) -> color_eyre::Result { - Ok(client_pangoro::config::ClientConfig { + pub async fn to_pangoro_client(&self) -> color_eyre::Result { + let config = client_pangoro::config::ClientConfig { endpoint: self.endpoint.clone(), relayer_private_key: self.signer.clone(), relayer_real_account: None, - }) + }; + Ok(PangoroClientComponent::component(config).await?) + } +} + +impl IndexConfig { + pub fn to_pangolin_subquery(&self) -> color_eyre::Result { + Ok(SubqueryComponent::component( + self.pangolin.clone(), + BridgeName::PangolinPangoro, + )) + } + + pub fn to_pangoro_subquery(&self) -> color_eyre::Result { + Ok(SubqueryComponent::component( + self.pangoro.clone(), + BridgeName::PangolinPangoro, + )) + } +} + +impl RelayConfig { + pub fn raw_lanes(&self) -> Vec<[u8; 4]> { + self.lanes.iter().map(|item| item.0).collect() } } diff --git a/bridges/pangolin-pangoro/bridge/src/command/handler/init.rs b/bridges/pangolin-pangoro/bridge/src/command/handler/init.rs index 0c7e5783e..b501e7757 100644 --- a/bridges/pangolin-pangoro/bridge/src/command/handler/init.rs +++ b/bridges/pangolin-pangoro/bridge/src/command/handler/init.rs @@ -1,5 +1,5 @@ -use client_pangolin::component::PangolinClientComponent; -use client_pangoro::component::PangoroClientComponent; +use abstract_bridge_s2s::client::S2SClientGeneric; +use support_toolkit::convert::SmartCodecMapper; use support_common::config::{Config, Names}; @@ -21,30 +21,18 @@ async fn init_bridge( config_pangolin: ChainInfoConfig, config_pangoro: ChainInfoConfig, ) -> color_eyre::Result<()> { - let client_pangolin = PangolinClientComponent::component(config_pangolin.into()).await?; - let client_pangoro = PangoroClientComponent::component(config_pangoro.into()).await?; + let client_pangolin = config_pangolin.to_pangolin_client().await?; + let client_pangoro = config_pangoro.to_pangoro_client().await?; let hash = match bridge { BridgeName::PangolinToPangoro => { let initialization_data = client_pangolin.prepare_initialization_data().await?; - let encoded = codec::Encode::encode(&initialization_data); - client_pangoro - .runtime() - .tx() - .bridge_pangolin_grandpa() - .initialize(codec::Decode::decode(&mut &encoded[..])?) - .sign_and_submit(client_pangoro.account().signer()) - .await? + let expected_data = SmartCodecMapper::map_to(&initialization_data)?; + client_pangoro.initialize(expected_data).await? } BridgeName::PangoroToPangolin => { let initialization_data = client_pangoro.prepare_initialization_data().await?; - let encoded = codec::Encode::encode(&initialization_data); - client_pangolin - .runtime() - .tx() - .bridge_pangoro_grandpa() - .initialize(codec::Decode::decode(&mut &encoded[..])?) - .sign_and_submit(client_pangolin.account().signer()) - .await? + let expected_data = SmartCodecMapper::map_to(&initialization_data)?; + client_pangolin.initialize(expected_data).await? } }; tracing::info!( diff --git a/bridges/pangolin-pangoro/bridge/src/service/header/pangolin_to_pangoro.rs b/bridges/pangolin-pangoro/bridge/src/service/header/pangolin_to_pangoro.rs index 973919db3..d265d8e8c 100644 --- a/bridges/pangolin-pangoro/bridge/src/service/header/pangolin_to_pangoro.rs +++ b/bridges/pangolin-pangoro/bridge/src/service/header/pangolin_to_pangoro.rs @@ -1,24 +1,12 @@ -use std::str::FromStr; - -use client_pangolin::client::PangolinClient; -use client_pangolin::component::PangolinClientComponent; -use client_pangolin::types::runtime_types::bp_header_chain::justification::GrandpaJustification; -use client_pangolin::types::runtime_types::sp_runtime::generic::header::Header; -use client_pangolin::types::runtime_types::sp_runtime::traits::BlakeTwo256; -use client_pangoro::client::PangoroClient; -use client_pangoro::component::PangoroClientComponent; -use client_pangoro::types::runtime_types::sp_runtime::generic::header::Header as FinalityTarget; -use codec::{Decode, Encode}; use lifeline::{Lifeline, Service, Task}; -use subquery_s2s::types::{BridgeName, OriginType}; -use subquery_s2s::{Subquery, SubqueryComponent}; +use subquery_s2s::types::OriginType; +use relay_s2s::header::SolochainHeaderRunner; +use relay_s2s::types::SolochainHeaderInput; use support_common::config::{Config, Names}; -use support_common::error::BridgerError; use support_lifeline::service::BridgeService; use crate::bridge::{BridgeBus, BridgeConfig}; -use crate::service::subscribe::PANGOLIN_JUSTIFICATIONS; #[derive(Debug)] pub struct PangolinToPangoroHeaderRelayService { @@ -33,255 +21,46 @@ impl Service for PangolinToPangoroHeaderRelayService { fn spawn(_bus: &Self::Bus) -> Self::Lifeline { let _greet = Self::try_task("pangolin-to-pangoro-header-relay-service", async move { - start().await.map_err(|e| { - BridgerError::Custom(format!( - "Failed to start pangolin-to-pangoro header relay: {:?}", - e - )) - })?; - Ok(()) - }); - Ok(Self { _greet }) - } -} - -struct HeaderRelay { - client_pangolin: PangolinClient, - client_pangoro: PangoroClient, - subquery_pangolin: Subquery, -} - -impl HeaderRelay { - async fn new() -> color_eyre::Result { - let bridge_config: BridgeConfig = Config::restore(Names::BridgePangolinPangoro)?; - - let config_pangolin = bridge_config.pangolin; - let config_pangoro = bridge_config.pangoro; - - let client_pangolin = - PangolinClientComponent::component(config_pangolin.to_pangolin_client_config()?) - .await?; - let client_pangoro = - PangoroClientComponent::component(config_pangoro.to_pangoro_client_config()?).await?; - - let config_index = bridge_config.index; - let subquery_pangolin = - SubqueryComponent::component(config_index.pangolin, BridgeName::PangolinPangoro); - Ok(Self { - client_pangolin, - client_pangoro, - subquery_pangolin, - }) - } -} - -async fn start() -> color_eyre::Result<()> { - tracing::info!( - target: "pangolin-pangoro", - "[header-pangolin-to-pangoro] SERVICE RESTARTING..." - ); - let mut header_relay = HeaderRelay::new().await?; - loop { - match run(&header_relay).await { - Ok(_) => {} - Err(err) => { - header_relay = HeaderRelay::new().await?; + while let Err(e) = start().await { tracing::error!( target: "pangolin-pangoro", - "[header-pangolin-to-pangoro] Failed to relay header: {:?}", - err + "[header-relay] [pangolin-to-pangoro] An error occurred for header relay {:?}", + e, ); - } - } - tokio::time::sleep(std::time::Duration::from_secs(5)).await; - } -} - -async fn run(header_relay: &HeaderRelay) -> color_eyre::Result<()> { - let last_relayed_pangolin_hash_in_pangoro = header_relay - .client_pangoro - .runtime() - .storage() - .bridge_pangolin_grandpa() - .best_finalized(None) - .await?; - let last_relayed_pangolin_block_in_pangoro = header_relay - .client_pangolin - .subxt() - .rpc() - .block(Some(last_relayed_pangolin_hash_in_pangoro)) - .await? - .ok_or_else(|| { - BridgerError::Custom(format!( - "Failed to query block by [{}] in pangolin", - last_relayed_pangolin_hash_in_pangoro - )) - })?; - let block_number = last_relayed_pangolin_block_in_pangoro.block.header.number; - tracing::trace!( - target: "pangolin-pangoro", - "[header-pangolin-to-pangoro] The latest relayed pangolin block is: {:?}", - block_number - ); - - if try_to_relay_mandatory(header_relay, block_number) - .await? - .is_none() - { - try_to_relay_header_on_demand(header_relay, block_number).await?; - } - - Ok(()) -} - -/// Try to relay mandatory headers, return Ok(Some(block_number)) if success, else Ok(None) -async fn try_to_relay_mandatory( - header_relay: &HeaderRelay, - last_block_number: u32, -) -> color_eyre::Result> { - let next_mandatory_block = header_relay - .subquery_pangolin - .next_mandatory_header(last_block_number) - .await?; - if let Some(block_to_relay) = next_mandatory_block { - tracing::info!( - target: "pangolin-pangoro", - "[header-pangolin-to-pangoro] Next mandatory block: {:?} ", - &block_to_relay.block_number - ); - let justification = header_relay - .subquery_pangolin - .find_justification(block_to_relay.block_hash.clone(), true) - .await? - .ok_or_else(|| { - BridgerError::Custom(format!( - "Failed to query justification for block hash: {:?}", - &block_to_relay.block_hash - )) - })?; - submit_finality( - header_relay, - block_to_relay.block_hash, - justification.justification, - ) - .await?; - - return Ok(Some(block_to_relay.block_number)); - } - tracing::info!( - target: "pangolin-pangoro", - "[header-pangolin-to-pangoro] Next mandatory block not found", - ); - Ok(None) -} - -async fn try_to_relay_header_on_demand( - header_relay: &HeaderRelay, - last_block_number: u32, -) -> color_eyre::Result<()> { - let next_header = match header_relay - .subquery_pangolin - .next_needed_header(OriginType::BridgePangoro) - .await? - { - Some(v) => { - if v.block_number <= last_block_number { - tracing::debug!( + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + tracing::info!( target: "pangolin-pangoro", - "[header-pangolin-to-pangoro] The last storage block ({}) is less or equal last relayed block ({}). nothing to do.", - v.block_number, - last_block_number, + "[header-relay] [pangolin-to-pangoro] Try to restart header relay service.", ); - return Ok(()); } - v - } - None => { - tracing::debug!( - target: "pangolin-pangoro", - "[header-pangolin-to-pangoro] Try relay header on-demand, but not found any on-demand block", - ); - return Ok(()); - } - }; - tracing::debug!( - target: "pangolin-pangoro", - "[header-pangolin-to-pangoro] Try relay header on-demand, the on-demand block is {}", - next_header.block_number, - ); - - let pangolin_justification_queue = PANGOLIN_JUSTIFICATIONS.lock().await; - match pangolin_justification_queue.back().cloned() { - Some(justification) => { - tracing::trace!( - target: "pangolin-pangoro", - "[header-pangoro-to-pangolin] Found on-demand block {}, and found new justification, ready to relay header", - next_header.block_number, - ); - let grandpa_justification = GrandpaJustification::>::decode( - &mut justification.as_ref(), - ) - .map_err(|err| { - BridgerError::Custom(format!( - "Failed to decode justification of pangolin: {:?}", - err - )) - })?; - if grandpa_justification.commit.target_number > last_block_number { - submit_finality( - header_relay, - array_bytes::bytes2hex("", grandpa_justification.commit.target_hash.0), - justification.to_vec(), - ) - .await?; - } - } - None => { - tracing::warn!( - target: "pangolin-pangoro", - "[header-pangoro-to-pangolin] Found on-demand block {}, but not have justification to relay.", - next_header.block_number, - ); - } + Ok(()) + }); + Ok(Self { _greet }) } - - Ok(()) } -async fn submit_finality( - header_relay: &HeaderRelay, - block_hash: impl AsRef, - justification: Vec, -) -> color_eyre::Result<()> { - let header = header_relay - .client_pangolin - .subxt() - .rpc() - .header(Some(sp_core::H256::from_str(block_hash.as_ref()).unwrap())) - .await? - .unwrap(); - let finality_target = FinalityTarget { - parent_hash: header.parent_hash, - number: header.number, - state_root: header.state_root, - extrinsics_root: header.extrinsics_root, - digest: Decode::decode(&mut header.digest.encode().as_slice())?, - __subxt_unused_type_params: Default::default(), - }; - let grandpa_justification = Decode::decode(&mut justification.as_slice())?; - let runtime = header_relay.client_pangoro.runtime(); - let track = runtime - .tx() - .bridge_pangolin_grandpa() - .submit_finality_proof(finality_target, grandpa_justification) - .sign_and_submit_then_watch(header_relay.client_pangoro.account().signer()) - .await?; - - let events = track.wait_for_finalized_success().await?; +async fn start() -> color_eyre::Result<()> { tracing::info!( - target: "pangolin-pangoro", - "[header-pangolin-to-pangoro] Header relayed: {:?}", - events.extrinsic_hash() + target: "pangolin-pangoro", + "[header-pangolin-to-pangoro] [pangolin-to-pangoro] SERVICE RESTARTING..." ); - Ok(()) + let bridge_config: BridgeConfig = Config::restore(Names::BridgePangolinPangoro)?; + let relay_config = bridge_config.relay; + + let client_pangolin = bridge_config.pangolin.to_pangolin_client().await?; + let client_pangoro = bridge_config.pangoro.to_pangoro_client().await?; + + let config_index = bridge_config.index; + let subquery_pangolin = config_index.to_pangolin_subquery()?; + let lanes = relay_config.raw_lanes(); + + let input = SolochainHeaderInput { + lanes, + client_source: client_pangolin, + client_target: client_pangoro, + subquery_source: subquery_pangolin, + index_origin_type: OriginType::BridgePangoro, + }; + let runner = SolochainHeaderRunner::new(input); + Ok(runner.start().await?) } diff --git a/bridges/pangolin-pangoro/bridge/src/service/header/pangoro_to_pangolin.rs b/bridges/pangolin-pangoro/bridge/src/service/header/pangoro_to_pangolin.rs index 083f7d1e8..8e351e2e0 100644 --- a/bridges/pangolin-pangoro/bridge/src/service/header/pangoro_to_pangolin.rs +++ b/bridges/pangolin-pangoro/bridge/src/service/header/pangoro_to_pangolin.rs @@ -1,24 +1,12 @@ -use std::str::FromStr; - -use client_pangolin::client::PangolinClient; -use client_pangolin::component::PangolinClientComponent; -use client_pangolin::types::runtime_types::sp_runtime::generic::header::Header as FinalityTarget; -use client_pangoro::client::PangoroClient; -use client_pangoro::component::PangoroClientComponent; -use client_pangoro::types::runtime_types::bp_header_chain::justification::GrandpaJustification; -use client_pangoro::types::runtime_types::sp_runtime::generic::header::Header; -use client_pangoro::types::runtime_types::sp_runtime::traits::BlakeTwo256; -use codec::{Decode, Encode}; use lifeline::{Lifeline, Service, Task}; -use subquery_s2s::types::{BridgeName, OriginType}; -use subquery_s2s::{Subquery, SubqueryComponent}; +use subquery_s2s::types::OriginType; +use relay_s2s::header::SolochainHeaderRunner; +use relay_s2s::types::SolochainHeaderInput; use support_common::config::{Config, Names}; -use support_common::error::BridgerError; use support_lifeline::service::BridgeService; use crate::bridge::{BridgeBus, BridgeConfig}; -use crate::service::subscribe::PANGORO_JUSTIFICATIONS; #[derive(Debug)] pub struct PangoroToPangolinHeaderRelayService { @@ -33,256 +21,46 @@ impl Service for PangoroToPangolinHeaderRelayService { fn spawn(_bus: &Self::Bus) -> Self::Lifeline { let _greet = Self::try_task("pangoro-to-pangolin-header-relay-service", async move { - start().await.map_err(|e| { - BridgerError::Custom(format!( - "Failed to start pangoro-to-pangolin header relay: {:?}", - e - )) - })?; - Ok(()) - }); - Ok(Self { _greet }) - } -} - -struct HeaderRelay { - client_pangoro: PangoroClient, - client_pangolin: PangolinClient, - subquery_pangoro: Subquery, -} - -impl HeaderRelay { - async fn new() -> color_eyre::Result { - let bridge_config: BridgeConfig = Config::restore(Names::BridgePangolinPangoro)?; - - let config_pangoro = bridge_config.pangoro; - let config_pangolin = bridge_config.pangolin; - - let client_pangoro = - PangoroClientComponent::component(config_pangoro.to_pangoro_client_config()?).await?; - let client_pangolin = - PangolinClientComponent::component(config_pangolin.to_pangolin_client_config()?) - .await?; - - let config_index = bridge_config.index; - let subquery_pangoro = - SubqueryComponent::component(config_index.pangoro, BridgeName::PangolinPangoro); - Ok(Self { - client_pangoro, - client_pangolin, - subquery_pangoro, - }) - } -} - -async fn start() -> color_eyre::Result<()> { - tracing::info!( - target: "pangolin-pangoro", - "[header-pangoro-to-pangolin] SERVICE RESTARTING..." - ); - let mut header_relay = HeaderRelay::new().await?; - loop { - match run(&header_relay).await { - Ok(_) => {} - Err(err) => { - header_relay = HeaderRelay::new().await?; + while let Err(e) = start().await { tracing::error!( target: "pangolin-pangoro", - "[header-pangoro-to-pangolin] Failed to relay header: {:?}", - err + "[header-relay] [pangoro-to-pangolin] An error occurred for header relay {:?}", + e, ); - } - } - tokio::time::sleep(std::time::Duration::from_secs(5)).await; - } -} - -async fn run(header_relay: &HeaderRelay) -> color_eyre::Result<()> { - let last_relayed_pangoro_hash_in_pangolin = header_relay - .client_pangolin - .runtime() - .storage() - .bridge_pangoro_grandpa() - .best_finalized(None) - .await?; - let last_relayed_pangoro_block_in_pangolin = header_relay - .client_pangoro - .subxt() - .rpc() - .block(Some(last_relayed_pangoro_hash_in_pangolin)) - .await? - .ok_or_else(|| { - BridgerError::Custom(format!( - "Failed to query block by [{}] in pangoro", - last_relayed_pangoro_hash_in_pangolin - )) - })?; - let block_number = last_relayed_pangoro_block_in_pangolin.block.header.number; - tracing::trace!( - target: "pangolin-pangoro", - "[header-pangoro-to-pangolin] The latest relayed pangoro block is: {:?}", - block_number - ); - - if try_to_relay_mandatory(header_relay, block_number) - .await? - .is_none() - { - try_to_relay_header_on_demand(header_relay, block_number).await?; - } - - Ok(()) -} - -/// Try to relay mandatory headers, return Ok(Some(block_number)) if success, else Ok(None) -async fn try_to_relay_mandatory( - header_relay: &HeaderRelay, - last_block_number: u32, -) -> color_eyre::Result> { - let next_mandatory_block = header_relay - .subquery_pangoro - .next_mandatory_header(last_block_number) - .await?; - if let Some(block_to_relay) = next_mandatory_block { - tracing::info!( - target: "pangolin-pangoro", - "[header-pangoro-to-pangolin] Next mandatory block: {:?} ", - &block_to_relay.block_number - ); - let justification = header_relay - .subquery_pangoro - .find_justification(block_to_relay.block_hash.clone(), true) - .await? - .ok_or_else(|| { - BridgerError::Custom(format!( - "Failed to query justification for block hash: {:?}", - &block_to_relay.block_hash - )) - })?; - submit_finality( - header_relay, - block_to_relay.block_hash, - justification.justification, - ) - .await?; - - return Ok(Some(block_to_relay.block_number)); - } - tracing::info!( - target: "pangolin-pangoro", - "[header-pangoro-to-pangolin] Next mandatory block not found", - ); - Ok(None) -} - -async fn try_to_relay_header_on_demand( - header_relay: &HeaderRelay, - last_block_number: u32, -) -> color_eyre::Result<()> { - let next_header = match header_relay - .subquery_pangoro - .next_needed_header(OriginType::BridgePangolin) - .await? - { - Some(v) => { - if v.block_number <= last_block_number { - tracing::debug!( + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + tracing::info!( target: "pangolin-pangoro", - "[header-pangoro-to-pangolin] The last storage block ({}) is less or equal last relayed block ({}). nothing to do.", - v.block_number, - last_block_number, + "[header-relay] [pangoro-to-pangolin] Try to restart header relay service.", ); - return Ok(()); - } - v - } - None => { - tracing::debug!( - target: "pangolin-pangoro", - "[header-pangoro-to-pangolin] Try relay header on-demand, but not found any on-demand block", - ); - return Ok(()); - } - }; - - tracing::debug!( - target: "pangolin-pangoro", - "[header-pangoro-to-pangolin] Try relay header on-demand, the on-demand block is {}", - next_header.block_number, - ); - - let pangoro_justification_queue = PANGORO_JUSTIFICATIONS.lock().await; - match pangoro_justification_queue.back().cloned() { - Some(justification) => { - tracing::trace!( - target: "pangolin-pangoro", - "[header-pangoro-to-pangolin] Found on-demand block {}, and found new justification, ready to relay header", - next_header.block_number, - ); - let grandpa_justification = GrandpaJustification::>::decode( - &mut justification.as_ref(), - ) - .map_err(|err| { - BridgerError::Custom(format!( - "Failed to decode justification of pangoro: {:?}", - err - )) - })?; - if grandpa_justification.commit.target_number > last_block_number { - submit_finality( - header_relay, - array_bytes::bytes2hex("", grandpa_justification.commit.target_hash.0), - justification.to_vec(), - ) - .await?; } - } - None => { - tracing::warn!( - target: "pangolin-pangoro", - "[header-pangoro-to-pangolin] Found on-demand block {}, but not have justification to relay.", - next_header.block_number, - ); - } + Ok(()) + }); + Ok(Self { _greet }) } - - Ok(()) } -async fn submit_finality( - header_relay: &HeaderRelay, - block_hash: impl AsRef, - justification: Vec, -) -> color_eyre::Result<()> { - let header = header_relay - .client_pangoro - .subxt() - .rpc() - .header(Some(sp_core::H256::from_str(block_hash.as_ref()).unwrap())) - .await? - .unwrap(); - let finality_target = FinalityTarget { - parent_hash: header.parent_hash, - number: header.number, - state_root: header.state_root, - extrinsics_root: header.extrinsics_root, - digest: Decode::decode(&mut header.digest.encode().as_slice())?, - __subxt_unused_type_params: Default::default(), - }; - let grandpa_justification = Decode::decode(&mut justification.as_slice())?; - let runtime = header_relay.client_pangolin.runtime(); - let track = runtime - .tx() - .bridge_pangoro_grandpa() - .submit_finality_proof(finality_target, grandpa_justification) - .sign_and_submit_then_watch(header_relay.client_pangolin.account().signer()) - .await?; - - let events = track.wait_for_finalized_success().await?; +async fn start() -> color_eyre::Result<()> { tracing::info!( - target: "pangolin-pangoro", - "[header-pangoro-to-pangolin] Header relayed: {:?}", - events.extrinsic_hash() + target: "pangolin-pangoro", + "[header-pangolin-to-pangoro] [pangoro-to-pangolin] SERVICE RESTARTING..." ); - Ok(()) + let bridge_config: BridgeConfig = Config::restore(Names::BridgePangolinPangoro)?; + let relay_config = bridge_config.relay; + + let client_pangolin = bridge_config.pangolin.to_pangolin_client().await?; + let client_pangoro = bridge_config.pangoro.to_pangoro_client().await?; + + let config_index = bridge_config.index; + let subquery_pangoro = config_index.to_pangoro_subquery()?; + let lanes = relay_config.raw_lanes(); + + let input = SolochainHeaderInput { + lanes, + client_source: client_pangoro, + client_target: client_pangolin, + subquery_source: subquery_pangoro, + index_origin_type: OriginType::BridgePangolin, + }; + let runner = SolochainHeaderRunner::new(input); + Ok(runner.start().await?) } diff --git a/bridges/pangolin-pangoro/bridge/src/service/message.rs b/bridges/pangolin-pangoro/bridge/src/service/message.rs index 79837e1a3..32ec5479b 100644 --- a/bridges/pangolin-pangoro/bridge/src/service/message.rs +++ b/bridges/pangolin-pangoro/bridge/src/service/message.rs @@ -1,4 +1,2 @@ pub mod pangolin_to_pangoro; pub mod pangoro_to_pangolin; - -mod types; diff --git a/bridges/pangolin-pangoro/bridge/src/service/message/pangolin_to_pangoro.rs b/bridges/pangolin-pangoro/bridge/src/service/message/pangolin_to_pangoro.rs index e257f1a9f..bdb8e1b8e 100644 --- a/bridges/pangolin-pangoro/bridge/src/service/message/pangolin_to_pangoro.rs +++ b/bridges/pangolin-pangoro/bridge/src/service/message/pangolin_to_pangoro.rs @@ -1,13 +1,15 @@ +use client_pangolin::client::PangolinClient; +use client_pangoro::client::PangoroClient; +use feemarket_ns2s::relay::basic::BasicRelayStrategy; use lifeline::{Lifeline, Service, Task}; +use subquery_s2s::types::RelayBlockOrigin; +use relay_s2s::message::{DeliveryRunner, ReceivingRunner}; +use relay_s2s::types::{MessageDeliveryInput, MessageReceivingInput}; +use support_common::config::{Config, Names}; use support_lifeline::service::BridgeService; -use crate::bridge::BridgeBus; -use crate::service::message::pangolin_to_pangoro::delivery_relay::DeliveryRunner; -use crate::service::message::pangolin_to_pangoro::receiving_relay::ReceivingRunner; - -mod delivery_relay; -mod receiving_relay; +use crate::bridge::{BridgeBus, BridgeConfig}; #[derive(Debug)] pub struct PangolinToPangoroMessageRelayService { @@ -25,14 +27,17 @@ impl Service for PangolinToPangoroMessageRelayService { let _greet_delivery = Self::try_task( "pangolin-to-pangoro-message-delivery-service", async move { - while let Err(e) = start_delivery_runner().await { + while let Err(e) = start_delivery().await { tracing::error!( target: "pangolin-pangoro", - "[delivery-pangolin-to-pangoro] Failed to start pangolin-to-pangoro message delivery relay, \ - wait some seconds try again: {:?}", + "[message-relay] [pangolin-to-pangoro] An error occurred for message delivery relay {:?}", e, ); tokio::time::sleep(std::time::Duration::from_secs(5)).await; + tracing::info!( + target: "pangolin-pangoro", + "[message-relay] [pangolin-to-pangoro] Try to restart message delivery relay service.", + ); } Ok(()) }, @@ -40,14 +45,17 @@ impl Service for PangolinToPangoroMessageRelayService { let _greet_receiving = Self::try_task( "pangolin-to-pangoro-message-receiving-service", async move { - while let Err(e) = start_receiving_runner().await { + while let Err(e) = start_receiving().await { tracing::error!( target: "pangolin-pangoro", - "[receiving-pangolin-pangoro] Failed to start pangolin-to-pangoro message confirm relay, \ - wait some seconds try again: {:?}", + "[message-relay] [pangolin-to-pangoro] An error occurred for message receiving relay {:?}", e, ); tokio::time::sleep(std::time::Duration::from_secs(5)).await; + tracing::info!( + target: "pangolin-pangoro", + "[message-relay] [pangolin-to-pangoro] Try to restart message receiving relay service.", + ); } Ok(()) }, @@ -59,12 +67,62 @@ impl Service for PangolinToPangoroMessageRelayService { } } -async fn start_delivery_runner() -> color_eyre::Result<()> { - let mut runner = DeliveryRunner::new().await?; - runner.start().await +async fn message_input() -> color_eyre::Result> +{ + let bridge_config: BridgeConfig = Config::restore(Names::BridgePangolinPangoro)?; + let relay_config = bridge_config.relay; + + let client_pangolin = bridge_config.pangolin.to_pangolin_client().await?; + let client_pangoro = bridge_config.pangoro.to_pangoro_client().await?; + + let config_index = bridge_config.index; + let subquery_pangolin = config_index.to_pangolin_subquery()?; + let subquery_pangoro = config_index.to_pangoro_subquery()?; + + let lanes = relay_config.raw_lanes(); + + let input = MessageReceivingInput { + lanes, + relayer_account: client_pangolin.account().account_id().clone(), + client_source: client_pangolin, + client_target: client_pangoro, + subquery_source: subquery_pangolin, + subquery_target: subquery_pangoro, + }; + Ok(input) +} + +async fn start_delivery() -> color_eyre::Result<()> { + tracing::info!( + target: "pangolin-pangoro", + "[message-delivery] [delivery-pangolin-to-pangoro] SERVICE RESTARTING..." + ); + let input = message_input().await?; + let relay_strategy = BasicRelayStrategy::new( + input.client_source.clone(), + input.client_source.account().account_id().clone(), + ); + let input = MessageDeliveryInput { + lanes: input.lanes, + nonces_limit: 11, + relayer_account: input.relayer_account, + client_source: input.client_source, + client_target: input.client_target, + subquery_source: input.subquery_source, + subquery_target: input.subquery_target, + relay_block_origin: RelayBlockOrigin::BridgePangoro, + relay_strategy, + }; + let runner = DeliveryRunner::new(input); + Ok(runner.start().await?) } -async fn start_receiving_runner() -> color_eyre::Result<()> { - let mut runner = ReceivingRunner::new().await?; - runner.start().await +async fn start_receiving() -> color_eyre::Result<()> { + tracing::info!( + target: "pangolin-pangoro", + "[message-receiving] [receiving-pangolin-to-pangoro] SERVICE RESTARTING..." + ); + let input = message_input().await?; + let runner = ReceivingRunner::new(input); + Ok(runner.start().await?) } diff --git a/bridges/pangolin-pangoro/bridge/src/service/message/pangolin_to_pangoro/delivery_relay.rs b/bridges/pangolin-pangoro/bridge/src/service/message/pangolin_to_pangoro/delivery_relay.rs deleted file mode 100644 index 0c3124db5..000000000 --- a/bridges/pangolin-pangoro/bridge/src/service/message/pangolin_to_pangoro/delivery_relay.rs +++ /dev/null @@ -1,298 +0,0 @@ -use std::ops::RangeInclusive; - -use client_pangolin::subxt_runtime::api::bridge_pangoro_messages::storage::{ - OutboundLanes, OutboundMessages, -}; -use client_pangolin::types::runtime_types as pangolin_runtime_types; -use client_pangolin::types::runtime_types::bp_messages::{MessageKey, OutboundLaneData}; -use client_pangoro::types::runtime_types::bridge_runtime_common::messages::target::FromBridgedChainMessagesProof; -use subquery_s2s::types::RelayBlockOrigin; -use subxt::storage::StorageKeyPrefix; -use subxt::StorageEntry; - -use support_common::error::BridgerError; - -use crate::service::message::types::MessageRelay; - -/// Message payload for This -> Bridged chain messages. -type FromThisChainMessagePayload = pangolin_runtime_types::bp_message_dispatch::MessagePayload< - sp_core::crypto::AccountId32, - pangolin_runtime_types::sp_runtime::MultiSigner, - pangolin_runtime_types::sp_runtime::MultiSignature, - Vec, ->; - -pub struct DeliveryRunner { - message_relay: MessageRelay, - last_relayed_nonce: Option, -} - -impl DeliveryRunner { - pub async fn new() -> color_eyre::Result { - let message_relay = MessageRelay::new().await?; - Ok(Self { - message_relay, - last_relayed_nonce: None, - }) - } -} - -// defined -impl DeliveryRunner { - async fn source_outbound_lane_data(&self) -> color_eyre::Result { - let lane = self.message_relay.lane()?; - let outbound_lane_data = self - .message_relay - .client_pangolin - .runtime() - .storage() - .bridge_pangoro_messages() - .outbound_lanes(lane.0, None) - .await?; - Ok(outbound_lane_data) - } - - async fn assemble_nonces( - &self, - limit: u64, - outbound_lane_data: &OutboundLaneData, - ) -> color_eyre::Result>> { - let (latest_confirmed_nonce, latest_generated_nonce) = ( - outbound_lane_data.latest_received_nonce, - outbound_lane_data.latest_generated_nonce, - ); - if latest_confirmed_nonce == latest_generated_nonce { - return Ok(None); - } - - // assemble nonce range - let start: u64 = latest_confirmed_nonce + 1; - if let Some(last_relayed_nonce) = self.last_relayed_nonce { - if last_relayed_nonce >= start { - tracing::warn!( - target: "pangolin-pangoro", - "[delivery-pangolin-to-pangoro] There is already a batch of transactions in progress. \ - Will wait for the previous batch to complete. last relayed noce is {} and expect to start with {}. \ - please wait receiving.", - last_relayed_nonce, - start, - ); - return Ok(None); - } - } - - let inclusive_limit = limit - 1; - tracing::info!( - target: "pangolin-pangoro", - "[delivery-pangolin-to-pangoro] Assemble nonces, start from {} and last generated is {}", - start, - latest_generated_nonce, - ); - let end: u64 = if latest_generated_nonce - start > inclusive_limit { - start + inclusive_limit - } else { - latest_generated_nonce - }; - let nonces = start..=end; - Ok(Some(nonces)) - } -} - -impl DeliveryRunner { - pub async fn start(&mut self) -> color_eyre::Result<()> { - tracing::info!( - target: "pangolin-pangoro", - "[delivery-pangolin-to-pangoro] SERVICE RESTARTING..." - ); - loop { - match self.run(10).await { - Ok(last_relayed_nonce) => { - if last_relayed_nonce.is_some() { - self.last_relayed_nonce = last_relayed_nonce; - } - } - Err(err) => { - tracing::error!( - target: "pangolin-pangoro", - "[delivery-pangolin-to-pangoro] Failed to delivery message: {:?}", - err - ); - self.message_relay = MessageRelay::new().await?; - } - } - tokio::time::sleep(std::time::Duration::from_secs(5)).await; - } - } - - async fn run(&self, limit: u64) -> color_eyre::Result> { - let lane = self.message_relay.lane()?; - let source_outbound_lane_data = self.source_outbound_lane_data().await?; - - // alias - let client_pangolin = &self.message_relay.client_pangolin; - let client_pangoro = &self.message_relay.client_pangoro; - let subquery_pangolin = &self.message_relay.subquery_pangolin; - - let nonces = match self - .assemble_nonces(limit, &source_outbound_lane_data) - .await? - { - Some(v) => v, - None => { - tracing::info!( - target: "pangolin-pangoro", - "[delivery-pangolin-to-pangoro] All nonces delivered, nothing to do." - ); - return Ok(None); - } - }; - tracing::info!( - target: "pangolin-pangoro", - "[delivery-pangolin-to-pangoro] Assembled nonces {:?}", - nonces, - ); - - // query last nonce block information - let last_relay = match subquery_pangolin - .query_need_relay(RelayBlockOrigin::BridgePangoro, lane.0, *nonces.end()) - .await? - { - Some(v) => v, - None => { - tracing::warn!( - target: "pangolin-pangoro", - "[delivery-pangolin-to-pangoro] The last nonce({}) isn't storage by indexer", - nonces.end(), - ); - return Ok(None); - } - }; - - // query last relayed header - let last_relayed_pangolin_hash_in_pangoro = client_pangoro - .runtime() - .storage() - .bridge_pangolin_grandpa() - .best_finalized(None) - .await?; - let last_relayed_pangolin_block_in_pangoro = client_pangolin - .subxt() - .rpc() - .block(Some(last_relayed_pangolin_hash_in_pangoro)) - .await? - .ok_or_else(|| { - BridgerError::Custom(format!( - "Failed to query block by [{}] in pangolin", - last_relayed_pangolin_hash_in_pangoro - )) - })?; - - // compare last nonce block with last relayed header - let relayed_block_number = last_relayed_pangolin_block_in_pangoro.block.header.number; - if relayed_block_number < last_relay.block_number { - tracing::warn!( - target: "pangolin-pangoro", - "[delivery-pangolin-to-pangoro] The last nonce({}) at block {} is less then last relayed header {}, \ - please wait header relay.", - nonces.end(), - last_relay.block_number, - relayed_block_number, - ); - return Ok(None); - } - - // read proof - let mut storage_keys = Vec::with_capacity((nonces.end() - nonces.start()) as usize + 1); - let mut message_nonce = *nonces.start(); - while message_nonce <= *nonces.end() { - let prefix = StorageKeyPrefix::new::(); - let message_key = OutboundMessages(MessageKey { - lane_id: lane.0, - nonce: message_nonce, - }) - .key() - .final_key(prefix); - storage_keys.push(message_key); - message_nonce += 1; - } - - //- query inbound land data - let target_inbound_lane_data = client_pangoro - .runtime() - .storage() - .bridge_pangolin_messages() - .inbound_lanes(lane.0, None) - .await?; - let outbound_state_proof_required = target_inbound_lane_data.last_confirmed_nonce - < source_outbound_lane_data.latest_received_nonce; - if outbound_state_proof_required { - storage_keys.push( - OutboundLanes(lane.0) - .key() - .final_key(StorageKeyPrefix::new::()), - ); - } - - // fill delivery data - let mut total_weight = 0u64; - for message_nonce in nonces.clone() { - let message_data = client_pangolin - .runtime() - .storage() - .bridge_pangoro_messages() - .outbound_messages( - MessageKey { - lane_id: lane.0, - nonce: message_nonce, - }, - None, - ) - .await? - .ok_or_else(|| { - BridgerError::Custom(format!( - "Can not read message data by nonce {} in pangolin", - message_nonce - )) - })?; - let decoded_payload: FromThisChainMessagePayload = - codec::Decode::decode(&mut &message_data.payload[..])?; - total_weight += decoded_payload.weight; - } - - // query last relayed header - let read_proof = client_pangolin - .subxt() - .rpc() - .read_proof(storage_keys, Some(last_relayed_pangolin_hash_in_pangoro)) - .await?; - let proof: Vec> = read_proof.proof.into_iter().map(|item| item.0).collect(); - let proof = FromBridgedChainMessagesProof { - bridged_header_hash: last_relayed_pangolin_hash_in_pangoro, - storage_proof: proof, - lane: lane.0, - nonces_start: *nonces.start(), - nonces_end: *nonces.end(), - }; - - let hash = client_pangoro - .runtime() - .tx() - .bridge_pangolin_messages() - .receive_messages_proof( - client_pangoro.account().account_id().clone(), - proof, - (nonces.end() - nonces.start() + 1) as _, - total_weight, - ) - .sign_and_submit(client_pangoro.account().signer()) - .await?; - - tracing::debug!( - target: "pangolin-pangoro", - "[delivery-pangolin-to-pangoro] The nonces {:?} in pangolin delivered to pangoro -> {}", - nonces, - array_bytes::bytes2hex("0x", hash.0), - ); - Ok(Some(*nonces.end())) - } -} diff --git a/bridges/pangolin-pangoro/bridge/src/service/message/pangolin_to_pangoro/receiving_relay.rs b/bridges/pangolin-pangoro/bridge/src/service/message/pangolin_to_pangoro/receiving_relay.rs deleted file mode 100644 index 5fc22b268..000000000 --- a/bridges/pangolin-pangoro/bridge/src/service/message/pangolin_to_pangoro/receiving_relay.rs +++ /dev/null @@ -1,230 +0,0 @@ -use std::collections::VecDeque; - -use client_pangolin::types::runtime_types::bp_messages::{ - OutboundLaneData, UnrewardedRelayersState, -}; -use client_pangolin::types::runtime_types::bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof; -use client_pangoro::subxt_runtime::api::bridge_pangolin_messages::storage::InboundLanes; -use subxt::storage::StorageKeyPrefix; -use subxt::StorageEntry; - -use crate::service::message::types::MessageRelay; - -pub struct ReceivingRunner { - message_relay: MessageRelay, - last_relayed_nonce: Option, -} - -impl ReceivingRunner { - pub async fn new() -> color_eyre::Result { - let message_relay = MessageRelay::new().await?; - Ok(Self { - message_relay, - last_relayed_nonce: None, - }) - } -} - -impl ReceivingRunner { - async fn source_outbound_lane_data(&self) -> color_eyre::Result { - let lane = self.message_relay.lane()?; - let outbound_lane_data = self - .message_relay - .client_pangolin - .runtime() - .storage() - .bridge_pangoro_messages() - .outbound_lanes(lane.0, None) - .await?; - Ok(outbound_lane_data) - } - - async fn target_unrewarded_relayers_state( - &self, - at_block: sp_core::H256, - source_outbound_lane_data: &OutboundLaneData, - ) -> color_eyre::Result> { - let block_hex = array_bytes::bytes2hex("0x", at_block.0); - let lane = self.message_relay.lane()?; - let inbound_lane_data = self - .message_relay - .client_pangoro - .runtime() - .storage() - .bridge_pangolin_messages() - .inbound_lanes(lane.0, Some(at_block)) - .await?; - let max_confirm_end_at_target = inbound_lane_data - .relayers - .iter() - .map(|item| item.messages.end) - .max() - .unwrap_or(0u64); - tracing::trace!( - target: "pangolin-pangoro", - "[receiving-pangolin-to-pangoro] max dispatch nonce({}) at target and last received nonce from source is {}. \ - queried by relayed block {}", - max_confirm_end_at_target, - source_outbound_lane_data.latest_received_nonce, - block_hex, - ); - if max_confirm_end_at_target == source_outbound_lane_data.latest_received_nonce { - tracing::info!( - target: "pangolin-pangoro", - "[receiving-pangolin-to-pangoro] max dispatch nonce({}) at target is same with last received nonce({}) at source. \ - queried by relayed block {}. nothing to do.", - max_confirm_end_at_target, - source_outbound_lane_data.latest_received_nonce, - block_hex, - ); - return Ok(None); - } - if let Some(last_relayed_nonce) = self.last_relayed_nonce { - if last_relayed_nonce >= max_confirm_end_at_target { - tracing::warn!( - target: "pangolin-pangoro", - "[receiving-pangolin-to-pangoro] This nonce({}) is being processed. Please waiting for the processing to finish.", - max_confirm_end_at_target, - ); - return Ok(None); - } - } - let relayers = VecDeque::from_iter(inbound_lane_data.relayers.as_slice()); - let total_unrewarded_messages = match (relayers.front(), relayers.back()) { - (Some(front), Some(back)) => { - if back.messages.end < front.messages.begin { - Some(0) - } else { - let difference = back.messages.end - front.messages.begin; - Some(difference + 1) - } - } - _ => Some(0), - }; - if total_unrewarded_messages.is_none() { - tracing::info!( - target: "pangolin-pangoro", - "[receiving-pangolin-to-pangoro] Not have unrewarded message. nothing to do.", - ); - return Ok(None); - } - Ok(Some(( - max_confirm_end_at_target, - UnrewardedRelayersState { - unrewarded_relayer_entries: relayers.len() as _, - messages_in_oldest_entry: relayers - .front() - .map(|entry| 1 + entry.messages.end - entry.messages.begin) - .unwrap_or(u64::MAX), - total_messages: total_unrewarded_messages.expect("Unreachable"), - }, - ))) - } -} - -impl ReceivingRunner { - pub async fn start(&mut self) -> color_eyre::Result<()> { - tracing::info!( - target: "pangolin-pangoro", - "[receiving-pangolin-to-pangoro] SERVICE RESTARTING..." - ); - loop { - match self.run().await { - Ok(last_relayed_nonce) => { - if last_relayed_nonce.is_some() { - self.last_relayed_nonce = last_relayed_nonce; - } - } - Err(err) => { - tracing::error!( - target: "pangolin-pangoro", - "[receiving-pangolin-to-pangoro] Failed to receiving message: {:?}", - err - ); - self.message_relay = MessageRelay::new().await?; - } - } - tokio::time::sleep(std::time::Duration::from_secs(5)).await; - } - } - - async fn run(&self) -> color_eyre::Result> { - let lane = self.message_relay.lane()?; - - // alias - let client_pangolin = &self.message_relay.client_pangolin; - let client_pangoro = &self.message_relay.client_pangoro; - - let source_outbound_lane_data = self.source_outbound_lane_data().await?; - if source_outbound_lane_data.latest_received_nonce - == source_outbound_lane_data.latest_generated_nonce - { - tracing::info!( - target: "pangolin-pangoro", - "[receiving-pangolin-to-pangoro] All nonces received, nothing to do.", - ); - return Ok(None); - } - - // query last relayed header - let last_relayed_pangoro_hash_in_pangolin = client_pangolin - .runtime() - .storage() - .bridge_pangoro_grandpa() - .best_finalized(None) - .await?; - - // assemble unrewarded relayers state - let (max_confirmed_nonce_at_target, relayers_state) = match self - .target_unrewarded_relayers_state( - last_relayed_pangoro_hash_in_pangolin, - &source_outbound_lane_data, - ) - .await? - { - Some(v) => v, - None => { - tracing::warn!( - target: "pangolin-pangoro", - "[receiving-pangolin-to-pangoro] No unrewarded relayers state found by pangoro", - ); - return Ok(None); - } - }; - - // read proof - let inbound_data_key = InboundLanes(lane.0) - .key() - .final_key(StorageKeyPrefix::new::()); - let read_proof = client_pangoro - .subxt() - .rpc() - .read_proof( - vec![inbound_data_key], - Some(last_relayed_pangoro_hash_in_pangolin), - ) - .await?; - let proof: Vec> = read_proof.proof.into_iter().map(|item| item.0).collect(); - let proof = FromBridgedChainMessagesDeliveryProof { - bridged_header_hash: last_relayed_pangoro_hash_in_pangolin, - storage_proof: proof, - lane: lane.0, - }; - - // send proof - let hash = client_pangolin - .runtime() - .tx() - .bridge_pangoro_messages() - .receive_messages_delivery_proof(proof, relayers_state) - .sign_and_submit(client_pangolin.account().signer()) - .await?; - - tracing::debug!( - target: "pangolin-pangoro", - "[receiving-pangolin-to-pangoro] receiving extensics sent successful: {}", - array_bytes::bytes2hex("0x", hash.0), - ); - Ok(Some(max_confirmed_nonce_at_target)) - } -} diff --git a/bridges/pangolin-pangoro/bridge/src/service/message/pangoro_to_pangolin.rs b/bridges/pangolin-pangoro/bridge/src/service/message/pangoro_to_pangolin.rs index 1ed64e6b1..b2940dc4e 100644 --- a/bridges/pangolin-pangoro/bridge/src/service/message/pangoro_to_pangolin.rs +++ b/bridges/pangolin-pangoro/bridge/src/service/message/pangoro_to_pangolin.rs @@ -1,13 +1,15 @@ +use client_pangolin::client::PangolinClient; +use client_pangoro::client::PangoroClient; use lifeline::{Lifeline, Service, Task}; +use subquery_s2s::types::RelayBlockOrigin; +use feemarket_ns2s::relay::basic::BasicRelayStrategy; +use relay_s2s::message::{DeliveryRunner, ReceivingRunner}; +use relay_s2s::types::{MessageDeliveryInput, MessageReceivingInput}; +use support_common::config::{Config, Names}; use support_lifeline::service::BridgeService; -use crate::bridge::BridgeBus; -use crate::service::message::pangoro_to_pangolin::delivery_relay::DeliveryRunner; -use crate::service::message::pangoro_to_pangolin::receiving_relay::ReceivingRunner; - -mod delivery_relay; -mod receiving_relay; +use crate::bridge::{BridgeBus, BridgeConfig}; #[derive(Debug)] pub struct PangoroToPangolinMessageRelayService { @@ -25,14 +27,17 @@ impl Service for PangoroToPangolinMessageRelayService { let _greet_delivery = Self::try_task( "pangoro-to-pangolin-message-delivery-service", async move { - while let Err(e) = start_delivery_runner().await { + while let Err(e) = start_delivery().await { tracing::error!( target: "pangolin-pangoro", - "[delivery-pangoro-to-pangolin] Failed to start pangoro-to-pangolin message delivery relay, \ - wait some seconds try again: {:?}", + "[message-relay] [pangoro-to-pangolin] An error occurred for message delivery relay {:?}", e, ); tokio::time::sleep(std::time::Duration::from_secs(5)).await; + tracing::info!( + target: "pangolin-pangoro", + "[message-relay] [pangoro-to-pangolin] Try to restart message delivery relay service.", + ); } Ok(()) }, @@ -40,14 +45,17 @@ impl Service for PangoroToPangolinMessageRelayService { let _greet_receiving = Self::try_task( "pangoro-to-pangolin-message-receiving-service", async move { - while let Err(e) = start_receiving_runner().await { + while let Err(e) = start_receiving().await { tracing::error!( target: "pangolin-pangoro", - "[receiving-pangoro-to-pangolin] Failed to start pangoro-to-pangolin message confirm relay, \ - wait some seconds try again: {:?}", + "[message-relay] [pangoro-to-pangolin] An error occurred for message receiving relay {:?}", e, ); tokio::time::sleep(std::time::Duration::from_secs(5)).await; + tracing::info!( + target: "pangolin-pangoro", + "[message-relay] [pangoro-to-pangolin] Try to restart message receiving relay service.", + ); } Ok(()) }, @@ -59,12 +67,62 @@ impl Service for PangoroToPangolinMessageRelayService { } } -async fn start_delivery_runner() -> color_eyre::Result<()> { - let mut runner = DeliveryRunner::new().await?; - runner.start().await +async fn message_input() -> color_eyre::Result> +{ + let bridge_config: BridgeConfig = Config::restore(Names::BridgePangolinPangoro)?; + let relay_config = bridge_config.relay; + + let client_pangoro = bridge_config.pangoro.to_pangoro_client().await?; + let client_pangolin = bridge_config.pangolin.to_pangolin_client().await?; + + let config_index = bridge_config.index; + let subquery_pangolin = config_index.to_pangolin_subquery()?; + let subquery_pangoro = config_index.to_pangoro_subquery()?; + + let lanes = relay_config.raw_lanes(); + + let input = MessageReceivingInput { + lanes, + relayer_account: client_pangoro.account().account_id().clone(), + client_source: client_pangoro, + client_target: client_pangolin, + subquery_source: subquery_pangoro, + subquery_target: subquery_pangolin, + }; + Ok(input) +} + +async fn start_delivery() -> color_eyre::Result<()> { + tracing::info!( + target: "pangolin-pangoro", + "[message-delivery] [delivery-pangoro-to-pangolin] SERVICE RESTARTING..." + ); + let input = message_input().await?; + let relay_strategy = BasicRelayStrategy::new( + input.client_source.clone(), + input.client_source.account().account_id().clone(), + ); + let input = MessageDeliveryInput { + lanes: input.lanes, + nonces_limit: 11, + relayer_account: input.relayer_account, + client_source: input.client_source, + client_target: input.client_target, + subquery_source: input.subquery_source, + subquery_target: input.subquery_target, + relay_block_origin: RelayBlockOrigin::BridgePangolin, + relay_strategy, + }; + let runner = DeliveryRunner::new(input); + Ok(runner.start().await?) } -async fn start_receiving_runner() -> color_eyre::Result<()> { - let mut runner = ReceivingRunner::new().await?; - runner.start().await +async fn start_receiving() -> color_eyre::Result<()> { + tracing::info!( + target: "pangolin-pangoro", + "[message-receiving] [receiving-pangoro-to-pangolin] SERVICE RESTARTING..." + ); + let input = message_input().await?; + let runner = ReceivingRunner::new(input); + Ok(runner.start().await?) } diff --git a/bridges/pangolin-pangoro/bridge/src/service/message/pangoro_to_pangolin/delivery_relay.rs b/bridges/pangolin-pangoro/bridge/src/service/message/pangoro_to_pangolin/delivery_relay.rs deleted file mode 100644 index 858c81efd..000000000 --- a/bridges/pangolin-pangoro/bridge/src/service/message/pangoro_to_pangolin/delivery_relay.rs +++ /dev/null @@ -1,298 +0,0 @@ -use std::ops::RangeInclusive; - -use client_pangolin::types::runtime_types::bridge_runtime_common::messages::target::FromBridgedChainMessagesProof; -use client_pangoro::subxt_runtime::api::bridge_pangolin_messages::storage::{ - OutboundLanes, OutboundMessages, -}; -use client_pangoro::types::runtime_types as pangoro_runtime_types; -use client_pangoro::types::runtime_types::bp_messages::{MessageKey, OutboundLaneData}; -use subquery_s2s::types::RelayBlockOrigin; -use subxt::storage::StorageKeyPrefix; -use subxt::StorageEntry; - -use support_common::error::BridgerError; - -use crate::service::message::types::MessageRelay; - -/// Message payload for This -> Bridged chain messages. -type FromThisChainMessagePayload = pangoro_runtime_types::bp_message_dispatch::MessagePayload< - sp_core::crypto::AccountId32, - pangoro_runtime_types::sp_runtime::MultiSigner, - pangoro_runtime_types::sp_runtime::MultiSignature, - Vec, ->; - -pub struct DeliveryRunner { - message_relay: MessageRelay, - last_relayed_nonce: Option, -} - -impl DeliveryRunner { - pub async fn new() -> color_eyre::Result { - let message_relay = MessageRelay::new().await?; - Ok(Self { - message_relay, - last_relayed_nonce: None, - }) - } -} - -// defined -impl DeliveryRunner { - async fn source_outbound_lane_data(&self) -> color_eyre::Result { - let lane = self.message_relay.lane()?; - let outbound_lane_data = self - .message_relay - .client_pangoro - .runtime() - .storage() - .bridge_pangolin_messages() - .outbound_lanes(lane.0, None) - .await?; - Ok(outbound_lane_data) - } - - async fn assemble_nonces( - &self, - limit: u64, - outbound_lane_data: &OutboundLaneData, - ) -> color_eyre::Result>> { - let (latest_confirmed_nonce, latest_generated_nonce) = ( - outbound_lane_data.latest_received_nonce, - outbound_lane_data.latest_generated_nonce, - ); - if latest_confirmed_nonce == latest_generated_nonce { - return Ok(None); - } - - // assemble nonce range - let start: u64 = latest_confirmed_nonce + 1; - if let Some(last_relayed_nonce) = self.last_relayed_nonce { - if last_relayed_nonce >= start { - tracing::warn!( - target: "pangolin-pangoro", - "[delivery-pangoro-to-pangolin] There is already a batch of transactions in progress. \ - Will wait for the previous batch to complete. last relayed noce is {} and expect to start with {}. \ - please wait receiving.", - last_relayed_nonce, - start, - ); - return Ok(None); - } - } - - let inclusive_limit = limit - 1; - tracing::info!( - target: "pangolin-pangoro", - "[delivery-pangoro-to-pangolin] Assemble nonces, start from {} and last generated is {}", - start, - latest_generated_nonce, - ); - let end: u64 = if latest_generated_nonce - start > inclusive_limit { - start + inclusive_limit - } else { - latest_generated_nonce - }; - let nonces = start..=end; - Ok(Some(nonces)) - } -} - -impl DeliveryRunner { - pub async fn start(&mut self) -> color_eyre::Result<()> { - tracing::info!( - target: "pangolin-pangoro", - "[delivery-pangoro-to-pangolin] SERVICE RESTARTING..." - ); - loop { - match self.run(10).await { - Ok(last_relayed_nonce) => { - if last_relayed_nonce.is_some() { - self.last_relayed_nonce = last_relayed_nonce; - } - } - Err(err) => { - tracing::error!( - target: "pangolin-pangoro", - "[delivery-pangoro-to-pangolin] Failed to delivery message: {:?}", - err - ); - self.message_relay = MessageRelay::new().await?; - } - } - tokio::time::sleep(std::time::Duration::from_secs(5)).await; - } - } - - async fn run(&self, limit: u64) -> color_eyre::Result> { - let lane = self.message_relay.lane()?; - let source_outbound_lane_data = self.source_outbound_lane_data().await?; - - // alias - let client_pangoro = &self.message_relay.client_pangoro; - let client_pangolin = &self.message_relay.client_pangolin; - let subquery_pangoro = &self.message_relay.subquery_pangoro; - - let nonces = match self - .assemble_nonces(limit, &source_outbound_lane_data) - .await? - { - Some(v) => v, - None => { - tracing::info!( - target: "pangolin-pangoro", - "[delivery-pangoro-to-pangolin] All nonces delivered, nothing to do." - ); - return Ok(None); - } - }; - tracing::info!( - target: "pangolin-pangoro", - "[delivery-pangoro-to-pangolin] Assembled nonces {:?}", - nonces, - ); - - // query last nonce block information - let last_relay = match subquery_pangoro - .query_need_relay(RelayBlockOrigin::BridgePangolin, lane.0, *nonces.end()) - .await? - { - Some(v) => v, - None => { - tracing::warn!( - target: "pangolin-pangoro", - "[delivery-pangoro-to-pangolin] The last nonce({}) isn't storage by indexer", - nonces.end(), - ); - return Ok(None); - } - }; - - // query last relayed header - let last_relayed_pangoro_hash_in_pangolin = client_pangolin - .runtime() - .storage() - .bridge_pangoro_grandpa() - .best_finalized(None) - .await?; - let last_relayed_pangoro_block_in_pangolin = client_pangoro - .subxt() - .rpc() - .block(Some(last_relayed_pangoro_hash_in_pangolin)) - .await? - .ok_or_else(|| { - BridgerError::Custom(format!( - "Failed to query block by [{}] in pangoro", - last_relayed_pangoro_hash_in_pangolin - )) - })?; - - // compare last nonce block with last relayed header - let relayed_block_number = last_relayed_pangoro_block_in_pangolin.block.header.number; - if relayed_block_number < last_relay.block_number { - tracing::warn!( - target: "pangolin-pangoro", - "[delivery-pangoro-to-pangolin] The last nonce({}) at block {} is less then last relayed header {}, \ - please wait header relay.", - nonces.end(), - last_relay.block_number, - relayed_block_number, - ); - return Ok(None); - } - - // read proof - let mut storage_keys = Vec::with_capacity((nonces.end() - nonces.start()) as usize + 1); - let mut message_nonce = *nonces.start(); - while message_nonce <= *nonces.end() { - let prefix = StorageKeyPrefix::new::(); - let message_key = OutboundMessages(MessageKey { - lane_id: lane.0, - nonce: message_nonce, - }) - .key() - .final_key(prefix); - storage_keys.push(message_key); - message_nonce += 1; - } - - //- query inbound land data - let target_inbound_lane_data = client_pangolin - .runtime() - .storage() - .bridge_pangoro_messages() - .inbound_lanes(lane.0, None) - .await?; - let outbound_state_proof_required = target_inbound_lane_data.last_confirmed_nonce - < source_outbound_lane_data.latest_received_nonce; - if outbound_state_proof_required { - storage_keys.push( - OutboundLanes(lane.0) - .key() - .final_key(StorageKeyPrefix::new::()), - ); - } - - // fill delivery data - let mut total_weight = 0u64; - for message_nonce in nonces.clone() { - let message_data = client_pangoro - .runtime() - .storage() - .bridge_pangolin_messages() - .outbound_messages( - MessageKey { - lane_id: lane.0, - nonce: message_nonce, - }, - None, - ) - .await? - .ok_or_else(|| { - BridgerError::Custom(format!( - "Can not read message data by nonce {} in pangoro", - message_nonce - )) - })?; - let decoded_payload: FromThisChainMessagePayload = - codec::Decode::decode(&mut &message_data.payload[..])?; - total_weight += decoded_payload.weight; - } - - // query last relayed header - let read_proof = client_pangoro - .subxt() - .rpc() - .read_proof(storage_keys, Some(last_relayed_pangoro_hash_in_pangolin)) - .await?; - let proof: Vec> = read_proof.proof.into_iter().map(|item| item.0).collect(); - let proof = FromBridgedChainMessagesProof { - bridged_header_hash: last_relayed_pangoro_hash_in_pangolin, - storage_proof: proof, - lane: lane.0, - nonces_start: *nonces.start(), - nonces_end: *nonces.end(), - }; - - let hash = client_pangolin - .runtime() - .tx() - .bridge_pangoro_messages() - .receive_messages_proof( - client_pangolin.account().account_id().clone(), - proof, - (nonces.end() - nonces.start() + 1) as _, - total_weight, - ) - .sign_and_submit(client_pangolin.account().signer()) - .await?; - - tracing::debug!( - target: "pangolin-pangoro", - "[delivery-pangoro-to-pangolin] The nonces {:?} in pangoro delivered to pangolin -> {}", - nonces, - array_bytes::bytes2hex("0x", hash.0), - ); - Ok(Some(*nonces.end())) - } -} diff --git a/bridges/pangolin-pangoro/bridge/src/service/message/pangoro_to_pangolin/receiving_relay.rs b/bridges/pangolin-pangoro/bridge/src/service/message/pangoro_to_pangolin/receiving_relay.rs deleted file mode 100644 index 5e1987037..000000000 --- a/bridges/pangolin-pangoro/bridge/src/service/message/pangoro_to_pangolin/receiving_relay.rs +++ /dev/null @@ -1,230 +0,0 @@ -use std::collections::VecDeque; - -use client_pangolin::subxt_runtime::api::bridge_pangoro_messages::storage::InboundLanes; -use client_pangoro::types::runtime_types::bp_messages::{ - OutboundLaneData, UnrewardedRelayersState, -}; -use client_pangoro::types::runtime_types::bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof; -use subxt::storage::StorageKeyPrefix; -use subxt::StorageEntry; - -use crate::service::message::types::MessageRelay; - -pub struct ReceivingRunner { - message_relay: MessageRelay, - last_relayed_nonce: Option, -} - -impl ReceivingRunner { - pub async fn new() -> color_eyre::Result { - let message_relay = MessageRelay::new().await?; - Ok(Self { - message_relay, - last_relayed_nonce: None, - }) - } -} - -impl ReceivingRunner { - async fn source_outbound_lane_data(&self) -> color_eyre::Result { - let lane = self.message_relay.lane()?; - let outbound_lane_data = self - .message_relay - .client_pangoro - .runtime() - .storage() - .bridge_pangolin_messages() - .outbound_lanes(lane.0, None) - .await?; - Ok(outbound_lane_data) - } - - async fn target_unrewarded_relayers_state( - &self, - at_block: sp_core::H256, - source_outbound_lane_data: &OutboundLaneData, - ) -> color_eyre::Result> { - let block_hex = array_bytes::bytes2hex("0x", at_block.0); - let lane = self.message_relay.lane()?; - let inbound_lane_data = self - .message_relay - .client_pangolin - .runtime() - .storage() - .bridge_pangoro_messages() - .inbound_lanes(lane.0, Some(at_block)) - .await?; - let max_confirm_end_at_target = inbound_lane_data - .relayers - .iter() - .map(|item| item.messages.end) - .max() - .unwrap_or(0u64); - tracing::trace!( - target: "pangolin-pangoro", - "[receiving-pangoro-to-pangolin] max dispatch nonce({}) at target and last received nonce from source is {}. \ - queried by relayed block {}", - max_confirm_end_at_target, - source_outbound_lane_data.latest_received_nonce, - block_hex, - ); - if max_confirm_end_at_target == source_outbound_lane_data.latest_received_nonce { - tracing::info!( - target: "pangolin-pangoro", - "[receiving-pangoro-to-pangolin] max dispatch nonce({}) at target is same with last received nonce({}) at source. \ - queried by relayed block {}. nothing to do.", - max_confirm_end_at_target, - source_outbound_lane_data.latest_received_nonce, - block_hex, - ); - return Ok(None); - } - if let Some(last_relayed_nonce) = self.last_relayed_nonce { - if last_relayed_nonce >= max_confirm_end_at_target { - tracing::warn!( - target: "pangolin-pangoro", - "[receiving-pangoro-to-pangolin] This nonce({}) is being processed. Please waiting for the processing to finish.", - max_confirm_end_at_target, - ); - return Ok(None); - } - } - let relayers = VecDeque::from_iter(inbound_lane_data.relayers.as_slice()); - let total_unrewarded_messages = match (relayers.front(), relayers.back()) { - (Some(front), Some(back)) => { - if back.messages.end < front.messages.begin { - Some(0) - } else { - let difference = back.messages.end - front.messages.begin; - Some(difference + 1) - } - } - _ => Some(0), - }; - if total_unrewarded_messages.is_none() { - tracing::info!( - target: "pangolin-pangoro", - "[receiving-pangoro-to-pangolin] Not have unrewarded message. nothing to do.", - ); - return Ok(None); - } - Ok(Some(( - max_confirm_end_at_target, - UnrewardedRelayersState { - unrewarded_relayer_entries: relayers.len() as _, - messages_in_oldest_entry: relayers - .front() - .map(|entry| 1 + entry.messages.end - entry.messages.begin) - .unwrap_or(u64::MAX), - total_messages: total_unrewarded_messages.expect("Unreachable"), - }, - ))) - } -} - -impl ReceivingRunner { - pub async fn start(&mut self) -> color_eyre::Result<()> { - tracing::info!( - target: "pangolin-pangoro", - "[receiving-pangoro-to-pangolin] SERVICE RESTARTING..." - ); - loop { - match self.run().await { - Ok(last_relayed_nonce) => { - if last_relayed_nonce.is_some() { - self.last_relayed_nonce = last_relayed_nonce; - } - } - Err(err) => { - tracing::error!( - target: "pangolin-pangoro", - "[receiving-pangoro-to-pangolin] Failed to receiving message: {:?}", - err - ); - self.message_relay = MessageRelay::new().await?; - } - } - tokio::time::sleep(std::time::Duration::from_secs(5)).await; - } - } - - async fn run(&self) -> color_eyre::Result> { - let lane = self.message_relay.lane()?; - - // alias - let client_pangoro = &self.message_relay.client_pangoro; - let client_pangolin = &self.message_relay.client_pangolin; - - let source_outbound_lane_data = self.source_outbound_lane_data().await?; - if source_outbound_lane_data.latest_received_nonce - == source_outbound_lane_data.latest_generated_nonce - { - tracing::info!( - target: "pangolin-pangoro", - "[receiving-pangoro-to-pangolin] All nonces received, nothing to do.", - ); - return Ok(None); - } - - // query last relayed header - let last_relayed_pangolin_hash_in_pangoro = client_pangoro - .runtime() - .storage() - .bridge_pangolin_grandpa() - .best_finalized(None) - .await?; - - // assemble unrewarded relayers state - let (max_confirmed_nonce_at_target, relayers_state) = match self - .target_unrewarded_relayers_state( - last_relayed_pangolin_hash_in_pangoro, - &source_outbound_lane_data, - ) - .await? - { - Some(v) => v, - None => { - tracing::warn!( - target: "pangolin-pangoro", - "[receiving-pangoro-to-pangolin] No unrewarded relayers state found by pangolin", - ); - return Ok(None); - } - }; - - // read proof - let inbound_data_key = InboundLanes(lane.0) - .key() - .final_key(StorageKeyPrefix::new::()); - let read_proof = client_pangolin - .subxt() - .rpc() - .read_proof( - vec![inbound_data_key], - Some(last_relayed_pangolin_hash_in_pangoro), - ) - .await?; - let proof: Vec> = read_proof.proof.into_iter().map(|item| item.0).collect(); - let proof = FromBridgedChainMessagesDeliveryProof { - bridged_header_hash: last_relayed_pangolin_hash_in_pangoro, - storage_proof: proof, - lane: lane.0, - }; - - // send proof - let hash = client_pangoro - .runtime() - .tx() - .bridge_pangolin_messages() - .receive_messages_delivery_proof(proof, relayers_state) - .sign_and_submit(client_pangoro.account().signer()) - .await?; - - tracing::debug!( - target: "pangolin-pangoro", - "[receiving-pangoro-to-pangolin] receiving extensics sent successful: {}", - array_bytes::bytes2hex("0x", hash.0), - ); - Ok(Some(max_confirmed_nonce_at_target)) - } -} diff --git a/bridges/pangolin-pangoro/bridge/src/service/message/types.rs b/bridges/pangolin-pangoro/bridge/src/service/message/types.rs deleted file mode 100644 index b3e90c42e..000000000 --- a/bridges/pangolin-pangoro/bridge/src/service/message/types.rs +++ /dev/null @@ -1,58 +0,0 @@ -use client_pangolin::client::PangolinClient; -use client_pangolin::component::PangolinClientComponent; -use client_pangoro::client::PangoroClient; -use client_pangoro::component::PangoroClientComponent; -use subquery_s2s::types::BridgeName; -use subquery_s2s::{Subquery, SubqueryComponent}; - -use support_common::config::{Config, Names}; -use support_common::error::BridgerError; - -use crate::bridge::{BridgeConfig, RelayConfig}; -use crate::types::HexLaneId; - -pub(crate) struct MessageRelay { - pub relay_config: RelayConfig, - pub client_pangolin: PangolinClient, - pub client_pangoro: PangoroClient, - pub subquery_pangoro: Subquery, - pub subquery_pangolin: Subquery, -} - -impl MessageRelay { - pub async fn new() -> color_eyre::Result { - let bridge_config: BridgeConfig = Config::restore(Names::BridgePangolinPangoro)?; - - let index_config = bridge_config.index; - let config_pangolin = bridge_config.pangolin; - let config_pangoro = bridge_config.pangoro; - - let client_pangolin = - PangolinClientComponent::component(config_pangolin.to_pangolin_client_config()?) - .await?; - let client_pangoro = - PangoroClientComponent::component(config_pangoro.to_pangoro_client_config()?).await?; - let subquery_pangoro = - SubqueryComponent::component(index_config.pangoro, BridgeName::PangolinPangoro); - let subquery_pangolin = - SubqueryComponent::component(index_config.pangolin, BridgeName::PangolinPangoro); - Ok(Self { - relay_config: bridge_config.relay, - client_pangolin, - client_pangoro, - subquery_pangoro, - subquery_pangolin, - }) - } -} - -impl MessageRelay { - pub fn lane(&self) -> Result { - self.relay_config - .lanes - .clone() - .get(0) - .cloned() - .ok_or_else(|| BridgerError::Custom("Missing lane id".to_string())) - } -} diff --git a/bridges/pangolin-pangoro/bridge/src/service/subscribe.rs b/bridges/pangolin-pangoro/bridge/src/service/subscribe.rs index 3b3e87dc9..45720f7b2 100644 --- a/bridges/pangolin-pangoro/bridge/src/service/subscribe.rs +++ b/bridges/pangolin-pangoro/bridge/src/service/subscribe.rs @@ -1,26 +1,12 @@ -use client_pangolin::{client::PangolinClient, component::PangolinClientComponent}; -use client_pangoro::{client::PangoroClient, component::PangoroClientComponent}; use lifeline::{Lifeline, Service, Task}; -use once_cell::sync::Lazy; -use std::collections::VecDeque; -// use std::sync::Mutex; -use futures::lock::Mutex; -// use subxt::rpc::Subscription; + +use relay_s2s::subscribe::SubscribeJustification; +use relay_s2s::types::JustificationInput; use support_common::config::{Config, Names}; use support_lifeline::service::BridgeService; use crate::bridge::{BridgeBus, BridgeConfig, BridgeTask}; -pub static PANGOLIN_JUSTIFICATIONS: Lazy>> = Lazy::new(|| { - let d = VecDeque::with_capacity(100); - Mutex::new(d) -}); - -pub static PANGORO_JUSTIFICATIONS: Lazy>> = Lazy::new(|| { - let d = VecDeque::with_capacity(100); - Mutex::new(d) -}); - #[derive(Debug)] pub struct SubscribeService { _greet: Lifeline, @@ -48,63 +34,14 @@ impl Service for SubscribeService { async fn start() -> color_eyre::Result<()> { let bridge_config: BridgeConfig = Config::restore(Names::BridgePangolinPangoro)?; - let client_pangolin = - PangolinClientComponent::component(bridge_config.pangolin.to_pangolin_client_config()?) - .await?; - let client_pangoro = - PangoroClientComponent::component(bridge_config.pangoro.to_pangoro_client_config()?) - .await?; - - let pangolin_handle = tokio::spawn(run_until_pangolin_connection_lost(client_pangolin)); - let pangoro_handle = tokio::spawn(run_until_pangoro_connection_lost(client_pangoro)); - let (_result_p, _result_r) = (pangolin_handle.await, pangoro_handle.await); - Ok(()) -} - -async fn run_until_pangolin_connection_lost(mut client: PangolinClient) -> color_eyre::Result<()> { - while let Err(err) = subscribe_pangolin(&client).await { - tracing::error!(target: "pangolin-pangoro", "[subscribe] [pangolin] Failed to get justification from pangolin: {:?}", err); - let bridge_config: BridgeConfig = Config::restore(Names::BridgePangolinPangoro)?; - let client_pangolin = - PangolinClientComponent::component(bridge_config.pangolin.to_pangolin_client_config()?) - .await?; - client = client_pangolin; - } - Ok(()) -} + let client_pangolin = bridge_config.pangolin.to_pangolin_client().await?; + let client_pangoro = bridge_config.pangoro.to_pangoro_client().await?; -async fn run_until_pangoro_connection_lost(mut client: PangoroClient) -> color_eyre::Result<()> { - while let Err(err) = subscribe_pangoro(&client).await { - tracing::error!(target: "pangolin-pangoro", "[subscribe] [pangoro] Failed to get justification from pangoro: {:?}", err); - let bridge_config: BridgeConfig = Config::restore(Names::BridgePangolinPangoro)?; - let client_pangoro = - PangoroClientComponent::component(bridge_config.pangoro.to_pangoro_client_config()?) - .await?; - client = client_pangoro; - } - Ok(()) -} - -async fn subscribe_pangolin(client: &PangolinClient) -> color_eyre::Result<()> { - let mut subscribe = client.subscribe_grandpa_justifications().await?; - while let Some(justification) = subscribe.next().await { - let mut data = PANGOLIN_JUSTIFICATIONS.lock().await; - data.push_back(justification?); - if data.len() >= 100 { - data.pop_front(); - } - } - Ok(()) -} - -async fn subscribe_pangoro(client: &PangoroClient) -> color_eyre::Result<()> { - let mut subscribe = client.subscribe_grandpa_justifications().await?; - while let Some(justification) = subscribe.next().await { - let mut data = PANGORO_JUSTIFICATIONS.lock().await; - data.push_back(justification?); - if data.len() >= 100 { - data.pop_front(); - } - } + let input = JustificationInput { + client_source: client_pangolin, + client_target: client_pangoro, + }; + let subscribe = SubscribeJustification::new(input); + subscribe.start().await?; Ok(()) } diff --git a/bridges/pangolin-ropsten/Cargo.lock b/bridges/pangolin-ropsten/Cargo.lock index 9dab4903e..d4f839d35 100644 --- a/bridges/pangolin-ropsten/Cargo.lock +++ b/bridges/pangolin-ropsten/Cargo.lock @@ -266,6 +266,72 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "bp-darwinia-core" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bp-messages", + "bp-runtime", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "bp-messages" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bitvec", + "bp-runtime", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "serde 1.0.136", + "sp-core", + "sp-std", +] + +[[package]] +name = "bp-pangolin" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bp-darwinia-core", + "bp-messages", + "bp-runtime", + "frame-support", + "sp-api", + "sp-runtime", + "sp-std", + "sp-version", +] + +[[package]] +name = "bp-runtime" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "frame-support", + "hash-db", + "num-traits 0.2.14", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-trie", +] + [[package]] name = "bridge-pangolin-ropsten" version = "0.5.7" @@ -395,6 +461,7 @@ name = "client-pangolin" version = "0.5.7" dependencies = [ "array-bytes", + "bp-pangolin", "parity-scale-codec", "secp256k1", "serde 1.0.136", @@ -951,6 +1018,84 @@ dependencies = [ "serde 1.0.136", ] +[[package]] +name = "frame-support" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "bitflags", + "frame-metadata", + "frame-support-procedural", + "impl-trait-for-tuples", + "log", + "once_cell", + "parity-scale-codec", + "paste", + "scale-info", + "serde 1.0.136", + "smallvec 1.8.0", + "sp-arithmetic", + "sp-core", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-state-machine", + "sp-std", + "sp-tracing", +] + +[[package]] +name = "frame-support-procedural" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "Inflector", + "frame-support-procedural-tools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "frame-support-procedural-tools-derive", + "proc-macro-crate 1.1.3", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools-derive" +version = "3.0.0" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-system" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "frame-support", + "log", + "parity-scale-codec", + "scale-info", + "serde 1.0.136", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-version", +] + [[package]] name = "funty" version = "1.1.0" @@ -3069,6 +3214,35 @@ dependencies = [ "sha-1 0.9.8", ] +[[package]] +name = "sp-api" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "sp-api-proc-macro", + "sp-core", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-version", + "thiserror", +] + +[[package]] +name = "sp-api-proc-macro" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "blake2-rfc", + "proc-macro-crate 1.1.3", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sp-application-crypto" version = "4.0.0-dev" @@ -3163,6 +3337,20 @@ dependencies = [ "sp-storage", ] +[[package]] +name = "sp-inherents" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "async-trait", + "impl-trait-for-tuples", + "parity-scale-codec", + "sp-core", + "sp-runtime", + "sp-std", + "thiserror", +] + [[package]] name = "sp-io" version = "4.0.0-dev" @@ -3262,6 +3450,17 @@ dependencies = [ "syn", ] +[[package]] +name = "sp-staking" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + [[package]] name = "sp-state-machine" version = "0.10.0-dev" @@ -3608,6 +3807,7 @@ dependencies = [ name = "support-toolkit" version = "0.5.7" dependencies = [ + "parity-scale-codec", "thiserror", ] diff --git a/bridges/pangoro-chapel/Cargo.lock b/bridges/pangoro-chapel/Cargo.lock index 721453e06..e985b2345 100644 --- a/bridges/pangoro-chapel/Cargo.lock +++ b/bridges/pangoro-chapel/Cargo.lock @@ -248,6 +248,72 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "bp-darwinia-core" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bp-messages", + "bp-runtime", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "bp-messages" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bitvec", + "bp-runtime", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "serde 1.0.136", + "sp-core", + "sp-std", +] + +[[package]] +name = "bp-pangoro" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "bp-darwinia-core", + "bp-messages", + "bp-runtime", + "frame-support", + "sp-api", + "sp-runtime", + "sp-std", + "sp-version", +] + +[[package]] +name = "bp-runtime" +version = "0.1.0" +source = "git+https://github.com/darwinia-network/darwinia-bridges-substrate.git?branch=darwinia-v0.12.2#9cbc639462552e6c770bffdb02c2fd9d937b9eee" +dependencies = [ + "frame-support", + "hash-db", + "num-traits 0.2.14", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-trie", +] + [[package]] name = "bridge-pangoro-ropsten" version = "0.5.7" @@ -355,6 +421,7 @@ name = "client-pangoro" version = "0.5.7" dependencies = [ "array-bytes", + "bp-pangoro", "parity-scale-codec", "serde 1.0.136", "serde_json", @@ -870,6 +937,84 @@ dependencies = [ "serde 1.0.136", ] +[[package]] +name = "frame-support" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "bitflags", + "frame-metadata", + "frame-support-procedural", + "impl-trait-for-tuples", + "log", + "once_cell", + "parity-scale-codec", + "paste", + "scale-info", + "serde 1.0.136", + "smallvec", + "sp-arithmetic", + "sp-core", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-state-machine", + "sp-std", + "sp-tracing", +] + +[[package]] +name = "frame-support-procedural" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "Inflector", + "frame-support-procedural-tools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "frame-support-procedural-tools-derive", + "proc-macro-crate 1.1.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools-derive" +version = "3.0.0" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-system" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "frame-support", + "log", + "parity-scale-codec", + "scale-info", + "serde 1.0.136", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-version", +] + [[package]] name = "funty" version = "1.1.0" @@ -2793,6 +2938,35 @@ dependencies = [ "sha-1 0.9.8", ] +[[package]] +name = "sp-api" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "sp-api-proc-macro", + "sp-core", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-version", + "thiserror", +] + +[[package]] +name = "sp-api-proc-macro" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "blake2-rfc", + "proc-macro-crate 1.1.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sp-application-crypto" version = "4.0.0-dev" @@ -2887,6 +3061,20 @@ dependencies = [ "sp-storage", ] +[[package]] +name = "sp-inherents" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "async-trait", + "impl-trait-for-tuples", + "parity-scale-codec", + "sp-core", + "sp-runtime", + "sp-std", + "thiserror", +] + [[package]] name = "sp-io" version = "4.0.0-dev" @@ -2986,6 +3174,17 @@ dependencies = [ "syn", ] +[[package]] +name = "sp-staking" +version = "4.0.0-dev" +source = "git+https://github.com/darwinia-network/substrate.git?branch=darwinia-v0.12.2#5cb17b488d600befa4918915e18d40c28a25353d" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + [[package]] name = "sp-state-machine" version = "0.10.0-dev" @@ -3290,6 +3489,7 @@ dependencies = [ name = "support-toolkit" version = "0.5.7" dependencies = [ + "parity-scale-codec", "thiserror", ] diff --git a/frame/Cargo.toml b/frame/Cargo.toml index ab1b9df39..be36ecf71 100644 --- a/frame/Cargo.toml +++ b/frame/Cargo.toml @@ -5,5 +5,6 @@ members = [ "components/*", ] exclude = [ - "assistants" + "assistants", + "abstract", ] diff --git a/frame/abstract/.gitignore b/frame/abstract/.gitignore new file mode 100644 index 000000000..405a0b804 --- /dev/null +++ b/frame/abstract/.gitignore @@ -0,0 +1,4 @@ + +target + +Cargo.lock diff --git a/frame/abstract/bridge-s2s/Cargo.toml b/frame/abstract/bridge-s2s/Cargo.toml new file mode 100644 index 000000000..1e7697f8d --- /dev/null +++ b/frame/abstract/bridge-s2s/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "abstract-bridge-s2s" +authors = ["Darwinia Network "] +description = "Darwinia bridger" +documentation = "https://rust-docs.darwinia.network/bridger" +edition = "2021" +homepage = "https://github.com/darwinia-network/bridger" +include = ["Cargo.toml", "**/*.rs", "README.md", "LICENSE"] +keywords = ["substrate", "darwinia"] +license = "MIT" +readme = 'README.md' +repository = "https://github.com/darwinia-network/bridger" +version = "0.5.7" + + +[dependencies] +thiserror = "1" +async-trait = "0.1" + +#num-traits = "0.2" +serde = { version = "1.0.124", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "2" } +scale-info = { version = "1.0.0", features = ["bit-vec"] } +jsonrpsee-core = "0.8" + +sp-core = { git = "https://github.com/darwinia-network/substrate.git", branch = "darwinia-v0.12.2" } +sp-runtime = { git = "https://github.com/darwinia-network/substrate.git", branch = "darwinia-v0.12.2" } + +bp-runtime = { git = "https://github.com/darwinia-network/darwinia-bridges-substrate.git", branch = "darwinia-v0.12.2" } +bp-messages = { git = "https://github.com/darwinia-network/darwinia-bridges-substrate.git", branch = "darwinia-v0.12.2" } +bridge-runtime-common = { git = "https://github.com/darwinia-network/darwinia-bridges-substrate.git", branch = "darwinia-v0.12.2" } +bp-header-chain = { git = "https://github.com/darwinia-network/darwinia-bridges-substrate.git", branch = "darwinia-v0.12.2" } + +array-bytes = { optional = true, version = "1.4" } +subxt = { optional = true, git = "https://github.com/darwinia-network/subxt.git", branch = "darwinia-v0.12.2" } + +[features] +default = [] +bridge-parachain = [] +advanced-types = [ + "array-bytes", + "subxt", +] diff --git a/frame/abstract/bridge-s2s/src/client.rs b/frame/abstract/bridge-s2s/src/client.rs new file mode 100644 index 000000000..afee8e06b --- /dev/null +++ b/frame/abstract/bridge-s2s/src/client.rs @@ -0,0 +1,174 @@ +use core::fmt::Debug; +use std::ops::RangeInclusive; + +use codec::{Codec, Decode, Encode, EncodeLike}; +use jsonrpsee_core::client::Subscription; +use sp_runtime::generic::{Block, SignedBlock}; +use sp_runtime::traits::{Extrinsic, MaybeSerializeDeserialize}; + +use crate::config::Config; +use crate::error::S2SClientResult; + +/// S2S bridge client types defined +pub trait S2SClientBase: Send + Sync + 'static { + const CHAIN: &'static str; + type Config: Config; + type Extrinsic: Codec + EncodeLike + Clone + Eq + Extrinsic + Debug + MaybeSerializeDeserialize; +} + +/// S2S bridge client generic trait +#[async_trait::async_trait] +pub trait S2SClientGeneric: S2SClientBase { + /// initialization data + type InitializationData: Encode + Decode; + + /// subscribe grandpa justifications + async fn subscribe_grandpa_justifications( + &self, + ) -> S2SClientResult>; + + /// prepare initialization data + async fn prepare_initialization_data(&self) -> S2SClientResult; + + /// initialize bridge + async fn initialize( + &self, + initialization_data: Self::InitializationData, + ) -> S2SClientResult<::Hash>; +} + +/// S2S bridge header/message api +#[async_trait::async_trait] +pub trait S2SClientRelay: S2SClientGeneric { + /// generate outbound messages storage key + fn gen_outbound_messages_storage_key( + &self, + lane: [u8; 4], + message_nonce: u64, + ) -> sp_core::storage::StorageKey; + + /// generate outbound lanes storage key + fn gen_outbound_lanes_storage_key(&self, lane: [u8; 4]) -> sp_core::storage::StorageKey; + + /// generate inbound lanes storage key + fn gen_inbound_lanes_storage_key(&self, lane: [u8; 4]) -> sp_core::storage::StorageKey; + + /// calculate dispatchh width by message nonces + async fn calculate_dispatch_weight( + &self, + lane: [u8; 4], + nonces: RangeInclusive, + ) -> S2SClientResult; + + /// query header by hash + async fn header( + &self, + hash: Option<::Hash>, + ) -> S2SClientResult::Header>>; + + /// query block by hash + async fn block( + &self, + hash: Option<::Hash>, + ) -> S2SClientResult< + Option::Header, Self::Extrinsic>>>, + >; + + /// query best target finalized at source + async fn best_target_finalized( + &self, + at_block: Option<::Hash>, + ) -> S2SClientResult<::Hash>; + + /// submit finality proof + async fn submit_finality_proof( + &self, + finality_target: ::Header, + justification: bp_header_chain::justification::GrandpaJustification< + ::Header, + >, + ) -> S2SClientResult<::Hash>; + + /// query outbound lane + async fn outbound_lanes( + &self, + lane: [u8; 4], + hash: Option<::Hash>, + ) -> S2SClientResult; + + /// query inbound lane + async fn inbound_lanes( + &self, + lane: [u8; 4], + hash: Option<::Hash>, + ) -> S2SClientResult>; + + /// query oubound message data + async fn outbound_messages( + &self, + message_key: bp_messages::MessageKey, + hash: Option<::Hash>, + ) -> S2SClientResult>>; + + /// read proof + async fn read_proof( + &self, + storage_keys: Vec, + hash: Option<::Hash>, + ) -> S2SClientResult>>; + + /// send receive messages proof extrinsics + async fn receive_messages_proof( + &self, + relayer_id_at_bridged_chain: sp_core::crypto::AccountId32, + proof: bridge_runtime_common::messages::target::FromBridgedChainMessagesProof< + ::Hash, + >, + messages_count: u32, + dispatch_weight: u64, + ) -> S2SClientResult<::Hash>; + + /// receive messages delivery proof + async fn receive_messages_delivery_proof( + &self, + proof: bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof< + ::Hash, + >, + relayers_state: bp_messages::UnrewardedRelayersState, + ) -> S2SClientResult<::Hash>; +} + +/// S2S with parachain bridge api for solo chain +#[cfg(feature = "bridge-parachain")] +#[async_trait::async_trait] +pub trait S2SParaBridgeClientSolochain: S2SClientRelay { + /// beat para heads + async fn best_para_heads( + &self, + para_id: crate::types::ParaId, + hash: Option<::Hash>, + ) -> S2SClientResult>; + + /// submit parachain heads + async fn submit_parachain_heads( + &self, + relay_block_hash: ::Hash, + parachains: Vec, + parachain_heads_proof: Vec>, + ) -> S2SClientResult<::Hash>; +} + +/// S2S with parachain bridge api for relay chain +#[cfg(feature = "bridge-parachain")] +#[async_trait::async_trait] +pub trait S2SParaBridgeClientRelaychain: S2SClientRelay { + /// generate parachain head storage key + fn gen_parachain_head_storage_key(&self, para_id: u32) -> sp_core::storage::StorageKey; + + /// query head data + async fn para_head_data( + &self, + para_id: crate::types::ParaId, + hash: Option<::Hash>, + ) -> S2SClientResult>; +} diff --git a/frame/abstract/bridge-s2s/src/config.rs b/frame/abstract/bridge-s2s/src/config.rs new file mode 100644 index 000000000..5ab620c65 --- /dev/null +++ b/frame/abstract/bridge-s2s/src/config.rs @@ -0,0 +1 @@ +pub use crate::types::bp_runtime::Chain as Config; diff --git a/frame/abstract/bridge-s2s/src/error.rs b/frame/abstract/bridge-s2s/src/error.rs new file mode 100644 index 000000000..667375182 --- /dev/null +++ b/frame/abstract/bridge-s2s/src/error.rs @@ -0,0 +1,34 @@ +use thiserror::Error as ThisError; + +pub type S2SClientResult = Result; + +#[derive(ThisError, Debug)] +pub enum S2SClientError { + #[error("RPC: {0}")] + RPC(String), + #[error(transparent)] + Codec(#[from] codec::Error), + #[error("Custom: {0}")] + Custom(String), +} + +#[cfg(feature = "subxt")] +impl From for S2SClientError { + fn from(error: subxt::BasicError) -> Self { + Self::RPC(format!("{:?}", error)) + } +} + +#[cfg(feature = "subxt")] +impl From for S2SClientError { + fn from(error: subxt::rpc::RpcError) -> Self { + Self::RPC(format!("{:?}", error)) + } +} + +#[cfg(feature = "array-bytes")] +impl From for S2SClientError { + fn from(error: array_bytes::Error) -> Self { + Self::Custom(format!("[bytes] {:?}", error)) + } +} diff --git a/frame/abstract/bridge-s2s/src/lib.rs b/frame/abstract/bridge-s2s/src/lib.rs new file mode 100644 index 000000000..eb83e386a --- /dev/null +++ b/frame/abstract/bridge-s2s/src/lib.rs @@ -0,0 +1,5 @@ +pub mod client; +pub mod config; +pub mod error; +pub mod strategy; +pub mod types; diff --git a/frame/abstract/bridge-s2s/src/strategy.rs b/frame/abstract/bridge-s2s/src/strategy.rs new file mode 100644 index 000000000..f7965bd49 --- /dev/null +++ b/frame/abstract/bridge-s2s/src/strategy.rs @@ -0,0 +1,26 @@ +use crate::error::S2SClientResult; +use crate::types::bp_messages::LaneId; + +/// relay decide +#[async_trait::async_trait] +pub trait RelayStrategy: 'static + Clone + Send + Sync { + /// decide to relay + async fn decide(&mut self, reference: RelayReference) -> S2SClientResult; +} + +/// decide reference +pub struct RelayReference { + pub lane: LaneId, + /// nonces + pub nonce: u64, +} + +#[derive(Clone)] +pub struct AlwaysRelayStrategy; + +#[async_trait::async_trait] +impl RelayStrategy for AlwaysRelayStrategy { + async fn decide(&mut self, _reference: RelayReference) -> S2SClientResult { + Ok(true) + } +} diff --git a/frame/abstract/bridge-s2s/src/types.rs b/frame/abstract/bridge-s2s/src/types.rs new file mode 100644 index 000000000..7bac8f3f8 --- /dev/null +++ b/frame/abstract/bridge-s2s/src/types.rs @@ -0,0 +1,34 @@ +pub use bp_header_chain; +pub use bp_messages; +pub use bp_runtime; +pub use bridge_runtime_common; + +#[cfg(feature = "bridge-parachain")] +pub use self::bridge_parachain::*; + +#[cfg(feature = "bridge-parachain")] +mod bridge_parachain { + use serde::{Deserialize, Serialize}; + use sp_core::Hasher as HasherT; + use sp_runtime::codec; + use sp_runtime::traits::BlakeTwo256; + + type ParaHash = ::Out; + + #[derive(codec::Encode, codec::Decode, Debug, Clone, Deserialize, Serialize)] + pub struct ParaId(pub u32); + + #[derive(codec::Encode, codec::Decode, Debug, Clone, Deserialize, Serialize)] + pub struct HeadData(pub Vec); + + /// Best known parachain head as it is stored in the runtime storage. + #[derive(codec::Encode, codec::Decode, Debug, Clone, PartialEq, Deserialize, Serialize)] + pub struct BestParaHead { + /// Number of relay block where this head has been updated. + pub at_relay_block_number: u32, + /// Hash of parachain head. + pub head_hash: ParaHash, + /// Current ring buffer position for this parachain. + pub next_imported_hash_position: u32, + } +} diff --git a/frame/abstract/feemarket-s2s/Cargo.toml b/frame/abstract/feemarket-s2s/Cargo.toml new file mode 100644 index 000000000..ae8861207 --- /dev/null +++ b/frame/abstract/feemarket-s2s/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "abstract-feemarket-s2s" +authors = ["Darwinia Network "] +description = "Darwinia bridger" +documentation = "https://rust-docs.darwinia.network/bridger" +edition = "2021" +homepage = "https://github.com/darwinia-network/bridger" +include = ["Cargo.toml", "**/*.rs", "README.md", "LICENSE"] +keywords = ["substrate", "darwinia"] +license = "MIT" +readme = 'README.md' +repository = "https://github.com/darwinia-network/bridger" +version = "0.5.7" + +[dependencies] +thiserror = "1" +async-trait = "0.1" + +codec = { package = "parity-scale-codec", version = "2" } +bp-runtime = { git = "https://github.com/darwinia-network/darwinia-bridges-substrate.git", branch = "darwinia-v0.12.2" } +pallet-fee-market = { git = "https://github.com/darwinia-network/darwinia-bridges-substrate.git", branch = "darwinia-v0.12.2" } + +subxt = { optional = true, git = "https://github.com/darwinia-network/subxt.git", branch = "darwinia-v0.12.2" } + +[features] +default = [] +advanced-types = [ + "subxt", +] diff --git a/frame/abstract/feemarket-s2s/src/api.rs b/frame/abstract/feemarket-s2s/src/api.rs new file mode 100644 index 000000000..e0a6a41b5 --- /dev/null +++ b/frame/abstract/feemarket-s2s/src/api.rs @@ -0,0 +1,72 @@ +use bp_runtime::Chain; + +use crate::error::AbstractFeemarketResult; +use crate::types::{LaneId, MessageNonce, Order, Relayer}; + +#[async_trait::async_trait] +pub trait FeemarketApiBase: 'static + Send + Sync + Clone { + /// chain name + const CHAIN: &'static str; + + /// chain types + type Chain: Chain; + + /// best finalized block number + async fn best_finalized_header_number( + &self, + ) -> AbstractFeemarketResult<::BlockNumber>; +} + +/// Fee market api +#[async_trait::async_trait] +pub trait FeemarketApiRelay: FeemarketApiBase { + /// order + async fn order( + &self, + laned_id: LaneId, + message_nonce: MessageNonce, + ) -> AbstractFeemarketResult< + Option< + Order< + ::AccountId, + ::BlockNumber, + ::Balance, + >, + >, + >; +} + +#[async_trait::async_trait] +pub trait FeemarketApiQuote: FeemarketApiBase { + /// Query assigned relayers + async fn assigned_relayers( + &self, + ) -> AbstractFeemarketResult< + Vec::AccountId, ::Balance>>, + >; + + async fn is_relayer(&self) -> AbstractFeemarketResult; + + /// all relayers + async fn relayers(&self) -> AbstractFeemarketResult::AccountId>>; + + /// Query relayer info by account id + async fn relayer( + &self, + account: ::AccountId, + ) -> AbstractFeemarketResult< + Option::AccountId, ::Balance>>, + >; + + /// Update relay fee + async fn update_relay_fee( + &self, + amount: ::Balance, + ) -> AbstractFeemarketResult<()>; + + /// Update locked collateral + async fn update_locked_collateral( + &self, + amount: ::Balance, + ) -> AbstractFeemarketResult<()>; +} diff --git a/frame/abstract/feemarket-s2s/src/error.rs b/frame/abstract/feemarket-s2s/src/error.rs new file mode 100644 index 000000000..50e1d685c --- /dev/null +++ b/frame/abstract/feemarket-s2s/src/error.rs @@ -0,0 +1,27 @@ +use thiserror::Error as ThisError; + +pub type AbstractFeemarketResult = Result; + +#[derive(ThisError, Debug)] +pub enum AbstractFeemarketError { + #[error("RPC: {0}")] + RPC(String), + #[error(transparent)] + Codec(#[from] codec::Error), + #[error("Custom: {0}")] + Custom(String), +} + +#[cfg(feature = "subxt")] +impl From for AbstractFeemarketError { + fn from(error: subxt::BasicError) -> Self { + Self::RPC(format!("{:?}", error)) + } +} + +#[cfg(feature = "subxt")] +impl From for AbstractFeemarketError { + fn from(error: subxt::rpc::RpcError) -> Self { + Self::RPC(format!("{:?}", error)) + } +} diff --git a/frame/abstract/feemarket-s2s/src/lib.rs b/frame/abstract/feemarket-s2s/src/lib.rs new file mode 100644 index 000000000..d66938372 --- /dev/null +++ b/frame/abstract/feemarket-s2s/src/lib.rs @@ -0,0 +1,3 @@ +pub mod api; +pub mod error; +pub mod types; diff --git a/frame/abstract/feemarket-s2s/src/types.rs b/frame/abstract/feemarket-s2s/src/types.rs new file mode 100644 index 000000000..a4693168d --- /dev/null +++ b/frame/abstract/feemarket-s2s/src/types.rs @@ -0,0 +1,5 @@ +pub use bp_runtime::Chain; +pub use pallet_fee_market::types::{Order, Relayer}; + +pub type LaneId = [u8; 4]; +pub type MessageNonce = u64; diff --git a/frame/assistants/client-pangolin/Cargo.toml b/frame/assistants/client-pangolin/Cargo.toml index 7e58d99ab..998e62507 100644 --- a/frame/assistants/client-pangolin/Cargo.toml +++ b/frame/assistants/client-pangolin/Cargo.toml @@ -20,30 +20,52 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" array-bytes = "1.4" -codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive", "full"] } +codec = { package = "parity-scale-codec", version = "2" } subxt = { git = "https://github.com/darwinia-network/subxt.git", branch = "darwinia-v0.12.2" } +bp-pangolin = { git = "https://github.com/darwinia-network/darwinia-bridges-substrate.git", branch = "darwinia-v0.12.2" } -web3 = { git = "https://github.com/wuminzhe/rust-web3.git", branch = "master", features = ["signing"], optional = true } -secp256k1 = { version = "0.20", features = ["recovery"], optional = true } -shadow-liketh = { path = "../shadow-liketh", optional = true } +## maybe common +async-trait = { optional = true, version = "0.1" } -finality-grandpa = { version = "0.14.0", optional = true } -bp-header-chain = { git = "https://github.com/darwinia-network/darwinia-bridges-substrate.git", branch = "darwinia-v0.12.2", optional = true } -sp-finality-grandpa = { git = "https://github.com/darwinia-network/substrate.git", branch = "darwinia-v0.12.2", optional = true } -sp-runtime = { git = "https://github.com/darwinia-network/substrate.git", branch = "darwinia-v0.12.2", optional = true } +## substrate +sp-finality-grandpa = { optional = true, git = "https://github.com/darwinia-network/substrate.git", branch = "darwinia-v0.12.2" } +sp-runtime = { optional = true, git = "https://github.com/darwinia-network/substrate.git", branch = "darwinia-v0.12.2" } +sp-core = { optional = true, git = "https://github.com/darwinia-network/substrate.git", branch = "darwinia-v0.12.2" } -support-toolkit = { path = "../../../frame/supports/support-toolkit" } +## eth-v1 +web3 = { optional = true, git = "https://github.com/wuminzhe/rust-web3.git", branch = "master", features = ["signing"] } +secp256k1 = { optional = true, version = "0.20", features = ["recovery"] } +shadow-liketh = { optional = true, path = "../shadow-liketh" } + +## s2s client +finality-grandpa = { optional = true, version = "0.14" } +abstract-bridge-s2s = { optional = true, path = "../../../frame/abstract/bridge-s2s", features = ["advanced-types"] } + +## feemarket s2s +abstract-feemarket-s2s = { optional = true, path = "../../../frame/abstract/feemarket-s2s", features = ["advanced-types"] } + +support-toolkit = { path = "../../../frame/supports/support-toolkit", features = ["convert"] } [dev-dependencies] tokio = { version = "1", features = ["full"] } [features] default = [] +substrate = [ + "sp-finality-grandpa", + "sp-runtime", + "sp-core", +] ethlike-v1 = ["web3", "secp256k1", "shadow-liketh"] -s2s = [ +bridge-s2s = [ + "substrate", + "async-trait", "finality-grandpa", - "bp-header-chain", - "sp-finality-grandpa", - "sp-runtime" + "abstract-bridge-s2s", +] +bridge-s2s-pangoro = ["bridge-s2s"] +feemarket-s2s = [ + "async-trait", + "abstract-feemarket-s2s", ] -s2s-pangoro = ["s2s"] +feemarket-s2s-pangoro = ["feemarket-s2s"] diff --git a/frame/assistants/client-pangolin/src/config.rs b/frame/assistants/client-pangolin/src/config.rs index daa01ba58..344c63cab 100644 --- a/frame/assistants/client-pangolin/src/config.rs +++ b/frame/assistants/client-pangolin/src/config.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use subxt::{sp_core, sp_runtime}; +use subxt::sp_runtime; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ClientConfig { @@ -20,13 +20,13 @@ pub struct ClientConfig { pub enum PangolinSubxtConfig {} impl subxt::Config for PangolinSubxtConfig { - type Index = u32; - type BlockNumber = u32; - type Hash = sp_core::H256; - type Hashing = sp_runtime::traits::BlakeTwo256; - type AccountId = sp_runtime::AccountId32; - type Address = sp_runtime::MultiAddress; - type Header = sp_runtime::generic::Header; - type Signature = sp_runtime::MultiSignature; + type Index = bp_pangolin::Nonce; + type BlockNumber = bp_pangolin::BlockNumber; + type Hash = bp_pangolin::Hash; + type Hashing = bp_pangolin::Hashing; + type AccountId = bp_pangolin::AccountId; + type Address = bp_pangolin::Address; + type Header = bp_pangolin::Header; + type Signature = bp_pangolin::Signature; type Extrinsic = sp_runtime::OpaqueExtrinsic; } diff --git a/frame/assistants/client-pangolin/src/error.rs b/frame/assistants/client-pangolin/src/error.rs index e2a9cc2c5..05ee5385e 100644 --- a/frame/assistants/client-pangolin/src/error.rs +++ b/frame/assistants/client-pangolin/src/error.rs @@ -100,3 +100,14 @@ impl From for ClientError { Self::Bytes(format!("{:?}", error)) } } + +#[cfg(feature = "bridge-s2s")] +impl From for abstract_bridge_s2s::error::S2SClientError { + fn from(error: ClientError) -> Self { + match error { + ClientError::SubxtBasicError(e) => Self::RPC(format!("{:?}", e)), + ClientError::ClientRestartNeed => Self::RPC(format!("Client restart need")), + _ => Self::Custom(format!("{:?}", error)), + } + } +} diff --git a/frame/assistants/client-pangolin/src/fastapi/bridge_s2s/bridge_pangoro.rs b/frame/assistants/client-pangolin/src/fastapi/bridge_s2s/bridge_pangoro.rs new file mode 100644 index 000000000..d29876cf2 --- /dev/null +++ b/frame/assistants/client-pangolin/src/fastapi/bridge_s2s/bridge_pangoro.rs @@ -0,0 +1,233 @@ +use std::ops::RangeInclusive; + +use abstract_bridge_s2s::error::S2SClientResult; +use abstract_bridge_s2s::{ + client::S2SClientRelay, + config::Config, + types::{bp_header_chain, bp_messages, bridge_runtime_common}, +}; +use sp_runtime::generic::{Block, SignedBlock}; +use sp_runtime::AccountId32; +use subxt::sp_core::storage::StorageKey; +use subxt::storage::StorageKeyPrefix; +use subxt::StorageEntry; +use support_toolkit::convert::SmartCodecMapper; + +use crate::client::PangolinClient; +use crate::error::ClientError; +use crate::subxt_runtime::api::bridge_pangoro_messages::storage::{ + InboundLanes, OutboundLanes, OutboundMessages, +}; + +type BundleMessageKey = crate::types::runtime_types::bp_messages::MessageKey; + +/// Message payload for This -> Bridged chain messages. +type FromThisChainMessagePayload = crate::types::runtime_types::bp_message_dispatch::MessagePayload< + sp_core::crypto::AccountId32, + crate::types::runtime_types::sp_runtime::MultiSigner, + crate::types::runtime_types::sp_runtime::MultiSignature, + Vec, +>; + +#[async_trait::async_trait] +impl S2SClientRelay for PangolinClient { + fn gen_outbound_messages_storage_key(&self, lane: [u8; 4], message_nonce: u64) -> StorageKey { + let prefix = StorageKeyPrefix::new::(); + OutboundMessages(BundleMessageKey { + lane_id: lane, + nonce: message_nonce, + }) + .key() + .final_key(prefix) + } + + fn gen_outbound_lanes_storage_key(&self, lane: [u8; 4]) -> StorageKey { + OutboundLanes(lane) + .key() + .final_key(StorageKeyPrefix::new::()) + } + + fn gen_inbound_lanes_storage_key(&self, lane: [u8; 4]) -> StorageKey { + InboundLanes(lane) + .key() + .final_key(StorageKeyPrefix::new::()) + } + + async fn calculate_dispatch_weight( + &self, + lane: [u8; 4], + nonces: RangeInclusive, + ) -> S2SClientResult { + let mut total_weight = 0u64; + for message_nonce in nonces { + let message_data = self + .outbound_messages( + bp_messages::MessageKey { + lane_id: lane, + nonce: message_nonce, + }, + None, + ) + .await? + .ok_or_else(|| { + ClientError::Custom(format!( + "Can not read message data by nonce {} in pangolin", + message_nonce + )) + })?; + let decoded_payload: FromThisChainMessagePayload = + codec::Decode::decode(&mut &message_data.payload[..])?; + total_weight += decoded_payload.weight; + } + Ok(total_weight) + } + + async fn header( + &self, + hash: Option<::Hash>, + ) -> S2SClientResult::Header>> { + match self.subxt().rpc().header(hash).await? { + Some(v) => Ok(Some(SmartCodecMapper::map_to(&v)?)), + None => Ok(None), + } + } + + async fn block( + &self, + hash: Option<::Hash>, + ) -> S2SClientResult< + Option::Header, Self::Extrinsic>>>, + > { + Ok(self.subxt().rpc().block(hash).await?) + } + + async fn best_target_finalized( + &self, + at_block: Option<::Hash>, + ) -> S2SClientResult<::Hash> { + Ok(self + .runtime() + .storage() + .bridge_pangoro_grandpa() + .best_finalized(at_block) + .await?) + } + + async fn submit_finality_proof( + &self, + finality_target: ::Header, + justification: bp_header_chain::justification::GrandpaJustification< + ::Header, + >, + ) -> S2SClientResult<::Hash> { + let expected_target = SmartCodecMapper::map_to(&finality_target)?; + let expected_justification = SmartCodecMapper::map_to(&justification)?; + Ok(self + .runtime() + .tx() + .bridge_pangoro_grandpa() + .submit_finality_proof(expected_target, expected_justification) + .sign_and_submit(self.account().signer()) + .await?) + } + + async fn outbound_lanes( + &self, + lane: [u8; 4], + hash: Option<::Hash>, + ) -> S2SClientResult { + let outbound_lane_data = self + .runtime() + .storage() + .bridge_pangoro_messages() + .outbound_lanes(lane, hash) + .await?; + let expected = SmartCodecMapper::map_to(&outbound_lane_data)?; + Ok(expected) + } + + async fn inbound_lanes( + &self, + lane: [u8; 4], + hash: Option<::Hash>, + ) -> S2SClientResult> { + let inbound_lane_data = self + .runtime() + .storage() + .bridge_pangoro_messages() + .inbound_lanes(lane, hash) + .await?; + let expected = SmartCodecMapper::map_to(&inbound_lane_data)?; + Ok(expected) + } + + async fn outbound_messages( + &self, + message_key: bp_messages::MessageKey, + hash: Option<::Hash>, + ) -> S2SClientResult>> { + let expected_message_key = SmartCodecMapper::map_to(&message_key)?; + match self + .runtime() + .storage() + .bridge_pangoro_messages() + .outbound_messages(expected_message_key, hash) + .await? + { + Some(v) => Ok(Some(SmartCodecMapper::map_to(&v)?)), + None => Ok(None), + } + } + + async fn read_proof( + &self, + storage_keys: Vec, + hash: Option<::Hash>, + ) -> S2SClientResult>> { + let read_proof = self.subxt().rpc().read_proof(storage_keys, hash).await?; + let proof: Vec> = read_proof.proof.into_iter().map(|item| item.0).collect(); + Ok(proof) + } + + async fn receive_messages_proof( + &self, + relayer_id_at_bridged_chain: AccountId32, + proof: bridge_runtime_common::messages::target::FromBridgedChainMessagesProof< + ::Hash, + >, + messages_count: u32, + dispatch_weight: u64, + ) -> S2SClientResult<::Hash> { + let expected_proof = SmartCodecMapper::map_to(&proof)?; + Ok(self + .runtime() + .tx() + .bridge_pangoro_messages() + .receive_messages_proof( + relayer_id_at_bridged_chain, + expected_proof, + messages_count, + dispatch_weight, + ) + .sign_and_submit(self.account().signer()) + .await?) + } + + async fn receive_messages_delivery_proof( + &self, + proof: bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof< + ::Hash, + >, + relayers_state: bp_messages::UnrewardedRelayersState, + ) -> S2SClientResult<::Hash> { + let expected_proof = SmartCodecMapper::map_to(&proof)?; + let expected_relayers_state = SmartCodecMapper::map_to(&relayers_state)?; + Ok(self + .runtime() + .tx() + .bridge_pangoro_messages() + .receive_messages_delivery_proof(expected_proof, expected_relayers_state) + .sign_and_submit(self.account().signer()) + .await?) + } +} diff --git a/frame/assistants/client-pangolin/src/fastapi/s2s/generic.rs b/frame/assistants/client-pangolin/src/fastapi/bridge_s2s/generic.rs similarity index 80% rename from frame/assistants/client-pangolin/src/fastapi/s2s/generic.rs rename to frame/assistants/client-pangolin/src/fastapi/bridge_s2s/generic.rs index 482f25162..8bf35d0fd 100644 --- a/frame/assistants/client-pangolin/src/fastapi/s2s/generic.rs +++ b/frame/assistants/client-pangolin/src/fastapi/bridge_s2s/generic.rs @@ -1,3 +1,9 @@ +use abstract_bridge_s2s::config::Config; +use abstract_bridge_s2s::error::{S2SClientError, S2SClientResult}; +use abstract_bridge_s2s::{ + client::{S2SClientBase, S2SClientGeneric}, + types::bp_header_chain, +}; use finality_grandpa::voter_set::VoterSet; use sp_finality_grandpa::{AuthorityList, ConsensusLog, ScheduledChange}; use sp_runtime::{ConsensusEngineId, DigestItem}; @@ -10,14 +16,82 @@ use crate::types::runtime_types::bp_header_chain::InitializationData; const GRANDPA_ENGINE_ID: ConsensusEngineId = *b"FRNK"; -type BundleHeader = crate::types::runtime_types::sp_runtime::generic::header::Header< +pub(crate) type BundleHeader = crate::types::runtime_types::sp_runtime::generic::header::Header< u32, crate::types::runtime_types::sp_runtime::traits::BlakeTwo256, >; type SpHeader = sp_runtime::generic::Header; impl PangolinClient { - pub async fn subscribe_grandpa_justifications(&self) -> ClientResult> { + async fn grandpa_authorities(&self, at: sp_core::H256) -> ClientResult { + let params = subxt::rpc::rpc_params![ + "GrandpaApi_grandpa_authorities", + sp_core::Bytes(Vec::new()), + at + ]; + let hex: String = self + .subxt() + .rpc() + .client + .request("state_call", params) + .await?; + let raw_authorities_set = array_bytes::hex2bytes(hex)?; + let authorities = codec::Decode::decode(&mut &raw_authorities_set[..]).map_err(|err| { + ClientError::Custom(format!( + "[DecodeAuthorities] Can not decode authorities: {:?}", + err + )) + })?; + Ok(authorities) + } + + /// Find header digest that schedules next GRANDPA authorities set. + fn find_grandpa_authorities_scheduled_change( + &self, + header: &SpHeader, + ) -> Option> { + let filter_log = |log: ConsensusLog| match log { + ConsensusLog::ScheduledChange(change) => Some(change), + _ => None, + }; + + // find the first consensus digest with the right ID which converts to + // the right kind of consensus log. + header + .digest + .logs + .iter() + .filter_map(|item| match item { + DigestItem::Consensus(engine, logs) => { + if engine == &GRANDPA_ENGINE_ID { + Some(&logs[..]) + } else { + None + } + } + _ => None, + }) + .find_map(|mut l| { + let log = codec::Decode::decode(&mut l).ok(); + log.and_then(filter_log) + }) + } +} + +impl S2SClientBase for PangolinClient { + const CHAIN: &'static str = "pangolin"; + + type Config = bp_pangolin::Pangolin; + type Extrinsic = sp_runtime::OpaqueExtrinsic; +} + +#[async_trait::async_trait] +impl S2SClientGeneric for PangolinClient { + type InitializationData = InitializationData; + + async fn subscribe_grandpa_justifications( + &self, + ) -> S2SClientResult> { Ok(self .subxt() .rpc() @@ -30,17 +104,15 @@ impl PangolinClient { .await?) } - pub async fn prepare_initialization_data( - &self, - ) -> ClientResult> { + async fn prepare_initialization_data(&self) -> S2SClientResult { let mut subscription = self.subscribe_grandpa_justifications().await?; let justification = subscription .next() .await - .ok_or_else(|| ClientError::Custom("The subscribe is closed".to_string()))??; + .ok_or_else(|| S2SClientError::Custom("The subscribe is closed".to_string()))??; let justification: bp_header_chain::justification::GrandpaJustification = codec::Decode::decode(&mut &justification.0[..]) - .map_err(|err| ClientError::Custom(format!("Wrong justification: {:?}", err)))?; + .map_err(|err| S2SClientError::Custom(format!("Wrong justification: {:?}", err)))?; let (initial_header_hash, initial_header_number) = ( justification.commit.target_hash, @@ -52,7 +124,7 @@ impl PangolinClient { .header(Some(initial_header_hash)) .await? .ok_or_else(|| { - ClientError::Custom(format!( + S2SClientError::Custom(format!( "Can not get initial header by hash: {:?}", initial_header_hash )) @@ -75,7 +147,7 @@ impl PangolinClient { .map(|c| c.delay == 0) .unwrap_or(false) { - return Err(ClientError::Custom(format!( + return Err(S2SClientError::Custom(format!( "GRANDPA authorities change at {} scheduled to happen in {:?} blocks. \ We expect regular hange to have zero delay", initial_header_hash, @@ -97,7 +169,7 @@ impl PangolinClient { let mut initial_authorities_set_id = 0; let mut min_possible_block_number = 0; let authorities_for_verification = VoterSet::new(authorities_for_verification.clone()) - .ok_or(ClientError::Custom(format!( + .ok_or(S2SClientError::Custom(format!( "[ReadInvalidAuthorities]: {:?}", authorities_for_verification, )))?; @@ -126,7 +198,7 @@ impl PangolinClient { // there can't be more authorities set changes than headers => if we have reached // `initial_block_number` and still have not found correct value of // `initial_authorities_set_id`, then something else is broken => fail - return Err(ClientError::Custom(format!( + return Err(S2SClientError::Custom(format!( "[GuessInitialAuthorities]: {}", initial_header_number ))); @@ -145,63 +217,21 @@ impl PangolinClient { }; let bytes = codec::Encode::encode(&initialization_data); Ok(codec::Decode::decode(&mut &bytes[..]).map_err(|e| { - ClientError::Custom(format!("Failed to decode initialization data: {:?}", e)) + S2SClientError::Custom(format!("Failed to decode initialization data: {:?}", e)) })?) } -} -impl PangolinClient { - async fn grandpa_authorities(&self, at: sp_core::H256) -> ClientResult { - let params = subxt::rpc::rpc_params![ - "GrandpaApi_grandpa_authorities", - sp_core::Bytes(Vec::new()), - at - ]; - let hex: String = self - .subxt() - .rpc() - .client - .request("state_call", params) - .await?; - let raw_authorities_set = array_bytes::hex2bytes(hex)?; - let authorities = codec::Decode::decode(&mut &raw_authorities_set[..]).map_err(|err| { - ClientError::Custom(format!( - "[DecodeAuthorities] Can not decode authorities: {:?}", - err - )) - })?; - Ok(authorities) - } - - /// Find header digest that schedules next GRANDPA authorities set. - fn find_grandpa_authorities_scheduled_change( + async fn initialize( &self, - header: &SpHeader, - ) -> Option> { - let filter_log = |log: ConsensusLog| match log { - ConsensusLog::ScheduledChange(change) => Some(change), - _ => None, - }; - - // find the first consensus digest with the right ID which converts to - // the right kind of consensus log. - header - .digest - .logs - .iter() - .filter_map(|item| match item { - DigestItem::Consensus(engine, logs) => { - if engine == &GRANDPA_ENGINE_ID { - Some(&logs[..]) - } else { - None - } - } - _ => None, - }) - .find_map(|mut l| { - let log = codec::Decode::decode(&mut l).ok(); - log.and_then(filter_log) - }) + initialization_data: Self::InitializationData, + ) -> S2SClientResult<::Hash> { + let hash = self + .runtime() + .tx() + .bridge_pangoro_grandpa() + .initialize(initialization_data) + .sign_and_submit(self.account().signer()) + .await?; + Ok(hash) } } diff --git a/frame/assistants/client-pangolin/src/fastapi/bridge_s2s/mod.rs b/frame/assistants/client-pangolin/src/fastapi/bridge_s2s/mod.rs new file mode 100644 index 000000000..d2c0fe244 --- /dev/null +++ b/frame/assistants/client-pangolin/src/fastapi/bridge_s2s/mod.rs @@ -0,0 +1,3 @@ +#[cfg(feature = "bridge-s2s-pangoro")] +pub mod bridge_pangoro; +pub mod generic; diff --git a/frame/assistants/client-pangolin/src/fastapi/feemarket_s2s/feemarket_pangoro.rs b/frame/assistants/client-pangolin/src/fastapi/feemarket_s2s/feemarket_pangoro.rs new file mode 100644 index 000000000..fb8cfc8dd --- /dev/null +++ b/frame/assistants/client-pangolin/src/fastapi/feemarket_s2s/feemarket_pangoro.rs @@ -0,0 +1,34 @@ +use abstract_feemarket_s2s::api::FeemarketApiRelay; +use abstract_feemarket_s2s::error::AbstractFeemarketResult; +use abstract_feemarket_s2s::types::{Chain, Order}; +use support_toolkit::convert::SmartCodecMapper; + +use crate::client::PangolinClient; + +#[async_trait::async_trait] +impl FeemarketApiRelay for PangolinClient { + async fn order( + &self, + laned_id: abstract_feemarket_s2s::types::LaneId, + message_nonce: abstract_feemarket_s2s::types::MessageNonce, + ) -> AbstractFeemarketResult< + Option< + Order< + ::AccountId, + ::BlockNumber, + ::Balance, + >, + >, + > { + match self + .runtime() + .storage() + .fee_market() + .orders(laned_id, message_nonce, None) + .await? + { + Some(v) => Ok(Some(SmartCodecMapper::map_to(&v)?)), + None => Ok(None), + } + } +} diff --git a/frame/assistants/client-pangolin/src/fastapi/feemarket_s2s/generic.rs b/frame/assistants/client-pangolin/src/fastapi/feemarket_s2s/generic.rs new file mode 100644 index 000000000..c900b9693 --- /dev/null +++ b/frame/assistants/client-pangolin/src/fastapi/feemarket_s2s/generic.rs @@ -0,0 +1,27 @@ +use abstract_feemarket_s2s::api::FeemarketApiBase; +use abstract_feemarket_s2s::error::{AbstractFeemarketError, AbstractFeemarketResult}; +use abstract_feemarket_s2s::types::Chain; + +use crate::client::PangolinClient; + +#[async_trait::async_trait] +impl FeemarketApiBase for PangolinClient { + const CHAIN: &'static str = "pangolin"; + + type Chain = bp_pangolin::Pangolin; + + async fn best_finalized_header_number( + &self, + ) -> AbstractFeemarketResult<::BlockNumber> { + let head_hash = self.subxt().rpc().finalized_head().await?; + let header = self + .subxt() + .rpc() + .header(Some(head_hash)) + .await? + .ok_or_else(|| { + AbstractFeemarketError::Custom("Can not query best finalized header".to_string()) + })?; + Ok(header.number) + } +} diff --git a/frame/assistants/client-pangolin/src/fastapi/feemarket_s2s/mod.rs b/frame/assistants/client-pangolin/src/fastapi/feemarket_s2s/mod.rs new file mode 100644 index 000000000..9bd6c256f --- /dev/null +++ b/frame/assistants/client-pangolin/src/fastapi/feemarket_s2s/mod.rs @@ -0,0 +1,3 @@ +#[cfg(feature = "feemarket-s2s-pangoro")] +pub mod feemarket_pangoro; +pub mod generic; diff --git a/frame/assistants/client-pangolin/src/fastapi/generic.rs b/frame/assistants/client-pangolin/src/fastapi/generic.rs index 869daa757..03751c54d 100644 --- a/frame/assistants/client-pangolin/src/fastapi/generic.rs +++ b/frame/assistants/client-pangolin/src/fastapi/generic.rs @@ -106,3 +106,22 @@ impl PangolinClient { // Ok(()) // } } + +// todo: Remove it when all s2s bridge use relay-s2s +use subxt::rpc::{Subscription, SubscriptionClientT}; +impl PangolinClient { + pub async fn subscribe_grandpa_justifications( + &self, + ) -> ClientResult> { + Ok(self + .subxt() + .rpc() + .client + .subscribe( + "grandpa_subscribeJustifications", + None, + "grandpa_unsubscribeJustifications", + ) + .await?) + } +} diff --git a/frame/assistants/client-pangolin/src/fastapi/mod.rs b/frame/assistants/client-pangolin/src/fastapi/mod.rs index 3f0a61bee..1975ab37e 100644 --- a/frame/assistants/client-pangolin/src/fastapi/mod.rs +++ b/frame/assistants/client-pangolin/src/fastapi/mod.rs @@ -1,5 +1,7 @@ +#[cfg(feature = "bridge-s2s")] +pub mod bridge_s2s; #[cfg(feature = "ethlike-v1")] pub mod ethereum; +#[cfg(feature = "feemarket-s2s")] +pub mod feemarket_s2s; pub mod generic; -#[cfg(feature = "s2s")] -pub mod s2s; diff --git a/frame/assistants/client-pangolin/src/fastapi/s2s/mod.rs b/frame/assistants/client-pangolin/src/fastapi/s2s/mod.rs deleted file mode 100644 index 0add7dbc2..000000000 --- a/frame/assistants/client-pangolin/src/fastapi/s2s/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod generic; diff --git a/frame/assistants/client-pangoro/Cargo.toml b/frame/assistants/client-pangoro/Cargo.toml index 225583435..2dad1dfd2 100644 --- a/frame/assistants/client-pangoro/Cargo.toml +++ b/frame/assistants/client-pangoro/Cargo.toml @@ -25,25 +25,46 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" array-bytes = "1.4" -codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive", "full"] } +codec = { package = "parity-scale-codec", version = "2" } subxt = { git = "https://github.com/darwinia-network/subxt.git", branch = "darwinia-v0.12.2" } +bp-pangoro = { git = "https://github.com/darwinia-network/darwinia-bridges-substrate.git", branch = "darwinia-v0.12.2" } -finality-grandpa = { version = "0.14.0", optional = true } -bp-header-chain = { git = "https://github.com/darwinia-network/darwinia-bridges-substrate.git", branch = "darwinia-v0.12.2", optional = true } -sp-finality-grandpa = { git = "https://github.com/darwinia-network/substrate.git", branch = "darwinia-v0.12.2", optional = true } -sp-runtime = { git = "https://github.com/darwinia-network/substrate.git", branch = "darwinia-v0.12.2", optional = true } +## maybe common +async-trait = { optional = true, version = "0.1" } -support-toolkit = { path = "../../../frame/supports/support-toolkit" } +## substrate +sp-finality-grandpa = { optional = true, git = "https://github.com/darwinia-network/substrate.git", branch = "darwinia-v0.12.2" } +sp-runtime = { optional = true, git = "https://github.com/darwinia-network/substrate.git", branch = "darwinia-v0.12.2" } +sp-core = { optional = true, git = "https://github.com/darwinia-network/substrate.git", branch = "darwinia-v0.12.2" } + +## s2s client +finality-grandpa = { optional = true, version = "0.14" } +abstract-bridge-s2s = { optional = true, path = "../../../frame/abstract/bridge-s2s", features = ["advanced-types"] } + +## feemarket s2s +abstract-feemarket-s2s = { optional = true, path = "../../../frame/abstract/feemarket-s2s", features = ["advanced-types"] } + +support-toolkit = { path = "../../../frame/supports/support-toolkit", features = ["convert"] } [features] -default = [] -s2s = [ - "finality-grandpa", - "bp-header-chain", +default = [] +substrate = [ "sp-finality-grandpa", - "sp-runtime" + "sp-runtime", + "sp-core", +] +bridge-s2s = [ + "substrate", + "async-trait", + "finality-grandpa", + "abstract-bridge-s2s", +] +bridge-s2s-pangolin = ["bridge-s2s"] +feemarket-s2s = [ + "async-trait", + "abstract-feemarket-s2s", ] -s2s-pangolin = ["s2s"] +feemarket-s2s-pangolin = ["feemarket-s2s"] [dev-dependencies] tokio = { version = "1", features = ["full"] } diff --git a/frame/assistants/client-pangoro/src/config.rs b/frame/assistants/client-pangoro/src/config.rs index cf10e4343..2bf473e59 100644 --- a/frame/assistants/client-pangoro/src/config.rs +++ b/frame/assistants/client-pangoro/src/config.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use subxt::{sp_core, sp_runtime}; +use subxt::sp_runtime; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ClientConfig { @@ -15,13 +15,13 @@ pub struct ClientConfig { pub enum PangoroSubxtConfig {} impl subxt::Config for PangoroSubxtConfig { - type Index = u32; - type BlockNumber = u32; - type Hash = sp_core::H256; - type Hashing = sp_runtime::traits::BlakeTwo256; - type AccountId = sp_runtime::AccountId32; - type Address = sp_runtime::MultiAddress; - type Header = sp_runtime::generic::Header; - type Signature = sp_runtime::MultiSignature; + type Index = bp_pangoro::Nonce; + type BlockNumber = bp_pangoro::BlockNumber; + type Hash = bp_pangoro::Hash; + type Hashing = bp_pangoro::Hashing; + type AccountId = bp_pangoro::AccountId; + type Address = bp_pangoro::Address; + type Header = bp_pangoro::Header; + type Signature = bp_pangoro::Signature; type Extrinsic = sp_runtime::OpaqueExtrinsic; } diff --git a/frame/assistants/client-pangoro/src/error.rs b/frame/assistants/client-pangoro/src/error.rs index 6bd833309..b4ecdfd53 100644 --- a/frame/assistants/client-pangoro/src/error.rs +++ b/frame/assistants/client-pangoro/src/error.rs @@ -1,8 +1,9 @@ #![allow(missing_docs)] -use support_toolkit::error::TkError; use thiserror::Error as ThisError; +use support_toolkit::error::TkError; + pub type ClientResult = Result; /// Error enum. @@ -63,3 +64,14 @@ impl From for ClientError { Self::Bytes(format!("{:?}", error)) } } + +#[cfg(feature = "bridge-s2s")] +impl From for abstract_bridge_s2s::error::S2SClientError { + fn from(error: ClientError) -> Self { + match error { + ClientError::SubxtBasicError(e) => Self::RPC(format!("{:?}", e)), + ClientError::ClientRestartNeed => Self::RPC(format!("Client restart need")), + _ => Self::Custom(format!("{:?}", error)), + } + } +} diff --git a/frame/assistants/client-pangoro/src/fastapi/bridge_s2s/bridge_pangolin.rs b/frame/assistants/client-pangoro/src/fastapi/bridge_s2s/bridge_pangolin.rs new file mode 100644 index 000000000..58a622e82 --- /dev/null +++ b/frame/assistants/client-pangoro/src/fastapi/bridge_s2s/bridge_pangolin.rs @@ -0,0 +1,233 @@ +use std::ops::RangeInclusive; + +use abstract_bridge_s2s::error::S2SClientResult; +use abstract_bridge_s2s::{ + client::S2SClientRelay, + config::Config, + types::{bp_header_chain, bp_messages, bridge_runtime_common}, +}; +use sp_runtime::generic::{Block, SignedBlock}; +use sp_runtime::AccountId32; +use subxt::sp_core::storage::StorageKey; +use subxt::storage::StorageKeyPrefix; +use subxt::StorageEntry; +use support_toolkit::convert::SmartCodecMapper; + +use crate::client::PangoroClient; +use crate::error::ClientError; +use crate::subxt_runtime::api::bridge_pangolin_messages::storage::{ + InboundLanes, OutboundLanes, OutboundMessages, +}; + +type BundleMessageKey = crate::types::runtime_types::bp_messages::MessageKey; + +/// Message payload for This -> Bridged chain messages. +type FromThisChainMessagePayload = crate::types::runtime_types::bp_message_dispatch::MessagePayload< + sp_core::crypto::AccountId32, + crate::types::runtime_types::sp_runtime::MultiSigner, + crate::types::runtime_types::sp_runtime::MultiSignature, + Vec, +>; + +#[async_trait::async_trait] +impl S2SClientRelay for PangoroClient { + fn gen_outbound_messages_storage_key(&self, lane: [u8; 4], message_nonce: u64) -> StorageKey { + let prefix = StorageKeyPrefix::new::(); + OutboundMessages(BundleMessageKey { + lane_id: lane, + nonce: message_nonce, + }) + .key() + .final_key(prefix) + } + + fn gen_outbound_lanes_storage_key(&self, lane: [u8; 4]) -> StorageKey { + OutboundLanes(lane) + .key() + .final_key(StorageKeyPrefix::new::()) + } + + fn gen_inbound_lanes_storage_key(&self, lane: [u8; 4]) -> StorageKey { + InboundLanes(lane) + .key() + .final_key(StorageKeyPrefix::new::()) + } + + async fn calculate_dispatch_weight( + &self, + lane: [u8; 4], + nonces: RangeInclusive, + ) -> S2SClientResult { + let mut total_weight = 0u64; + for message_nonce in nonces { + let message_data = self + .outbound_messages( + bp_messages::MessageKey { + lane_id: lane, + nonce: message_nonce, + }, + None, + ) + .await? + .ok_or_else(|| { + ClientError::Custom(format!( + "Can not read message data by nonce {} in pangolin", + message_nonce + )) + })?; + let decoded_payload: FromThisChainMessagePayload = + codec::Decode::decode(&mut &message_data.payload[..])?; + total_weight += decoded_payload.weight; + } + Ok(total_weight) + } + + async fn header( + &self, + hash: Option<::Hash>, + ) -> S2SClientResult::Header>> { + match self.subxt().rpc().header(hash).await? { + Some(v) => Ok(Some(SmartCodecMapper::map_to(&v)?)), + None => Ok(None), + } + } + + async fn block( + &self, + hash: Option<::Hash>, + ) -> S2SClientResult< + Option::Header, Self::Extrinsic>>>, + > { + Ok(self.subxt().rpc().block(hash).await?) + } + + async fn best_target_finalized( + &self, + at_block: Option<::Hash>, + ) -> S2SClientResult<::Hash> { + Ok(self + .runtime() + .storage() + .bridge_pangolin_grandpa() + .best_finalized(at_block) + .await?) + } + + async fn submit_finality_proof( + &self, + finality_target: ::Header, + justification: bp_header_chain::justification::GrandpaJustification< + ::Header, + >, + ) -> S2SClientResult<::Hash> { + let expected_target = SmartCodecMapper::map_to(&finality_target)?; + let expected_justification = SmartCodecMapper::map_to(&justification)?; + Ok(self + .runtime() + .tx() + .bridge_pangolin_grandpa() + .submit_finality_proof(expected_target, expected_justification) + .sign_and_submit(self.account().signer()) + .await?) + } + + async fn outbound_lanes( + &self, + lane: [u8; 4], + hash: Option<::Hash>, + ) -> S2SClientResult { + let outbound_lane_data = self + .runtime() + .storage() + .bridge_pangolin_messages() + .outbound_lanes(lane, hash) + .await?; + let expected = SmartCodecMapper::map_to(&outbound_lane_data)?; + Ok(expected) + } + + async fn inbound_lanes( + &self, + lane: [u8; 4], + hash: Option<::Hash>, + ) -> S2SClientResult> { + let inbound_lane_data = self + .runtime() + .storage() + .bridge_pangolin_messages() + .inbound_lanes(lane, hash) + .await?; + let expected = SmartCodecMapper::map_to(&inbound_lane_data)?; + Ok(expected) + } + + async fn outbound_messages( + &self, + message_key: bp_messages::MessageKey, + hash: Option<::Hash>, + ) -> S2SClientResult>> { + let expected_message_key = SmartCodecMapper::map_to(&message_key)?; + match self + .runtime() + .storage() + .bridge_pangolin_messages() + .outbound_messages(expected_message_key, hash) + .await? + { + Some(v) => Ok(Some(SmartCodecMapper::map_to(&v)?)), + None => Ok(None), + } + } + + async fn read_proof( + &self, + storage_keys: Vec, + hash: Option<::Hash>, + ) -> S2SClientResult>> { + let read_proof = self.subxt().rpc().read_proof(storage_keys, hash).await?; + let proof: Vec> = read_proof.proof.into_iter().map(|item| item.0).collect(); + Ok(proof) + } + + async fn receive_messages_proof( + &self, + relayer_id_at_bridged_chain: AccountId32, + proof: bridge_runtime_common::messages::target::FromBridgedChainMessagesProof< + ::Hash, + >, + messages_count: u32, + dispatch_weight: u64, + ) -> S2SClientResult<::Hash> { + let expected_proof = SmartCodecMapper::map_to(&proof)?; + Ok(self + .runtime() + .tx() + .bridge_pangolin_messages() + .receive_messages_proof( + relayer_id_at_bridged_chain, + expected_proof, + messages_count, + dispatch_weight, + ) + .sign_and_submit(self.account().signer()) + .await?) + } + + async fn receive_messages_delivery_proof( + &self, + proof: bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof< + ::Hash, + >, + relayers_state: bp_messages::UnrewardedRelayersState, + ) -> S2SClientResult<::Hash> { + let expected_proof = SmartCodecMapper::map_to(&proof)?; + let expected_relayers_state = SmartCodecMapper::map_to(&relayers_state)?; + Ok(self + .runtime() + .tx() + .bridge_pangolin_messages() + .receive_messages_delivery_proof(expected_proof, expected_relayers_state) + .sign_and_submit(self.account().signer()) + .await?) + } +} diff --git a/frame/assistants/client-pangoro/src/fastapi/s2s/generic.rs b/frame/assistants/client-pangoro/src/fastapi/bridge_s2s/generic.rs similarity index 80% rename from frame/assistants/client-pangoro/src/fastapi/s2s/generic.rs rename to frame/assistants/client-pangoro/src/fastapi/bridge_s2s/generic.rs index 33107fd2a..efd1bf52f 100644 --- a/frame/assistants/client-pangoro/src/fastapi/s2s/generic.rs +++ b/frame/assistants/client-pangoro/src/fastapi/bridge_s2s/generic.rs @@ -1,3 +1,9 @@ +use abstract_bridge_s2s::config::Config; +use abstract_bridge_s2s::error::{S2SClientError, S2SClientResult}; +use abstract_bridge_s2s::{ + client::{S2SClientBase, S2SClientGeneric}, + types::bp_header_chain, +}; use finality_grandpa::voter_set::VoterSet; use sp_finality_grandpa::{AuthorityList, ConsensusLog, ScheduledChange}; use sp_runtime::{ConsensusEngineId, DigestItem}; @@ -10,14 +16,82 @@ use crate::types::runtime_types::bp_header_chain::InitializationData; const GRANDPA_ENGINE_ID: ConsensusEngineId = *b"FRNK"; -type BundleHeader = crate::types::runtime_types::sp_runtime::generic::header::Header< +pub(crate) type BundleHeader = crate::types::runtime_types::sp_runtime::generic::header::Header< u32, crate::types::runtime_types::sp_runtime::traits::BlakeTwo256, >; type SpHeader = sp_runtime::generic::Header; impl PangoroClient { - pub async fn subscribe_grandpa_justifications(&self) -> ClientResult> { + async fn grandpa_authorities(&self, at: sp_core::H256) -> ClientResult { + let params = subxt::rpc::rpc_params![ + "GrandpaApi_grandpa_authorities", + sp_core::Bytes(Vec::new()), + at + ]; + let hex: String = self + .subxt() + .rpc() + .client + .request("state_call", params) + .await?; + let raw_authorities_set = array_bytes::hex2bytes(hex)?; + let authorities = codec::Decode::decode(&mut &raw_authorities_set[..]).map_err(|err| { + ClientError::Custom(format!( + "[DecodeAuthorities] Can not decode authorities: {:?}", + err + )) + })?; + Ok(authorities) + } + + /// Find header digest that schedules next GRANDPA authorities set. + fn find_grandpa_authorities_scheduled_change( + &self, + header: &SpHeader, + ) -> Option> { + let filter_log = |log: ConsensusLog| match log { + ConsensusLog::ScheduledChange(change) => Some(change), + _ => None, + }; + + // find the first consensus digest with the right ID which converts to + // the right kind of consensus log. + header + .digest + .logs + .iter() + .filter_map(|item| match item { + DigestItem::Consensus(engine, logs) => { + if engine == &GRANDPA_ENGINE_ID { + Some(&logs[..]) + } else { + None + } + } + _ => None, + }) + .find_map(|mut l| { + let log = codec::Decode::decode(&mut l).ok(); + log.and_then(filter_log) + }) + } +} + +impl S2SClientBase for PangoroClient { + const CHAIN: &'static str = "pangoro"; + + type Config = bp_pangoro::Pangoro; + type Extrinsic = sp_runtime::OpaqueExtrinsic; +} + +#[async_trait::async_trait] +impl S2SClientGeneric for PangoroClient { + type InitializationData = InitializationData; + + async fn subscribe_grandpa_justifications( + &self, + ) -> S2SClientResult> { Ok(self .subxt() .rpc() @@ -30,17 +104,15 @@ impl PangoroClient { .await?) } - pub async fn prepare_initialization_data( - &self, - ) -> ClientResult> { + async fn prepare_initialization_data(&self) -> S2SClientResult { let mut subscription = self.subscribe_grandpa_justifications().await?; let justification = subscription .next() .await - .ok_or_else(|| ClientError::Custom("The subscribe is closed".to_string()))??; + .ok_or_else(|| S2SClientError::Custom("The subscribe is closed".to_string()))??; let justification: bp_header_chain::justification::GrandpaJustification = codec::Decode::decode(&mut &justification.0[..]) - .map_err(|err| ClientError::Custom(format!("Wrong justification: {:?}", err)))?; + .map_err(|err| S2SClientError::Custom(format!("Wrong justification: {:?}", err)))?; let (initial_header_hash, initial_header_number) = ( justification.commit.target_hash, @@ -52,7 +124,7 @@ impl PangoroClient { .header(Some(initial_header_hash)) .await? .ok_or_else(|| { - ClientError::Custom(format!( + S2SClientError::Custom(format!( "Can not get initial header by hash: {:?}", initial_header_hash )) @@ -75,7 +147,7 @@ impl PangoroClient { .map(|c| c.delay == 0) .unwrap_or(false) { - return Err(ClientError::Custom(format!( + return Err(S2SClientError::Custom(format!( "GRANDPA authorities change at {} scheduled to happen in {:?} blocks. \ We expect regular hange to have zero delay", initial_header_hash, @@ -97,7 +169,7 @@ impl PangoroClient { let mut initial_authorities_set_id = 0; let mut min_possible_block_number = 0; let authorities_for_verification = VoterSet::new(authorities_for_verification.clone()) - .ok_or(ClientError::Custom(format!( + .ok_or(S2SClientError::Custom(format!( "[ReadInvalidAuthorities]: {:?}", authorities_for_verification, )))?; @@ -126,7 +198,7 @@ impl PangoroClient { // there can't be more authorities set changes than headers => if we have reached // `initial_block_number` and still have not found correct value of // `initial_authorities_set_id`, then something else is broken => fail - return Err(ClientError::Custom(format!( + return Err(S2SClientError::Custom(format!( "[GuessInitialAuthorities]: {}", initial_header_number ))); @@ -145,63 +217,21 @@ impl PangoroClient { }; let bytes = codec::Encode::encode(&initialization_data); Ok(codec::Decode::decode(&mut &bytes[..]).map_err(|e| { - ClientError::Custom(format!("Failed to decode initialization data: {:?}", e)) + S2SClientError::Custom(format!("Failed to decode initialization data: {:?}", e)) })?) } -} -impl PangoroClient { - async fn grandpa_authorities(&self, at: sp_core::H256) -> ClientResult { - let params = subxt::rpc::rpc_params![ - "GrandpaApi_grandpa_authorities", - sp_core::Bytes(Vec::new()), - at - ]; - let hex: String = self - .subxt() - .rpc() - .client - .request("state_call", params) - .await?; - let raw_authorities_set = array_bytes::hex2bytes(hex)?; - let authorities = codec::Decode::decode(&mut &raw_authorities_set[..]).map_err(|err| { - ClientError::Custom(format!( - "[DecodeAuthorities] Can not decode authorities: {:?}", - err - )) - })?; - Ok(authorities) - } - - /// Find header digest that schedules next GRANDPA authorities set. - fn find_grandpa_authorities_scheduled_change( + async fn initialize( &self, - header: &SpHeader, - ) -> Option> { - let filter_log = |log: ConsensusLog| match log { - ConsensusLog::ScheduledChange(change) => Some(change), - _ => None, - }; - - // find the first consensus digest with the right ID which converts to - // the right kind of consensus log. - header - .digest - .logs - .iter() - .filter_map(|item| match item { - DigestItem::Consensus(engine, logs) => { - if engine == &GRANDPA_ENGINE_ID { - Some(&logs[..]) - } else { - None - } - } - _ => None, - }) - .find_map(|mut l| { - let log = codec::Decode::decode(&mut l).ok(); - log.and_then(filter_log) - }) + initialization_data: Self::InitializationData, + ) -> S2SClientResult<::Hash> { + let hash = self + .runtime() + .tx() + .bridge_pangolin_grandpa() + .initialize(initialization_data) + .sign_and_submit(self.account().signer()) + .await?; + Ok(hash) } } diff --git a/frame/assistants/client-pangoro/src/fastapi/bridge_s2s/mod.rs b/frame/assistants/client-pangoro/src/fastapi/bridge_s2s/mod.rs new file mode 100644 index 000000000..f1c5eae6f --- /dev/null +++ b/frame/assistants/client-pangoro/src/fastapi/bridge_s2s/mod.rs @@ -0,0 +1,3 @@ +#[cfg(feature = "bridge-s2s-pangolin")] +pub mod bridge_pangolin; +pub mod generic; diff --git a/frame/assistants/client-pangoro/src/fastapi/feemarket_s2s/feemarket_pangolin.rs b/frame/assistants/client-pangoro/src/fastapi/feemarket_s2s/feemarket_pangolin.rs new file mode 100644 index 000000000..1e108461f --- /dev/null +++ b/frame/assistants/client-pangoro/src/fastapi/feemarket_s2s/feemarket_pangolin.rs @@ -0,0 +1,34 @@ +use abstract_feemarket_s2s::api::FeemarketApiRelay; +use abstract_feemarket_s2s::error::AbstractFeemarketResult; +use abstract_feemarket_s2s::types::{Chain, Order}; +use support_toolkit::convert::SmartCodecMapper; + +use crate::client::PangoroClient; + +#[async_trait::async_trait] +impl FeemarketApiRelay for PangoroClient { + async fn order( + &self, + laned_id: abstract_feemarket_s2s::types::LaneId, + message_nonce: abstract_feemarket_s2s::types::MessageNonce, + ) -> AbstractFeemarketResult< + Option< + Order< + ::AccountId, + ::BlockNumber, + ::Balance, + >, + >, + > { + match self + .runtime() + .storage() + .fee_market() + .orders(laned_id, message_nonce, None) + .await? + { + Some(v) => Ok(Some(SmartCodecMapper::map_to(&v)?)), + None => Ok(None), + } + } +} diff --git a/frame/assistants/client-pangoro/src/fastapi/feemarket_s2s/generic.rs b/frame/assistants/client-pangoro/src/fastapi/feemarket_s2s/generic.rs new file mode 100644 index 000000000..c6287b47a --- /dev/null +++ b/frame/assistants/client-pangoro/src/fastapi/feemarket_s2s/generic.rs @@ -0,0 +1,27 @@ +use abstract_feemarket_s2s::api::FeemarketApiBase; +use abstract_feemarket_s2s::error::{AbstractFeemarketError, AbstractFeemarketResult}; +use abstract_feemarket_s2s::types::Chain; + +use crate::client::PangoroClient; + +#[async_trait::async_trait] +impl FeemarketApiBase for PangoroClient { + const CHAIN: &'static str = "pangoro"; + + type Chain = bp_pangoro::Pangoro; + + async fn best_finalized_header_number( + &self, + ) -> AbstractFeemarketResult<::BlockNumber> { + let head_hash = self.subxt().rpc().finalized_head().await?; + let header = self + .subxt() + .rpc() + .header(Some(head_hash)) + .await? + .ok_or_else(|| { + AbstractFeemarketError::Custom("Can not query best finalized header".to_string()) + })?; + Ok(header.number) + } +} diff --git a/frame/assistants/client-pangoro/src/fastapi/feemarket_s2s/mod.rs b/frame/assistants/client-pangoro/src/fastapi/feemarket_s2s/mod.rs new file mode 100644 index 000000000..1942372c1 --- /dev/null +++ b/frame/assistants/client-pangoro/src/fastapi/feemarket_s2s/mod.rs @@ -0,0 +1,3 @@ +#[cfg(feature = "feemarket-s2s-pangolin")] +mod feemarket_pangolin; +mod generic; diff --git a/frame/assistants/client-pangoro/src/fastapi/mod.rs b/frame/assistants/client-pangoro/src/fastapi/mod.rs index e53441e18..7d2456ded 100644 --- a/frame/assistants/client-pangoro/src/fastapi/mod.rs +++ b/frame/assistants/client-pangoro/src/fastapi/mod.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "bridge-s2s")] +pub mod bridge_s2s; +#[cfg(feature = "feemarket-s2s")] +pub mod feemarket_s2s; pub mod generic; -#[cfg(feature = "s2s")] -pub mod s2s; diff --git a/frame/assistants/client-pangoro/src/fastapi/s2s/mod.rs b/frame/assistants/client-pangoro/src/fastapi/s2s/mod.rs deleted file mode 100644 index 0add7dbc2..000000000 --- a/frame/assistants/client-pangoro/src/fastapi/s2s/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod generic; diff --git a/frame/assistants/feemarket-ns2s/Cargo.toml b/frame/assistants/feemarket-ns2s/Cargo.toml new file mode 100644 index 000000000..c4a143997 --- /dev/null +++ b/frame/assistants/feemarket-ns2s/Cargo.toml @@ -0,0 +1,26 @@ +[package] +authors = ["Darwinia Network "] +description = "Darwinia bridger" +documentation = "https://rust-docs.darwinia.network/bridger" +edition = "2021" +homepage = "https://github.com/darwinia-network/bridger" +include = ["Cargo.toml", "**/*.rs", "README.md", "LICENSE"] +keywords = ["substrate", "darwinia"] +license = "MIT" +name = "feemarket-ns2s" +readme = 'README.md' +repository = "https://github.com/darwinia-network/bridger" +version = "0.5.7" + +[dependencies] + +async-trait = "0.1" +serde = { version = "1", features = ["derive"] } +thiserror = "1.0" +tracing = "0.1" + +component-subscan = { path = "../../components/subscan" } + +abstract-bridge-s2s = { path = "../../abstract/bridge-s2s" } +abstract-feemarket-s2s = { path = "../../abstract/feemarket-s2s" } +support-toolkit = { path = "../../supports/support-toolkit", features = ["log"] } diff --git a/frame/assistants/feemarket-ns2s/src/error.rs b/frame/assistants/feemarket-ns2s/src/error.rs new file mode 100644 index 000000000..dd760ed11 --- /dev/null +++ b/frame/assistants/feemarket-ns2s/src/error.rs @@ -0,0 +1,13 @@ +use thiserror::Error as ThisError; + +pub type FeemarketResult = Result; + +#[derive(ThisError, Debug)] +pub enum FeemarketError { + #[error("Wrong data convert: {0}")] + WrongConvert(String), + #[error(transparent)] + Subscan(#[from] component_subscan::SubscanComponentError), + #[error("Custom: {0}")] + Custom(String), +} diff --git a/frame/assistants/feemarket-ns2s/src/lib.rs b/frame/assistants/feemarket-ns2s/src/lib.rs new file mode 100644 index 000000000..291d8f86c --- /dev/null +++ b/frame/assistants/feemarket-ns2s/src/lib.rs @@ -0,0 +1,2 @@ +pub mod error; +pub mod relay; diff --git a/frame/assistants/feemarket-ns2s/src/relay.rs b/frame/assistants/feemarket-ns2s/src/relay.rs new file mode 100644 index 000000000..38883ee04 --- /dev/null +++ b/frame/assistants/feemarket-ns2s/src/relay.rs @@ -0,0 +1 @@ +pub mod basic; diff --git a/frame/assistants/feemarket-ns2s/src/relay/basic.rs b/frame/assistants/feemarket-ns2s/src/relay/basic.rs new file mode 100644 index 000000000..c5a2a247a --- /dev/null +++ b/frame/assistants/feemarket-ns2s/src/relay/basic.rs @@ -0,0 +1,136 @@ +use std::ops::Range; + +use abstract_bridge_s2s::error::{S2SClientError, S2SClientResult}; +use abstract_bridge_s2s::strategy::{RelayReference, RelayStrategy}; + +use abstract_feemarket_s2s::api::FeemarketApiRelay; +use abstract_feemarket_s2s::types::Chain; +use support_toolkit::logk; + +/// Basic relay strategy +/// 1. if you are assigned relayer you will relay all order whether or not it times out +/// 2. if you aren't assigned relayer, only participate in the part about time out, earn more rewards +/// 3. if not have any assigned relayers, everyone participates in the relay. +// #[derive(Clone)] +pub struct BasicRelayStrategy { + api: A, + account: ::AccountId, +} + +impl BasicRelayStrategy { + pub fn new(api: A, account: ::AccountId) -> Self { + Self { api, account } + } +} + +impl Clone for BasicRelayStrategy { + fn clone(&self) -> Self { + Self { + api: self.api.clone(), + account: self.account.clone(), + } + } +} + +#[async_trait::async_trait] +impl RelayStrategy for BasicRelayStrategy { + async fn decide(&mut self, reference: RelayReference) -> S2SClientResult { + let lane = reference.lane; + let nonce = reference.nonce; + tracing::trace!( + target: "feemarket", + "{} determine whether to relay for nonce: {}", + logk::prefix_with_relation("feemarket", "relay", A::CHAIN, "::"), + nonce, + ); + let order = self + .api + .order(lane, nonce) + .await + .map_err(|e| S2SClientError::Custom(format!("[feemarket]: {:?}", e)))?; + + // If the order is not exists. + // 1. You are too behind. + // 2. The network question + // So, you can skip this currently + // Related: https://github.com/darwinia-network/darwinia-common/blob/90add536ed320ec7e17898e695c65ee9d7ce79b0/frame/fee-market/src/lib.rs?#L177 + if order.is_none() { + tracing::info!( + target: "feemarket", + "{} not found order by nonce: {}, so decide don't relay this nonce", + logk::prefix_with_relation("feemarket", "relay", A::CHAIN, "::"), + nonce, + ); + return Ok(false); + } + // ----- + + let order = order.unwrap(); + let relayers = order.relayers; + + // If not have any assigned relayers, everyone participates in the relay. + if relayers.is_empty() { + tracing::info!( + target: "feemarket", + "{} not found any assigned relayers so relay this nonce({}) anyway", + logk::prefix_with_relation("feemarket", "relay", A::CHAIN, "::"), + nonce, + ); + return Ok(true); + } + + // ----- + + let prior_relayer = relayers.iter().find(|item| item.id == self.account); + let is_assigned_relayer = prior_relayer.is_some(); + + // If you are assigned relayer, you must relay this nonce. + // If you don't do that, the fee market pallet will slash your deposit. + // Even though it is a timeout, although it will slash your deposit after the timeout is delivered, + // you can still get relay rewards. + if is_assigned_relayer { + tracing::info!( + target: "feemarket", + "{} you are assigned relayer, you must be relay this nonce({})", + logk::prefix_with_relation("feemarket", "relay", A::CHAIN, "::"), + nonce, + ); + return Ok(true); + } + + // ----- + + // If you aren't assigned relayer, only participate in the part about time out, earn more rewards + let latest_block_number = self + .api + .best_finalized_header_number() + .await + .map_err(|e| S2SClientError::Custom(format!("[feemarket]: {:?}", e)))?; + let ranges = relayers + .iter() + .map(|item| item.valid_range.clone()) + .collect::::BlockNumber>>>(); + + let mut maximum_timeout: ::BlockNumber = Default::default(); + for range in ranges { + maximum_timeout = std::cmp::max(maximum_timeout, range.end); + } + // If this order has timed out, decide to relay + if latest_block_number > maximum_timeout { + tracing::info!( + target: "feemarket", + "{} you aren't assigned relayer. but this nonce is timeout. so the decide is relay this nonce: {}", + logk::prefix_with_relation("feemarket", "relay", A::CHAIN, "::"), + nonce, + ); + return Ok(true); + } + tracing::info!( + target: "feemarket", + "{} you aren't assigned relay. and this nonce({}) is on-time. so don't relay this", + logk::prefix_with_relation("feemarket", "relay", A::CHAIN, "::"), + nonce, + ); + Ok(false) + } +} diff --git a/frame/assistants/relay-s2s/Cargo.toml b/frame/assistants/relay-s2s/Cargo.toml new file mode 100644 index 000000000..4ae0f2bb4 --- /dev/null +++ b/frame/assistants/relay-s2s/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "relay-s2s" +version = "0.5.7" +authors = ["Darwinia Network "] +description = "Darwinia bridger" +repository = "https://github.com/darwinia-network/bridger" +license = "MIT" +documentation = "https://rust-docs.darwinia.network/bridger" +homepage = "https://github.com/darwinia-network/bridger" +include = [ + "Cargo.toml", + "**/*.rs", + "README.md", + "LICENSE" +] +keywords = ["substrate", "darwinia"] +readme = 'README.md' +edition = "2021" + +[dependencies] +tracing = "0.1" +thiserror = "1.0" +array-bytes = "1.4" +tokio = { version = "1", features = ["time"] } +once_cell = "1" + +sp-core = { git = "https://github.com/darwinia-network/substrate.git", branch = "darwinia-v0.12.2" } +sp-runtime = { git = "https://github.com/darwinia-network/substrate.git", branch = "darwinia-v0.12.2" } + +support-toolkit = { path = "../../supports/support-toolkit", features = ["convert", "log"] } + +abstract-bridge-s2s = { path = "../../abstract/bridge-s2s" } +subquery-s2s = { path = "../subquery-s2s" } +subquery-parachain = { path = "../subquery-parachain" } + +[features] +default = [] +bridge-parachain = [ + "abstract-bridge-s2s/bridge-parachain", +] diff --git a/frame/assistants/relay-s2s/src/error.rs b/frame/assistants/relay-s2s/src/error.rs new file mode 100644 index 000000000..19091eba8 --- /dev/null +++ b/frame/assistants/relay-s2s/src/error.rs @@ -0,0 +1,28 @@ +use abstract_bridge_s2s::error::S2SClientError; +use sp_runtime::codec; +use subquery_s2s::SubqueryComponentError; +use thiserror::Error as ThisError; + +pub type RelayResult = Result; + +#[derive(ThisError, Debug)] +pub enum RelayError { + #[error(transparent)] + Subquery(#[from] SubqueryComponentError), + #[error(transparent)] + Client(#[from] S2SClientError), + #[error(transparent)] + Codec(#[from] codec::Error), + #[error("Custom: {0}")] + Custom(String), +} + +impl From for RelayError { + fn from(error: subquery_parachain::SubqueryComponentError) -> Self { + match error { + subquery_parachain::SubqueryComponentError::GraphQL(message) => { + RelayError::Subquery(SubqueryComponentError::GraphQL(message)) + } + } + } +} diff --git a/frame/assistants/relay-s2s/src/header/mod.rs b/frame/assistants/relay-s2s/src/header/mod.rs new file mode 100644 index 000000000..59cf4b1d5 --- /dev/null +++ b/frame/assistants/relay-s2s/src/header/mod.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "bridge-parachain")] +pub use self::para_head_relay::*; +#[cfg(feature = "bridge-parachain")] +pub use self::relaychain_head_relay::*; +pub use self::solochain_head_relay::*; + +#[cfg(feature = "bridge-parachain")] +mod para_head_relay; +#[cfg(feature = "bridge-parachain")] +mod relaychain_head_relay; +mod solochain_head_relay; diff --git a/frame/assistants/relay-s2s/src/header/para_head_relay.rs b/frame/assistants/relay-s2s/src/header/para_head_relay.rs new file mode 100644 index 000000000..5e19b3f8a --- /dev/null +++ b/frame/assistants/relay-s2s/src/header/para_head_relay.rs @@ -0,0 +1,139 @@ +use abstract_bridge_s2s::client::{S2SParaBridgeClientRelaychain, S2SParaBridgeClientSolochain}; +use abstract_bridge_s2s::types::ParaId; +use sp_runtime::traits::Hash; +use sp_runtime::traits::Header; +use support_toolkit::{convert::SmartCodecMapper, logk}; + +use crate::error::{RelayError, RelayResult}; +use crate::types::{ParaHeaderInput, M_PARA_HEAD}; + +/// para head to solo chain header relay runner +pub struct ParaHeaderRunner { + input: ParaHeaderInput, +} + +impl ParaHeaderRunner { + pub async fn start(&self) -> RelayResult<()> { + loop { + self.run().await?; + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + } + } + + async fn run(&self) -> RelayResult<()> { + let client_solochain = &self.input.client_solochain; + let client_relaychain = &self.input.client_relaychain; + + let best_target_header = client_solochain + .header(None) + .await? + .ok_or_else(|| RelayError::Custom(format!("Failed to get {} header", SC::CHAIN)))?; + tracing::trace!( + target: "relay-s2s", + "{} current {} block: {:?}", + logk::prefix_with_bridge(M_PARA_HEAD, SC::CHAIN, TC::CHAIN), + SC::CHAIN, + best_target_header.number(), + ); + let para_head_at_target = client_solochain + .best_para_heads(ParaId(self.input.para_id), Some(best_target_header.hash())) + .await?; + tracing::trace!( + target: "relay-s2s", + "{} the last para-head on {}: {:?}", + logk::prefix_with_bridge(M_PARA_HEAD, SC::CHAIN, TC::CHAIN), + SC::CHAIN, + ¶_head_at_target, + ); + + let best_finalized_source_block_hash = client_solochain + .best_target_finalized(Some(best_target_header.hash())) + .await?; + + let expected_source_block_hash = + SmartCodecMapper::map_to(&best_finalized_source_block_hash)?; + let best_finalized_source_block_at_target = client_relaychain + .block(Some(expected_source_block_hash)) + .await? + .ok_or_else(|| RelayError::Custom("Failed to get Rococo block".to_string()))?; + // todo: fix this types + let best_finalized_source_block_at_target_number: u32 = + SmartCodecMapper::map_to(best_finalized_source_block_at_target.block.header.number())?; + tracing::trace!( + target: "relay-s2s", + "{} the last relaychain block on solochain: {:?}", + logk::prefix_with_bridge(M_PARA_HEAD, SC::CHAIN, TC::CHAIN), + best_finalized_source_block_at_target_number, + ); + let para_head_at_source = client_relaychain + .para_head_data(ParaId(self.input.para_id), Some(expected_source_block_hash)) + .await?; + tracing::trace!( + target: "relay-s2s", + "{} the last para-head on relaychain {:?}", + logk::prefix_with_bridge(M_PARA_HEAD, SC::CHAIN, TC::CHAIN), + best_finalized_source_block_at_target_number, + ); + + let need_relay = match (para_head_at_source, para_head_at_target) { + (Some(head_at_source), Some(head_at_target)) + if head_at_target.at_relay_block_number + < best_finalized_source_block_at_target_number + && head_at_target.head_hash + != sp_runtime::traits::BlakeTwo256::hash(head_at_source.0.as_slice()) => + { + true + } + (Some(_), None) => true, + (None, Some(_)) => true, + (None, None) => { + tracing::info!( + target: "relay-s2s", + "{} parachain is unknown to both clients", + logk::prefix_with_bridge(M_PARA_HEAD, SC::CHAIN, TC::CHAIN), + ); + false + } + (Some(_), Some(_)) => { + tracing::info!( + target: "relay-s2s", + "{} not need need to relay", + logk::prefix_with_bridge(M_PARA_HEAD, SC::CHAIN, TC::CHAIN), + ); + false + } + }; + + if !need_relay { + return Ok(()); + } + + let heads_proofs = client_relaychain + .read_proof( + vec![client_relaychain.gen_parachain_head_storage_key(self.input.para_id)], + Some(expected_source_block_hash), + ) + .await?; + tracing::info!( + target: "relay-s2s", + "{} submitting parachain heads update transaction to {}", + logk::prefix_with_bridge(M_PARA_HEAD, SC::CHAIN, TC::CHAIN), + SC::CHAIN, + ); + + let hash = client_solochain + .submit_parachain_heads( + best_finalized_source_block_hash, + vec![ParaId(self.input.para_id)], + heads_proofs, + ) + .await?; + tracing::info!( + target: "relay-s2s", + "{} the tx hash {} emitted", + logk::prefix_with_bridge(M_PARA_HEAD, SC::CHAIN, TC::CHAIN), + array_bytes::bytes2hex("0x", hash), + ); + Ok(()) + } +} diff --git a/frame/assistants/relay-s2s/src/header/relaychain_head_relay.rs b/frame/assistants/relay-s2s/src/header/relaychain_head_relay.rs new file mode 100644 index 000000000..487578daf --- /dev/null +++ b/frame/assistants/relay-s2s/src/header/relaychain_head_relay.rs @@ -0,0 +1,212 @@ +use std::str::FromStr; + +use abstract_bridge_s2s::client::S2SClientRelay; +use abstract_bridge_s2s::config::Config; +use abstract_bridge_s2s::types::bp_header_chain; +use sp_runtime::codec; +use sp_runtime::traits::Header; +use support_toolkit::{convert::SmartCodecMapper, logk}; + +use crate::error::{RelayError, RelayResult}; +use crate::types::{RelaychainHeaderInput, M_HEADER}; + +/// relay chain to solo chain header relay runner +pub struct RelaychainHeaderRunner { + input: RelaychainHeaderInput, +} + +impl RelaychainHeaderRunner { + pub async fn start(&self) -> RelayResult<()> { + loop { + self.run().await?; + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + } + } + + async fn run(&self) -> RelayResult<()> { + let client_relaychain = &self.input.client_relaychain; + let client_solochain = &self.input.client_solochain; + let last_relayed_relaychain_hash_in_solochain = + client_solochain.best_target_finalized(None).await?; + let expected_relaychain_hash = + SmartCodecMapper::map_to(&last_relayed_relaychain_hash_in_solochain)?; + tracing::debug!( + target: "relay-s2s", + "{} get last relayed rococo block hash: {:?}", + logk::prefix_with_bridge(M_HEADER, SC::CHAIN, TC::CHAIN), + array_bytes::bytes2hex("0x", expected_relaychain_hash), + ); + let last_relayed_relaychain_block_in_solochain = client_relaychain + .block(Some(expected_relaychain_hash)) + .await? + .ok_or_else(|| { + RelayError::Custom(format!( + "Failed to query block by [{}] in rococo", + array_bytes::bytes2hex("0x", expected_relaychain_hash) + )) + })?; + + let block_number = last_relayed_relaychain_block_in_solochain + .block + .header + .number(); + let block_number: u32 = SmartCodecMapper::map_to(block_number)?; + tracing::info!( + target: "relay-s2s", + "{} get last relayed rococo block number: {:?}", + logk::prefix_with_bridge(M_HEADER, SC::CHAIN, TC::CHAIN), + block_number, + ); + + if self.try_to_relay_mandatory(block_number).await?.is_none() { + self.try_to_relay_header_on_demand(block_number).await?; + } + + Ok(()) + } + + async fn submit_finality( + &self, + block_hash: impl AsRef, + justification: Vec, + ) -> RelayResult<()> { + let client_relaychain = &self.input.client_relaychain; + let client_solochain = &self.input.client_solochain; + let block_hash = block_hash.as_ref(); + let block_hash = sp_core::H256::from_str(block_hash).map_err(|e| { + RelayError::Custom(format!("Wrong block hash [{}] {:?}", block_hash, e)) + })?; + let expected_block_hash = SmartCodecMapper::map_to(&block_hash)?; + let header = client_relaychain + .header(Some(expected_block_hash)) + .await? + .ok_or_else(|| { + RelayError::Custom(format!("Not found header by hash: {}", block_hash)) + })?; + let grandpa_justification = + sp_runtime::codec::Decode::decode(&mut justification.as_slice())?; + let expected_header = SmartCodecMapper::map_to(&header)?; + let hash = client_solochain + .submit_finality_proof(expected_header, grandpa_justification) + .await?; + tracing::info!( + target: "relay-s2s", + "{} header relayed: {:?}", + logk::prefix_with_bridge(M_HEADER, SC::CHAIN, TC::CHAIN), + array_bytes::bytes2hex("0x", hash), + ); + Ok(()) + } + + /// Try to relay mandatory headers, return Ok(Some(block_number)) if success, else Ok(None) + async fn try_to_relay_mandatory(&self, last_block_number: u32) -> RelayResult> { + let subquery_relaychain = &self.input.subquery_relaychain; + + let next_mandatory_block = subquery_relaychain + .next_mandatory_header(last_block_number) + .await?; + + if let Some(block_to_relay) = next_mandatory_block { + tracing::info!( + target: "relay-s2s", + "{} the next mandatory block: {:?}", + logk::prefix_with_bridge(M_HEADER, SC::CHAIN, TC::CHAIN), + &block_to_relay.block_number, + ); + let justification = subquery_relaychain + .find_justification(block_to_relay.block_hash.clone(), true) + .await? + .ok_or_else(|| { + RelayError::Custom(format!( + "Failed to query justification for block hash: {:?}", + &block_to_relay.block_hash + )) + })?; + self.submit_finality(block_to_relay.block_hash, justification.justification) + .await?; + + Ok(Some(block_to_relay.block_number)) + } else { + tracing::info!( + target: "relay-s2s", + "{} the next mandatory block not found", + logk::prefix_with_bridge(M_HEADER, SC::CHAIN, TC::CHAIN), + ); + Ok(None) + } + } + + async fn try_to_relay_header_on_demand(&self, last_block_number: u32) -> RelayResult<()> { + let subquery_parachain = &self.input.subquery_parachain; + let next_para_header = subquery_parachain + .next_needed_header(self.input.index_origin_type.clone()) + .await?; + if next_para_header.is_none() { + return Ok(()); + } + let next_para_header = next_para_header.expect("Unreachable"); + + let subquery_candidate = &self.input.subquery_candidate; + let next_header = subquery_candidate + .get_block_with_para_head(next_para_header.block_hash) + .await? + .filter(|header| { + tracing::debug!( + target: "relay-s2s", + "{} get related realy chain header: {:?}", + logk::prefix_with_bridge(M_HEADER, SC::CHAIN, TC::CHAIN), + header.included_relay_block, + ); + header.included_relay_block > last_block_number + }); + + if next_header.is_none() { + tracing::debug!( + target: "relay-s2s", + "{} para head has not been finalized", + logk::prefix_with_bridge(M_HEADER, SC::CHAIN, TC::CHAIN), + ); + return Ok(()); + } + let next_header = next_header.expect("Unreachable"); + + match crate::keepstate::get_recently_justification(SC::CHAIN) { + Some(justification) => { + let grandpa_justification: bp_header_chain::justification::GrandpaJustification< + ::Header, + > = codec::Decode::decode(&mut justification.as_ref()).map_err(|err| { + RelayError::Custom(format!( + "Failed to decode justification of rococo: {:?}", + err + )) + })?; + tracing::debug!( + target: "relay-s2s", + "{} test justification: {:?}", + logk::prefix_with_bridge(M_HEADER, SC::CHAIN, TC::CHAIN), + grandpa_justification.commit.target_number, + ); + + let target_number: u32 = + SmartCodecMapper::map_to(&grandpa_justification.commit.target_number)?; + if target_number > last_block_number { + self.submit_finality( + array_bytes::bytes2hex("", grandpa_justification.commit.target_hash), + justification.to_vec(), + ) + .await?; + } + } + None => { + tracing::warn!( + target: "relay-s2s", + "{} found on-demand block {}, but not have justification to relay.", + logk::prefix_with_bridge(M_HEADER, SC::CHAIN, TC::CHAIN), + next_header.para_head, + ); + } + } + + Ok(()) + } +} diff --git a/frame/assistants/relay-s2s/src/header/solochain_head_relay.rs b/frame/assistants/relay-s2s/src/header/solochain_head_relay.rs new file mode 100644 index 000000000..a6b9e45d7 --- /dev/null +++ b/frame/assistants/relay-s2s/src/header/solochain_head_relay.rs @@ -0,0 +1,209 @@ +use std::str::FromStr; + +use abstract_bridge_s2s::client::S2SClientRelay; +use abstract_bridge_s2s::config::Config; +use abstract_bridge_s2s::types::bp_header_chain; +use sp_runtime::codec; +use sp_runtime::traits::Header; +use support_toolkit::{convert::SmartCodecMapper, logk}; + +use crate::error::{RelayError, RelayResult}; +use crate::types::{SolochainHeaderInput, M_HEADER}; + +/// solo chain to solo chain header relay runner +pub struct SolochainHeaderRunner { + input: SolochainHeaderInput, +} + +impl SolochainHeaderRunner { + pub fn new(input: SolochainHeaderInput) -> Self { + Self { input } + } +} + +impl SolochainHeaderRunner { + /// start header relay + pub async fn start(&self) -> RelayResult<()> { + loop { + self.run().await?; + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + } + } + + async fn run(&self) -> RelayResult<()> { + let client_source = &self.input.client_source; + let client_target = &self.input.client_target; + + let last_relayed_source_hash_in_target = client_target.best_target_finalized(None).await?; + let expected_source_hash = SmartCodecMapper::map_to(&last_relayed_source_hash_in_target)?; + let last_relayed_source_block_in_target = client_source + .block(Some(expected_source_hash)) + .await? + .ok_or_else(|| { + RelayError::Custom(format!( + "Failed to query block by [{}] in {}", + array_bytes::bytes2hex("0x", expected_source_hash), + SC::CHAIN, + )) + })?; + + let block_number = last_relayed_source_block_in_target.block.header.number(); + let block_number: u32 = SmartCodecMapper::map_to(block_number)?; + tracing::trace!( + target: "relay-s2s", + "{} the last relayed {} block is: {:?}", + logk::prefix_with_bridge(M_HEADER, SC::CHAIN, TC::CHAIN), + SC::CHAIN, + block_number, + ); + + if self.try_to_relay_mandatory(block_number).await?.is_none() { + self.try_to_relay_header_on_demand(block_number).await?; + } + + Ok(()) + } + + async fn submit_finality( + &self, + block_hash: impl AsRef, + justification: Vec, + ) -> RelayResult<()> { + let client_source = &self.input.client_source; + let client_target = &self.input.client_target; + let block_hash = block_hash.as_ref(); + let block_hash = sp_core::H256::from_str(block_hash).map_err(|e| { + RelayError::Custom(format!("Wrong block hash [{}] {:?}", block_hash, e)) + })?; + let expected_block_hash = SmartCodecMapper::map_to(&block_hash)?; + let header = client_source + .header(Some(expected_block_hash)) + .await? + .ok_or_else(|| { + RelayError::Custom(format!("Not found header by hash: {}", block_hash)) + })?; + let grandpa_justification = + sp_runtime::codec::Decode::decode(&mut justification.as_slice())?; + let expected_header = SmartCodecMapper::map_to(&header)?; + let hash = client_target + .submit_finality_proof(expected_header, grandpa_justification) + .await?; + tracing::info!( + target: "relay-s2s", + "{} header relayed: {:?}", + logk::prefix_with_bridge(M_HEADER, SC::CHAIN, TC::CHAIN), + array_bytes::bytes2hex("0x", hash), + ); + Ok(()) + } + + /// Try to relay mandatory headers, return Ok(Some(block_number)) if success, else Ok(None) + async fn try_to_relay_mandatory(&self, last_block_number: u32) -> RelayResult> { + let subquery_source = &self.input.subquery_source; + let next_mandatory_block = subquery_source + .next_mandatory_header(last_block_number) + .await?; + if let Some(block_to_relay) = next_mandatory_block { + tracing::info!( + target: "relay-s2s", + "{} the next mandatory block: {:?} ", + logk::prefix_with_bridge(M_HEADER, SC::CHAIN, TC::CHAIN), + &block_to_relay.block_number + ); + let justification = subquery_source + .find_justification(block_to_relay.block_hash.clone(), true) + .await? + .ok_or_else(|| { + RelayError::Custom(format!( + "Failed to query justification for block hash: {:?}", + &block_to_relay.block_hash + )) + })?; + self.submit_finality(block_to_relay.block_hash, justification.justification) + .await?; + + return Ok(Some(block_to_relay.block_number)); + } + tracing::info!( + target: "relay-s2s", + "{} the next mandatory block not found", + logk::prefix_with_bridge(M_HEADER, SC::CHAIN, TC::CHAIN), + ); + Ok(None) + } + + async fn try_to_relay_header_on_demand(&self, last_block_number: u32) -> RelayResult<()> { + let subquery_source = &self.input.subquery_source; + let next_header = match subquery_source + .next_needed_header(self.input.index_origin_type.clone()) + .await? + { + Some(v) => { + if v.block_number <= last_block_number { + tracing::debug!( + target: "relay-s2s", + "{} the last storage block ({}) is less or equal last relayed block ({}). nothing to do.", + logk::prefix_with_bridge(M_HEADER, SC::CHAIN, TC::CHAIN), + v.block_number, + last_block_number, + ); + return Ok(()); + } + v + } + None => { + tracing::debug!( + target: "relay-s2s", + "{} try relay header on-demand, but not found any on-demand block", + logk::prefix_with_bridge(M_HEADER, SC::CHAIN, TC::CHAIN), + ); + return Ok(()); + } + }; + tracing::debug!( + target: "relay-s2s", + "{} try relay header on-demand, the on-demand block is {}", + logk::prefix_with_bridge(M_HEADER, SC::CHAIN, TC::CHAIN), + next_header.block_number, + ); + + match crate::keepstate::get_recently_justification(SC::CHAIN) { + Some(justification) => { + tracing::trace!( + target: "relay-s2s", + "{} found on-demand block {}, and found new justification, ready to relay header", + logk::prefix_with_bridge(M_HEADER, SC::CHAIN, TC::CHAIN), + next_header.block_number, + ); + let grandpa_justification: bp_header_chain::justification::GrandpaJustification< + ::Header, + > = codec::Decode::decode(&mut justification.as_ref()).map_err(|err| { + RelayError::Custom(format!( + "Failed to decode justification of {}: {:?}", + SC::CHAIN, + err, + )) + })?; + let target_number: u32 = + SmartCodecMapper::map_to(&grandpa_justification.commit.target_number)?; + if target_number > last_block_number { + self.submit_finality( + array_bytes::bytes2hex("", grandpa_justification.commit.target_hash), + justification.to_vec(), + ) + .await?; + } + } + None => { + tracing::warn!( + target: "relay-s2s", + "{} found on-demand block {}, but not have justification to relay.", + logk::prefix_with_bridge(M_HEADER, SC::CHAIN, TC::CHAIN), + next_header.block_number, + ); + } + } + + Ok(()) + } +} diff --git a/frame/assistants/relay-s2s/src/keepstate.rs b/frame/assistants/relay-s2s/src/keepstate.rs new file mode 100644 index 000000000..e4595b043 --- /dev/null +++ b/frame/assistants/relay-s2s/src/keepstate.rs @@ -0,0 +1,53 @@ +use std::collections::HashMap; +use std::collections::VecDeque; +use std::sync::Mutex; + +use once_cell::sync::Lazy; + +static LAST_RELAYED_NONCE: Lazy>> = Lazy::new(|| { + let map = HashMap::new(); + Mutex::new(map) +}); + +static RECENTLY_JUSTIFICATIONS: Lazy>>> = + Lazy::new(|| { + let map = HashMap::new(); + Mutex::new(map) + }); + +pub fn get_last_delivery_relayed_nonce() -> Option { + let data = LAST_RELAYED_NONCE.lock().unwrap(); + data.get("delivery").cloned() +} + +pub fn set_last_delivery_relayed_nonce(nonce: u64) { + let mut data = LAST_RELAYED_NONCE.lock().unwrap(); + data.insert("delivery", nonce); +} + +pub fn get_last_receiving_relayed_nonce() -> Option { + let data = LAST_RELAYED_NONCE.lock().unwrap(); + data.get("receiving").cloned() +} + +pub fn set_last_receiving_relayed_nonce(nonce: u64) { + let mut data = LAST_RELAYED_NONCE.lock().unwrap(); + data.insert("receiving", nonce); +} + +pub fn set_recently_justification(chain: &'static str, justification: sp_core::Bytes) { + let mut data = RECENTLY_JUSTIFICATIONS.lock().unwrap(); + let queue = data + .entry(chain) + .or_insert_with(|| VecDeque::with_capacity(100)); + queue.push_back(justification); + if queue.len() >= 100 { + queue.pop_front(); + } +} + +pub(crate) fn get_recently_justification(chain: &'static str) -> Option { + let recently_justifications = RECENTLY_JUSTIFICATIONS.lock().unwrap(); + let justification_queue = recently_justifications.get(chain); + justification_queue.map(|v| v.back().cloned()).flatten() +} diff --git a/frame/assistants/relay-s2s/src/lib.rs b/frame/assistants/relay-s2s/src/lib.rs new file mode 100644 index 000000000..281bff829 --- /dev/null +++ b/frame/assistants/relay-s2s/src/lib.rs @@ -0,0 +1,8 @@ +pub mod error; +pub mod header; +pub mod message; +pub mod subscribe; +pub mod types; + +mod keepstate; +mod strategy; diff --git a/frame/assistants/relay-s2s/src/message/delivery_relay.rs b/frame/assistants/relay-s2s/src/message/delivery_relay.rs new file mode 100644 index 000000000..0c393d154 --- /dev/null +++ b/frame/assistants/relay-s2s/src/message/delivery_relay.rs @@ -0,0 +1,271 @@ +use std::ops::RangeInclusive; + +use abstract_bridge_s2s::client::S2SClientRelay; +use abstract_bridge_s2s::strategy::RelayStrategy; +use abstract_bridge_s2s::types::bp_messages::OutboundLaneData; +use abstract_bridge_s2s::types::bridge_runtime_common::messages::target::FromBridgedChainMessagesProof; +use sp_runtime::traits::Header; +use support_toolkit::{convert::SmartCodecMapper, logk}; + +use crate::error::{RelayError, RelayResult}; +use crate::keepstate; +use crate::strategy::{EnforcementDecideReference, EnforcementRelayStrategy}; +use crate::types::{MessageDeliveryInput, M_DELIVERY}; + +pub struct DeliveryRunner +where + SC: S2SClientRelay, + TC: S2SClientRelay, + Strategy: RelayStrategy, +{ + input: MessageDeliveryInput, +} + +impl DeliveryRunner +where + SC: S2SClientRelay, + TC: S2SClientRelay, + Strategy: RelayStrategy, +{ + pub fn new(input: MessageDeliveryInput) -> Self { + Self { input } + } +} + +// defined +impl DeliveryRunner +where + SC: S2SClientRelay, + TC: S2SClientRelay, + Strategy: RelayStrategy, +{ + async fn source_outbound_lane_data(&self) -> RelayResult { + let lane = self.input.lane()?; + let outbound_lane_data = self.input.client_source.outbound_lanes(lane, None).await?; + Ok(outbound_lane_data) + } + + async fn assemble_nonces( + &self, + limit: u64, + outbound_lane_data: &OutboundLaneData, + ) -> RelayResult>> { + let (latest_confirmed_nonce, latest_generated_nonce) = ( + outbound_lane_data.latest_received_nonce, + outbound_lane_data.latest_generated_nonce, + ); + if latest_confirmed_nonce == latest_generated_nonce { + return Ok(None); + } + + // assemble nonce range + let start: u64 = latest_confirmed_nonce + 1; + if let Some(last_relayed_nonce) = keepstate::get_last_delivery_relayed_nonce() { + if last_relayed_nonce >= start { + tracing::warn!( + target: "relay-s2s", + "{} have a batches of transactions in progress. \ + waiting for this batches to complete. last relayed noce is {} and expect to start with {}. \ + please wait receiving.", + logk::prefix_with_bridge(M_DELIVERY, SC::CHAIN, TC::CHAIN), + last_relayed_nonce, + start, + ); + return Ok(None); + } + } + + let inclusive_limit = limit - 1; + tracing::trace!( + target: "relay-s2s", + "{} assemble nonces, start from {} and last generated is {}", + logk::prefix_with_bridge(M_DELIVERY, SC::CHAIN, TC::CHAIN), + start, + latest_generated_nonce, + ); + let end: u64 = if latest_generated_nonce - start > inclusive_limit { + start + inclusive_limit + } else { + latest_generated_nonce + }; + let nonces = start..=end; + Ok(Some(nonces)) + } +} + +impl DeliveryRunner +where + SC: S2SClientRelay, + TC: S2SClientRelay, + Strategy: RelayStrategy, +{ + pub async fn start(&self) -> RelayResult<()> { + tracing::info!( + target: "relay-s2s", + "{} SERVICE RESTARTING...", + logk::prefix_with_bridge(M_DELIVERY, SC::CHAIN, TC::CHAIN), + ); + loop { + let last_relayed_nonce = self.run(self.input.nonces_limit).await?; + if last_relayed_nonce.is_some() { + keepstate::set_last_delivery_relayed_nonce( + last_relayed_nonce.expect("Unreachable"), + ); + } + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + } + } + + async fn run(&self, limit: u64) -> RelayResult> { + let lane = self.input.lane()?; + let source_outbound_lane_data = self.source_outbound_lane_data().await?; + + // alias + let client_source = &self.input.client_source; + let client_target = &self.input.client_target; + let subquery_source = &self.input.subquery_source; + + let nonces = match self + .assemble_nonces(limit, &source_outbound_lane_data) + .await? + { + Some(v) => v, + None => { + tracing::info!( + target: "relay-s2s", + "{} all nonces delivered, nothing to do.", + logk::prefix_with_bridge(M_DELIVERY, SC::CHAIN, TC::CHAIN), + ); + return Ok(None); + } + }; + tracing::info!( + target: "relay-s2s", + "{} assembled nonces {:?}", + logk::prefix_with_bridge(M_DELIVERY, SC::CHAIN, TC::CHAIN), + nonces, + ); + + // query last nonce block information + let last_relay = match subquery_source + .query_need_relay(self.input.relay_block_origin.clone(), lane, *nonces.end()) + .await? + { + Some(v) => v, + None => { + tracing::warn!( + target: "relay-s2s", + "{} the last nonce({}) isn't storage by indexer for {} chain", + logk::prefix_with_bridge(M_DELIVERY, SC::CHAIN, TC::CHAIN), + nonces.end(), + SC::CHAIN, + ); + return Ok(None); + } + }; + + // query last relayed header + let last_relayed_source_hash_in_target = client_target.best_target_finalized(None).await?; + let expected_source_hash = SmartCodecMapper::map_to(&last_relayed_source_hash_in_target)?; + let last_relayed_source_block_in_target = client_source + .block(Some(expected_source_hash)) + .await? + .ok_or_else(|| { + RelayError::Custom(format!( + "Failed to query block by [{}] in {}", + array_bytes::bytes2hex("0x", expected_source_hash), + SC::CHAIN, + )) + })?; + + // compare last nonce block with last relayed header + let relayed_block_number = last_relayed_source_block_in_target.block.header.number(); + let relayed_block_number: u32 = SmartCodecMapper::map_to(relayed_block_number)?; + if relayed_block_number < last_relay.block_number { + tracing::warn!( + target: "relay-s2s", + "{} the last nonce({}) at block {} is less then last relayed header {}, please wait header relay.", + logk::prefix_with_bridge(M_DELIVERY, SC::CHAIN, TC::CHAIN), + nonces.end(), + last_relay.block_number, + relayed_block_number, + ); + return Ok(None); + } + + // read proof + let mut storage_keys = Vec::with_capacity((nonces.end() - nonces.start()) as usize + 1); + let mut message_nonce = *nonces.start(); + while message_nonce <= *nonces.end() { + let message_key = client_source.gen_outbound_messages_storage_key(lane, message_nonce); + storage_keys.push(message_key); + message_nonce += 1; + } + + //- query inbound land data + let target_inbound_lane_data = client_target.inbound_lanes(lane, None).await?; + let outbound_state_proof_required = target_inbound_lane_data.last_confirmed_nonce + < source_outbound_lane_data.latest_received_nonce; + if outbound_state_proof_required { + storage_keys.push(client_source.gen_outbound_lanes_storage_key(lane)); + } + + // fill delivery data + let total_weight = client_source + .calculate_dispatch_weight(lane, nonces.clone()) + .await?; + + // query last relayed header + let proof = client_source + .read_proof(storage_keys, Some(expected_source_hash)) + .await?; + let message_size = proof.len(); + let proof = FromBridgedChainMessagesProof { + bridged_header_hash: expected_source_hash, + storage_proof: proof, + lane, + nonces_start: *nonces.start(), + nonces_end: *nonces.end(), + }; + + // relay strategy + let reference = EnforcementDecideReference { + lane, + nonces: nonces.clone(), + message_size, + total_weight, + }; + let mut relay_strategy = EnforcementRelayStrategy::new(self.input.relay_strategy.clone()); + if !relay_strategy.decide(reference).await? { + tracing::warn!( + target: "relay-s2s", + "{} the relay strategy decide not relay these nonces({:?})", + logk::prefix_with_bridge(M_DELIVERY, SC::CHAIN, TC::CHAIN), + nonces, + ); + return Ok(None); + } + + // submit messages proof to target chain + let expected_proof = SmartCodecMapper::map_to(&proof)?; + let relayer_account_source_chain = self.input.relayer_account.clone(); + let expected_relayer_id = SmartCodecMapper::map_to(&relayer_account_source_chain)?; + let hash = client_target + .receive_messages_proof( + expected_relayer_id, + expected_proof, + (nonces.end() - nonces.start() + 1) as _, + total_weight, + ) + .await?; + + tracing::debug!( + target: "relay-s2s", + "{} the nonces {:?} in delivered to target chain -> {}", + logk::prefix_with_bridge(M_DELIVERY, SC::CHAIN, TC::CHAIN), + nonces, + array_bytes::bytes2hex("0x", hash), + ); + Ok(Some(*nonces.end())) + } +} diff --git a/frame/assistants/relay-s2s/src/message/mod.rs b/frame/assistants/relay-s2s/src/message/mod.rs new file mode 100644 index 000000000..d467d9807 --- /dev/null +++ b/frame/assistants/relay-s2s/src/message/mod.rs @@ -0,0 +1,5 @@ +pub use delivery_relay::*; +pub use receiving_relay::*; + +mod delivery_relay; +mod receiving_relay; diff --git a/frame/assistants/relay-s2s/src/message/receiving_relay.rs b/frame/assistants/relay-s2s/src/message/receiving_relay.rs new file mode 100644 index 000000000..285ca6da4 --- /dev/null +++ b/frame/assistants/relay-s2s/src/message/receiving_relay.rs @@ -0,0 +1,185 @@ +use abstract_bridge_s2s::client::S2SClientRelay; +use abstract_bridge_s2s::config::Config; +use abstract_bridge_s2s::types::bp_messages::{OutboundLaneData, UnrewardedRelayersState}; +use abstract_bridge_s2s::types::bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof; +use support_toolkit::{convert::SmartCodecMapper, logk}; + +use crate::error::RelayResult; +use crate::keepstate; +use crate::types::{MessageReceivingInput, M_RECEIVING}; + +pub struct ReceivingRunner { + input: MessageReceivingInput, +} + +impl ReceivingRunner { + pub fn new(message_relay: MessageReceivingInput) -> Self { + Self { + input: message_relay, + } + } +} + +impl ReceivingRunner { + async fn source_outbound_lane_data(&self) -> RelayResult { + let lane = self.input.lane()?; + let outbound_lane_data = self.input.client_source.outbound_lanes(lane, None).await?; + Ok(outbound_lane_data) + } + + async fn target_unrewarded_relayers_state( + &self, + at_block: ::Hash, + source_outbound_lane_data: &OutboundLaneData, + ) -> RelayResult> { + // let block_hex = array_bytes::bytes2hex("0x", at_block); + let lane = self.input.lane()?; + let inbound_lane_data = self + .input + .client_target + .inbound_lanes(lane, Some(at_block)) + .await?; + let max_confirm_end_at_target = inbound_lane_data + .relayers + .iter() + .map(|item| item.messages.end) + .max() + .unwrap_or(0u64); + if max_confirm_end_at_target == source_outbound_lane_data.latest_received_nonce { + tracing::info!( + target: "relay-s2s", + "{} max dispatch nonce({}) at target is same with last received nonce({}) at source. nothing to do.", + logk::prefix_with_bridge(M_RECEIVING, SC::CHAIN, TC::CHAIN), + max_confirm_end_at_target, + source_outbound_lane_data.latest_received_nonce, + ); + return Ok(None); + } + if let Some(last_relayed_nonce) = keepstate::get_last_receiving_relayed_nonce() { + if last_relayed_nonce >= max_confirm_end_at_target { + tracing::warn!( + target: "relay-s2s", + "{} the nonce({}) is being processed. please waiting for the processing to finish.", + logk::prefix_with_bridge(M_RECEIVING, SC::CHAIN, TC::CHAIN), + max_confirm_end_at_target, + ); + return Ok(None); + } + } + let relayers = inbound_lane_data.relayers; + let total_unrewarded_messages = match (relayers.front(), relayers.back()) { + (Some(front), Some(back)) => { + if back.messages.end < front.messages.begin { + Some(0) + } else { + let difference = back.messages.end - front.messages.begin; + Some(difference + 1) + } + } + _ => Some(0), + }; + if total_unrewarded_messages.is_none() { + tracing::info!( + target: "relay-s2s", + "{} not have unrewarded message. nothing to do.", + logk::prefix_with_bridge(M_RECEIVING, SC::CHAIN, TC::CHAIN), + ); + return Ok(None); + } + Ok(Some(( + max_confirm_end_at_target, + UnrewardedRelayersState { + unrewarded_relayer_entries: relayers.len() as _, + messages_in_oldest_entry: relayers + .front() + .map(|entry| 1 + entry.messages.end - entry.messages.begin) + .unwrap_or(u64::MAX), + total_messages: total_unrewarded_messages.expect("Unreachable"), + }, + ))) + } +} + +impl ReceivingRunner { + pub async fn start(&self) -> RelayResult<()> { + tracing::info!( + target: "relay-s2s", + "{} SERVICE RESTARTING...", + logk::prefix_with_bridge(M_RECEIVING, SC::CHAIN, TC::CHAIN), + ); + loop { + let last_relayed_nonce = self.run().await?; + if last_relayed_nonce.is_some() { + keepstate::set_last_receiving_relayed_nonce( + last_relayed_nonce.expect("Unreachable"), + ); + } + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + } + } + + async fn run(&self) -> RelayResult> { + let lane = self.input.lane()?; + + // alias + let client_source = &self.input.client_source; + let client_target = &self.input.client_target; + + let source_outbound_lane_data = self.source_outbound_lane_data().await?; + if source_outbound_lane_data.latest_received_nonce + == source_outbound_lane_data.latest_generated_nonce + { + tracing::info!( + target: "relay-s2s", + "{} all nonces received, nothing to do.", + logk::prefix_with_bridge(M_RECEIVING, SC::CHAIN, TC::CHAIN), + ); + return Ok(None); + } + + // query last relayed header + let last_relayed_target_hash_in_source = client_source.best_target_finalized(None).await?; + let expected_target_hash = SmartCodecMapper::map_to(&last_relayed_target_hash_in_source)?; + + // assemble unrewarded relayers state + let (max_confirmed_nonce_at_target, relayers_state) = match self + .target_unrewarded_relayers_state(expected_target_hash, &source_outbound_lane_data) + .await? + { + Some(v) => v, + None => { + tracing::warn!( + target: "relay-s2s", + "{} no unrewarded relayers state found by {}", + logk::prefix_with_bridge(M_RECEIVING, SC::CHAIN, TC::CHAIN), + TC::CHAIN, + ); + return Ok(None); + } + }; + + // read proof + let inbound_data_key = client_target.gen_inbound_lanes_storage_key(lane); + let proof = client_target + .read_proof(vec![inbound_data_key], Some(expected_target_hash)) + .await?; + let proof = FromBridgedChainMessagesDeliveryProof { + bridged_header_hash: last_relayed_target_hash_in_source, + storage_proof: proof, + lane, + }; + + // send proof + let hash = client_source + .receive_messages_delivery_proof(proof, relayers_state) + .await?; + + tracing::debug!( + target: "relay-s2s", + "{} receiving extensics sent successful: {}", + logk::prefix_with_bridge(M_RECEIVING, SC::CHAIN, TC::CHAIN), + array_bytes::bytes2hex("0x", hash), + ); + Ok(Some(max_confirmed_nonce_at_target)) + } +} diff --git a/frame/assistants/relay-s2s/src/strategy/mod.rs b/frame/assistants/relay-s2s/src/strategy/mod.rs new file mode 100644 index 000000000..da5358d93 --- /dev/null +++ b/frame/assistants/relay-s2s/src/strategy/mod.rs @@ -0,0 +1,3 @@ +pub use self::relay::*; + +mod relay; diff --git a/frame/assistants/relay-s2s/src/strategy/relay.rs b/frame/assistants/relay-s2s/src/strategy/relay.rs new file mode 100644 index 000000000..9e71f4aa4 --- /dev/null +++ b/frame/assistants/relay-s2s/src/strategy/relay.rs @@ -0,0 +1,46 @@ +use std::ops::RangeInclusive; + +use abstract_bridge_s2s::error::S2SClientResult; +use abstract_bridge_s2s::strategy::{RelayReference, RelayStrategy}; + +use crate::types::LaneId; + +/// enforcement decide reference +pub struct EnforcementDecideReference { + pub lane: LaneId, + /// nonces + pub nonces: RangeInclusive, + /// message size + pub message_size: usize, + /// total weight + pub total_weight: u64, +} + +pub struct EnforcementRelayStrategy { + strategy: Strategy, +} + +impl EnforcementRelayStrategy { + pub fn new(strategy: Strategy) -> Self { + Self { strategy } + } +} + +impl EnforcementRelayStrategy { + pub async fn decide(&mut self, reference: EnforcementDecideReference) -> S2SClientResult { + let nonces = &reference.nonces; + let mut message_nonce = *nonces.start(); + while message_nonce <= *nonces.end() { + let decide_reference = RelayReference { + lane: reference.lane, + nonce: message_nonce, + }; + let result = self.strategy.decide(decide_reference).await?; + if !result { + return Ok(false); + } + message_nonce += 1; + } + Ok(true) + } +} diff --git a/frame/assistants/relay-s2s/src/subscribe/justification.rs b/frame/assistants/relay-s2s/src/subscribe/justification.rs new file mode 100644 index 000000000..867952d00 --- /dev/null +++ b/frame/assistants/relay-s2s/src/subscribe/justification.rs @@ -0,0 +1,76 @@ +use abstract_bridge_s2s::client::S2SClientRelay; +use abstract_bridge_s2s::error::S2SClientError; + +use support_toolkit::logk; + +use crate::error::RelayResult; +use crate::keepstate; +use crate::types::JustificationInput; + +pub struct SubscribeJustification { + input: JustificationInput, +} + +impl SubscribeJustification { + pub fn new(input: JustificationInput) -> Self { + Self { input } + } +} + +impl SubscribeJustification { + pub async fn start(self) -> RelayResult<()> { + let client_source = self.input.client_source; + let client_target = self.input.client_target; + let join_a = tokio::spawn(run_until_connection_lost(client_source, |justification| { + keepstate::set_recently_justification(SC::CHAIN, justification); + })); + let join_b = tokio::spawn(run_until_connection_lost(client_target, |justification| { + keepstate::set_recently_justification(TC::CHAIN, justification); + })); + let (_result_a, _result_b) = ( + join_a + .await + .map_err(|e| S2SClientError::RPC(format!("{:?}", e)))?, + join_b + .await + .map_err(|e| S2SClientError::RPC(format!("{:?}", e)))?, + ); + Ok(()) + } +} + +async fn run_until_connection_lost(client: T, callback: F) -> RelayResult<()> +where + T: S2SClientRelay, + F: Send + Sync + Fn(sp_core::Bytes), +{ + if let Err(err) = subscribe_justification(&client, callback).await { + tracing::error!( + target: "relay-s2s", + "{} Failed to get justification from {}: {:?}", + logk::prefix_multi("subscribe", vec![T::CHAIN]), + T::CHAIN, + err + ); + } + Ok(()) +} + +async fn subscribe_justification(client: &T, callback: F) -> RelayResult<()> +where + T: S2SClientRelay, + F: Send + Sync + Fn(sp_core::Bytes), +{ + let mut subscribe = client.subscribe_grandpa_justifications().await?; + while let Some(justification) = subscribe.next().await { + let justification = justification.map_err(|e| S2SClientError::RPC(format!("{:?}", e)))?; + tracing::info!( + target: "relay-s2s", + "{} subscribed new justification for {}", + logk::prefix_multi("subscribe", vec![T::CHAIN]), + T::CHAIN, + ); + callback(justification); + } + Ok(()) +} diff --git a/frame/assistants/relay-s2s/src/subscribe/mod.rs b/frame/assistants/relay-s2s/src/subscribe/mod.rs new file mode 100644 index 000000000..361d9b866 --- /dev/null +++ b/frame/assistants/relay-s2s/src/subscribe/mod.rs @@ -0,0 +1,3 @@ +pub use self::justification::*; + +mod justification; diff --git a/frame/assistants/relay-s2s/src/types.rs b/frame/assistants/relay-s2s/src/types.rs new file mode 100644 index 000000000..2542a84a2 --- /dev/null +++ b/frame/assistants/relay-s2s/src/types.rs @@ -0,0 +1,93 @@ +use abstract_bridge_s2s::client::S2SClientRelay; +#[cfg(feature = "bridge-parachain")] +use abstract_bridge_s2s::client::{S2SParaBridgeClientRelaychain, S2SParaBridgeClientSolochain}; +use abstract_bridge_s2s::config::Config; +use abstract_bridge_s2s::strategy::RelayStrategy; +use subquery_s2s::types::{OriginType, RelayBlockOrigin}; +use subquery_s2s::Subquery; + +use crate::error::{RelayError, RelayResult}; + +pub(crate) static M_HEADER: &str = "header"; +#[cfg(feature = "bridge-parachain")] +pub(crate) static M_PARA_HEAD: &str = "para-head"; +pub(crate) static M_DELIVERY: &str = "delivery"; +pub(crate) static M_RECEIVING: &str = "receiving"; + +pub type LaneId = [u8; 4]; + +pub struct SolochainHeaderInput { + pub lanes: Vec, + pub client_source: SC, + pub client_target: TC, + pub subquery_source: Subquery, + pub index_origin_type: OriginType, +} + +#[cfg(feature = "bridge-parachain")] +pub struct RelaychainHeaderInput { + pub client_relaychain: SC, + pub client_solochain: TC, + pub subquery_relaychain: Subquery, + pub subquery_parachain: Subquery, + pub index_origin_type: OriginType, + // todo: merge this subquery to subquery_relaychain + pub subquery_candidate: subquery_parachain::Subquery, +} + +#[cfg(feature = "bridge-parachain")] +pub struct ParaHeaderInput { + pub client_relaychain: SC, + pub client_solochain: TC, + pub para_id: u32, +} + +pub struct JustificationInput { + pub client_source: SC, + pub client_target: TC, +} + +pub struct MessageDeliveryInput { + pub lanes: Vec, + pub nonces_limit: u64, + pub relayer_account: ::AccountId, + pub client_source: SC, + pub client_target: TC, + pub subquery_source: Subquery, + pub subquery_target: Subquery, + pub relay_block_origin: RelayBlockOrigin, + pub relay_strategy: Strategy, +} + +impl + MessageDeliveryInput +{ + // todo: support multiple lanes + pub fn lane(&self) -> RelayResult { + self.lanes + .clone() + .get(0) + .cloned() + .ok_or_else(|| RelayError::Custom("Missing lane id".to_string())) + } +} + +pub struct MessageReceivingInput { + pub lanes: Vec, + pub relayer_account: ::AccountId, + pub client_source: SC, + pub client_target: TC, + pub subquery_source: Subquery, + pub subquery_target: Subquery, +} + +impl MessageReceivingInput { + // todo: support multiple lanes + pub fn lane(&self) -> RelayResult { + self.lanes + .clone() + .get(0) + .cloned() + .ok_or_else(|| RelayError::Custom("Missing lane id".to_string())) + } +} diff --git a/frame/supports/support-common/src/initialize.rs b/frame/supports/support-common/src/initialize.rs index 01d24966e..f6cf4decc 100644 --- a/frame/supports/support-common/src/initialize.rs +++ b/frame/supports/support-common/src/initialize.rs @@ -34,6 +34,7 @@ fn init_log() -> color_eyre::Result<()> { "client-pangoro=trace", "feemarket=trace", "shadow=trace", + "relay-s2s=trace", ] .join(","); diff --git a/frame/supports/support-toolkit/Cargo.toml b/frame/supports/support-toolkit/Cargo.toml index af2800979..f12b63dbd 100644 --- a/frame/supports/support-toolkit/Cargo.toml +++ b/frame/supports/support-toolkit/Cargo.toml @@ -19,3 +19,11 @@ edition = "2021" [dependencies] thiserror = "1" +once_cell = { optional = true, version = "1" } +pad = { optional = true, version = "0.1" } + +codec = { optional = true, package = "parity-scale-codec", version = "2" } + +[features] +convert = ["codec"] +log = ["once_cell", "pad"] diff --git a/frame/supports/support-toolkit/src/convert.rs b/frame/supports/support-toolkit/src/convert.rs new file mode 100644 index 000000000..a79a52807 --- /dev/null +++ b/frame/supports/support-toolkit/src/convert.rs @@ -0,0 +1,16 @@ +/// Smart codec types mapper +#[cfg(feature = "codec")] +pub struct SmartCodecMapper; + +#[cfg(feature = "codec")] +impl SmartCodecMapper { + /// map an encodeable types to new decodeable types + pub fn map_to(source: &S) -> Result + where + S: codec::Encode, + T: codec::Decode, + { + let decoded = codec::Encode::encode(source); + T::decode(&mut decoded.as_slice()) + } +} diff --git a/frame/supports/support-toolkit/src/lib.rs b/frame/supports/support-toolkit/src/lib.rs index 11fa33e1f..34ca29812 100644 --- a/frame/supports/support-toolkit/src/lib.rs +++ b/frame/supports/support-toolkit/src/lib.rs @@ -1,2 +1,6 @@ +#[cfg(feature = "convert")] +pub mod convert; pub mod error; +#[cfg(feature = "log")] +pub mod logk; pub mod url; diff --git a/frame/supports/support-toolkit/src/logk.rs b/frame/supports/support-toolkit/src/logk.rs new file mode 100644 index 000000000..b7d770151 --- /dev/null +++ b/frame/supports/support-toolkit/src/logk.rs @@ -0,0 +1,64 @@ +use std::collections::HashMap; +use std::sync::Mutex; + +use once_cell::sync::Lazy; +use pad::{Alignment, PadStr}; + +static MAX_LEN: usize = 26; + +static LEN_ARG: Lazy>> = Lazy::new(|| { + let map = HashMap::new(); + Mutex::new(map) +}); + +fn get_len_arg_with_def(index: u32, def: usize) -> usize { + let mut data = LEN_ARG.lock().unwrap(); + match data.get(&index) { + Some(v) => { + if def > *v { + data.insert(index, def); + def + } else { + *v + } + } + None => { + let len = if def > MAX_LEN { MAX_LEN } else { def }; + data.insert(index, len); + len + } + } +} + +/// prefix multiple aruments +pub fn prefix_multi(mark: impl AsRef, args: Vec>) -> String { + let mark = mark.as_ref(); + let len_mark = get_len_arg_with_def(0, mark.len()); + let mut prefix = vec![mark.pad(len_mark, ' ', Alignment::Left, true)]; + for (ix, v) in args.iter().enumerate() { + let pix = ix as u32 + 1; + let arg = v.as_ref(); + let len_arg = get_len_arg_with_def(pix, arg.len()); + prefix.push(arg.pad(len_arg, ' ', Alignment::Middle, true)) + } + format!("[{}]", prefix.join("] [")) +} + +pub fn prefix_with_relation( + mark: impl AsRef, + first: impl AsRef, + second: impl AsRef, + relation: impl AsRef, +) -> String { + let chain = format!("{}{}{}", first.as_ref(), relation.as_ref(), second.as_ref()); + prefix_multi(mark, vec![chain]) +} + +/// log prefix +pub fn prefix_with_bridge( + mark: impl AsRef, + source: impl AsRef, + target: impl AsRef, +) -> String { + prefix_with_relation(mark, source, target, ">") +}