diff --git a/Cargo.lock b/Cargo.lock index 19b32dc7aae56..fffed2700e4f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -817,10 +817,10 @@ dependencies = [ "asset-hub-rococo-runtime", "cumulus-primitives-core", "emulated-integration-tests-common", - "frame-support", + "frame-support 28.0.0", "parachains-common", "rococo-emulated-chain", - "sp-core", + "sp-core 28.0.0", "testnet-parachains-constants", ] @@ -833,10 +833,10 @@ dependencies = [ "asset-test-utils", "cumulus-pallet-parachain-system", "emulated-integration-tests-common", - "frame-support", + "frame-support 28.0.0", "pallet-asset-conversion", "pallet-assets", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-message-queue", "pallet-xcm", "parachains-common", @@ -844,9 +844,9 @@ dependencies = [ "penpal-runtime", "rococo-runtime", "rococo-system-emulated-network", - "sp-runtime", - "staging-xcm", - "staging-xcm-executor", + "sp-runtime 31.0.1", + "staging-xcm 7.0.0", + "staging-xcm-executor 7.0.0", "testnet-parachains-constants", ] @@ -869,10 +869,10 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-utility", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", @@ -883,7 +883,7 @@ dependencies = [ "pallet-assets", "pallet-aura", "pallet-authorship", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-collator-selection", "pallet-message-queue", "pallet-multisig", @@ -894,39 +894,39 @@ dependencies = [ "pallet-session", "pallet-state-trie-migration", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", "pallet-uniques", - "pallet-utility", + "pallet-utility 28.0.0", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub-router", "parachains-common", "parity-scale-codec", - "polkadot-parachain-primitives", + "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common", "primitive-types", "rococo-runtime-constants", "scale-info", "snowbridge-router-primitives", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", - "sp-weights", + "sp-version 29.0.0", + "sp-weights 27.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", "testnet-parachains-constants", ] @@ -938,9 +938,9 @@ dependencies = [ "asset-hub-westend-runtime", "cumulus-primitives-core", "emulated-integration-tests-common", - "frame-support", + "frame-support 28.0.0", "parachains-common", - "sp-core", + "sp-core 28.0.0", "testnet-parachains-constants", "westend-emulated-chain", ] @@ -955,10 +955,10 @@ dependencies = [ "cumulus-pallet-parachain-system", "cumulus-pallet-xcmp-queue", "emulated-integration-tests-common", - "frame-support", + "frame-support 28.0.0", "pallet-asset-conversion", "pallet-assets", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-message-queue", "pallet-treasury", "pallet-xcm", @@ -966,9 +966,9 @@ dependencies = [ "parity-scale-codec", "penpal-runtime", "polkadot-runtime-common", - "sp-runtime", - "staging-xcm", - "staging-xcm-executor", + "sp-runtime 31.0.1", + "staging-xcm 7.0.0", + "staging-xcm-executor 7.0.0", "testnet-parachains-constants", "westend-runtime", "westend-system-emulated-network", @@ -993,10 +993,10 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-utility", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", @@ -1007,7 +1007,7 @@ dependencies = [ "pallet-assets", "pallet-aura", "pallet-authorship", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-collator-selection", "pallet-message-queue", "pallet-multisig", @@ -1017,36 +1017,36 @@ dependencies = [ "pallet-proxy", "pallet-session", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", "pallet-uniques", - "pallet-utility", + "pallet-utility 28.0.0", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub-router", "parachains-common", "parity-scale-codec", - "polkadot-parachain-primitives", + "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common", "primitive-types", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", "testnet-parachains-constants", "westend-runtime-constants", @@ -1059,11 +1059,11 @@ dependencies = [ "cumulus-pallet-parachain-system", "cumulus-pallet-xcmp-queue", "cumulus-primitives-core", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "hex-literal", "pallet-assets", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-collator-selection", "pallet-session", "pallet-timestamp", @@ -1072,13 +1072,13 @@ dependencies = [ "parachains-common", "parachains-runtimes-test-utils", "parity-scale-codec", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", ] @@ -1087,7 +1087,7 @@ name = "assets-common" version = "0.7.0" dependencies = [ "cumulus-primitives-core", - "frame-support", + "frame-support 28.0.0", "impl-trait-for-tuples", "log", "pallet-asset-conversion", @@ -1095,12 +1095,12 @@ dependencies = [ "parachains-common", "parity-scale-codec", "scale-info", - "sp-api", - "sp-runtime", + "sp-api 26.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", ] @@ -1398,8 +1398,8 @@ dependencies = [ "env_logger 0.11.3", "hash-db", "log", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -1631,7 +1631,7 @@ name = "bp-asset-hub-rococo" version = "0.4.0" dependencies = [ "bp-xcm-bridge-hub-router", - "frame-support", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "parity-scale-codec", "scale-info", ] @@ -1641,7 +1641,7 @@ name = "bp-asset-hub-westend" version = "0.3.0" dependencies = [ "bp-xcm-bridge-hub-router", - "frame-support", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "parity-scale-codec", "scale-info", ] @@ -1653,11 +1653,11 @@ dependencies = [ "bp-messages", "bp-polkadot-core", "bp-runtime", - "frame-support", - "frame-system", - "polkadot-primitives", - "sp-api", - "sp-std 14.0.0", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-system 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "polkadot-primitives 7.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-api 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -1667,10 +1667,10 @@ dependencies = [ "bp-bridge-hub-cumulus", "bp-messages", "bp-runtime", - "frame-support", - "sp-api", - "sp-runtime", - "sp-std 14.0.0", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-api 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -1680,10 +1680,10 @@ dependencies = [ "bp-bridge-hub-cumulus", "bp-messages", "bp-runtime", - "frame-support", - "sp-api", - "sp-runtime", - "sp-std 14.0.0", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-api 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -1693,10 +1693,10 @@ dependencies = [ "bp-bridge-hub-cumulus", "bp-messages", "bp-runtime", - "frame-support", - "sp-api", - "sp-runtime", - "sp-std 14.0.0", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-api 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -1706,10 +1706,10 @@ dependencies = [ "bp-bridge-hub-cumulus", "bp-messages", "bp-runtime", - "frame-support", - "sp-api", - "sp-runtime", - "sp-std 14.0.0", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-api 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -1719,16 +1719,16 @@ dependencies = [ "bp-runtime", "bp-test-utils", "finality-grandpa", - "frame-support", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "hex", "hex-literal", "parity-scale-codec", "scale-info", "serde", - "sp-consensus-grandpa", - "sp-core", - "sp-runtime", - "sp-std 14.0.0", + "sp-consensus-grandpa 13.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -1738,9 +1738,9 @@ dependencies = [ "bp-header-chain", "bp-polkadot-core", "bp-runtime", - "frame-support", - "sp-api", - "sp-std 14.0.0", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-api 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -1749,14 +1749,14 @@ version = "0.7.0" dependencies = [ "bp-header-chain", "bp-runtime", - "frame-support", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "hex", "hex-literal", "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-std 14.0.0", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -1766,13 +1766,13 @@ dependencies = [ "bp-header-chain", "bp-polkadot-core", "bp-runtime", - "frame-support", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "impl-trait-for-tuples", "parity-scale-codec", "scale-info", - "sp-core", - "sp-runtime", - "sp-std 14.0.0", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -1782,9 +1782,9 @@ dependencies = [ "bp-header-chain", "bp-polkadot-core", "bp-runtime", - "frame-support", - "sp-api", - "sp-std 14.0.0", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-api 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -1795,13 +1795,13 @@ dependencies = [ "bp-messages", "bp-polkadot-core", "bp-runtime", - "frame-support", - "frame-system", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-system 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "parity-scale-codec", "scale-info", - "sp-api", - "sp-runtime", - "sp-std 14.0.0", + "sp-api 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -1810,16 +1810,16 @@ version = "0.7.0" dependencies = [ "bp-messages", "bp-runtime", - "frame-support", - "frame-system", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-system 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "hex", "parity-scale-codec", "parity-util-mem", "scale-info", "serde", - "sp-core", - "sp-runtime", - "sp-std 14.0.0", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -1828,13 +1828,13 @@ version = "0.7.0" dependencies = [ "bp-messages", "bp-runtime", - "frame-support", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "hex", "hex-literal", "parity-scale-codec", "scale-info", - "sp-runtime", - "sp-std 14.0.0", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -1844,17 +1844,17 @@ dependencies = [ "bp-header-chain", "bp-polkadot-core", "bp-runtime", - "frame-support", - "sp-api", - "sp-std 14.0.0", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-api 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] name = "bp-runtime" version = "0.7.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-system 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "hash-db", "hex-literal", "impl-trait-for-tuples", @@ -1863,12 +1863,12 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", - "sp-state-machine", - "sp-std 14.0.0", - "sp-trie", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-io 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-state-machine 0.35.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-trie 29.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "trie-db", ] @@ -1883,12 +1883,12 @@ dependencies = [ "ed25519-dalek", "finality-grandpa", "parity-scale-codec", - "sp-application-crypto", - "sp-consensus-grandpa", - "sp-core", - "sp-runtime", - "sp-std 14.0.0", - "sp-trie", + "sp-application-crypto 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-consensus-grandpa 13.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-trie 29.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -1898,16 +1898,16 @@ dependencies = [ "bp-header-chain", "bp-polkadot-core", "bp-runtime", - "frame-support", - "sp-api", - "sp-std 14.0.0", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-api 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] name = "bp-xcm-bridge-hub" version = "0.2.0" dependencies = [ - "sp-std 14.0.0", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -1916,8 +1916,8 @@ version = "0.6.0" dependencies = [ "parity-scale-codec", "scale-info", - "sp-core", - "sp-runtime", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -1925,15 +1925,15 @@ name = "bridge-hub-common" version = "0.1.0" dependencies = [ "cumulus-primitives-core", - "frame-support", + "frame-support 28.0.0", "pallet-message-queue", "parity-scale-codec", "scale-info", "snowbridge-core", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "staging-xcm", + "staging-xcm 7.0.0", ] [[package]] @@ -1943,9 +1943,9 @@ dependencies = [ "bridge-hub-common", "bridge-hub-rococo-runtime", "emulated-integration-tests-common", - "frame-support", + "frame-support 28.0.0", "parachains-common", - "sp-core", + "sp-core 28.0.0", "testnet-parachains-constants", ] @@ -1958,11 +1958,11 @@ dependencies = [ "bridge-hub-rococo-runtime", "cumulus-pallet-xcmp-queue", "emulated-integration-tests-common", - "frame-support", + "frame-support 28.0.0", "hex-literal", "pallet-asset-conversion", "pallet-assets", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-bridge-messages", "pallet-message-queue", "pallet-xcm", @@ -1977,10 +1977,10 @@ dependencies = [ "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", - "sp-core", - "sp-runtime", - "staging-xcm", - "staging-xcm-executor", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "staging-xcm 7.0.0", + "staging-xcm-executor 7.0.0", "testnet-parachains-constants", ] @@ -2014,10 +2014,10 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-utility", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", @@ -2025,7 +2025,7 @@ dependencies = [ "log", "pallet-aura", "pallet-authorship", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", @@ -2035,15 +2035,15 @@ dependencies = [ "pallet-multisig", "pallet-session", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", - "pallet-utility", + "pallet-utility 28.0.0", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "parachains-common", "parity-scale-codec", - "polkadot-parachain-primitives", + "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common", "rococo-runtime-constants", "scale-info", @@ -2059,25 +2059,25 @@ dependencies = [ "snowbridge-runtime-common", "snowbridge-runtime-test-common", "snowbridge-system-runtime-api", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", - "sp-io", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-keyring", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "static_assertions", "substrate-wasm-builder", "testnet-parachains-constants", @@ -2097,29 +2097,29 @@ dependencies = [ "bridge-runtime-common", "cumulus-pallet-parachain-system", "cumulus-pallet-xcmp-queue", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "impl-trait-for-tuples", "log", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-timestamp", - "pallet-utility", + "pallet-utility 28.0.0", "parachains-common", "parachains-runtimes-test-utils", "parity-scale-codec", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", ] [[package]] @@ -2129,9 +2129,9 @@ dependencies = [ "bridge-hub-common", "bridge-hub-westend-runtime", "emulated-integration-tests-common", - "frame-support", + "frame-support 28.0.0", "parachains-common", - "sp-core", + "sp-core 28.0.0", "testnet-parachains-constants", ] @@ -2143,19 +2143,19 @@ dependencies = [ "bridge-hub-westend-runtime", "cumulus-pallet-xcmp-queue", "emulated-integration-tests-common", - "frame-support", + "frame-support 28.0.0", "pallet-asset-conversion", "pallet-assets", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-bridge-messages", "pallet-message-queue", "pallet-xcm", "parachains-common", "parity-scale-codec", "rococo-westend-system-emulated-network", - "sp-runtime", - "staging-xcm", - "staging-xcm-executor", + "sp-runtime 31.0.1", + "staging-xcm 7.0.0", + "staging-xcm-executor 7.0.0", ] [[package]] @@ -2186,10 +2186,10 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-utility", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", @@ -2197,7 +2197,7 @@ dependencies = [ "log", "pallet-aura", "pallet-authorship", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", @@ -2207,37 +2207,37 @@ dependencies = [ "pallet-multisig", "pallet-session", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", - "pallet-utility", + "pallet-utility 28.0.0", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "parachains-common", "parity-scale-codec", - "polkadot-parachain-primitives", + "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", - "sp-io", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-keyring", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "static_assertions", "substrate-wasm-builder", "testnet-parachains-constants", @@ -2257,27 +2257,27 @@ dependencies = [ "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", - "frame-support", - "frame-system", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-system 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "hash-db", "log", - "pallet-balances", + "pallet-balances 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", - "pallet-transaction-payment", - "pallet-utility", + "pallet-transaction-payment 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "pallet-utility 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "parity-scale-codec", "scale-info", - "sp-api", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std 14.0.0", - "sp-trie", - "staging-xcm", - "staging-xcm-builder", + "sp-api 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-io 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-trie 29.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "staging-xcm 7.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "staging-xcm-builder 7.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "static_assertions", ] @@ -2726,9 +2726,9 @@ dependencies = [ "collectives-westend-runtime", "cumulus-primitives-core", "emulated-integration-tests-common", - "frame-support", + "frame-support 28.0.0", "parachains-common", - "sp-core", + "sp-core 28.0.0", "testnet-parachains-constants", ] @@ -2745,10 +2745,10 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-utility", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", @@ -2758,7 +2758,7 @@ dependencies = [ "pallet-asset-rate", "pallet-aura", "pallet-authorship", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", @@ -2773,35 +2773,35 @@ dependencies = [ "pallet-scheduler", "pallet-session", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", "pallet-treasury", - "pallet-utility", + "pallet-utility 28.0.0", "pallet-xcm", "parachains-common", "parity-scale-codec", - "polkadot-parachain-primitives", + "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common", "scale-info", - "sp-api", - "sp-arithmetic", + "sp-api 26.0.0", + "sp-arithmetic 23.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", - "sp-io", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", "testnet-parachains-constants", "westend-runtime-constants", @@ -3001,10 +3001,10 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-utility", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", @@ -3012,7 +3012,7 @@ dependencies = [ "log", "pallet-aura", "pallet-authorship", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-collator-selection", "pallet-contracts", "pallet-insecure-randomness-collective-flip", @@ -3021,33 +3021,33 @@ dependencies = [ "pallet-session", "pallet-sudo", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", - "pallet-utility", + "pallet-utility 28.0.0", "pallet-xcm", "parachains-common", "parity-scale-codec", - "polkadot-parachain-primitives", + "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common", "rococo-runtime-constants", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", "testnet-parachains-constants", ] @@ -3096,10 +3096,10 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-utility", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", @@ -3107,7 +3107,7 @@ dependencies = [ "log", "pallet-aura", "pallet-authorship", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-broker", "pallet-collator-selection", "pallet-message-queue", @@ -3115,35 +3115,35 @@ dependencies = [ "pallet-session", "pallet-sudo", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", - "pallet-utility", + "pallet-utility 28.0.0", "pallet-xcm", "pallet-xcm-benchmarks", "parachains-common", "parity-scale-codec", - "polkadot-parachain-primitives", + "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common", "rococo-runtime-constants", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", "testnet-parachains-constants", ] @@ -3161,10 +3161,10 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-utility", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", @@ -3172,41 +3172,41 @@ dependencies = [ "log", "pallet-aura", "pallet-authorship", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-broker", "pallet-collator-selection", "pallet-message-queue", "pallet-multisig", "pallet-session", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", - "pallet-utility", + "pallet-utility 28.0.0", "pallet-xcm", "pallet-xcm-benchmarks", "parachains-common", "parity-scale-codec", - "polkadot-parachain-primitives", + "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", "testnet-parachains-constants", "westend-runtime-constants", @@ -3553,8 +3553,8 @@ dependencies = [ "sc-client-api", "sc-service", "sp-blockchain", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "url", ] @@ -3576,14 +3576,14 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-overseer", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "sc-client-api", - "sp-api", + "sp-api 26.0.0", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-maybe-compressed-blob", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-tracing 16.0.0", "tracing", ] @@ -3605,7 +3605,7 @@ dependencies = [ "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-overseer", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "sc-client-api", "sc-consensus", "sc-consensus-aura", @@ -3613,18 +3613,18 @@ dependencies = [ "sc-consensus-slots", "sc-telemetry", "schnellru", - "sp-api", - "sp-application-crypto", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus", "sp-consensus-aura", - "sp-core", - "sp-inherents", - "sp-keystore", - "sp-runtime", - "sp-state-machine", - "sp-timestamp", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", + "sp-timestamp 26.0.0", "substrate-prometheus-endpoint", "tracing", ] @@ -3644,19 +3644,19 @@ dependencies = [ "futures-timer", "log", "parity-scale-codec", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "sc-client-api", "sc-consensus", "sc-consensus-babe", "schnellru", "sp-blockchain", "sp-consensus", - "sp-consensus-slots", - "sp-core", - "sp-runtime", - "sp-timestamp", + "sp-consensus-slots 0.32.0", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "sp-timestamp 26.0.0", "sp-tracing 16.0.0", - "sp-trie", + "sp-trie 29.0.0", "substrate-prometheus-endpoint", "tracing", ] @@ -3669,9 +3669,9 @@ dependencies = [ "async-trait", "cumulus-primitives-parachain-inherent", "sp-consensus", - "sp-inherents", - "sp-runtime", - "sp-state-machine", + "sp-inherents 26.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "thiserror", ] @@ -3686,13 +3686,13 @@ dependencies = [ "futures", "parking_lot 0.12.1", "sc-consensus", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-inherents", - "sp-runtime", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-runtime 31.0.1", "substrate-prometheus-endpoint", "tracing", ] @@ -3711,19 +3711,19 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.1", "polkadot-node-primitives", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "polkadot-test-client", "portpicker", "sc-cli", "sc-client-api", "sp-blockchain", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", - "sp-state-machine", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "substrate-test-utils", "tokio", "tracing", @@ -3742,14 +3742,14 @@ dependencies = [ "parity-scale-codec", "sc-client-api", "scale-info", - "sp-api", - "sp-crypto-hashing", - "sp-inherents", - "sp-runtime", - "sp-state-machine", + "sp-api 26.0.0", + "sp-crypto-hashing 0.1.0", + "sp-inherents 26.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-std 14.0.0", "sp-storage 19.0.0", - "sp-trie", + "sp-trie 29.0.0", "tracing", ] @@ -3767,7 +3767,7 @@ dependencies = [ "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-overseer", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "portpicker", "rand", "sc-cli", @@ -3775,7 +3775,7 @@ dependencies = [ "sc-consensus", "sp-consensus", "sp-maybe-compressed-blob", - "sp-runtime", + "sp-runtime 31.0.1", "substrate-test-utils", "tokio", "tracing", @@ -3796,7 +3796,7 @@ dependencies = [ "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", "futures", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "sc-client-api", "sc-consensus", "sc-network", @@ -3808,12 +3808,12 @@ dependencies = [ "sc-telemetry", "sc-transaction-pool", "sc-utils", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-transaction-pool", ] @@ -3822,15 +3822,15 @@ name = "cumulus-pallet-aura-ext" version = "0.7.0" dependencies = [ "cumulus-pallet-parachain-system", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "pallet-aura", "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-consensus-aura", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -3839,18 +3839,18 @@ name = "cumulus-pallet-dmp-queue" version = "0.7.0" dependencies = [ "cumulus-primitives-core", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", - "staging-xcm", + "staging-xcm 7.0.0", ] [[package]] @@ -3866,9 +3866,9 @@ dependencies = [ "cumulus-test-client", "cumulus-test-relay-sproof-builder", "environmental", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "futures", "hex-literal", "impl-trait-for-tuples", @@ -3876,25 +3876,25 @@ dependencies = [ "log", "pallet-message-queue", "parity-scale-codec", - "polkadot-parachain-primitives", + "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common", "polkadot-runtime-parachains", "rand", "sc-client-api", "scale-info", - "sp-core", - "sp-crypto-hashing", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-externalities 0.25.0", - "sp-inherents", - "sp-io", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-std 14.0.0", "sp-tracing 16.0.0", - "sp-trie", - "sp-version", - "staging-xcm", + "sp-trie 29.0.0", + "sp-version 29.0.0", + "staging-xcm 7.0.0", "trie-db", "trie-standardmap", ] @@ -3913,12 +3913,12 @@ dependencies = [ name = "cumulus-pallet-session-benchmarking" version = "9.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "pallet-session", "parity-scale-codec", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -3927,13 +3927,13 @@ name = "cumulus-pallet-solo-to-para" version = "0.7.0" dependencies = [ "cumulus-pallet-parachain-system", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "pallet-sudo", "parity-scale-codec", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "scale-info", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -3942,14 +3942,14 @@ name = "cumulus-pallet-xcm" version = "0.7.0" dependencies = [ "cumulus-primitives-core", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "parity-scale-codec", "scale-info", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "staging-xcm", + "staging-xcm 7.0.0", ] [[package]] @@ -3960,23 +3960,23 @@ dependencies = [ "bp-xcm-bridge-hub-router", "cumulus-pallet-parachain-system", "cumulus-primitives-core", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-message-queue", "parity-scale-codec", "polkadot-runtime-common", "polkadot-runtime-parachains", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", ] [[package]] @@ -3985,13 +3985,13 @@ version = "0.7.0" dependencies = [ "cumulus-pallet-xcm", "cumulus-primitives-core", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "parity-scale-codec", "scale-info", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "staging-xcm", + "staging-xcm 7.0.0", ] [[package]] @@ -3999,11 +3999,11 @@ name = "cumulus-primitives-aura" version = "0.7.0" dependencies = [ "parity-scale-codec", - "polkadot-core-primitives", - "polkadot-primitives", - "sp-api", + "polkadot-core-primitives 7.0.0", + "polkadot-primitives 7.0.0", + "sp-api 26.0.0", "sp-consensus-aura", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -4012,15 +4012,15 @@ name = "cumulus-primitives-core" version = "0.7.0" dependencies = [ "parity-scale-codec", - "polkadot-core-primitives", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-core-primitives 7.0.0", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "scale-info", - "sp-api", - "sp-runtime", + "sp-api 26.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "sp-trie", - "staging-xcm", + "sp-trie 29.0.0", + "staging-xcm 7.0.0", ] [[package]] @@ -4031,24 +4031,24 @@ dependencies = [ "cumulus-primitives-core", "parity-scale-codec", "scale-info", - "sp-core", - "sp-inherents", - "sp-runtime", - "sp-state-machine", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-std 14.0.0", - "sp-trie", + "sp-trie 29.0.0", ] [[package]] name = "cumulus-primitives-proof-size-hostfunction" version = "0.2.0" dependencies = [ - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-io", + "sp-io 30.0.0", "sp-runtime-interface 24.0.0", - "sp-state-machine", - "sp-trie", + "sp-state-machine 0.35.0", + "sp-trie 29.0.0", ] [[package]] @@ -4059,15 +4059,15 @@ dependencies = [ "cumulus-primitives-proof-size-hostfunction", "cumulus-test-runtime", "docify", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "parity-scale-codec", "scale-info", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "sp-trie", + "sp-trie 29.0.0", ] [[package]] @@ -4077,9 +4077,9 @@ dependencies = [ "cumulus-primitives-core", "futures", "parity-scale-codec", - "sp-inherents", + "sp-inherents 26.0.0", "sp-std 14.0.0", - "sp-timestamp", + "sp-timestamp 26.0.0", ] [[package]] @@ -4087,18 +4087,18 @@ name = "cumulus-primitives-utility" version = "0.7.0" dependencies = [ "cumulus-primitives-core", - "frame-support", + "frame-support 28.0.0", "log", "pallet-asset-conversion", "parity-scale-codec", "polkadot-runtime-common", "polkadot-runtime-parachains", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", ] [[package]] @@ -4112,7 +4112,7 @@ dependencies = [ "futures", "futures-timer", "polkadot-cli", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-service", "polkadot-test-client", "prioritized-metered-channel", @@ -4121,12 +4121,12 @@ dependencies = [ "sc-sysinfo", "sc-telemetry", "sc-tracing", - "sp-api", + "sp-api 26.0.0", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", ] [[package]] @@ -4140,9 +4140,9 @@ dependencies = [ "parity-scale-codec", "polkadot-overseer", "sc-client-api", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", - "sp-state-machine", + "sp-state-machine 0.35.0", "thiserror", ] @@ -4159,7 +4159,7 @@ dependencies = [ "parking_lot 0.12.1", "polkadot-availability-recovery", "polkadot-collator-protocol", - "polkadot-core-primitives", + "polkadot-core-primitives 7.0.0", "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-chain-api", @@ -4168,7 +4168,7 @@ dependencies = [ "polkadot-node-network-protocol", "polkadot-node-subsystem-util", "polkadot-overseer", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-service", "sc-authority-discovery", "sc-client-api", @@ -4177,11 +4177,11 @@ dependencies = [ "sc-service", "sc-tracing", "sc-utils", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-consensus-babe", - "sp-runtime", + "sp-runtime 31.0.1", "substrate-prometheus-endpoint", "tokio", "tracing", @@ -4210,14 +4210,14 @@ dependencies = [ "serde_json", "smoldot", "smoldot-light", - "sp-api", - "sp-authority-discovery", + "sp-api 26.0.0", + "sp-authority-discovery 26.0.0", "sp-consensus-babe", - "sp-core", - "sp-runtime", - "sp-state-machine", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-storage 19.0.0", - "sp-version", + "sp-version 29.0.0", "thiserror", "tokio", "tokio-util", @@ -4236,25 +4236,25 @@ dependencies = [ "cumulus-test-relay-sproof-builder", "cumulus-test-runtime", "cumulus-test-service", - "frame-system", - "pallet-balances", - "pallet-transaction-payment", + "frame-system 28.0.0", + "pallet-balances 28.0.0", + "pallet-transaction-payment 28.0.0", "parity-scale-codec", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "sc-block-builder", "sc-consensus", "sc-executor", "sc-executor-common", "sc-service", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", - "sp-core", - "sp-inherents", - "sp-io", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", - "sp-timestamp", + "sp-runtime 31.0.1", + "sp-timestamp 26.0.0", "substrate-test-client", ] @@ -4264,11 +4264,11 @@ version = "0.7.0" dependencies = [ "cumulus-primitives-core", "parity-scale-codec", - "polkadot-primitives", - "sp-runtime", - "sp-state-machine", + "polkadot-primitives 7.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-std 14.0.0", - "sp-trie", + "sp-trie 29.0.0", ] [[package]] @@ -4279,29 +4279,29 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-storage-weight-reclaim", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-rpc-runtime-api", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-glutton", "pallet-message-queue", "pallet-sudo", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", - "sp-core", - "sp-genesis-builder", - "sp-inherents", - "sp-io", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "substrate-wasm-builder", ] @@ -4327,18 +4327,18 @@ dependencies = [ "cumulus-test-client", "cumulus-test-relay-sproof-builder", "cumulus-test-runtime", - "frame-system", + "frame-system 28.0.0", "frame-system-rpc-runtime-api", "futures", "jsonrpsee", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "parachains-common", "parity-scale-codec", "polkadot-cli", "polkadot-node-subsystem", "polkadot-overseer", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-service", "polkadot-test-service", "portpicker", @@ -4361,18 +4361,18 @@ dependencies = [ "sc-transaction-pool-api", "serde", "serde_json", - "sp-api", - "sp-arithmetic", - "sp-authority-discovery", + "sp-api 26.0.0", + "sp-arithmetic 23.0.0", + "sp-authority-discovery 26.0.0", "sp-blockchain", "sp-consensus", - "sp-consensus-grandpa", - "sp-core", - "sp-io", + "sp-consensus-grandpa 13.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", - "sp-state-machine", - "sp-timestamp", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", + "sp-timestamp 26.0.0", "sp-tracing 16.0.0", "substrate-test-client", "substrate-test-utils", @@ -4919,25 +4919,25 @@ dependencies = [ "cumulus-pallet-parachain-system", "cumulus-pallet-xcmp-queue", "cumulus-primitives-core", - "frame-support", + "frame-support 28.0.0", "pallet-assets", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-bridge-messages", "pallet-message-queue", "pallet-xcm", "parachains-common", "parity-scale-codec", "paste", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "polkadot-runtime-parachains", "sc-consensus-grandpa", - "sp-authority-discovery", + "sp-authority-discovery 26.0.0", "sp-consensus-babe", "sp-consensus-beefy", - "sp-core", - "sp-runtime", - "staging-xcm", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "staging-xcm 7.0.0", "xcm-emulator", ] @@ -5073,7 +5073,7 @@ dependencies = [ "honggfuzz", "polkadot-erasure-coding", "polkadot-node-primitives", - "polkadot-primitives", + "polkadot-primitives 7.0.0", ] [[package]] @@ -5459,9 +5459,9 @@ name = "frame-benchmarking" version = "28.0.0" dependencies = [ "array-bytes 6.1.0", - "frame-support", - "frame-support-procedural", - "frame-system", + "frame-support 28.0.0", + "frame-support-procedural 23.0.0", + "frame-system 28.0.0", "linregress", "log", "parity-scale-codec", @@ -5469,18 +5469,43 @@ dependencies = [ "rusty-fork", "scale-info", "serde", - "sp-api", - "sp-application-crypto", - "sp-core", - "sp-io", - "sp-keystore", - "sp-runtime", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", "sp-std 14.0.0", "sp-storage 19.0.0", "static_assertions", ] +[[package]] +name = "frame-benchmarking" +version = "28.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-support-procedural 23.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-system 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "linregress", + "log", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "sp-api 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-application-crypto 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-io 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "static_assertions", +] + [[package]] name = "frame-benchmarking-cli" version = "32.0.0" @@ -5490,9 +5515,9 @@ dependencies = [ "chrono", "clap 4.5.3", "comfy-table", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "gethostname", "handlebars", "itertools 0.10.5", @@ -5511,18 +5536,18 @@ dependencies = [ "sc-sysinfo", "serde", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", - "sp-core", + "sp-core 28.0.0", "sp-database", "sp-externalities 0.25.0", - "sp-inherents", - "sp-io", - "sp-keystore", - "sp-runtime", - "sp-state-machine", + "sp-inherents 26.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-storage 19.0.0", - "sp-trie", + "sp-trie 29.0.0", "sp-wasm-interface 20.0.0", "thiserror", "thousands", @@ -5532,13 +5557,13 @@ dependencies = [ name = "frame-benchmarking-pallet-pov" version = "18.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "parity-scale-codec", "scale-info", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -5547,13 +5572,13 @@ name = "frame-election-provider-solution-type" version = "13.0.0" dependencies = [ "frame-election-provider-support", - "frame-support", + "frame-support 28.0.0", "parity-scale-codec", "proc-macro-crate 3.0.0", "proc-macro2", "quote", "scale-info", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "syn 2.0.53", "trybuild", ] @@ -5563,16 +5588,16 @@ name = "frame-election-provider-support" version = "28.0.0" dependencies = [ "frame-election-provider-solution-type", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "parity-scale-codec", "rand", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-npos-elections", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -5583,14 +5608,14 @@ dependencies = [ "clap 4.5.3", "frame-election-provider-solution-type", "frame-election-provider-support", - "frame-support", + "frame-support 28.0.0", "honggfuzz", "parity-scale-codec", "rand", "scale-info", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-npos-elections", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -5599,21 +5624,21 @@ version = "28.0.0" dependencies = [ "aquamarine 0.3.3", "array-bytes 6.1.0", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-try-runtime", "log", - "pallet-balances", - "pallet-transaction-payment", + "pallet-balances 28.0.0", + "pallet-transaction-payment 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-inherents", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", - "sp-version", + "sp-version 29.0.0", ] [[package]] @@ -5638,11 +5663,11 @@ dependencies = [ "log", "parity-scale-codec", "serde", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-runtime", - "sp-state-machine", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-tracing 16.0.0", "spinners", "substrate-rpc-client", @@ -5661,8 +5686,8 @@ dependencies = [ "docify", "environmental", "frame-metadata", - "frame-support-procedural", - "frame-system", + "frame-support-procedural 23.0.0", + "frame-system 28.0.0", "impl-trait-for-tuples", "k256", "log", @@ -5674,23 +5699,64 @@ dependencies = [ "serde", "serde_json", "smallvec", - "sp-api", - "sp-arithmetic", - "sp-core", - "sp-crypto-hashing", - "sp-crypto-hashing-proc-macro", + "sp-api 26.0.0", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-crypto-hashing-proc-macro 0.1.0", "sp-debug-derive 14.0.0", - "sp-genesis-builder", - "sp-inherents", - "sp-io", - "sp-metadata-ir", - "sp-runtime", - "sp-staking", - "sp-state-machine", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", + "sp-metadata-ir 0.6.0", + "sp-runtime 31.0.1", + "sp-staking 26.0.0", + "sp-state-machine 0.35.0", "sp-std 14.0.0", - "sp-timestamp", + "sp-timestamp 26.0.0", "sp-tracing 16.0.0", - "sp-weights", + "sp-weights 27.0.0", + "static_assertions", + "tt-call", +] + +[[package]] +name = "frame-support" +version = "28.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "aquamarine 0.5.0", + "array-bytes 6.1.0", + "bitflags 1.3.2", + "docify", + "environmental", + "frame-metadata", + "frame-support-procedural 23.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "impl-trait-for-tuples", + "k256", + "log", + "macro_magic", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "serde_json", + "smallvec", + "sp-api 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-arithmetic 23.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-crypto-hashing-proc-macro 0.1.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-genesis-builder 0.8.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-inherents 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-io 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-metadata-ir 0.6.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-staking 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-state-machine 0.35.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-weights 27.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "static_assertions", "tt-call", ] @@ -5703,22 +5769,53 @@ dependencies = [ "cfg-expr", "derive-syn-parse 0.2.0", "expander 2.0.0", - "frame-support-procedural-tools", + "frame-support-procedural-tools 10.0.0", "itertools 0.10.5", "macro_magic", "proc-macro-warning", "proc-macro2", "quote", "regex", - "sp-crypto-hashing", + "sp-crypto-hashing 0.1.0", + "syn 2.0.53", +] + +[[package]] +name = "frame-support-procedural" +version = "23.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "Inflector", + "cfg-expr", + "derive-syn-parse 0.2.0", + "expander 2.0.0", + "frame-support-procedural-tools 10.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "itertools 0.10.5", + "macro_magic", + "proc-macro-warning", + "proc-macro2", + "quote", + "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "syn 2.0.53", +] + +[[package]] +name = "frame-support-procedural-tools" +version = "10.0.0" +dependencies = [ + "frame-support-procedural-tools-derive 11.0.0", + "proc-macro-crate 3.0.0", + "proc-macro2", + "quote", "syn 2.0.53", ] [[package]] name = "frame-support-procedural-tools" version = "10.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" dependencies = [ - "frame-support-procedural-tools-derive", + "frame-support-procedural-tools-derive 11.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "proc-macro-crate 3.0.0", "proc-macro2", "quote", @@ -5734,30 +5831,40 @@ dependencies = [ "syn 2.0.53", ] +[[package]] +name = "frame-support-procedural-tools-derive" +version = "11.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + [[package]] name = "frame-support-test" version = "3.0.0" dependencies = [ - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", "frame-metadata", - "frame-support", + "frame-support 28.0.0", "frame-support-test-pallet", - "frame-system", + "frame-system 28.0.0", "parity-scale-codec", "pretty_assertions", "rustversion", "scale-info", "serde", - "sp-api", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-metadata-ir", - "sp-runtime", - "sp-state-machine", + "sp-api 26.0.0", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-metadata-ir 0.6.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-std 14.0.0", - "sp-version", + "sp-version 29.0.0", "static_assertions", "trybuild", ] @@ -5766,25 +5873,25 @@ dependencies = [ name = "frame-support-test-compile-pass" version = "4.0.0-dev" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-runtime", - "sp-version", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "sp-version 29.0.0", ] [[package]] name = "frame-support-test-pallet" version = "4.0.0-dev" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "parity-scale-codec", "scale-info", "serde", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -5803,36 +5910,56 @@ dependencies = [ "cfg-if", "criterion 0.4.0", "docify", - "frame-support", + "frame-support 28.0.0", "log", "parity-scale-codec", "scale-info", "serde", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "sp-version", - "sp-weights", + "sp-version 29.0.0", + "sp-weights 27.0.0", "substrate-test-runtime-client", ] +[[package]] +name = "frame-system" +version = "28.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "cfg-if", + "docify", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-io 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-version 29.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-weights 27.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", +] + [[package]] name = "frame-system-benchmarking" version = "28.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "sp-version", + "sp-version 29.0.0", ] [[package]] @@ -5840,17 +5967,17 @@ name = "frame-system-rpc-runtime-api" version = "26.0.0" dependencies = [ "parity-scale-codec", - "sp-api", + "sp-api 26.0.0", ] [[package]] name = "frame-try-runtime" version = "0.34.0" dependencies = [ - "frame-support", + "frame-support 28.0.0", "parity-scale-codec", - "sp-api", - "sp-runtime", + "sp-api 26.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -6029,11 +6156,11 @@ version = "28.0.0" dependencies = [ "chrono", "frame-election-provider-support", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "num-format", "pallet-staking", - "sp-staking", + "sp-staking 26.0.0", ] [[package]] @@ -6155,10 +6282,10 @@ dependencies = [ "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-timestamp", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", @@ -6170,23 +6297,23 @@ dependencies = [ "parachains-common", "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", "testnet-parachains-constants", ] @@ -7018,12 +7145,12 @@ checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" name = "kitchensink-runtime" version = "3.0.0-dev" dependencies = [ - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-benchmarking-pallet-pov", "frame-election-provider-support", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", @@ -7039,7 +7166,7 @@ dependencies = [ "pallet-authorship", "pallet-babe", "pallet-bags-list", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", @@ -7101,39 +7228,39 @@ dependencies = [ "pallet-sudo", "pallet-timestamp", "pallet-tips", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", - "pallet-utility", + "pallet-utility 28.0.0", "pallet-vesting", "pallet-whitelist", "parity-scale-codec", "primitive-types", "scale-info", "serde_json", - "sp-api", - "sp-authority-discovery", + "sp-api 26.0.0", + "sp-authority-discovery 26.0.0", "sp-block-builder", "sp-consensus-babe", "sp-consensus-beefy", - "sp-consensus-grandpa", - "sp-core", - "sp-genesis-builder", - "sp-inherents", - "sp-io", + "sp-consensus-grandpa 13.0.0", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-mixnet", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", - "sp-staking", + "sp-staking 26.0.0", "sp-statement-store", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "static_assertions", "substrate-wasm-builder", ] @@ -8123,13 +8250,13 @@ dependencies = [ "sc-transaction-pool", "sc-transaction-pool-api", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-blockchain", - "sp-io", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", - "sp-timestamp", + "sp-runtime 31.0.1", + "sp-timestamp 26.0.0", "substrate-build-script-utils", "substrate-frame-rpc-system", ] @@ -8138,17 +8265,17 @@ dependencies = [ name = "minimal-template-runtime" version = "0.0.0" dependencies = [ - "pallet-balances", + "pallet-balances 28.0.0", "pallet-minimal-template", "pallet-sudo", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", "polkadot-sdk-frame", "scale-info", - "sp-genesis-builder", - "sp-runtime", + "sp-genesis-builder 0.8.0", + "sp-runtime 31.0.1", "substrate-wasm-builder", ] @@ -8208,13 +8335,13 @@ dependencies = [ "sc-block-builder", "sc-client-api", "sc-offchain", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-consensus-beefy", - "sp-core", + "sp-core 28.0.0", "sp-mmr-primitives", - "sp-runtime", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "substrate-test-runtime-client", "tokio", @@ -8228,11 +8355,11 @@ dependencies = [ "parity-scale-codec", "serde", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", - "sp-core", + "sp-core 28.0.0", "sp-mmr-primitives", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -8594,13 +8721,13 @@ dependencies = [ "serde", "serde_json", "sp-consensus", - "sp-core", - "sp-inherents", - "sp-runtime", - "sp-state-machine", - "sp-timestamp", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", + "sp-timestamp 26.0.0", "sp-tracing 16.0.0", - "sp-trie", + "sp-trie 29.0.0", "tempfile", ] @@ -8608,8 +8735,8 @@ dependencies = [ name = "node-primitives" version = "2.0.0" dependencies = [ - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -8634,13 +8761,13 @@ dependencies = [ "sc-rpc-spec-v2", "sc-sync-state-rpc", "sc-transaction-pool-api", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus", "sp-consensus-babe", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-statement-store", "substrate-frame-rpc-system", "substrate-state-trie-migration-rpc", @@ -8673,7 +8800,7 @@ dependencies = [ name = "node-testing" version = "3.0.0-dev" dependencies = [ - "frame-system", + "frame-system 28.0.0", "fs_extra", "futures", "kitchensink-runtime", @@ -8691,17 +8818,17 @@ dependencies = [ "sc-consensus", "sc-executor", "sc-service", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-crypto-hashing", - "sp-inherents", - "sp-io", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", - "sp-timestamp", + "sp-runtime 31.0.1", + "sp-timestamp 26.0.0", "staging-node-cli", "substrate-test-client", "tempfile", @@ -8985,19 +9112,19 @@ name = "pallet-alliance" version = "27.0.0" dependencies = [ "array-bytes 6.1.0", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-collective", "pallet-identity", "parity-scale-codec", "scale-info", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9005,19 +9132,19 @@ dependencies = [ name = "pallet-asset-conversion" version = "10.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "pallet-assets", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "primitive-types", "scale-info", - "sp-api", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-api 26.0.0", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9025,17 +9152,17 @@ dependencies = [ name = "pallet-asset-conversion-tx-payment" version = "10.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "pallet-asset-conversion", "pallet-assets", - "pallet-balances", - "pallet-transaction-payment", + "pallet-balances 28.0.0", + "pallet-transaction-payment 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-storage 19.0.0", ] @@ -9044,15 +9171,15 @@ dependencies = [ name = "pallet-asset-rate" version = "7.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "pallet-balances", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9060,20 +9187,20 @@ dependencies = [ name = "pallet-asset-tx-payment" version = "28.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "pallet-assets", "pallet-authorship", - "pallet-balances", - "pallet-transaction-payment", + "pallet-balances 28.0.0", + "pallet-transaction-payment 28.0.0", "parity-scale-codec", "scale-info", "serde", "serde_json", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-storage 19.0.0", ] @@ -9082,16 +9209,16 @@ dependencies = [ name = "pallet-assets" version = "29.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9099,14 +9226,14 @@ dependencies = [ name = "pallet-atomic-swap" version = "28.0.0" dependencies = [ - "frame-support", - "frame-system", - "pallet-balances", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9114,17 +9241,17 @@ dependencies = [ name = "pallet-aura" version = "27.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-consensus-aura", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9132,16 +9259,16 @@ dependencies = [ name = "pallet-authority-discovery" version = "28.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "pallet-session", "parity-scale-codec", "scale-info", - "sp-application-crypto", - "sp-authority-discovery", - "sp-core", - "sp-io", - "sp-runtime", + "sp-application-crypto 30.0.0", + "sp-authority-discovery 26.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9149,14 +9276,14 @@ dependencies = [ name = "pallet-authorship" version = "28.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "impl-trait-for-tuples", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9164,13 +9291,13 @@ dependencies = [ name = "pallet-babe" version = "28.0.0" dependencies = [ - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-election-provider-support", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-authorship", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-offences", "pallet-session", "pallet-staking", @@ -9178,13 +9305,13 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-consensus-babe", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-session", - "sp-staking", + "sp-staking 26.0.0", "sp-std 14.0.0", ] @@ -9194,17 +9321,17 @@ version = "27.0.0" dependencies = [ "aquamarine 0.5.0", "docify", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-election-provider-support", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", ] @@ -9225,13 +9352,13 @@ version = "4.0.0-dev" dependencies = [ "frame-election-provider-support", "frame-remote-externalities", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-bags-list", "pallet-staking", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-tracing 16.0.0", @@ -9242,30 +9369,46 @@ name = "pallet-balances" version = "28.0.0" dependencies = [ "docify", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "parity-scale-codec", "paste", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] +[[package]] +name = "pallet-balances" +version = "28.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "docify", + "frame-benchmarking 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-system 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", +] + [[package]] name = "pallet-beefy" version = "28.0.0" dependencies = [ "frame-election-provider-support", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-authorship", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-offences", "pallet-session", "pallet-staking", @@ -9275,12 +9418,12 @@ dependencies = [ "scale-info", "serde", "sp-consensus-beefy", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-session", - "sp-staking", - "sp-state-machine", + "sp-staking 26.0.0", + "sp-state-machine 0.35.0", "sp-std 14.0.0", ] @@ -9290,8 +9433,8 @@ version = "28.0.0" dependencies = [ "array-bytes 6.1.0", "binary-merkle-tree", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-beefy", "pallet-mmr", @@ -9299,13 +9442,13 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-consensus-beefy", - "sp-core", - "sp-io", - "sp-runtime", - "sp-staking", - "sp-state-machine", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-staking 26.0.0", + "sp-state-machine 0.35.0", "sp-std 14.0.0", ] @@ -9313,17 +9456,17 @@ dependencies = [ name = "pallet-bounties" version = "27.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-treasury", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9335,18 +9478,18 @@ dependencies = [ "bp-runtime", "bp-test-utils", "finality-grandpa", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-system 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "log", "parity-scale-codec", "scale-info", - "sp-consensus-grandpa", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std 14.0.0", - "sp-trie", + "sp-consensus-grandpa 13.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-io 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-trie 29.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -9356,17 +9499,17 @@ dependencies = [ "bp-messages", "bp-runtime", "bp-test-utils", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-system 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "log", "num-traits", - "pallet-balances", + "pallet-balances 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "parity-scale-codec", "scale-info", - "sp-io", - "sp-runtime", - "sp-std 14.0.0", + "sp-io 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -9378,18 +9521,18 @@ dependencies = [ "bp-polkadot-core", "bp-runtime", "bp-test-utils", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-system 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "log", "pallet-bridge-grandpa", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std 14.0.0", - "sp-trie", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-io 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-trie 29.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -9399,18 +9542,18 @@ dependencies = [ "bp-messages", "bp-relayers", "bp-runtime", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-system 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "log", - "pallet-balances", + "pallet-balances 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "pallet-bridge-messages", "parity-scale-codec", "scale-info", - "sp-arithmetic", - "sp-io", - "sp-runtime", - "sp-std 14.0.0", + "sp-arithmetic 23.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-io 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -9418,16 +9561,16 @@ name = "pallet-broker" version = "0.6.0" dependencies = [ "bitvec", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "parity-scale-codec", "scale-info", - "sp-api", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-api 26.0.0", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9435,18 +9578,18 @@ dependencies = [ name = "pallet-child-bounties" version = "27.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-bounties", "pallet-treasury", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9454,23 +9597,23 @@ dependencies = [ name = "pallet-collator-selection" version = "9.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-aura", "pallet-authorship", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-session", "pallet-timestamp", "parity-scale-codec", "rand", "scale-info", "sp-consensus-aura", - "sp-core", - "sp-io", - "sp-runtime", - "sp-staking", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-staking 26.0.0", "sp-std 14.0.0", "sp-tracing 16.0.0", ] @@ -9479,15 +9622,15 @@ dependencies = [ name = "pallet-collective" version = "28.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9495,14 +9638,14 @@ dependencies = [ name = "pallet-collective-content" version = "0.6.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9515,13 +9658,13 @@ dependencies = [ "bitflags 1.3.2", "env_logger 0.11.3", "environmental", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "impl-trait-for-tuples", "log", "pallet-assets", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-contracts-fixtures", "pallet-contracts-proc-macro", "pallet-contracts-uapi", @@ -9529,7 +9672,7 @@ dependencies = [ "pallet-message-queue", "pallet-proxy", "pallet-timestamp", - "pallet-utility", + "pallet-utility 28.0.0", "parity-scale-codec", "paste", "pretty_assertions", @@ -9538,15 +9681,15 @@ dependencies = [ "scale-info", "serde", "smallvec", - "sp-api", - "sp-core", - "sp-io", - "sp-keystore", - "sp-runtime", + "sp-api 26.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", - "staging-xcm", - "staging-xcm-builder", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", "wasm-instrument", "wasmi", "wat", @@ -9557,10 +9700,10 @@ name = "pallet-contracts-fixtures" version = "1.0.0" dependencies = [ "anyhow", - "frame-system", + "frame-system 28.0.0", "parity-wasm", "polkavm-linker", - "sp-runtime", + "sp-runtime 31.0.1", "tempfile", "toml 0.8.8", "twox-hash", @@ -9571,10 +9714,10 @@ name = "pallet-contracts-mock-network" version = "3.0.0" dependencies = [ "assert_matches", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "pallet-assets", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-contracts", "pallet-contracts-fixtures", "pallet-contracts-proc-macro", @@ -9583,24 +9726,24 @@ dependencies = [ "pallet-message-queue", "pallet-proxy", "pallet-timestamp", - "pallet-utility", + "pallet-utility 28.0.0", "pallet-xcm", "parity-scale-codec", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "polkadot-runtime-parachains", "pretty_assertions", "scale-info", - "sp-api", - "sp-core", - "sp-io", - "sp-keystore", - "sp-runtime", + "sp-api 26.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "xcm-simulator", ] @@ -9629,17 +9772,17 @@ name = "pallet-conviction-voting" version = "28.0.0" dependencies = [ "assert_matches", - "frame-benchmarking", - "frame-support", - "frame-system", - "pallet-balances", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-balances 28.0.0", "pallet-scheduler", "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9647,17 +9790,17 @@ dependencies = [ name = "pallet-core-fellowship" version = "12.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-ranked-collective", "parity-scale-codec", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9665,13 +9808,13 @@ dependencies = [ name = "pallet-default-config-example" version = "10.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "parity-scale-codec", "scale-info", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9679,19 +9822,19 @@ dependencies = [ name = "pallet-democracy" version = "28.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-preimage", "pallet-scheduler", "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9699,15 +9842,15 @@ dependencies = [ name = "pallet-dev-mode" version = "10.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9716,11 +9859,11 @@ name = "pallet-election-provider-e2e-test" version = "1.0.0" dependencies = [ "frame-election-provider-support", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-bags-list", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-election-provider-multi-phase", "pallet-nomination-pools", "pallet-session", @@ -9729,11 +9872,11 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.1", "scale-info", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-npos-elections", - "sp-runtime", - "sp-staking", + "sp-runtime 31.0.1", + "sp-staking 26.0.0", "sp-std 14.0.0", "sp-tracing 16.0.0", ] @@ -9742,22 +9885,22 @@ dependencies = [ name = "pallet-election-provider-multi-phase" version = "27.0.0" dependencies = [ - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-election-provider-support", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-election-provider-support-benchmarking", "parity-scale-codec", "parking_lot 0.12.1", "rand", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-npos-elections", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", "strum 0.26.2", @@ -9767,12 +9910,12 @@ dependencies = [ name = "pallet-election-provider-support-benchmarking" version = "27.0.0" dependencies = [ - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-election-provider-support", - "frame-system", + "frame-system 28.0.0", "parity-scale-codec", "sp-npos-elections", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9780,18 +9923,18 @@ dependencies = [ name = "pallet-elections-phragmen" version = "29.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-npos-elections", - "sp-runtime", - "sp-staking", + "sp-runtime 31.0.1", + "sp-staking 26.0.0", "sp-std 14.0.0", "sp-tracing 16.0.0", "substrate-test-utils", @@ -9801,16 +9944,16 @@ dependencies = [ name = "pallet-example-basic" version = "27.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9827,16 +9970,16 @@ dependencies = [ name = "pallet-example-kitchensink" version = "4.0.0-dev" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9844,30 +9987,30 @@ dependencies = [ name = "pallet-example-mbm" version = "0.1.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-migrations", "parity-scale-codec", "scale-info", - "sp-io", + "sp-io 30.0.0", ] [[package]] name = "pallet-example-offchain-worker" version = "28.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "lite-json", "log", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-keystore", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9877,32 +10020,32 @@ version = "0.0.1" dependencies = [ "docify", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-try-runtime", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "sp-version", + "sp-version 29.0.0", ] [[package]] name = "pallet-example-split" version = "10.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-std 14.0.0", ] @@ -9910,15 +10053,15 @@ dependencies = [ name = "pallet-example-tasks" version = "1.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9942,21 +10085,21 @@ name = "pallet-fast-unstake" version = "27.0.0" dependencies = [ "docify", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-election-provider-support", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-staking", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-staking 26.0.0", "sp-std 14.0.0", "sp-tracing 16.0.0", "substrate-test-utils", @@ -9967,16 +10110,16 @@ name = "pallet-glutton" version = "14.0.0" dependencies = [ "blake2 0.10.6", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -9985,13 +10128,13 @@ name = "pallet-grandpa" version = "28.0.0" dependencies = [ "finality-grandpa", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-election-provider-support", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-authorship", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-offences", "pallet-session", "pallet-staking", @@ -9999,14 +10142,14 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-application-crypto", - "sp-consensus-grandpa", - "sp-core", - "sp-io", + "sp-application-crypto 30.0.0", + "sp-consensus-grandpa 13.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", - "sp-staking", + "sp-staking 26.0.0", "sp-std 14.0.0", ] @@ -10015,17 +10158,17 @@ name = "pallet-identity" version = "28.0.0" dependencies = [ "enumflags2", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-keystore", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10033,19 +10176,19 @@ dependencies = [ name = "pallet-im-online" version = "27.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-authorship", "pallet-session", "parity-scale-codec", "scale-info", - "sp-application-crypto", - "sp-core", - "sp-io", - "sp-runtime", - "sp-staking", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-staking 26.0.0", "sp-std 14.0.0", ] @@ -10053,16 +10196,16 @@ dependencies = [ name = "pallet-indices" version = "28.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "pallet-balances", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10070,14 +10213,14 @@ dependencies = [ name = "pallet-insecure-randomness-collective-flip" version = "16.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "parity-scale-codec", "safe-mix", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10085,16 +10228,16 @@ dependencies = [ name = "pallet-lottery" version = "28.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", "frame-support-test", - "frame-system", - "pallet-balances", + "frame-system 28.0.0", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10102,15 +10245,15 @@ dependencies = [ name = "pallet-membership" version = "28.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10119,23 +10262,23 @@ name = "pallet-message-queue" version = "31.0.0" dependencies = [ "environmental", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "parity-scale-codec", "rand", "rand_distr", "scale-info", "serde", - "sp-arithmetic", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", - "sp-weights", + "sp-weights 27.0.0", ] [[package]] @@ -10143,23 +10286,23 @@ name = "pallet-migrations" version = "1.0.0" dependencies = [ "docify", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "impl-trait-for-tuples", "log", "parity-scale-codec", "pretty_assertions", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", - "sp-version", + "sp-version 29.0.0", ] [[package]] @@ -10175,18 +10318,18 @@ dependencies = [ name = "pallet-mixnet" version = "0.4.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "parity-scale-codec", "scale-info", "serde", - "sp-application-crypto", - "sp-arithmetic", - "sp-io", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", + "sp-io 30.0.0", "sp-mixnet", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10196,17 +10339,17 @@ version = "27.0.0" dependencies = [ "array-bytes 6.1.0", "env_logger 0.11.3", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "itertools 0.10.5", "log", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-mmr-primitives", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10214,15 +10357,15 @@ dependencies = [ name = "pallet-multisig" version = "28.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10230,18 +10373,18 @@ dependencies = [ name = "pallet-nft-fractionalization" version = "10.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-assets", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-nfts", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10250,17 +10393,17 @@ name = "pallet-nfts" version = "22.0.0" dependencies = [ "enumflags2", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-keystore", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10270,7 +10413,7 @@ version = "14.0.0" dependencies = [ "pallet-nfts", "parity-scale-codec", - "sp-api", + "sp-api 26.0.0", "sp-std 14.0.0", ] @@ -10278,16 +10421,16 @@ dependencies = [ name = "pallet-nis" version = "28.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "pallet-balances", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10295,14 +10438,14 @@ dependencies = [ name = "pallet-node-authorization" version = "28.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10310,16 +10453,16 @@ dependencies = [ name = "pallet-nomination-pools" version = "25.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-staking", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-staking 26.0.0", "sp-std 14.0.0", "sp-tracing 16.0.0", ] @@ -10328,23 +10471,23 @@ dependencies = [ name = "pallet-nomination-pools-benchmarking" version = "26.0.0" dependencies = [ - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-election-provider-support", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "pallet-bags-list", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-nomination-pools", "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", - "sp-staking", + "sp-staking 26.0.0", "sp-std 14.0.0", ] @@ -10352,14 +10495,14 @@ dependencies = [ name = "pallet-nomination-pools-fuzzer" version = "2.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "honggfuzz", "log", "pallet-nomination-pools", "rand", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", ] @@ -10369,7 +10512,7 @@ version = "23.0.0" dependencies = [ "pallet-nomination-pools", "parity-scale-codec", - "sp-api", + "sp-api 26.0.0", "sp-std 14.0.0", ] @@ -10378,21 +10521,21 @@ name = "pallet-nomination-pools-test-staking" version = "1.0.0" dependencies = [ "frame-election-provider-support", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-bags-list", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-nomination-pools", "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-staking", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-staking 26.0.0", "sp-std 14.0.0", "sp-tracing 16.0.0", ] @@ -10401,17 +10544,17 @@ dependencies = [ name = "pallet-offences" version = "27.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", - "sp-staking", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-staking 26.0.0", "sp-std 14.0.0", ] @@ -10419,13 +10562,13 @@ dependencies = [ name = "pallet-offences-benchmarking" version = "28.0.0" dependencies = [ - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-election-provider-support", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-babe", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-grandpa", "pallet-im-online", "pallet-offences", @@ -10435,10 +10578,10 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-staking", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-staking 26.0.0", "sp-std 14.0.0", ] @@ -10447,15 +10590,15 @@ name = "pallet-paged-list" version = "0.6.0" dependencies = [ "docify", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-metadata-ir", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-metadata-ir 0.6.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10464,24 +10607,24 @@ name = "pallet-paged-list-fuzzer" version = "0.1.0" dependencies = [ "arbitrary", - "frame-support", + "frame-support 28.0.0", "honggfuzz", "pallet-paged-list", - "sp-io", + "sp-io 30.0.0", ] [[package]] name = "pallet-parachain-template" version = "0.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10489,18 +10632,18 @@ name = "pallet-parameters" version = "0.1.0" dependencies = [ "docify", - "frame-benchmarking", - "frame-support", - "frame-system", - "pallet-balances", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-balances 28.0.0", "pallet-example-basic", "parity-scale-codec", "paste", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10508,16 +10651,16 @@ dependencies = [ name = "pallet-preimage" version = "28.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10525,16 +10668,16 @@ dependencies = [ name = "pallet-proxy" version = "28.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "pallet-balances", - "pallet-utility", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-balances 28.0.0", + "pallet-utility 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10542,17 +10685,17 @@ dependencies = [ name = "pallet-ranked-collective" version = "28.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "impl-trait-for-tuples", "log", "parity-scale-codec", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10560,15 +10703,15 @@ dependencies = [ name = "pallet-recovery" version = "28.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "pallet-balances", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10577,20 +10720,20 @@ name = "pallet-referenda" version = "28.0.0" dependencies = [ "assert_matches", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-preimage", "pallet-scheduler", "parity-scale-codec", "scale-info", "serde", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10598,15 +10741,15 @@ dependencies = [ name = "pallet-remark" version = "28.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10615,19 +10758,19 @@ name = "pallet-root-offences" version = "25.0.0" dependencies = [ "frame-election-provider-support", - "frame-support", - "frame-system", - "pallet-balances", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-balances 28.0.0", "pallet-session", "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-staking", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-staking 26.0.0", "sp-std 14.0.0", ] @@ -10635,13 +10778,13 @@ dependencies = [ name = "pallet-root-testing" version = "4.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10650,18 +10793,18 @@ name = "pallet-safe-mode" version = "9.0.0" dependencies = [ "docify", - "frame-benchmarking", - "frame-support", - "frame-system", - "pallet-balances", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-balances 28.0.0", "pallet-proxy", - "pallet-utility", + "pallet-utility 28.0.0", "parity-scale-codec", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10669,17 +10812,17 @@ dependencies = [ name = "pallet-salary" version = "13.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-ranked-collective", "parity-scale-codec", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10688,17 +10831,17 @@ name = "pallet-sassafras" version = "0.3.5-dev" dependencies = [ "array-bytes 6.1.0", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "parity-scale-codec", "scale-info", "sp-consensus-sassafras", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10707,18 +10850,18 @@ name = "pallet-scheduler" version = "29.0.0" dependencies = [ "docify", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-preimage", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "sp-weights", + "sp-weights 27.0.0", "substrate-test-utils", ] @@ -10726,14 +10869,14 @@ dependencies = [ name = "pallet-scored-pool" version = "28.0.0" dependencies = [ - "frame-support", - "frame-system", - "pallet-balances", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10741,32 +10884,32 @@ dependencies = [ name = "pallet-session" version = "28.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "impl-trait-for-tuples", "log", "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-session", - "sp-staking", - "sp-state-machine", + "sp-staking 26.0.0", + "sp-state-machine 0.35.0", "sp-std 14.0.0", - "sp-trie", + "sp-trie 29.0.0", ] [[package]] name = "pallet-session-benchmarking" version = "28.0.0" dependencies = [ - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-election-provider-support", - "frame-support", - "frame-system", - "pallet-balances", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-balances 28.0.0", "pallet-session", "pallet-staking", "pallet-staking-reward-curve", @@ -10774,9 +10917,9 @@ dependencies = [ "parity-scale-codec", "rand", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", ] @@ -10785,11 +10928,11 @@ dependencies = [ name = "pallet-skip-feeless-payment" version = "3.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "parity-scale-codec", "scale-info", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10797,20 +10940,20 @@ dependencies = [ name = "pallet-society" version = "28.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", "frame-support-test", - "frame-system", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "rand_chacha 0.2.2", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10818,14 +10961,14 @@ dependencies = [ name = "pallet-staking" version = "28.0.0" dependencies = [ - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-election-provider-support", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-authorship", "pallet-bags-list", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-session", "pallet-staking-reward-curve", "pallet-timestamp", @@ -10833,12 +10976,12 @@ dependencies = [ "rand_chacha 0.2.2", "scale-info", "serde", - "sp-application-crypto", - "sp-core", - "sp-io", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-npos-elections", - "sp-runtime", - "sp-staking", + "sp-runtime 31.0.1", + "sp-staking 26.0.0", "sp-std 14.0.0", "sp-tracing 16.0.0", "substrate-test-utils", @@ -10851,7 +10994,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "sp-runtime", + "sp-runtime 31.0.1", "syn 2.0.53", ] @@ -10860,7 +11003,7 @@ name = "pallet-staking-reward-fn" version = "19.0.0" dependencies = [ "log", - "sp-arithmetic", + "sp-arithmetic 23.0.0", ] [[package]] @@ -10868,27 +11011,27 @@ name = "pallet-staking-runtime-api" version = "14.0.0" dependencies = [ "parity-scale-codec", - "sp-api", - "sp-staking", + "sp-api 26.0.0", + "sp-staking 26.0.0", ] [[package]] name = "pallet-state-trie-migration" version = "29.0.0" dependencies = [ - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-remote-externalities", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "parking_lot 0.12.1", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", "substrate-state-trie-migration-rpc", @@ -10901,16 +11044,16 @@ dependencies = [ name = "pallet-statement" version = "10.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-api", - "sp-core", - "sp-io", - "sp-runtime", + "sp-api 26.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-statement-store", "sp-std 14.0.0", ] @@ -10920,14 +11063,14 @@ name = "pallet-sudo" version = "28.0.0" dependencies = [ "docify", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10935,14 +11078,14 @@ dependencies = [ name = "pallet-template" version = "0.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10950,37 +11093,37 @@ name = "pallet-timestamp" version = "27.0.0" dependencies = [ "docify", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "parity-scale-codec", "scale-info", - "sp-core", - "sp-inherents", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-storage 19.0.0", - "sp-timestamp", + "sp-timestamp 26.0.0", ] [[package]] name = "pallet-tips" version = "27.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-treasury", "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-storage 19.0.0", ] @@ -10989,19 +11132,35 @@ dependencies = [ name = "pallet-transaction-payment" version = "28.0.0" dependencies = [ - "frame-support", - "frame-system", - "pallet-balances", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", "serde", "serde_json", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] +[[package]] +name = "pallet-transaction-payment" +version = "28.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-system 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-io 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", +] + [[package]] name = "pallet-transaction-payment-rpc" version = "30.0.0" @@ -11009,23 +11168,23 @@ dependencies = [ "jsonrpsee", "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", - "sp-core", + "sp-core 28.0.0", "sp-rpc", - "sp-runtime", - "sp-weights", + "sp-runtime 31.0.1", + "sp-weights 27.0.0", ] [[package]] name = "pallet-transaction-payment-rpc-runtime-api" version = "28.0.0" dependencies = [ - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "parity-scale-codec", - "sp-api", - "sp-runtime", - "sp-weights", + "sp-api 26.0.0", + "sp-runtime 31.0.1", + "sp-weights 27.0.0", ] [[package]] @@ -11033,18 +11192,18 @@ name = "pallet-transaction-storage" version = "27.0.0" dependencies = [ "array-bytes 6.1.0", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-inherents", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-transaction-storage-proof", ] @@ -11054,18 +11213,18 @@ name = "pallet-treasury" version = "27.0.0" dependencies = [ "docify", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "impl-trait-for-tuples", - "pallet-balances", - "pallet-utility", + "pallet-balances 28.0.0", + "pallet-utility 28.0.0", "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -11074,17 +11233,17 @@ name = "pallet-tx-pause" version = "9.0.0" dependencies = [ "docify", - "frame-benchmarking", - "frame-support", - "frame-system", - "pallet-balances", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-balances 28.0.0", "pallet-proxy", - "pallet-utility", + "pallet-utility 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -11092,16 +11251,16 @@ dependencies = [ name = "pallet-uniques" version = "28.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -11109,35 +11268,51 @@ dependencies = [ name = "pallet-utility" version = "28.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "pallet-balances", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-balances 28.0.0", "pallet-collective", "pallet-root-testing", "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] +[[package]] +name = "pallet-utility" +version = "28.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "frame-benchmarking 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-system 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "parity-scale-codec", + "scale-info", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-io 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", +] + [[package]] name = "pallet-vesting" version = "28.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -11145,17 +11320,17 @@ dependencies = [ name = "pallet-whitelist" version = "27.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "pallet-balances", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-balances 28.0.0", "pallet-preimage", "parity-scale-codec", "scale-info", - "sp-api", - "sp-core", - "sp-io", - "sp-runtime", + "sp-api 26.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -11164,24 +11339,24 @@ name = "pallet-xcm" version = "7.0.0" dependencies = [ "bounded-collections", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-assets", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", - "polkadot-parachain-primitives", + "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-parachains", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "xcm-fee-payment-runtime-api", ] @@ -11189,25 +11364,25 @@ dependencies = [ name = "pallet-xcm-benchmarks" version = "7.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-assets", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-xcm", "parity-scale-codec", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-runtime-common", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", ] [[package]] @@ -11219,20 +11394,20 @@ dependencies = [ "bp-runtime", "bp-xcm-bridge-hub", "bridge-runtime-common", - "frame-support", - "frame-system", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-system 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "log", - "pallet-balances", + "pallet-balances 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "pallet-bridge-messages", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std 14.0.0", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-io 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "staging-xcm 7.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "staging-xcm-builder 7.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "staging-xcm-executor 7.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -11240,18 +11415,18 @@ name = "pallet-xcm-bridge-hub-router" version = "0.5.0" dependencies = [ "bp-xcm-bridge-hub-router", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-system 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "log", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std 14.0.0", - "staging-xcm", - "staging-xcm-builder", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-io 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "staging-xcm 7.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "staging-xcm-builder 7.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -11269,7 +11444,7 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-relay-chain-interface", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-benchmarking-cli", "futures", "jsonrpsee", @@ -11278,7 +11453,7 @@ dependencies = [ "parachain-template-runtime", "parity-scale-codec", "polkadot-cli", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "sc-basic-authorship", "sc-chain-spec", "sc-cli", @@ -11297,16 +11472,16 @@ dependencies = [ "sc-transaction-pool-api", "serde", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus-aura", - "sp-core", - "sp-io", - "sp-keystore", - "sp-runtime", - "sp-timestamp", - "staging-xcm", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-timestamp 26.0.0", + "staging-xcm 7.0.0", "substrate-build-script-utils", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", @@ -11324,10 +11499,10 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-utility", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", @@ -11335,38 +11510,38 @@ dependencies = [ "log", "pallet-aura", "pallet-authorship", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-collator-selection", "pallet-message-queue", "pallet-parachain-template", "pallet-session", "pallet-sudo", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", "pallet-xcm", "parachains-common", "parity-scale-codec", - "polkadot-parachain-primitives", + "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common", "scale-info", "smallvec", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", ] @@ -11376,27 +11551,27 @@ version = "7.0.0" dependencies = [ "cumulus-primitives-core", "cumulus-primitives-utility", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "pallet-asset-tx-payment", "pallet-assets", "pallet-authorship", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-collator-selection", "pallet-message-queue", "pallet-xcm", "parity-scale-codec", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "scale-info", "sp-consensus-aura", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", ] @@ -11409,25 +11584,25 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-test-relay-sproof-builder", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "hex-literal", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-collator-selection", "pallet-session", "pallet-timestamp", "pallet-xcm", "parity-scale-codec", - "polkadot-parachain-primitives", + "polkadot-parachain-primitives 6.0.0", "sp-consensus-aura", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", ] @@ -11646,11 +11821,11 @@ version = "0.0.0" dependencies = [ "cumulus-primitives-core", "emulated-integration-tests-common", - "frame-support", + "frame-support 28.0.0", "parachains-common", "penpal-runtime", - "sp-core", - "staging-xcm", + "sp-core 28.0.0", + "staging-xcm 7.0.0", ] [[package]] @@ -11665,10 +11840,10 @@ dependencies = [ "cumulus-pallet-xcmp-queue", "cumulus-primitives-core", "cumulus-primitives-utility", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", @@ -11678,39 +11853,39 @@ dependencies = [ "pallet-assets", "pallet-aura", "pallet-authorship", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-collator-selection", "pallet-message-queue", "pallet-session", "pallet-sudo", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", "pallet-xcm", "parachains-common", "parity-scale-codec", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "polkadot-runtime-common", "scale-info", "smallvec", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", ] @@ -11720,10 +11895,10 @@ version = "0.1.0" dependencies = [ "cumulus-primitives-core", "emulated-integration-tests-common", - "frame-support", + "frame-support 28.0.0", "parachains-common", "people-rococo-runtime", - "sp-core", + "sp-core 28.0.0", "testnet-parachains-constants", ] @@ -11733,8 +11908,8 @@ version = "0.1.0" dependencies = [ "asset-test-utils", "emulated-integration-tests-common", - "frame-support", - "pallet-balances", + "frame-support 28.0.0", + "pallet-balances 28.0.0", "pallet-identity", "pallet-message-queue", "parachains-common", @@ -11744,9 +11919,9 @@ dependencies = [ "rococo-runtime", "rococo-runtime-constants", "rococo-system-emulated-network", - "sp-runtime", - "staging-xcm", - "staging-xcm-executor", + "sp-runtime 31.0.1", + "staging-xcm 7.0.0", + "staging-xcm-executor 7.0.0", ] [[package]] @@ -11763,10 +11938,10 @@ dependencies = [ "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-utility", "enumflags2", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", @@ -11774,42 +11949,42 @@ dependencies = [ "log", "pallet-aura", "pallet-authorship", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-collator-selection", "pallet-identity", "pallet-message-queue", "pallet-multisig", "pallet-session", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", - "pallet-utility", + "pallet-utility 28.0.0", "pallet-xcm", "pallet-xcm-benchmarks", "parachains-common", "parity-scale-codec", - "polkadot-parachain-primitives", + "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common", "rococo-runtime-constants", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", "testnet-parachains-constants", ] @@ -11820,10 +11995,10 @@ version = "0.1.0" dependencies = [ "cumulus-primitives-core", "emulated-integration-tests-common", - "frame-support", + "frame-support 28.0.0", "parachains-common", "people-westend-runtime", - "sp-core", + "sp-core 28.0.0", "testnet-parachains-constants", ] @@ -11833,17 +12008,17 @@ version = "0.1.0" dependencies = [ "asset-test-utils", "emulated-integration-tests-common", - "frame-support", - "pallet-balances", + "frame-support 28.0.0", + "pallet-balances 28.0.0", "pallet-identity", "pallet-message-queue", "parachains-common", "parity-scale-codec", "people-westend-runtime", "polkadot-runtime-common", - "sp-runtime", - "staging-xcm", - "staging-xcm-executor", + "sp-runtime 31.0.1", + "staging-xcm 7.0.0", + "staging-xcm-executor 7.0.0", "westend-runtime", "westend-runtime-constants", "westend-system-emulated-network", @@ -11863,10 +12038,10 @@ dependencies = [ "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-utility", "enumflags2", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", @@ -11874,41 +12049,41 @@ dependencies = [ "log", "pallet-aura", "pallet-authorship", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-collator-selection", "pallet-identity", "pallet-message-queue", "pallet-multisig", "pallet-session", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", - "pallet-utility", + "pallet-utility 28.0.0", "pallet-xcm", "pallet-xcm-benchmarks", "parachains-common", "parity-scale-codec", - "polkadot-parachain-primitives", + "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", "testnet-parachains-constants", "westend-runtime-constants", @@ -12070,7 +12245,7 @@ dependencies = [ "color-eyre", "nix 0.26.2", "polkadot-cli", - "polkadot-core-primitives", + "polkadot-core-primitives 7.0.0", "polkadot-node-core-pvf", "polkadot-node-core-pvf-common", "polkadot-node-core-pvf-execute-worker", @@ -12101,14 +12276,14 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "rand", "rand_chacha 0.3.1", "rand_core 0.6.4", "schnorrkel 0.11.4", - "sp-authority-discovery", - "sp-core", + "sp-authority-discovery 26.0.0", + "sp-core 28.0.0", "tracing-gum", ] @@ -12128,14 +12303,14 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "rand", "rand_chacha 0.3.1", - "sp-application-crypto", - "sp-authority-discovery", - "sp-core", + "sp-application-crypto 30.0.0", + "sp-authority-discovery 26.0.0", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "tracing-gum", ] @@ -12155,15 +12330,15 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "polkadot-subsystem-bench", "rand", "sc-network", "schnellru", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-tracing 16.0.0", "thiserror", "tracing-gum", @@ -12187,14 +12362,14 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "polkadot-subsystem-bench", "rand", "sc-network", "schnellru", - "sp-application-crypto", - "sp-core", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", "sp-keyring", "thiserror", "tokio", @@ -12221,11 +12396,11 @@ dependencies = [ "sc-storage-monitor", "sc-sysinfo", "sc-tracing", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", "sp-maybe-compressed-blob", - "sp-runtime", + "sp-runtime 31.0.1", "substrate-build-script-utils", "thiserror", "try-runtime-cli", @@ -12248,14 +12423,14 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "sc-keystore", "sc-network", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "thiserror", "tokio-util", "tracing-gum", @@ -12267,11 +12442,23 @@ version = "7.0.0" dependencies = [ "parity-scale-codec", "scale-info", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] +[[package]] +name = "polkadot-core-primitives" +version = "7.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", +] + [[package]] name = "polkadot-dispute-distribution" version = "7.0.0" @@ -12292,14 +12479,14 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "sc-keystore", "sc-network", "schnellru", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-tracing 16.0.0", "thiserror", "tracing-gum", @@ -12312,10 +12499,10 @@ dependencies = [ "criterion 0.4.0", "parity-scale-codec", "polkadot-node-primitives", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "reed-solomon-novelpoly", - "sp-core", - "sp-trie", + "sp-core 28.0.0", + "sp-trie 29.0.0", "thiserror", ] @@ -12333,19 +12520,19 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "quickcheck", "rand", "rand_chacha 0.3.1", "sc-network", "sc-network-common", - "sp-application-crypto", - "sp-authority-discovery", + "sp-application-crypto 30.0.0", + "sp-authority-discovery 26.0.0", "sp-consensus-babe", - "sp-core", - "sp-crypto-hashing", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-tracing 16.0.0", "tracing-gum", ] @@ -12369,11 +12556,11 @@ dependencies = [ "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", "polkadot-overseer", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "sc-network", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-keyring", "thiserror", "tracing-gum", @@ -12391,10 +12578,10 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "rstest", - "sp-core", + "sp-core 28.0.0", "sp-keyring", "sp-maybe-compressed-blob", "thiserror", @@ -12425,7 +12612,7 @@ dependencies = [ "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", "polkadot-overseer", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "rand", "rand_chacha 0.3.1", @@ -12433,14 +12620,14 @@ dependencies = [ "sc-keystore", "schnellru", "schnorrkel 0.11.4", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-consensus", "sp-consensus-babe", - "sp-consensus-slots", - "sp-core", + "sp-consensus-slots 0.32.0", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "thiserror", "tracing-gum", ] @@ -12466,10 +12653,10 @@ dependencies = [ "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", "polkadot-overseer", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-keyring", "thiserror", "tracing-gum", @@ -12488,16 +12675,16 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "polkadot-statement-table", "rstest", "sc-keystore", "schnellru", - "sp-application-crypto", - "sp-core", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-tracing 16.0.0", "thiserror", "tracing-gum", @@ -12511,9 +12698,9 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", - "sp-keystore", + "sp-keystore 0.34.0", "thiserror", "tracing-gum", "wasm-timer", @@ -12535,10 +12722,10 @@ dependencies = [ "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", "polkadot-overseer", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", - "sp-core", + "sp-core 28.0.0", "sp-keyring", "sp-maybe-compressed-blob", "tracing-gum", @@ -12556,11 +12743,11 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-types", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "sc-client-api", "sc-consensus-babe", "sp-blockchain", - "sp-core", + "sp-core 28.0.0", "tracing-gum", ] @@ -12579,8 +12766,8 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", - "polkadot-primitives", - "sp-core", + "polkadot-primitives 7.0.0", + "sp-core 28.0.0", "thiserror", "tracing-gum", ] @@ -12600,14 +12787,14 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "sc-keystore", "schnellru", - "sp-application-crypto", - "sp-core", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-tracing 16.0.0", "thiserror", "tracing-gum", @@ -12622,9 +12809,9 @@ dependencies = [ "futures-timer", "polkadot-node-subsystem", "polkadot-overseer", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "sp-blockchain", - "sp-inherents", + "sp-inherents 26.0.0", "thiserror", "tracing-gum", ] @@ -12643,14 +12830,14 @@ dependencies = [ "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "rstest", "sc-keystore", - "sp-application-crypto", - "sp-core", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "thiserror", "tracing-gum", ] @@ -12667,12 +12854,12 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "rstest", "schnellru", - "sp-application-crypto", - "sp-keystore", + "sp-application-crypto 30.0.0", + "sp-keystore 0.34.0", "thiserror", "tracing-gum", ] @@ -12694,7 +12881,7 @@ dependencies = [ "libc", "parity-scale-codec", "pin-project", - "polkadot-core-primitives", + "polkadot-core-primitives 7.0.0", "polkadot-node-core-pvf", "polkadot-node-core-pvf-common", "polkadot-node-core-pvf-execute-worker", @@ -12702,15 +12889,15 @@ dependencies = [ "polkadot-node-metrics", "polkadot-node-primitives", "polkadot-node-subsystem", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "procfs", "rand", "rococo-runtime", "rusty-fork", "sc-sysinfo", "slotmap", - "sp-core", + "sp-core 28.0.0", "sp-maybe-compressed-blob", "sp-wasm-interface 20.0.0", "tempfile", @@ -12732,14 +12919,14 @@ dependencies = [ "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", "polkadot-overseer", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "sc-keystore", - "sp-application-crypto", - "sp-core", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "thiserror", "tracing-gum", ] @@ -12756,16 +12943,16 @@ dependencies = [ "libc", "nix 0.27.1", "parity-scale-codec", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "sc-executor", "sc-executor-common", "sc-executor-wasmtime", "seccompiler", - "sp-core", - "sp-crypto-hashing", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-externalities 0.25.0", - "sp-io", + "sp-io 30.0.0", "sp-tracing 16.0.0", "tempfile", "thiserror", @@ -12782,8 +12969,8 @@ dependencies = [ "nix 0.27.1", "parity-scale-codec", "polkadot-node-core-pvf-common", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "tracing-gum", ] @@ -12798,7 +12985,7 @@ dependencies = [ "nix 0.27.1", "parity-scale-codec", "polkadot-node-core-pvf-common", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "rayon", "rococo-runtime", "sc-executor-common", @@ -12821,12 +13008,12 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-types", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "schnellru", - "sp-api", + "sp-api 26.0.0", "sp-consensus-babe", - "sp-core", + "sp-core 28.0.0", "sp-keyring", "tracing-gum", ] @@ -12841,9 +13028,9 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.1", "polkadot-node-primitives", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "sc-network", - "sp-core", + "sp-core 28.0.0", "thiserror", "tokio", ] @@ -12859,7 +13046,7 @@ dependencies = [ "hyper", "log", "parity-scale-codec", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-test-service", "prioritized-metered-channel", "prometheus-parse", @@ -12888,7 +13075,7 @@ dependencies = [ "parity-scale-codec", "polkadot-node-jaeger", "polkadot-node-primitives", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "rand", "rand_chacha 0.3.1", "sc-authority-discovery", @@ -12907,16 +13094,16 @@ dependencies = [ "futures", "parity-scale-codec", "polkadot-erasure-coding", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "schnorrkel 0.11.4", "serde", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-consensus-babe", - "sp-core", - "sp-keystore", + "sp-core 28.0.0", + "sp-keystore 0.34.0", "sp-maybe-compressed-blob", - "sp-runtime", + "sp-runtime 31.0.1", "thiserror", "zstd 0.12.4", ] @@ -12941,14 +13128,14 @@ dependencies = [ "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-util", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "sc-client-api", "sc-keystore", "sc-utils", - "sp-application-crypto", - "sp-core", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", ] [[package]] @@ -12963,17 +13150,17 @@ dependencies = [ "polkadot-node-jaeger", "polkadot-node-network-protocol", "polkadot-node-primitives", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-statement-table", "sc-client-api", "sc-network", "sc-transaction-pool-api", "smallvec", - "sp-api", - "sp-authority-discovery", + "sp-api 26.0.0", + "sp-authority-discovery 26.0.0", "sp-blockchain", "sp-consensus-babe", - "sp-runtime", + "sp-runtime 31.0.1", "substrate-prometheus-endpoint", "thiserror", ] @@ -13007,15 +13194,15 @@ dependencies = [ "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-types", "polkadot-overseer", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "prioritized-metered-channel", "rand", "sc-client-api", "schnellru", - "sp-application-crypto", - "sp-core", - "sp-keystore", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-keystore 0.34.0", "tempfile", "thiserror", "tracing-gum", @@ -13037,12 +13224,12 @@ dependencies = [ "polkadot-node-primitives", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-types", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "prioritized-metered-channel", "sc-client-api", - "sp-api", - "sp-core", + "sp-api 26.0.0", + "sp-core 28.0.0", "tikv-jemalloc-ctl", "tracing-gum", ] @@ -13074,9 +13261,9 @@ dependencies = [ "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-relay-chain-interface", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-benchmarking-cli", - "frame-support", + "frame-support 28.0.0", "frame-system-rpc-runtime-api", "frame-try-runtime", "futures", @@ -13085,7 +13272,7 @@ dependencies = [ "jsonrpsee", "log", "nix 0.26.2", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc", "pallet-transaction-payment-rpc-runtime-api", "parachains-common", @@ -13094,7 +13281,7 @@ dependencies = [ "people-rococo-runtime", "people-westend-runtime", "polkadot-cli", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-service", "rococo-parachain-runtime", "sc-basic-authorship", @@ -13116,24 +13303,24 @@ dependencies = [ "serde", "serde_json", "shell-runtime", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", - "sp-io", - "sp-keystore", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", - "sp-timestamp", + "sp-timestamp 26.0.0", "sp-tracing 16.0.0", "sp-transaction-pool", - "sp-version", - "staging-xcm", + "sp-version 29.0.0", + "staging-xcm 7.0.0", "substrate-build-script-utils", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", @@ -13151,13 +13338,30 @@ dependencies = [ "bounded-collections", "derive_more", "parity-scale-codec", - "polkadot-core-primitives", + "polkadot-core-primitives 7.0.0", "scale-info", "serde", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "sp-weights", + "sp-weights 27.0.0", +] + +[[package]] +name = "polkadot-parachain-primitives" +version = "6.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "bounded-collections", + "derive_more", + "parity-scale-codec", + "polkadot-core-primitives 7.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "scale-info", + "serde", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-weights 27.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -13168,34 +13372,61 @@ dependencies = [ "hex-literal", "log", "parity-scale-codec", - "polkadot-core-primitives", - "polkadot-parachain-primitives", + "polkadot-core-primitives 7.0.0", + "polkadot-parachain-primitives 6.0.0", "scale-info", "serde", - "sp-api", - "sp-application-crypto", - "sp-arithmetic", - "sp-authority-discovery", - "sp-consensus-slots", - "sp-core", - "sp-inherents", - "sp-io", - "sp-keystore", - "sp-runtime", - "sp-staking", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", + "sp-authority-discovery 26.0.0", + "sp-consensus-slots 0.32.0", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-staking 26.0.0", "sp-std 14.0.0", ] +[[package]] +name = "polkadot-primitives" +version = "7.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "bitvec", + "hex-literal", + "log", + "parity-scale-codec", + "polkadot-core-primitives 7.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "polkadot-parachain-primitives 6.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "scale-info", + "serde", + "sp-api 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-application-crypto 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-arithmetic 23.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-authority-discovery 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-consensus-slots 0.32.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-inherents 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-io 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-keystore 0.34.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-staking 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", +] + [[package]] name = "polkadot-primitives-test-helpers" version = "1.0.0" dependencies = [ - "polkadot-primitives", + "polkadot-primitives 7.0.0", "rand", - "sp-application-crypto", - "sp-core", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -13205,7 +13436,7 @@ dependencies = [ "jsonrpsee", "mmr-rpc", "pallet-transaction-payment-rpc", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "sc-chain-spec", "sc-client-api", "sc-consensus-babe", @@ -13219,13 +13450,13 @@ dependencies = [ "sc-rpc-spec-v2", "sc-sync-state-rpc", "sc-transaction-pool-api", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus", "sp-consensus-babe", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "substrate-frame-rpc-system", "substrate-state-trie-migration-rpc", ] @@ -13235,11 +13466,11 @@ name = "polkadot-runtime-common" version = "7.0.0" dependencies = [ "bitvec", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-election-provider-support", - "frame-support", + "frame-support 28.0.0", "frame-support-test", - "frame-system", + "frame-system 28.0.0", "hex-literal", "impl-trait-for-tuples", "libsecp256k1", @@ -13247,7 +13478,7 @@ dependencies = [ "pallet-asset-rate", "pallet-authorship", "pallet-babe", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-broker", "pallet-election-provider-multi-phase", "pallet-fast-unstake", @@ -13256,11 +13487,11 @@ dependencies = [ "pallet-staking", "pallet-staking-reward-fn", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-treasury", "pallet-vesting", "parity-scale-codec", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "polkadot-runtime-parachains", "rustc-hex", @@ -13269,20 +13500,20 @@ dependencies = [ "serde_derive", "serde_json", "slot-range-helper", - "sp-api", - "sp-core", - "sp-inherents", - "sp-io", + "sp-api 26.0.0", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-npos-elections", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", - "sp-staking", + "sp-staking 26.0.0", "sp-std 14.0.0", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "static_assertions", ] @@ -13291,9 +13522,9 @@ name = "polkadot-runtime-metrics" version = "7.0.0" dependencies = [ "bs58 0.5.0", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "parity-scale-codec", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "sp-std 14.0.0", "sp-tracing 16.0.0", ] @@ -13306,10 +13537,10 @@ dependencies = [ "bitflags 1.3.2", "bitvec", "derive_more", - "frame-benchmarking", - "frame-support", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", "frame-support-test", - "frame-system", + "frame-system 28.0.0", "futures", "hex-literal", "impl-trait-for-tuples", @@ -13317,7 +13548,7 @@ dependencies = [ "pallet-authority-discovery", "pallet-authorship", "pallet-babe", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-broker", "pallet-message-queue", "pallet-session", @@ -13325,9 +13556,9 @@ dependencies = [ "pallet-timestamp", "pallet-vesting", "parity-scale-codec", - "polkadot-core-primitives", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-core-primitives 7.0.0", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "polkadot-runtime-metrics", "rand", @@ -13338,22 +13569,22 @@ dependencies = [ "scale-info", "serde", "serde_json", - "sp-api", - "sp-application-crypto", - "sp-arithmetic", - "sp-core", - "sp-crypto-hashing", - "sp-inherents", - "sp-io", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-session", - "sp-staking", + "sp-staking 26.0.0", "sp-std 14.0.0", "sp-tracing 16.0.0", - "staging-xcm", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-executor 7.0.0", "static_assertions", "thousands", ] @@ -13366,14 +13597,14 @@ dependencies = [ "cumulus-pallet-parachain-system", "docify", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "kitchensink-runtime", "pallet-assets", "pallet-aura", "pallet-authorship", "pallet-babe", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-broker", "pallet-collective", "pallet-default-config-example", @@ -13388,9 +13619,9 @@ dependencies = [ "pallet-referenda", "pallet-scheduler", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-uniques", - "pallet-utility", + "pallet-utility 28.0.0", "parity-scale-codec", "polkadot-sdk-frame", "sc-cli", @@ -13406,18 +13637,18 @@ dependencies = [ "sc-rpc-api", "scale-info", "simple-mermaid", - "sp-api", - "sp-arithmetic", - "sp-core", - "sp-io", + "sp-api 26.0.0", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", "sp-offchain", - "sp-runtime", - "sp-version", + "sp-runtime 31.0.1", + "sp-version 29.0.0", "staging-chain-spec-builder", "staging-node-cli", "staging-parachain-info", - "staging-xcm", + "staging-xcm 7.0.0", "subkey", "substrate-wasm-builder", ] @@ -13428,27 +13659,27 @@ version = "0.1.0" dependencies = [ "docify", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-rpc-runtime-api", "log", "pallet-examples", "parity-scale-codec", "scale-info", - "sp-api", - "sp-arithmetic", + "sp-api 26.0.0", + "sp-arithmetic 23.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-consensus-grandpa", - "sp-core", - "sp-inherents", - "sp-io", + "sp-consensus-grandpa 13.0.0", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", ] [[package]] @@ -13459,10 +13690,10 @@ dependencies = [ "async-trait", "bitvec", "env_logger 0.11.3", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-benchmarking-cli", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-rpc-runtime-api", "futures", "hex-literal", @@ -13473,7 +13704,7 @@ dependencies = [ "mmr-gadget", "pallet-babe", "pallet-staking", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", "parity-db", "parity-scale-codec", @@ -13483,7 +13714,7 @@ dependencies = [ "polkadot-availability-distribution", "polkadot-availability-recovery", "polkadot-collator-protocol", - "polkadot-core-primitives", + "polkadot-core-primitives 7.0.0", "polkadot-dispute-distribution", "polkadot-gossip-support", "polkadot-network-bridge", @@ -13509,8 +13740,8 @@ dependencies = [ "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-overseer", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "polkadot-rpc", "polkadot-runtime-parachains", @@ -13545,30 +13776,30 @@ dependencies = [ "serde", "serde_json", "serial_test", - "sp-api", - "sp-authority-discovery", + "sp-api 26.0.0", + "sp-authority-discovery 26.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus", "sp-consensus-babe", "sp-consensus-beefy", - "sp-consensus-grandpa", - "sp-core", - "sp-inherents", - "sp-io", + "sp-consensus-grandpa 13.0.0", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-mmr-primitives", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", - "sp-state-machine", + "sp-state-machine 0.35.0", "sp-storage 19.0.0", - "sp-timestamp", + "sp-timestamp 26.0.0", "sp-transaction-pool", - "sp-version", - "sp-weights", - "staging-xcm", + "sp-version 29.0.0", + "sp-weights 27.0.0", + "staging-xcm 7.0.0", "substrate-prometheus-endpoint", "tempfile", "thiserror", @@ -13596,17 +13827,17 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "rand_chacha 0.3.1", "sc-keystore", "sc-network", - "sp-application-crypto", - "sp-authority-discovery", - "sp-core", + "sp-application-crypto 30.0.0", + "sp-authority-discovery 26.0.0", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", - "sp-staking", + "sp-keystore 0.34.0", + "sp-staking 26.0.0", "sp-tracing 16.0.0", "thiserror", "tracing-gum", @@ -13617,8 +13848,8 @@ name = "polkadot-statement-table" version = "7.0.0" dependencies = [ "parity-scale-codec", - "polkadot-primitives", - "sp-core", + "polkadot-primitives 7.0.0", + "sp-core 28.0.0", "tracing-gum", ] @@ -13660,7 +13891,7 @@ dependencies = [ "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-overseer", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "prometheus", "pyroscope", @@ -13677,14 +13908,14 @@ dependencies = [ "serde_json", "serde_yaml", "sha1", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-consensus", "sp-consensus-babe", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", - "sp-timestamp", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-timestamp 26.0.0", "substrate-prometheus-endpoint", "tokio", "tracing-gum", @@ -13694,28 +13925,28 @@ dependencies = [ name = "polkadot-test-client" version = "1.0.0" dependencies = [ - "frame-benchmarking", + "frame-benchmarking 28.0.0", "futures", "parity-scale-codec", "polkadot-node-subsystem", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "polkadot-test-runtime", "polkadot-test-service", "sc-block-builder", "sc-consensus", "sc-offchain", "sc-service", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-consensus-babe", - "sp-core", - "sp-inherents", - "sp-io", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", - "sp-state-machine", - "sp-timestamp", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", + "sp-timestamp 26.0.0", "substrate-test-client", ] @@ -13742,10 +13973,10 @@ dependencies = [ "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "rand", - "sp-core", - "sp-keystore", + "sp-core 28.0.0", + "sp-keystore 0.34.0", "substrate-build-script-utils", "tracing-gum", ] @@ -13757,15 +13988,15 @@ dependencies = [ "bitvec", "frame-election-provider-support", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-rpc-runtime-api", "hex-literal", "log", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-grandpa", "pallet-indices", "pallet-offences", @@ -13774,13 +14005,13 @@ dependencies = [ "pallet-staking-reward-curve", "pallet-sudo", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", "pallet-vesting", "pallet-xcm", "parity-scale-codec", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "polkadot-runtime-common", "polkadot-runtime-parachains", "rustc-hex", @@ -13789,28 +14020,28 @@ dependencies = [ "serde_derive", "serde_json", "smallvec", - "sp-api", - "sp-authority-discovery", + "sp-api 26.0.0", + "sp-authority-discovery 26.0.0", "sp-block-builder", "sp-consensus-babe", "sp-consensus-beefy", - "sp-core", - "sp-genesis-builder", - "sp-inherents", - "sp-io", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-keyring", "sp-mmr-primitives", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", - "sp-staking", + "sp-staking 26.0.0", "sp-std 14.0.0", "sp-transaction-pool", - "sp-trie", - "sp-version", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "sp-trie 29.0.0", + "sp-version 29.0.0", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", "test-runtime-constants", "tiny-keccak", @@ -13820,17 +14051,17 @@ dependencies = [ name = "polkadot-test-service" version = "1.0.0" dependencies = [ - "frame-system", + "frame-system 28.0.0", "futures", "hex", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-staking", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-overseer", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "polkadot-rpc", "polkadot-runtime-common", "polkadot-runtime-parachains", @@ -13849,17 +14080,17 @@ dependencies = [ "sc-tracing", "sc-transaction-pool", "serde_json", - "sp-arithmetic", - "sp-authority-discovery", + "sp-arithmetic 23.0.0", + "sp-authority-discovery 26.0.0", "sp-blockchain", "sp-consensus", "sp-consensus-babe", - "sp-consensus-grandpa", - "sp-core", - "sp-inherents", + "sp-consensus-grandpa 13.0.0", + "sp-core 28.0.0", + "sp-inherents 26.0.0", "sp-keyring", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "substrate-test-client", "substrate-test-utils", "tempfile", @@ -13874,7 +14105,7 @@ version = "7.0.0" dependencies = [ "clap 4.5.3", "generate-bags", - "sp-io", + "sp-io 30.0.0", "westend-runtime", ] @@ -14828,10 +15059,10 @@ name = "remote-ext-tests-bags-list" version = "1.0.0" dependencies = [ "clap 4.5.3", - "frame-system", + "frame-system 28.0.0", "log", "pallet-bags-list-remote-tests", - "sp-core", + "sp-core 28.0.0", "sp-tracing 16.0.0", "tokio", "westend-runtime", @@ -14983,14 +15214,14 @@ version = "0.0.0" dependencies = [ "emulated-integration-tests-common", "parachains-common", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "rococo-runtime", "rococo-runtime-constants", "sc-consensus-grandpa", - "sp-authority-discovery", + "sp-authority-discovery 26.0.0", "sp-consensus-babe", "sp-consensus-beefy", - "sp-core", + "sp-core 28.0.0", ] [[package]] @@ -15006,41 +15237,41 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-utility", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-rpc-runtime-api", "pallet-assets", "pallet-aura", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-message-queue", "pallet-sudo", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", "pallet-xcm", "parachains-common", "parity-scale-codec", - "polkadot-parachain-primitives", + "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", "testnet-parachains-constants", ] @@ -15051,11 +15282,11 @@ version = "7.0.0" dependencies = [ "binary-merkle-tree", "bitvec", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", "frame-remote-externalities", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", @@ -15065,7 +15296,7 @@ dependencies = [ "pallet-authority-discovery", "pallet-authorship", "pallet-babe", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", @@ -15097,17 +15328,17 @@ dependencies = [ "pallet-sudo", "pallet-timestamp", "pallet-tips", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", "pallet-treasury", - "pallet-utility", + "pallet-utility 28.0.0", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "parity-scale-codec", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "polkadot-runtime-common", "polkadot-runtime-parachains", "rococo-runtime-constants", @@ -15117,32 +15348,32 @@ dependencies = [ "serde_derive", "serde_json", "smallvec", - "sp-api", - "sp-arithmetic", - "sp-authority-discovery", + "sp-api 26.0.0", + "sp-arithmetic 23.0.0", + "sp-authority-discovery 26.0.0", "sp-block-builder", "sp-consensus-babe", "sp-consensus-beefy", - "sp-consensus-grandpa", - "sp-core", - "sp-genesis-builder", - "sp-inherents", - "sp-io", + "sp-consensus-grandpa 13.0.0", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-keyring", "sp-mmr-primitives", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", - "sp-staking", + "sp-staking 26.0.0", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-tracing 16.0.0", "sp-transaction-pool", - "sp-trie", - "sp-version", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "sp-trie 29.0.0", + "sp-version 29.0.0", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "static_assertions", "substrate-wasm-builder", "tiny-keccak", @@ -15154,15 +15385,15 @@ dependencies = [ name = "rococo-runtime-constants" version = "7.0.0" dependencies = [ - "frame-support", - "polkadot-primitives", + "frame-support 28.0.0", + "polkadot-primitives 7.0.0", "polkadot-runtime-common", "smallvec", - "sp-core", - "sp-runtime", - "sp-weights", - "staging-xcm", - "staging-xcm-builder", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "sp-weights 27.0.0", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", ] [[package]] @@ -15574,7 +15805,7 @@ name = "sc-allocator" version = "23.0.0" dependencies = [ "log", - "sp-core", + "sp-core 28.0.0", "sp-wasm-interface 20.0.0", "thiserror", ] @@ -15599,12 +15830,12 @@ dependencies = [ "rand", "sc-client-api", "sc-network", - "sp-api", - "sp-authority-discovery", + "sp-api 26.0.0", + "sp-authority-discovery 26.0.0", "sp-blockchain", - "sp-core", - "sp-keystore", - "sp-runtime", + "sp-core 28.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", @@ -15626,12 +15857,12 @@ dependencies = [ "sc-telemetry", "sc-transaction-pool", "sc-transaction-pool-api", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-inherents", - "sp-runtime", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-runtime 31.0.1", "substrate-prometheus-endpoint", "substrate-test-runtime-client", ] @@ -15641,14 +15872,14 @@ name = "sc-block-builder" version = "0.33.0" dependencies = [ "parity-scale-codec", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-blockchain", - "sp-core", - "sp-inherents", - "sp-runtime", - "sp-state-machine", - "sp-trie", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", + "sp-trie 29.0.0", "substrate-test-runtime-client", ] @@ -15668,16 +15899,16 @@ dependencies = [ "sc-telemetry", "serde", "serde_json", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-blockchain", "sp-consensus-babe", - "sp-core", - "sp-crypto-hashing", - "sp-genesis-builder", - "sp-io", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-genesis-builder 0.8.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-tracing 16.0.0", "substrate-test-runtime", ] @@ -15723,13 +15954,13 @@ dependencies = [ "serde", "serde_json", "sp-blockchain", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", - "sp-panic-handler", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-panic-handler 13.0.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", - "sp-version", + "sp-version 29.0.0", "tempfile", "thiserror", "tokio", @@ -15747,18 +15978,18 @@ dependencies = [ "sc-executor", "sc-transaction-pool-api", "sc-utils", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-database", "sp-externalities 0.25.0", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-statement-store", "sp-storage 19.0.0", "sp-test-primitives", - "sp-trie", + "sp-trie 29.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime", "thiserror", @@ -15785,14 +16016,14 @@ dependencies = [ "sc-client-api", "sc-state-db", "schnellru", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-blockchain", - "sp-core", + "sp-core 28.0.0", "sp-database", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-tracing 16.0.0", - "sp-trie", + "sp-trie 29.0.0", "substrate-test-runtime-client", "tempfile", ] @@ -15811,12 +16042,12 @@ dependencies = [ "sc-client-api", "sc-utils", "serde", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-runtime", - "sp-state-machine", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-test-primitives", "substrate-prometheus-endpoint", "thiserror", @@ -15839,19 +16070,19 @@ dependencies = [ "sc-network", "sc-network-test", "sc-telemetry", - "sp-api", - "sp-application-crypto", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus", "sp-consensus-aura", - "sp-consensus-slots", - "sp-core", - "sp-inherents", + "sp-consensus-slots 0.32.0", + "sp-core 28.0.0", + "sp-inherents 26.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", - "sp-timestamp", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-timestamp 26.0.0", "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", @@ -15881,20 +16112,20 @@ dependencies = [ "sc-network-test", "sc-telemetry", "sc-transaction-pool-api", - "sp-api", - "sp-application-crypto", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus", "sp-consensus-babe", - "sp-consensus-slots", - "sp-core", - "sp-crypto-hashing", - "sp-inherents", + "sp-consensus-slots 0.32.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-inherents 26.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", - "sp-timestamp", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-timestamp 26.0.0", "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", @@ -15916,15 +16147,15 @@ dependencies = [ "sc-transaction-pool-api", "serde", "serde_json", - "sp-api", - "sp-application-crypto", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", "sp-blockchain", "sp-consensus", "sp-consensus-babe", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "substrate-test-runtime-client", "thiserror", "tokio", @@ -15951,19 +16182,19 @@ dependencies = [ "sc-network-test", "sc-utils", "serde", - "sp-api", - "sp-application-crypto", - "sp-arithmetic", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", "sp-blockchain", "sp-consensus", "sp-consensus-beefy", - "sp-consensus-grandpa", - "sp-core", - "sp-crypto-hashing", + "sp-consensus-grandpa 13.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-mmr-primitives", - "sp-runtime", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", @@ -15987,8 +16218,8 @@ dependencies = [ "serde", "serde_json", "sp-consensus-beefy", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "substrate-test-runtime-client", "thiserror", "tokio", @@ -16003,7 +16234,7 @@ dependencies = [ "sc-client-api", "sc-consensus", "sp-blockchain", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -16037,17 +16268,17 @@ dependencies = [ "sc-utils", "serde", "serde_json", - "sp-api", - "sp-application-crypto", - "sp-arithmetic", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", "sp-blockchain", "sp-consensus", - "sp-consensus-grandpa", - "sp-core", - "sp-crypto-hashing", + "sp-consensus-grandpa 13.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-keyring", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", @@ -16070,10 +16301,10 @@ dependencies = [ "sc-rpc", "serde", "sp-blockchain", - "sp-consensus-grandpa", - "sp-core", + "sp-consensus-grandpa 13.0.0", + "sp-core 28.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "substrate-test-runtime-client", "thiserror", "tokio", @@ -16099,17 +16330,17 @@ dependencies = [ "sc-transaction-pool", "sc-transaction-pool-api", "serde", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-consensus-aura", "sp-consensus-babe", - "sp-consensus-slots", - "sp-core", - "sp-inherents", - "sp-keystore", - "sp-runtime", - "sp-timestamp", + "sp-consensus-slots 0.32.0", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-timestamp 26.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", "substrate-test-runtime-transaction-pool", @@ -16129,14 +16360,14 @@ dependencies = [ "parking_lot 0.12.1", "sc-client-api", "sc-consensus", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus", "sp-consensus-pow", - "sp-core", - "sp-inherents", - "sp-runtime", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-runtime 31.0.1", "substrate-prometheus-endpoint", "thiserror", ] @@ -16153,14 +16384,14 @@ dependencies = [ "sc-client-api", "sc-consensus", "sc-telemetry", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-blockchain", "sp-consensus", - "sp-consensus-slots", - "sp-core", - "sp-inherents", - "sp-runtime", - "sp-state-machine", + "sp-consensus-slots 0.32.0", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "substrate-test-runtime-client", ] @@ -16183,19 +16414,19 @@ dependencies = [ "sc-runtime-test", "sc-tracing", "schnellru", - "sp-api", - "sp-core", - "sp-crypto-hashing", + "sp-api 26.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-externalities 0.25.0", - "sp-io", + "sp-io 30.0.0", "sp-maybe-compressed-blob", - "sp-panic-handler", - "sp-runtime", + "sp-panic-handler 13.0.0", + "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", - "sp-state-machine", + "sp-state-machine 0.35.0", "sp-tracing 16.0.0", - "sp-trie", - "sp-version", + "sp-trie 29.0.0", + "sp-version 29.0.0", "sp-wasm-interface 20.0.0", "substrate-test-runtime", "tempfile", @@ -16242,7 +16473,7 @@ dependencies = [ "sc-allocator", "sc-executor-common", "sc-runtime-test", - "sp-io", + "sp-io 30.0.0", "sp-runtime-interface 24.0.0", "sp-wasm-interface 20.0.0", "tempfile", @@ -16263,7 +16494,7 @@ dependencies = [ "sc-network-common", "sc-network-sync", "sp-blockchain", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -16273,9 +16504,9 @@ dependencies = [ "array-bytes 6.1.0", "parking_lot 0.12.1", "serde_json", - "sp-application-crypto", - "sp-core", - "sp-keystore", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-keystore 0.34.0", "tempfile", "thiserror", ] @@ -16299,12 +16530,12 @@ dependencies = [ "sc-client-api", "sc-network", "sc-transaction-pool-api", - "sp-api", + "sp-api 26.0.0", "sp-consensus", - "sp-core", - "sp-keystore", + "sp-core 28.0.0", + "sp-keystore 0.34.0", "sp-mixnet", - "sp-runtime", + "sp-runtime 31.0.1", "thiserror", ] @@ -16341,10 +16572,10 @@ dependencies = [ "serde", "serde_json", "smallvec", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-blockchain", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-test-primitives", "sp-tracing 16.0.0", "substrate-prometheus-endpoint", @@ -16378,8 +16609,8 @@ dependencies = [ "sc-network", "sp-blockchain", "sp-consensus", - "sp-crypto-hashing", - "sp-runtime", + "sp-crypto-hashing 0.1.0", + "sp-runtime 31.0.1", "substrate-test-runtime", "substrate-test-runtime-client", "thiserror", @@ -16399,8 +16630,8 @@ dependencies = [ "prost-build", "sc-consensus", "sp-consensus", - "sp-consensus-grandpa", - "sp-runtime", + "sp-consensus-grandpa 13.0.0", + "sp-runtime 31.0.1", "tempfile", ] @@ -16420,7 +16651,7 @@ dependencies = [ "sc-network-common", "sc-network-sync", "schnellru", - "sp-runtime", + "sp-runtime 31.0.1", "substrate-prometheus-endpoint", "substrate-test-runtime-client", "tokio", @@ -16442,8 +16673,8 @@ dependencies = [ "sc-client-api", "sc-network", "sp-blockchain", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "thiserror", ] @@ -16490,12 +16721,12 @@ dependencies = [ "sc-utils", "schnellru", "smallvec", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-blockchain", "sp-consensus", - "sp-consensus-grandpa", - "sp-core", - "sp-runtime", + "sp-consensus-grandpa 13.0.0", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-test-primitives", "sp-tracing 16.0.0", "substrate-prometheus-endpoint", @@ -16527,8 +16758,8 @@ dependencies = [ "sc-utils", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "substrate-test-runtime", "substrate-test-runtime-client", @@ -16549,7 +16780,7 @@ dependencies = [ "sc-network-sync", "sc-utils", "sp-consensus", - "sp-runtime", + "sp-runtime 31.0.1", "substrate-prometheus-endpoint", ] @@ -16580,13 +16811,13 @@ dependencies = [ "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", - "sp-api", + "sp-api 26.0.0", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-keystore", + "sp-keystore 0.34.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "substrate-test-runtime-client", "threadpool", @@ -16626,19 +16857,19 @@ dependencies = [ "sc-transaction-pool-api", "sc-utils", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-keystore", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", "sp-offchain", "sp-rpc", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-statement-store", - "sp-version", + "sp-version 29.0.0", "substrate-test-runtime-client", "tokio", "tracing-subscriber 0.3.18", @@ -16656,10 +16887,10 @@ dependencies = [ "scale-info", "serde", "serde_json", - "sp-core", + "sp-core 28.0.0", "sp-rpc", - "sp-runtime", - "sp-version", + "sp-runtime 31.0.1", + "sp-version 29.0.0", "thiserror", ] @@ -16705,15 +16936,15 @@ dependencies = [ "sc-utils", "serde", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", "sp-maybe-compressed-blob", "sp-rpc", - "sp-runtime", - "sp-version", + "sp-runtime 31.0.1", + "sp-version 29.0.0", "substrate-test-runtime", "substrate-test-runtime-client", "substrate-test-runtime-transaction-pool", @@ -16726,9 +16957,9 @@ dependencies = [ name = "sc-runtime-test" version = "2.0.0" dependencies = [ - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", "sp-std 14.0.0", "substrate-wasm-builder", @@ -16774,20 +17005,20 @@ dependencies = [ "schnellru", "serde", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-session", - "sp-state-machine", + "sp-state-machine 0.35.0", "sp-storage 19.0.0", "sp-transaction-pool", "sp-transaction-storage-proof", - "sp-trie", - "sp-version", + "sp-trie 29.0.0", + "sp-version 29.0.0", "static_init", "substrate-prometheus-endpoint", "substrate-test-runtime", @@ -16819,16 +17050,16 @@ dependencies = [ "sc-network-sync", "sc-service", "sc-transaction-pool-api", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-io", - "sp-runtime", - "sp-state-machine", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-storage 19.0.0", "sp-tracing 16.0.0", - "sp-trie", + "sp-trie 29.0.0", "substrate-test-runtime", "substrate-test-runtime-client", "tempfile", @@ -16842,7 +17073,7 @@ dependencies = [ "log", "parity-scale-codec", "parking_lot 0.12.1", - "sp-core", + "sp-core 28.0.0", ] [[package]] @@ -16855,10 +17086,10 @@ dependencies = [ "parking_lot 0.12.1", "sc-client-api", "sc-keystore", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-statement-store", "substrate-prometheus-endpoint", "tempfile", @@ -16872,7 +17103,7 @@ dependencies = [ "clap 4.5.3", "fs4", "log", - "sp-core", + "sp-core 28.0.0", "thiserror", "tokio", ] @@ -16891,7 +17122,7 @@ dependencies = [ "serde", "serde_json", "sp-blockchain", - "sp-runtime", + "sp-runtime 31.0.1", "thiserror", ] @@ -16909,10 +17140,10 @@ dependencies = [ "sc-telemetry", "serde", "serde_json", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -16952,11 +17183,11 @@ dependencies = [ "sc-client-api", "sc-tracing-proc-macro", "serde", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", - "sp-core", + "sp-core 28.0.0", "sp-rpc", - "sp-runtime", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "thiserror", "tracing", @@ -16993,12 +17224,12 @@ dependencies = [ "sc-transaction-pool-api", "sc-utils", "serde", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-crypto-hashing", - "sp-runtime", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "sp-transaction-pool", "substrate-prometheus-endpoint", @@ -17019,8 +17250,8 @@ dependencies = [ "serde", "serde_json", "sp-blockchain", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "thiserror", ] @@ -17035,7 +17266,7 @@ dependencies = [ "log", "parking_lot 0.12.1", "prometheus", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "tokio-test", ] @@ -17256,27 +17487,27 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-timestamp", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "pallet-aura", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-sudo", "pallet-timestamp", "parachains-common", "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", "substrate-wasm-builder", ] @@ -17563,8 +17794,8 @@ dependencies = [ "cumulus-pallet-xcm", "cumulus-primitives-core", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-try-runtime", "pallet-aura", "pallet-message-queue", @@ -17572,22 +17803,22 @@ dependencies = [ "parachains-common", "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", ] @@ -17673,7 +17904,7 @@ dependencies = [ "enumn", "parity-scale-codec", "paste", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -17846,8 +18077,8 @@ name = "snowbridge-beacon-primitives" version = "0.2.0" dependencies = [ "byte-slice-cast", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "hex", "hex-literal", "parity-scale-codec", @@ -17856,9 +18087,9 @@ dependencies = [ "serde", "snowbridge-ethereum", "snowbridge-milagro-bls", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "ssz_rs", "ssz_rs_derive", @@ -17870,22 +18101,22 @@ name = "snowbridge-core" version = "0.2.0" dependencies = [ "ethabi-decode", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "hex", "hex-literal", "parity-scale-codec", - "polkadot-parachain-primitives", + "polkadot-parachain-primitives 6.0.0", "scale-info", "serde", "snowbridge-beacon-primitives", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "staging-xcm", - "staging-xcm-builder", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", ] [[package]] @@ -17905,9 +18136,9 @@ dependencies = [ "serde", "serde-big-array", "serde_json", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "wasm-bindgen-test", ] @@ -17937,23 +18168,23 @@ dependencies = [ "hex-literal", "parity-scale-codec", "scale-info", - "sp-core", - "sp-crypto-hashing", - "sp-runtime", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-runtime 31.0.1", ] [[package]] name = "snowbridge-outbound-queue-runtime-api" version = "0.2.0" dependencies = [ - "frame-support", + "frame-support 28.0.0", "parity-scale-codec", "snowbridge-core", "snowbridge-outbound-queue-merkle-tree", - "sp-api", - "sp-core", + "sp-api 26.0.0", + "sp-core 28.0.0", "sp-std 14.0.0", - "staging-xcm", + "staging-xcm 7.0.0", ] [[package]] @@ -17962,9 +18193,9 @@ version = "0.2.0" dependencies = [ "bp-runtime", "byte-slice-cast", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "hex-literal", "log", "pallet-timestamp", @@ -17978,10 +18209,10 @@ dependencies = [ "snowbridge-core", "snowbridge-ethereum", "snowbridge-pallet-ethereum-client-fixtures", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", "ssz_rs", "ssz_rs_derive", @@ -17992,13 +18223,13 @@ dependencies = [ name = "snowbridge-pallet-ethereum-client-fixtures" version = "0.9.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "hex-literal", "snowbridge-beacon-primitives", "snowbridge-core", - "sp-core", + "sp-core 28.0.0", "sp-std 14.0.0", ] @@ -18009,13 +18240,13 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "alloy-sol-types", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "hex-literal", "log", "num-traits", - "pallet-balances", + "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", "serde", @@ -18025,27 +18256,27 @@ dependencies = [ "snowbridge-pallet-ethereum-client", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-router-primitives", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", ] [[package]] name = "snowbridge-pallet-inbound-queue-fixtures" version = "0.10.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "hex-literal", "snowbridge-beacon-primitives", "snowbridge-core", - "sp-core", + "sp-core 28.0.0", "sp-std 14.0.0", ] @@ -18055,9 +18286,9 @@ version = "0.2.0" dependencies = [ "bridge-hub-common", "ethabi-decode", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "hex-literal", "pallet-message-queue", "parity-scale-codec", @@ -18065,13 +18296,13 @@ dependencies = [ "serde", "snowbridge-core", "snowbridge-outbound-queue-merkle-tree", - "sp-arithmetic", - "sp-core", - "sp-io", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "staging-xcm", + "staging-xcm 7.0.0", ] [[package]] @@ -18079,27 +18310,27 @@ name = "snowbridge-pallet-system" version = "0.2.0" dependencies = [ "ethabi-decode", - "frame-benchmarking", - "frame-support", - "frame-system", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", "hex", "hex-literal", "log", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-message-queue", "parity-scale-codec", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "scale-info", "snowbridge-core", "snowbridge-pallet-outbound-queue", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", ] [[package]] @@ -18107,8 +18338,8 @@ name = "snowbridge-router-primitives" version = "0.9.0" dependencies = [ "ethabi-decode", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "hex-literal", "log", "parity-scale-codec", @@ -18116,29 +18347,29 @@ dependencies = [ "scale-info", "serde", "snowbridge-core", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", ] [[package]] name = "snowbridge-runtime-common" version = "0.2.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", "parity-scale-codec", "snowbridge-core", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-std 14.0.0", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", ] [[package]] @@ -18155,10 +18386,10 @@ dependencies = [ "cumulus-pallet-xcmp-queue", "cumulus-primitives-core", "cumulus-primitives-utility", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", @@ -18166,22 +18397,22 @@ dependencies = [ "log", "pallet-aura", "pallet-authorship", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-collator-selection", "pallet-message-queue", "pallet-multisig", "pallet-session", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", - "pallet-utility", + "pallet-utility 28.0.0", "pallet-xcm", "pallet-xcm-benchmarks", "parachains-common", "parachains-runtimes-test-utils", "parity-scale-codec", - "polkadot-core-primitives", - "polkadot-parachain-primitives", + "polkadot-core-primitives 7.0.0", + "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common", "scale-info", "serde", @@ -18196,25 +18427,25 @@ dependencies = [ "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-system-runtime-api", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", - "sp-io", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-keyring", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "static_assertions", ] @@ -18224,10 +18455,10 @@ version = "0.2.0" dependencies = [ "parity-scale-codec", "snowbridge-core", - "sp-api", - "sp-core", + "sp-api 26.0.0", + "sp-core 28.0.0", "sp-std 14.0.0", - "staging-xcm", + "staging-xcm 7.0.0", ] [[package]] @@ -18273,10 +18504,10 @@ version = "0.0.0" dependencies = [ "clap 4.5.3", "frame-benchmarking-cli", - "frame-system", + "frame-system 28.0.0", "futures", "jsonrpsee", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc", "sc-basic-authorship", "sc-cli", @@ -18294,17 +18525,17 @@ dependencies = [ "sc-transaction-pool-api", "serde_json", "solochain-template-runtime", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus-aura", - "sp-consensus-grandpa", - "sp-core", - "sp-inherents", - "sp-io", + "sp-consensus-grandpa 13.0.0", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", - "sp-timestamp", + "sp-runtime 31.0.1", + "sp-timestamp 26.0.0", "substrate-build-script-utils", "substrate-frame-rpc-system", "try-runtime-cli", @@ -18314,37 +18545,37 @@ dependencies = [ name = "solochain-template-runtime" version = "0.0.0" dependencies = [ - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-aura", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-grandpa", "pallet-sudo", "pallet-template", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-consensus-grandpa", - "sp-core", - "sp-genesis-builder", - "sp-inherents", + "sp-consensus-grandpa 13.0.0", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "substrate-wasm-builder", ] @@ -18356,17 +18587,39 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-api-proc-macro", - "sp-core", + "sp-api-proc-macro 15.0.0", + "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-metadata-ir", - "sp-runtime", + "sp-metadata-ir 0.6.0", + "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", - "sp-state-machine", + "sp-state-machine 0.35.0", "sp-std 14.0.0", "sp-test-primitives", - "sp-trie", - "sp-version", + "sp-trie 29.0.0", + "sp-version 29.0.0", + "thiserror", +] + +[[package]] +name = "sp-api" +version = "26.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "scale-info", + "sp-api-proc-macro 15.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-metadata-ir 0.6.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-state-machine 0.35.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-trie 29.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-version 29.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "thiserror", ] @@ -18384,6 +18637,20 @@ dependencies = [ "syn 2.0.53", ] +[[package]] +name = "sp-api-proc-macro" +version = "15.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "Inflector", + "blake2 0.10.6", + "expander 2.0.0", + "proc-macro-crate 3.0.0", + "proc-macro2", + "quote", + "syn 2.0.53", +] + [[package]] name = "sp-api-test" version = "2.0.1" @@ -18395,13 +18662,13 @@ dependencies = [ "rustversion", "sc-block-builder", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-consensus", - "sp-core", - "sp-runtime", - "sp-state-machine", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-tracing 16.0.0", - "sp-version", + "sp-version 29.0.0", "static_assertions", "substrate-test-runtime-client", "trybuild", @@ -18414,19 +18681,32 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-std 14.0.0", ] +[[package]] +name = "sp-application-crypto" +version = "30.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-io 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", +] + [[package]] name = "sp-application-crypto-test" version = "2.0.0" dependencies = [ - "sp-api", - "sp-application-crypto", - "sp-core", - "sp-keystore", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-keystore 0.34.0", "substrate-test-runtime-client", ] @@ -18443,11 +18723,26 @@ dependencies = [ "rand", "scale-info", "serde", - "sp-crypto-hashing", + "sp-crypto-hashing 0.1.0", "sp-std 14.0.0", "static_assertions", ] +[[package]] +name = "sp-arithmetic" +version = "23.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "docify", + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "static_assertions", +] + [[package]] name = "sp-arithmetic-fuzzer" version = "2.0.0" @@ -18456,7 +18751,7 @@ dependencies = [ "fraction", "honggfuzz", "num-bigint", - "sp-arithmetic", + "sp-arithmetic 23.0.0", ] [[package]] @@ -18483,18 +18778,30 @@ version = "26.0.0" dependencies = [ "parity-scale-codec", "scale-info", - "sp-api", - "sp-application-crypto", - "sp-runtime", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", + "sp-runtime 31.0.1", +] + +[[package]] +name = "sp-authority-discovery" +version = "26.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-api 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-application-crypto 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] name = "sp-block-builder" version = "26.0.0" dependencies = [ - "sp-api", - "sp-inherents", - "sp-runtime", + "sp-api 26.0.0", + "sp-inherents 26.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -18506,11 +18813,11 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.1", "schnellru", - "sp-api", + "sp-api 26.0.0", "sp-consensus", "sp-database", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "thiserror", ] @@ -18521,10 +18828,10 @@ dependencies = [ "async-trait", "futures", "log", - "sp-core", - "sp-inherents", - "sp-runtime", - "sp-state-machine", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-test-primitives", "thiserror", ] @@ -18536,12 +18843,12 @@ dependencies = [ "async-trait", "parity-scale-codec", "scale-info", - "sp-api", - "sp-application-crypto", - "sp-consensus-slots", - "sp-inherents", - "sp-runtime", - "sp-timestamp", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", + "sp-consensus-slots 0.32.0", + "sp-inherents 26.0.0", + "sp-runtime 31.0.1", + "sp-timestamp 26.0.0", ] [[package]] @@ -18552,13 +18859,13 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-api", - "sp-application-crypto", - "sp-consensus-slots", - "sp-core", - "sp-inherents", - "sp-runtime", - "sp-timestamp", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", + "sp-consensus-slots 0.32.0", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-runtime 31.0.1", + "sp-timestamp 26.0.0", ] [[package]] @@ -18570,14 +18877,14 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-api", - "sp-application-crypto", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-keystore", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", "sp-mmr-primitives", - "sp-runtime", + "sp-runtime 31.0.1", "strum 0.26.2", "w3f-bls", ] @@ -18591,11 +18898,28 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-api", - "sp-application-crypto", - "sp-core", - "sp-keystore", - "sp-runtime", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", +] + +[[package]] +name = "sp-consensus-grandpa" +version = "13.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "finality-grandpa", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-application-crypto 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-keystore 0.34.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -18603,9 +18927,9 @@ name = "sp-consensus-pow" version = "0.32.0" dependencies = [ "parity-scale-codec", - "sp-api", - "sp-core", - "sp-runtime", + "sp-api 26.0.0", + "sp-core 28.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -18615,21 +18939,32 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-api", - "sp-application-crypto", - "sp-consensus-slots", - "sp-core", - "sp-runtime", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", + "sp-consensus-slots 0.32.0", + "sp-core 28.0.0", + "sp-runtime 31.0.1", +] + +[[package]] +name = "sp-consensus-slots" +version = "0.32.0" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-timestamp 26.0.0", ] [[package]] name = "sp-consensus-slots" version = "0.32.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-timestamp", + "sp-timestamp 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -18668,14 +19003,14 @@ dependencies = [ "secrecy", "serde", "serde_json", - "sp-crypto-hashing", + "sp-crypto-hashing 0.1.0", "sp-debug-derive 14.0.0", "sp-externalities 0.25.0", "sp-runtime-interface 24.0.0", "sp-std 14.0.0", "sp-storage 19.0.0", "ss58-registry", - "substrate-bip39", + "substrate-bip39 0.4.7", "thiserror", "tracing", "w3f-bls", @@ -18683,27 +19018,74 @@ dependencies = [ ] [[package]] -name = "sp-core-fuzz" -version = "0.0.0" -dependencies = [ - "lazy_static", - "libfuzzer-sys", - "regex", - "sp-core", -] - -[[package]] -name = "sp-core-hashing" -version = "15.0.0" +name = "sp-core" +version = "28.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" dependencies = [ - "sp-crypto-hashing", -] - + "array-bytes 6.1.0", + "bandersnatch_vrfs", + "bitflags 1.3.2", + "blake2 0.10.6", + "bounded-collections", + "bs58 0.5.0", + "dyn-clonable", + "ed25519-zebra 3.1.0", + "futures", + "hash-db", + "hash256-std-hasher", + "impl-serde", + "itertools 0.10.5", + "k256", + "libsecp256k1", + "log", + "merlin", + "parity-bip39", + "parity-scale-codec", + "parking_lot 0.12.1", + "paste", + "primitive-types", + "rand", + "scale-info", + "schnorrkel 0.11.4", + "secp256k1", + "secrecy", + "serde", + "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "ss58-registry", + "substrate-bip39 0.4.7 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "thiserror", + "tracing", + "w3f-bls", + "zeroize", +] + +[[package]] +name = "sp-core-fuzz" +version = "0.0.0" +dependencies = [ + "lazy_static", + "libfuzzer-sys", + "regex", + "sp-core 28.0.0", +] + +[[package]] +name = "sp-core-hashing" +version = "15.0.0" +dependencies = [ + "sp-crypto-hashing 0.1.0", +] + [[package]] name = "sp-core-hashing-proc-macro" version = "15.0.0" dependencies = [ - "sp-crypto-hashing-proc-macro", + "sp-crypto-hashing-proc-macro 0.1.0", ] [[package]] @@ -18756,16 +19138,39 @@ dependencies = [ "digest 0.10.7", "sha2 0.10.7", "sha3", - "sp-crypto-hashing-proc-macro", + "sp-crypto-hashing-proc-macro 0.1.0", "twox-hash", ] +[[package]] +name = "sp-crypto-hashing" +version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "blake2b_simd", + "byteorder", + "digest 0.10.7", + "sha2 0.10.7", + "sha3", + "twox-hash", +] + +[[package]] +name = "sp-crypto-hashing-proc-macro" +version = "0.1.0" +dependencies = [ + "quote", + "sp-crypto-hashing 0.1.0", + "syn 2.0.53", +] + [[package]] name = "sp-crypto-hashing-proc-macro" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" dependencies = [ "quote", - "sp-crypto-hashing", + "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "syn 2.0.53", ] @@ -18796,6 +19201,16 @@ dependencies = [ "syn 2.0.53", ] +[[package]] +name = "sp-debug-derive" +version = "14.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + [[package]] name = "sp-externalities" version = "0.19.0" @@ -18816,15 +19231,37 @@ dependencies = [ "sp-storage 19.0.0", ] +[[package]] +name = "sp-externalities" +version = "0.25.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", +] + +[[package]] +name = "sp-genesis-builder" +version = "0.8.0" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde_json", + "sp-api 26.0.0", + "sp-runtime 31.0.1", +] + [[package]] name = "sp-genesis-builder" version = "0.8.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" dependencies = [ "parity-scale-codec", "scale-info", "serde_json", - "sp-api", - "sp-runtime", + "sp-api 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -18836,7 +19273,20 @@ dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", "scale-info", - "sp-runtime", + "sp-runtime 31.0.1", + "thiserror", +] + +[[package]] +name = "sp-inherents" +version = "26.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "async-trait", + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "thiserror", ] @@ -18852,15 +19302,41 @@ dependencies = [ "polkavm-derive", "rustversion", "secp256k1", - "sp-core", - "sp-crypto-hashing", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-externalities 0.25.0", - "sp-keystore", + "sp-keystore 0.34.0", "sp-runtime-interface 24.0.0", - "sp-state-machine", + "sp-state-machine 0.35.0", "sp-std 14.0.0", "sp-tracing 16.0.0", - "sp-trie", + "sp-trie 29.0.0", + "tracing", + "tracing-core", +] + +[[package]] +name = "sp-io" +version = "30.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "bytes", + "ed25519-dalek", + "libsecp256k1", + "log", + "parity-scale-codec", + "polkavm-derive", + "rustversion", + "secp256k1", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-keystore 0.34.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-state-machine 0.35.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-trie 29.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "tracing", "tracing-core", ] @@ -18869,8 +19345,8 @@ dependencies = [ name = "sp-keyring" version = "31.0.0" dependencies = [ - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "strum 0.26.2", ] @@ -18882,10 +19358,21 @@ dependencies = [ "parking_lot 0.12.1", "rand", "rand_chacha 0.2.2", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", ] +[[package]] +name = "sp-keystore" +version = "0.34.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "parity-scale-codec", + "parking_lot 0.12.1", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", +] + [[package]] name = "sp-maybe-compressed-blob" version = "11.0.0" @@ -18903,14 +19390,24 @@ dependencies = [ "scale-info", ] +[[package]] +name = "sp-metadata-ir" +version = "0.6.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "frame-metadata", + "parity-scale-codec", + "scale-info", +] + [[package]] name = "sp-mixnet" version = "0.4.0" dependencies = [ "parity-scale-codec", "scale-info", - "sp-api", - "sp-application-crypto", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", ] [[package]] @@ -18923,10 +19420,10 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-api", - "sp-core", + "sp-api 26.0.0", + "sp-core 28.0.0", "sp-debug-derive 14.0.0", - "sp-runtime", + "sp-runtime 31.0.1", "thiserror", ] @@ -18938,9 +19435,9 @@ dependencies = [ "rand", "scale-info", "serde", - "sp-arithmetic", - "sp-core", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "substrate-test-utils", ] @@ -18952,21 +19449,31 @@ dependencies = [ "honggfuzz", "rand", "sp-npos-elections", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] name = "sp-offchain" version = "26.0.0" dependencies = [ - "sp-api", - "sp-core", - "sp-runtime", + "sp-api 26.0.0", + "sp-core 28.0.0", + "sp-runtime 31.0.1", +] + +[[package]] +name = "sp-panic-handler" +version = "13.0.0" +dependencies = [ + "backtrace", + "lazy_static", + "regex", ] [[package]] name = "sp-panic-handler" version = "13.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" dependencies = [ "backtrace", "lazy_static", @@ -18980,7 +19487,7 @@ dependencies = [ "rustc-hash", "serde", "serde_json", - "sp-core", + "sp-core 28.0.0", ] [[package]] @@ -18999,19 +19506,43 @@ dependencies = [ "serde", "serde_json", "simple-mermaid", - "sp-api", - "sp-application-crypto", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-state-machine", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-state-machine 0.35.0", "sp-std 14.0.0", "sp-tracing 16.0.0", - "sp-weights", + "sp-weights 27.0.0", "substrate-test-runtime-client", "zstd 0.12.4", ] +[[package]] +name = "sp-runtime" +version = "31.0.1" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "docify", + "either", + "hash256-std-hasher", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "paste", + "rand", + "scale-info", + "serde", + "simple-mermaid", + "sp-application-crypto 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-arithmetic 23.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-io 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-weights 27.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", +] + [[package]] name = "sp-runtime-interface" version = "17.0.0" @@ -19040,12 +19571,12 @@ dependencies = [ "polkavm-derive", "primitive-types", "rustversion", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-io", + "sp-io 30.0.0", "sp-runtime-interface-proc-macro 17.0.0", "sp-runtime-interface-test-wasm", - "sp-state-machine", + "sp-state-machine 0.35.0", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-tracing 16.0.0", @@ -19054,6 +19585,25 @@ dependencies = [ "trybuild", ] +[[package]] +name = "sp-runtime-interface" +version = "24.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec", + "polkavm-derive", + "primitive-types", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime-interface-proc-macro 17.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "static_assertions", +] + [[package]] name = "sp-runtime-interface-proc-macro" version = "11.0.0" @@ -19078,18 +19628,31 @@ dependencies = [ "syn 2.0.53", ] +[[package]] +name = "sp-runtime-interface-proc-macro" +version = "17.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "Inflector", + "expander 2.0.0", + "proc-macro-crate 3.0.0", + "proc-macro2", + "quote", + "syn 2.0.53", +] + [[package]] name = "sp-runtime-interface-test" version = "2.0.0" dependencies = [ "sc-executor", "sc-executor-common", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", "sp-runtime-interface-test-wasm", "sp-runtime-interface-test-wasm-deprecated", - "sp-state-machine", + "sp-state-machine 0.35.0", "tracing", "tracing-core", ] @@ -19099,8 +19662,8 @@ name = "sp-runtime-interface-test-wasm" version = "2.0.0" dependencies = [ "bytes", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-runtime-interface 24.0.0", "sp-std 14.0.0", "substrate-wasm-builder", @@ -19110,8 +19673,8 @@ dependencies = [ name = "sp-runtime-interface-test-wasm-deprecated" version = "2.0.0" dependencies = [ - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-runtime-interface 24.0.0", "substrate-wasm-builder", ] @@ -19122,11 +19685,11 @@ version = "27.0.0" dependencies = [ "parity-scale-codec", "scale-info", - "sp-api", - "sp-core", - "sp-keystore", - "sp-runtime", - "sp-staking", + "sp-api 26.0.0", + "sp-core 28.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-staking 26.0.0", ] [[package]] @@ -19137,8 +19700,21 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", +] + +[[package]] +name = "sp-staking" +version = "26.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -19154,11 +19730,31 @@ dependencies = [ "pretty_assertions", "rand", "smallvec", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-panic-handler", - "sp-runtime", - "sp-trie", + "sp-panic-handler 13.0.0", + "sp-runtime 31.0.1", + "sp-trie 29.0.0", + "thiserror", + "tracing", + "trie-db", +] + +[[package]] +name = "sp-state-machine" +version = "0.35.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "rand", + "smallvec", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-panic-handler 13.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-trie 29.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "thiserror", "tracing", "trie-db", @@ -19176,12 +19772,12 @@ dependencies = [ "rand", "scale-info", "sha2 0.10.7", - "sp-api", - "sp-application-crypto", - "sp-core", - "sp-crypto-hashing", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-externalities 0.25.0", - "sp-runtime", + "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", "thiserror", "x25519-dalek 2.0.0", @@ -19196,6 +19792,11 @@ source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf5 name = "sp-std" version = "14.0.0" +[[package]] +name = "sp-std" +version = "14.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" + [[package]] name = "sp-storage" version = "13.0.0" @@ -19220,6 +19821,18 @@ dependencies = [ "sp-debug-derive 14.0.0", ] +[[package]] +name = "sp-storage" +version = "19.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "ref-cast", + "serde", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", +] + [[package]] name = "sp-test-primitives" version = "2.0.0" @@ -19227,19 +19840,31 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-application-crypto", - "sp-core", - "sp-runtime", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-runtime 31.0.1", +] + +[[package]] +name = "sp-timestamp" +version = "26.0.0" +dependencies = [ + "async-trait", + "parity-scale-codec", + "sp-inherents 26.0.0", + "sp-runtime 31.0.1", + "thiserror", ] [[package]] name = "sp-timestamp" version = "26.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" dependencies = [ "async-trait", "parity-scale-codec", - "sp-inherents", - "sp-runtime", + "sp-inherents 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "thiserror", ] @@ -19265,12 +19890,23 @@ dependencies = [ "tracing-subscriber 0.3.18", ] +[[package]] +name = "sp-tracing" +version = "16.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "parity-scale-codec", + "tracing", + "tracing-core", + "tracing-subscriber 0.3.18", +] + [[package]] name = "sp-transaction-pool" version = "26.0.0" dependencies = [ - "sp-api", - "sp-runtime", + "sp-api 26.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -19280,10 +19916,10 @@ dependencies = [ "async-trait", "parity-scale-codec", "scale-info", - "sp-core", - "sp-inherents", - "sp-runtime", - "sp-trie", + "sp-core 28.0.0", + "sp-inherents 26.0.0", + "sp-runtime 31.0.1", + "sp-trie 29.0.0", ] [[package]] @@ -19302,9 +19938,9 @@ dependencies = [ "rand", "scale-info", "schnellru", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-runtime", + "sp-runtime 31.0.1", "thiserror", "tracing", "trie-bench", @@ -19313,6 +19949,29 @@ dependencies = [ "trie-standardmap", ] +[[package]] +name = "sp-trie" +version = "29.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "ahash 0.8.8", + "hash-db", + "lazy_static", + "memory-db", + "nohash-hasher", + "parity-scale-codec", + "parking_lot 0.12.1", + "rand", + "scale-info", + "schnellru", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "thiserror", + "tracing", + "trie-db", + "trie-root", +] + [[package]] name = "sp-version" version = "29.0.0" @@ -19322,10 +19981,27 @@ dependencies = [ "parity-wasm", "scale-info", "serde", - "sp-crypto-hashing-proc-macro", - "sp-runtime", + "sp-crypto-hashing-proc-macro 0.1.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "sp-version-proc-macro", + "sp-version-proc-macro 13.0.0", + "thiserror", +] + +[[package]] +name = "sp-version" +version = "29.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "parity-wasm", + "scale-info", + "serde", + "sp-crypto-hashing-proc-macro 0.1.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-version-proc-macro 13.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", "thiserror", ] @@ -19336,7 +20012,18 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "sp-version", + "sp-version 29.0.0", + "syn 2.0.53", +] + +[[package]] +name = "sp-version-proc-macro" +version = "13.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "parity-scale-codec", + "proc-macro2", + "quote", "syn 2.0.53", ] @@ -19364,6 +20051,16 @@ dependencies = [ "wasmtime", ] +[[package]] +name = "sp-wasm-interface" +version = "20.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "impl-trait-for-tuples", + "log", + "parity-scale-codec", +] + [[package]] name = "sp-weights" version = "27.0.0" @@ -19374,10 +20071,24 @@ dependencies = [ "schemars", "serde", "smallvec", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-debug-derive 14.0.0", ] +[[package]] +name = "sp-weights" +version = "27.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "bounded-collections", + "parity-scale-codec", + "scale-info", + "serde", + "smallvec", + "sp-arithmetic 23.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", +] + [[package]] name = "spin" version = "0.5.2" @@ -19475,10 +20186,10 @@ dependencies = [ "clap 4.5.3", "clap_complete", "criterion 0.4.0", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-benchmarking-cli", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-rpc-runtime-api", "futures", "jsonrpsee", @@ -19492,7 +20203,7 @@ dependencies = [ "pallet-asset-conversion-tx-payment", "pallet-asset-tx-payment", "pallet-assets", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-contracts", "pallet-glutton", "pallet-im-online", @@ -19500,7 +20211,7 @@ dependencies = [ "pallet-skip-feeless-payment", "pallet-sudo", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-treasury", "parity-scale-codec", "platforms", @@ -19541,31 +20252,31 @@ dependencies = [ "serde", "serde_json", "soketto", - "sp-api", - "sp-application-crypto", - "sp-authority-discovery", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", + "sp-authority-discovery 26.0.0", "sp-blockchain", "sp-consensus", "sp-consensus-babe", "sp-consensus-beefy", - "sp-consensus-grandpa", - "sp-core", - "sp-crypto-hashing", + "sp-consensus-grandpa 13.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-externalities 0.25.0", - "sp-genesis-builder", - "sp-inherents", - "sp-io", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-mixnet", "sp-mmr-primitives", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-statement-store", - "sp-timestamp", + "sp-timestamp 26.0.0", "sp-tracing 16.0.0", "sp-transaction-storage-proof", - "sp-trie", + "sp-trie 29.0.0", "staging-node-inspect", "substrate-build-script-utils", "substrate-cli-test-utils", @@ -19589,9 +20300,9 @@ dependencies = [ "sc-client-api", "sc-service", "sp-blockchain", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-statement-store", "thiserror", ] @@ -19601,11 +20312,11 @@ name = "staging-parachain-info" version = "0.7.0" dependencies = [ "cumulus-primitives-core", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "parity-scale-codec", "scale-info", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -19629,9 +20340,27 @@ dependencies = [ "scale-info", "schemars", "serde", - "sp-io", - "sp-weights", - "xcm-procedural", + "sp-io 30.0.0", + "sp-weights 27.0.0", + "xcm-procedural 7.0.0", +] + +[[package]] +name = "staging-xcm" +version = "7.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "array-bytes 6.1.0", + "bounded-collections", + "derivative", + "environmental", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-weights 27.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "xcm-procedural 7.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -19639,29 +20368,51 @@ name = "staging-xcm-builder" version = "7.0.0" dependencies = [ "assert_matches", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "impl-trait-for-tuples", "log", "pallet-assets", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-salary", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-xcm", "parity-scale-codec", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "polkadot-runtime-parachains", "polkadot-test-runtime", "primitive-types", "scale-info", - "sp-arithmetic", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "sp-weights", - "staging-xcm", - "staging-xcm-executor", + "sp-weights 27.0.0", + "staging-xcm 7.0.0", + "staging-xcm-executor 7.0.0", +] + +[[package]] +name = "staging-xcm-builder" +version = "7.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-system 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "impl-trait-for-tuples", + "log", + "pallet-transaction-payment 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "parity-scale-codec", + "polkadot-parachain-primitives 6.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "scale-info", + "sp-arithmetic 23.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-io 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-weights 27.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "staging-xcm 7.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "staging-xcm-executor 7.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -19669,19 +20420,40 @@ name = "staging-xcm-executor" version = "7.0.0" dependencies = [ "environmental", - "frame-benchmarking", - "frame-support", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", "impl-trait-for-tuples", "log", "parity-scale-codec", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "sp-weights", - "staging-xcm", + "sp-weights 27.0.0", + "staging-xcm 7.0.0", +] + +[[package]] +name = "staging-xcm-executor" +version = "7.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "environmental", + "frame-benchmarking 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "frame-support 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "scale-info", + "sp-arithmetic 23.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-core 28.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-io 30.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-runtime 31.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "sp-weights 27.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", + "staging-xcm 7.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] [[package]] @@ -19827,6 +20599,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "substrate-bip39" +version = "0.4.7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "hmac 0.12.1", + "pbkdf2", + "schnorrkel 0.11.4", + "sha2 0.10.7", + "zeroize", +] + [[package]] name = "substrate-build-script-utils" version = "11.0.0" @@ -19853,26 +20637,26 @@ name = "substrate-frame-cli" version = "32.0.0" dependencies = [ "clap 4.5.3", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "sc-cli", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", ] [[package]] name = "substrate-frame-rpc-support" version = "29.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "jsonrpsee", "parity-scale-codec", "sc-rpc-api", "scale-info", "serde", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-storage 19.0.0", "tokio", ] @@ -19890,11 +20674,11 @@ dependencies = [ "sc-rpc-api", "sc-transaction-pool", "sc-transaction-pool-api", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-blockchain", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "substrate-test-runtime-client", "tokio", @@ -19920,8 +20704,8 @@ dependencies = [ "log", "sc-rpc-api", "serde", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "tokio", ] @@ -19935,10 +20719,10 @@ dependencies = [ "sc-rpc-api", "serde", "serde_json", - "sp-core", - "sp-runtime", - "sp-state-machine", - "sp-trie", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", + "sp-trie 29.0.0", "trie-db", ] @@ -19960,11 +20744,11 @@ dependencies = [ "serde_json", "sp-blockchain", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", - "sp-state-machine", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "tokio", ] @@ -19974,14 +20758,14 @@ version = "2.0.0" dependencies = [ "array-bytes 6.1.0", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-rpc-runtime-api", "futures", "hex-literal", "log", "pallet-babe", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-timestamp", "parity-scale-codec", "sc-block-builder", @@ -19992,28 +20776,28 @@ dependencies = [ "scale-info", "serde", "serde_json", - "sp-api", - "sp-application-crypto", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", "sp-block-builder", "sp-consensus", "sp-consensus-aura", "sp-consensus-babe", - "sp-consensus-grandpa", - "sp-core", - "sp-crypto-hashing", + "sp-consensus-grandpa 13.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-externalities 0.25.0", - "sp-genesis-builder", - "sp-inherents", - "sp-io", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-keyring", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", - "sp-state-machine", + "sp-state-machine 0.35.0", "sp-tracing 16.0.0", "sp-transaction-pool", - "sp-trie", - "sp-version", + "sp-trie 29.0.0", + "sp-version 29.0.0", "substrate-test-runtime-client", "substrate-wasm-builder", "trie-db", @@ -20027,11 +20811,11 @@ dependencies = [ "sc-block-builder", "sc-client-api", "sc-consensus", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "substrate-test-client", "substrate-test-runtime", ] @@ -20046,7 +20830,7 @@ dependencies = [ "sc-transaction-pool", "sc-transaction-pool-api", "sp-blockchain", - "sp-runtime", + "sp-runtime 31.0.1", "substrate-test-runtime-client", "thiserror", ] @@ -20322,8 +21106,8 @@ version = "1.0.0" dependencies = [ "dlmalloc", "parity-scale-codec", - "polkadot-parachain-primitives", - "sp-io", + "polkadot-parachain-primitives 6.0.0", + "sp-io 30.0.0", "sp-std 14.0.0", "substrate-wasm-builder", "tiny-keccak", @@ -20342,13 +21126,13 @@ dependencies = [ "polkadot-node-core-pvf", "polkadot-node-primitives", "polkadot-node-subsystem", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "polkadot-service", "polkadot-test-service", "sc-cli", "sc-service", - "sp-core", + "sp-core 28.0.0", "sp-keyring", "substrate-test-utils", "test-parachain-adder", @@ -20370,8 +21154,8 @@ dependencies = [ "dlmalloc", "log", "parity-scale-codec", - "polkadot-parachain-primitives", - "sp-io", + "polkadot-parachain-primitives 6.0.0", + "sp-io 30.0.0", "sp-std 14.0.0", "substrate-wasm-builder", "tiny-keccak", @@ -20390,13 +21174,13 @@ dependencies = [ "polkadot-node-core-pvf", "polkadot-node-primitives", "polkadot-node-subsystem", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "polkadot-service", "polkadot-test-service", "sc-cli", "sc-service", - "sp-core", + "sp-core 28.0.0", "sp-keyring", "substrate-test-utils", "test-parachain-undying", @@ -20408,7 +21192,7 @@ name = "test-parachains" version = "1.0.0" dependencies = [ "parity-scale-codec", - "sp-core", + "sp-core 28.0.0", "test-parachain-adder", "test-parachain-halt", "tiny-keccak", @@ -20418,13 +21202,13 @@ dependencies = [ name = "test-runtime-constants" version = "1.0.0" dependencies = [ - "frame-support", - "polkadot-primitives", + "frame-support 28.0.0", + "polkadot-primitives 7.0.0", "polkadot-runtime-common", "smallvec", - "sp-core", - "sp-runtime", - "sp-weights", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "sp-weights 27.0.0", ] [[package]] @@ -20432,12 +21216,12 @@ name = "testnet-parachains-constants" version = "1.0.0" dependencies = [ "cumulus-primitives-core", - "frame-support", - "polkadot-core-primitives", + "frame-support 28.0.0", + "polkadot-core-primitives 7.0.0", "rococo-runtime-constants", "smallvec", - "sp-runtime", - "staging-xcm", + "sp-runtime 31.0.1", + "staging-xcm 7.0.0", "westend-runtime-constants", ] @@ -20880,7 +21664,7 @@ name = "tracing-gum" version = "7.0.0" dependencies = [ "coarsetime", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "tracing", "tracing-gum-proc-macro", ] @@ -21089,22 +21873,22 @@ dependencies = [ "sc-executor", "serde", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-consensus-aura", "sp-consensus-babe", - "sp-core", + "sp-core 28.0.0", "sp-debug-derive 14.0.0", "sp-externalities 0.25.0", - "sp-inherents", - "sp-io", - "sp-keystore", + "sp-inherents 26.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", "sp-rpc", - "sp-runtime", - "sp-state-machine", - "sp-timestamp", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", + "sp-timestamp 26.0.0", "sp-transaction-storage-proof", - "sp-version", - "sp-weights", + "sp-version 29.0.0", + "sp-weights 27.0.0", "substrate-cli-test-utils", "substrate-rpc-client", "tempfile", @@ -21910,13 +22694,13 @@ dependencies = [ "emulated-integration-tests-common", "pallet-staking", "parachains-common", - "polkadot-primitives", + "polkadot-primitives 7.0.0", "sc-consensus-grandpa", - "sp-authority-discovery", + "sp-authority-discovery 26.0.0", "sp-consensus-babe", "sp-consensus-beefy", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "westend-runtime", "westend-runtime-constants", ] @@ -21927,12 +22711,12 @@ version = "7.0.0" dependencies = [ "binary-merkle-tree", "bitvec", - "frame-benchmarking", + "frame-benchmarking 28.0.0", "frame-election-provider-support", "frame-executive", "frame-remote-externalities", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", @@ -21943,7 +22727,7 @@ dependencies = [ "pallet-authorship", "pallet-babe", "pallet-bags-list", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-beefy", "pallet-beefy-mmr", "pallet-collective", @@ -21980,17 +22764,17 @@ dependencies = [ "pallet-state-trie-migration", "pallet-sudo", "pallet-timestamp", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc-runtime-api", "pallet-treasury", - "pallet-utility", + "pallet-utility 28.0.0", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "parity-scale-codec", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "polkadot-runtime-common", "polkadot-runtime-parachains", "rustc-hex", @@ -21999,32 +22783,32 @@ dependencies = [ "serde_derive", "serde_json", "smallvec", - "sp-api", - "sp-application-crypto", - "sp-arithmetic", - "sp-authority-discovery", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", + "sp-authority-discovery 26.0.0", "sp-block-builder", "sp-consensus-babe", "sp-consensus-beefy", - "sp-core", - "sp-genesis-builder", - "sp-inherents", - "sp-io", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-keyring", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", - "sp-staking", + "sp-staking 26.0.0", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-tracing 16.0.0", "sp-transaction-pool", - "sp-version", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "sp-version 29.0.0", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "substrate-wasm-builder", "tiny-keccak", "tokio", @@ -22036,15 +22820,15 @@ dependencies = [ name = "westend-runtime-constants" version = "7.0.0" dependencies = [ - "frame-support", - "polkadot-primitives", + "frame-support 28.0.0", + "polkadot-primitives 7.0.0", "polkadot-runtime-common", "smallvec", - "sp-core", - "sp-runtime", - "sp-weights", - "staging-xcm", - "staging-xcm-builder", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "sp-weights 27.0.0", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", ] [[package]] @@ -22455,38 +23239,38 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-test-relay-sproof-builder", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "impl-trait-for-tuples", "lazy_static", "log", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-message-queue", "parachains-common", "parity-scale-codec", "paste", - "polkadot-parachain-primitives", - "polkadot-primitives", + "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "polkadot-runtime-parachains", - "sp-arithmetic", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", - "staging-xcm", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-executor 7.0.0", ] [[package]] name = "xcm-executor-integration-tests" version = "1.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "futures", - "pallet-transaction-payment", + "pallet-transaction-payment 28.0.0", "pallet-xcm", "parity-scale-codec", "polkadot-service", @@ -22494,27 +23278,27 @@ dependencies = [ "polkadot-test-runtime", "polkadot-test-service", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-tracing 16.0.0", - "staging-xcm", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-executor 7.0.0", ] [[package]] name = "xcm-fee-payment-runtime-api" version = "0.1.0" dependencies = [ - "frame-support", + "frame-support 28.0.0", "parity-scale-codec", "scale-info", - "sp-api", - "sp-runtime", + "sp-api 26.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "sp-weights", - "staging-xcm", + "sp-weights 27.0.0", + "staging-xcm 7.0.0", ] [[package]] @@ -22524,52 +23308,63 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "staging-xcm", + "staging-xcm 7.0.0", "syn 2.0.53", "trybuild", ] +[[package]] +name = "xcm-procedural" +version = "7.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#59f868d1e9502cb4e434127cac6e439d01d7dd2b" +dependencies = [ + "Inflector", + "proc-macro2", + "quote", + "syn 2.0.53", +] + [[package]] name = "xcm-simulator" version = "7.0.0" dependencies = [ - "frame-support", + "frame-support 28.0.0", "parity-scale-codec", "paste", - "polkadot-core-primitives", - "polkadot-parachain-primitives", + "polkadot-core-primitives 7.0.0", + "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-parachains", - "sp-io", + "sp-io 30.0.0", "sp-std 14.0.0", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", ] [[package]] name = "xcm-simulator-example" version = "7.0.0" dependencies = [ - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "log", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-message-queue", "pallet-uniques", "pallet-xcm", "parity-scale-codec", - "polkadot-core-primitives", - "polkadot-parachain-primitives", + "polkadot-core-primitives 7.0.0", + "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-parachains", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "xcm-simulator", ] @@ -22579,25 +23374,25 @@ version = "1.0.0" dependencies = [ "arbitrary", "frame-executive", - "frame-support", - "frame-system", + "frame-support 28.0.0", + "frame-system 28.0.0", "frame-try-runtime", "honggfuzz", - "pallet-balances", + "pallet-balances 28.0.0", "pallet-message-queue", "pallet-xcm", "parity-scale-codec", - "polkadot-core-primitives", - "polkadot-parachain-primitives", + "polkadot-core-primitives 7.0.0", + "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-parachains", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", "xcm-simulator", ] diff --git a/bridges/bin/runtime-common/Cargo.toml b/bridges/bin/runtime-common/Cargo.toml index 67b91a16a302d..6304c83b905b2 100644 --- a/bridges/bin/runtime-common/Cargo.toml +++ b/bridges/bin/runtime-common/Cargo.toml @@ -34,24 +34,24 @@ pallet-bridge-relayers = { path = "../../modules/relayers", default-features = f # Substrate dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../substrate/frame/system", default-features = false } -pallet-transaction-payment = { path = "../../../substrate/frame/transaction-payment", default-features = false } -pallet-utility = { path = "../../../substrate/frame/utility", default-features = false } -sp-api = { path = "../../../substrate/primitives/api", default-features = false } -sp-core = { path = "../../../substrate/primitives/core", default-features = false } -sp-io = { path = "../../../substrate/primitives/io", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } -sp-trie = { path = "../../../substrate/primitives/trie", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +pallet-utility = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-io = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-trie = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } # Polkadot dependencies -xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false } -xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/xcm-builder", default-features = false } +xcm = { package = "staging-xcm", git = "https://github.com/paritytech/polkadot-sdk", default-features = false , branch = "master" } +xcm-builder = { package = "staging-xcm-builder", git = "https://github.com/paritytech/polkadot-sdk", default-features = false , branch = "master" } [dev-dependencies] bp-test-utils = { path = "../../primitives/test-utils" } -pallet-balances = { path = "../../../substrate/frame/balances" } +pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } [features] default = ["std"] diff --git a/bridges/bin/runtime-common/src/mock.rs b/bridges/bin/runtime-common/src/mock.rs index ad71cd0d456d8..8c4cb2233e17c 100644 --- a/bridges/bin/runtime-common/src/mock.rs +++ b/bridges/bin/runtime-common/src/mock.rs @@ -166,7 +166,7 @@ impl pallet_balances::Config for TestRuntime { #[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)] impl pallet_transaction_payment::Config for TestRuntime { - type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter; + type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter; type OperationalFeeMultiplier = ConstU8<5>; type WeightToFee = IdentityFee; type LengthToFee = ConstantMultiplier; diff --git a/bridges/chains/chain-asset-hub-rococo/Cargo.toml b/bridges/chains/chain-asset-hub-rococo/Cargo.toml index 9a6419a5b4055..660f0f4dbfcf0 100644 --- a/bridges/chains/chain-asset-hub-rococo/Cargo.toml +++ b/bridges/chains/chain-asset-hub-rococo/Cargo.toml @@ -15,7 +15,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = scale-info = { version = "2.11.1", default-features = false, features = ["derive"] } # Substrate Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } # Bridge Dependencies bp-xcm-bridge-hub-router = { path = "../../primitives/xcm-bridge-hub-router", default-features = false } diff --git a/bridges/chains/chain-asset-hub-westend/Cargo.toml b/bridges/chains/chain-asset-hub-westend/Cargo.toml index 1c08ee28e417c..4022258acd030 100644 --- a/bridges/chains/chain-asset-hub-westend/Cargo.toml +++ b/bridges/chains/chain-asset-hub-westend/Cargo.toml @@ -15,7 +15,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = scale-info = { version = "2.11.1", default-features = false, features = ["derive"] } # Substrate Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } # Bridge Dependencies bp-xcm-bridge-hub-router = { path = "../../primitives/xcm-bridge-hub-router", default-features = false } diff --git a/bridges/chains/chain-bridge-hub-cumulus/Cargo.toml b/bridges/chains/chain-bridge-hub-cumulus/Cargo.toml index 4b900002a4d81..b87b5fefd9c7f 100644 --- a/bridges/chains/chain-bridge-hub-cumulus/Cargo.toml +++ b/bridges/chains/chain-bridge-hub-cumulus/Cargo.toml @@ -19,13 +19,13 @@ bp-runtime = { path = "../../primitives/runtime", default-features = false } # Substrate Based Dependencies -frame-system = { path = "../../../substrate/frame/system", default-features = false } -frame-support = { path = "../../../substrate/frame/support", default-features = false } -sp-api = { path = "../../../substrate/primitives/api", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } # Polkadot Dependencies -polkadot-primitives = { path = "../../../polkadot/primitives", default-features = false } +polkadot-primitives = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false , branch = "master" } [features] default = ["std"] diff --git a/bridges/chains/chain-bridge-hub-kusama/Cargo.toml b/bridges/chains/chain-bridge-hub-kusama/Cargo.toml index ff6dd8849abe3..71ee785d4b214 100644 --- a/bridges/chains/chain-bridge-hub-kusama/Cargo.toml +++ b/bridges/chains/chain-bridge-hub-kusama/Cargo.toml @@ -19,10 +19,10 @@ bp-messages = { path = "../../primitives/messages", default-features = false } # Substrate Based Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -sp-api = { path = "../../../substrate/primitives/api", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [features] default = ["std"] diff --git a/bridges/chains/chain-bridge-hub-polkadot/Cargo.toml b/bridges/chains/chain-bridge-hub-polkadot/Cargo.toml index da8b8a82fa702..dd4729673c0ea 100644 --- a/bridges/chains/chain-bridge-hub-polkadot/Cargo.toml +++ b/bridges/chains/chain-bridge-hub-polkadot/Cargo.toml @@ -20,10 +20,10 @@ bp-messages = { path = "../../primitives/messages", default-features = false } # Substrate Based Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -sp-api = { path = "../../../substrate/primitives/api", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [features] default = ["std"] diff --git a/bridges/chains/chain-bridge-hub-rococo/Cargo.toml b/bridges/chains/chain-bridge-hub-rococo/Cargo.toml index f7672df012f2f..a8e0003ee68ea 100644 --- a/bridges/chains/chain-bridge-hub-rococo/Cargo.toml +++ b/bridges/chains/chain-bridge-hub-rococo/Cargo.toml @@ -19,10 +19,10 @@ bp-messages = { path = "../../primitives/messages", default-features = false } # Substrate Based Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -sp-api = { path = "../../../substrate/primitives/api", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [features] default = ["std"] diff --git a/bridges/chains/chain-bridge-hub-westend/Cargo.toml b/bridges/chains/chain-bridge-hub-westend/Cargo.toml index ec74c4b947d69..09bf743c66447 100644 --- a/bridges/chains/chain-bridge-hub-westend/Cargo.toml +++ b/bridges/chains/chain-bridge-hub-westend/Cargo.toml @@ -20,10 +20,10 @@ bp-messages = { path = "../../primitives/messages", default-features = false } # Substrate Based Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -sp-api = { path = "../../../substrate/primitives/api", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [features] default = ["std"] diff --git a/bridges/chains/chain-kusama/Cargo.toml b/bridges/chains/chain-kusama/Cargo.toml index 66061ff2793cb..2a59937daa760 100644 --- a/bridges/chains/chain-kusama/Cargo.toml +++ b/bridges/chains/chain-kusama/Cargo.toml @@ -20,9 +20,9 @@ bp-runtime = { path = "../../primitives/runtime", default-features = false } # Substrate Based Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -sp-api = { path = "../../../substrate/primitives/api", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [features] default = ["std"] diff --git a/bridges/chains/chain-polkadot-bulletin/Cargo.toml b/bridges/chains/chain-polkadot-bulletin/Cargo.toml index 2db16a00e9249..c20a94cfdaab4 100644 --- a/bridges/chains/chain-polkadot-bulletin/Cargo.toml +++ b/bridges/chains/chain-polkadot-bulletin/Cargo.toml @@ -23,11 +23,11 @@ bp-runtime = { path = "../../primitives/runtime", default-features = false } # Substrate Based Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../substrate/frame/system", default-features = false } -sp-api = { path = "../../../substrate/primitives/api", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [features] default = ["std"] diff --git a/bridges/chains/chain-polkadot/Cargo.toml b/bridges/chains/chain-polkadot/Cargo.toml index c700935f3083b..f942e4fe8dd48 100644 --- a/bridges/chains/chain-polkadot/Cargo.toml +++ b/bridges/chains/chain-polkadot/Cargo.toml @@ -20,9 +20,9 @@ bp-runtime = { path = "../../primitives/runtime", default-features = false } # Substrate Based Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -sp-api = { path = "../../../substrate/primitives/api", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [features] default = ["std"] diff --git a/bridges/chains/chain-rococo/Cargo.toml b/bridges/chains/chain-rococo/Cargo.toml index 5a5613bb376a5..a86e875517b39 100644 --- a/bridges/chains/chain-rococo/Cargo.toml +++ b/bridges/chains/chain-rococo/Cargo.toml @@ -20,9 +20,9 @@ bp-runtime = { path = "../../primitives/runtime", default-features = false } # Substrate Based Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -sp-api = { path = "../../../substrate/primitives/api", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [features] default = ["std"] diff --git a/bridges/chains/chain-westend/Cargo.toml b/bridges/chains/chain-westend/Cargo.toml index 10b06d76507ef..6f5c48139c6ea 100644 --- a/bridges/chains/chain-westend/Cargo.toml +++ b/bridges/chains/chain-westend/Cargo.toml @@ -20,9 +20,9 @@ bp-runtime = { path = "../../primitives/runtime", default-features = false } # Substrate Based Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -sp-api = { path = "../../../substrate/primitives/api", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [features] default = ["std"] diff --git a/bridges/modules/beefy/Cargo.toml b/bridges/modules/beefy/Cargo.toml new file mode 100644 index 0000000000000..2c552430c98f9 --- /dev/null +++ b/bridges/modules/beefy/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "pallet-bridge-beefy" +version = "0.1.0" +description = "Module implementing BEEFY on-chain light client used for bridging consensus of substrate-based chains." +authors.workspace = true +edition.workspace = true +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +repository.workspace = true +publish = false + +[lints] +workspace = true + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +log = { workspace = true } +scale-info = { version = "2.11.1", default-features = false, features = ["derive"] } +serde = { optional = true, workspace = true } + +# Bridge Dependencies + +bp-beefy = { path = "../../primitives/beefy", default-features = false } +bp-runtime = { path = "../../primitives/runtime", default-features = false } + +# Substrate Dependencies + +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } + +[dev-dependencies] +sp-consensus-beefy = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +mmr-lib = { package = "ckb-merkle-mountain-range", version = "0.5.2" } +pallet-beefy-mmr = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +pallet-mmr = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +rand = "0.8.5" +sp-io = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +bp-test-utils = { path = "../../primitives/test-utils" } + +[features] +default = ["std"] +std = [ + "bp-beefy/std", + "bp-runtime/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "serde/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-beefy-mmr/try-runtime", + "pallet-mmr/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/bridges/modules/beefy/src/lib.rs b/bridges/modules/beefy/src/lib.rs new file mode 100644 index 0000000000000..27c83921021bb --- /dev/null +++ b/bridges/modules/beefy/src/lib.rs @@ -0,0 +1,651 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! BEEFY bridge pallet. +//! +//! This pallet is an on-chain BEEFY light client for Substrate-based chains that are using the +//! following pallets bundle: `pallet-mmr`, `pallet-beefy` and `pallet-beefy-mmr`. +//! +//! The pallet is able to verify MMR leaf proofs and BEEFY commitments, so it has access +//! to the following data of the bridged chain: +//! +//! - header hashes +//! - changes of BEEFY authorities +//! - extra data of MMR leafs +//! +//! Given the header hash, other pallets are able to verify header-based proofs +//! (e.g. storage proofs, transaction inclusion proofs, etc.). + +#![warn(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] + +use bp_beefy::{ChainWithBeefy, InitializationData}; +use sp_std::{boxed::Box, prelude::*}; + +// Re-export in crate namespace for `construct_runtime!` +pub use pallet::*; + +mod utils; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod mock_chain; + +/// The target that will be used when publishing logs related to this pallet. +pub const LOG_TARGET: &str = "runtime::bridge-beefy"; + +/// Configured bridged chain. +pub type BridgedChain = >::BridgedChain; +/// Block number, used by configured bridged chain. +pub type BridgedBlockNumber = bp_runtime::BlockNumberOf>; +/// Block hash, used by configured bridged chain. +pub type BridgedBlockHash = bp_runtime::HashOf>; + +/// Pallet initialization data. +pub type InitializationDataOf = + InitializationData, bp_beefy::MmrHashOf>>; +/// BEEFY commitment hasher, used by configured bridged chain. +pub type BridgedBeefyCommitmentHasher = bp_beefy::BeefyCommitmentHasher>; +/// BEEFY validator id, used by configured bridged chain. +pub type BridgedBeefyAuthorityId = bp_beefy::BeefyAuthorityIdOf>; +/// BEEFY validator set, used by configured bridged chain. +pub type BridgedBeefyAuthoritySet = bp_beefy::BeefyAuthoritySetOf>; +/// BEEFY authority set, used by configured bridged chain. +pub type BridgedBeefyAuthoritySetInfo = bp_beefy::BeefyAuthoritySetInfoOf>; +/// BEEFY signed commitment, used by configured bridged chain. +pub type BridgedBeefySignedCommitment = bp_beefy::BeefySignedCommitmentOf>; +/// MMR hashing algorithm, used by configured bridged chain. +pub type BridgedMmrHashing = bp_beefy::MmrHashingOf>; +/// MMR hashing output type of `BridgedMmrHashing`. +pub type BridgedMmrHash = bp_beefy::MmrHashOf>; +/// The type of the MMR leaf extra data used by the configured bridged chain. +pub type BridgedBeefyMmrLeafExtra = bp_beefy::BeefyMmrLeafExtraOf>; +/// BEEFY MMR proof type used by the pallet +pub type BridgedMmrProof = bp_beefy::MmrProofOf>; +/// MMR leaf type, used by configured bridged chain. +pub type BridgedBeefyMmrLeaf = bp_beefy::BeefyMmrLeafOf>; +/// Imported commitment data, stored by the pallet. +pub type ImportedCommitment = bp_beefy::ImportedCommitment< + BridgedBlockNumber, + BridgedBlockHash, + BridgedMmrHash, +>; + +/// Some high level info about the imported commitments. +#[derive(codec::Encode, codec::Decode, scale_info::TypeInfo)] +pub struct ImportedCommitmentsInfoData { + /// Best known block number, provided in a BEEFY commitment. However this is not + /// the best proven block. The best proven block is this block's parent. + best_block_number: BlockNumber, + /// The head of the `ImportedBlockNumbers` ring buffer. + next_block_number_index: u32, +} + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use super::*; + use bp_runtime::{BasicOperatingMode, OwnedBridgeModule}; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The upper bound on the number of requests allowed by the pallet. + /// + /// A request refers to an action which writes a header to storage. + /// + /// Once this bound is reached the pallet will reject all commitments + /// until the request count has decreased. + #[pallet::constant] + type MaxRequests: Get; + + /// Maximal number of imported commitments to keep in the storage. + /// + /// The setting is there to prevent growing the on-chain state indefinitely. Note + /// the setting does not relate to block numbers - we will simply keep as much items + /// in the storage, so it doesn't guarantee any fixed timeframe for imported commitments. + #[pallet::constant] + type CommitmentsToKeep: Get; + + /// The chain we are bridging to here. + type BridgedChain: ChainWithBeefy; + } + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + fn on_initialize(_n: BlockNumberFor) -> frame_support::weights::Weight { + >::mutate(|count| *count = count.saturating_sub(1)); + + Weight::from_parts(0, 0) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + } + + impl, I: 'static> OwnedBridgeModule for Pallet { + const LOG_TARGET: &'static str = LOG_TARGET; + type OwnerStorage = PalletOwner; + type OperatingMode = BasicOperatingMode; + type OperatingModeStorage = PalletOperatingMode; + } + + #[pallet::call] + impl, I: 'static> Pallet + where + BridgedMmrHashing: 'static + Send + Sync, + { + /// Initialize pallet with BEEFY authority set and best known finalized block number. + #[pallet::call_index(0)] + #[pallet::weight((T::DbWeight::get().reads_writes(2, 3), DispatchClass::Operational))] + pub fn initialize( + origin: OriginFor, + init_data: InitializationDataOf, + ) -> DispatchResult { + Self::ensure_owner_or_root(origin)?; + + let is_initialized = >::exists(); + ensure!(!is_initialized, >::AlreadyInitialized); + + log::info!(target: LOG_TARGET, "Initializing bridge BEEFY pallet: {:?}", init_data); + Ok(initialize::(init_data)?) + } + + /// Change `PalletOwner`. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::call_index(1)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_owner(origin: OriginFor, new_owner: Option) -> DispatchResult { + >::set_owner(origin, new_owner) + } + + /// Halt or resume all pallet operations. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::call_index(2)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_operating_mode( + origin: OriginFor, + operating_mode: BasicOperatingMode, + ) -> DispatchResult { + >::set_operating_mode(origin, operating_mode) + } + + /// Submit a commitment generated by BEEFY authority set. + /// + /// It will use the underlying storage pallet to fetch information about the current + /// authority set and best finalized block number in order to verify that the commitment + /// is valid. + /// + /// If successful in verification, it will update the underlying storage with the data + /// provided in the newly submitted commitment. + #[pallet::call_index(3)] + #[pallet::weight(0)] + pub fn submit_commitment( + origin: OriginFor, + commitment: BridgedBeefySignedCommitment, + validator_set: BridgedBeefyAuthoritySet, + mmr_leaf: Box>, + mmr_proof: BridgedMmrProof, + ) -> DispatchResult + where + BridgedBeefySignedCommitment: Clone, + { + Self::ensure_not_halted().map_err(Error::::BridgeModule)?; + ensure_signed(origin)?; + + ensure!(Self::request_count() < T::MaxRequests::get(), >::TooManyRequests); + + // Ensure that the commitment is for a better block. + let commitments_info = + ImportedCommitmentsInfo::::get().ok_or(Error::::NotInitialized)?; + ensure!( + commitment.commitment.block_number > commitments_info.best_block_number, + Error::::OldCommitment + ); + + // Verify commitment and mmr leaf. + let current_authority_set_info = CurrentAuthoritySetInfo::::get(); + let mmr_root = utils::verify_commitment::( + &commitment, + ¤t_authority_set_info, + &validator_set, + )?; + utils::verify_beefy_mmr_leaf::(&mmr_leaf, mmr_proof, mmr_root)?; + + // Update request count. + RequestCount::::mutate(|count| *count += 1); + // Update authority set if needed. + if mmr_leaf.beefy_next_authority_set.id > current_authority_set_info.id { + CurrentAuthoritySetInfo::::put(mmr_leaf.beefy_next_authority_set); + } + + // Import commitment. + let block_number_index = commitments_info.next_block_number_index; + let to_prune = ImportedBlockNumbers::::try_get(block_number_index); + ImportedCommitments::::insert( + commitment.commitment.block_number, + ImportedCommitment:: { + parent_number_and_hash: mmr_leaf.parent_number_and_hash, + mmr_root, + }, + ); + ImportedBlockNumbers::::insert( + block_number_index, + commitment.commitment.block_number, + ); + ImportedCommitmentsInfo::::put(ImportedCommitmentsInfoData { + best_block_number: commitment.commitment.block_number, + next_block_number_index: (block_number_index + 1) % T::CommitmentsToKeep::get(), + }); + if let Ok(old_block_number) = to_prune { + log::debug!( + target: LOG_TARGET, + "Pruning commitment for old block: {:?}.", + old_block_number + ); + ImportedCommitments::::remove(old_block_number); + } + + log::info!( + target: LOG_TARGET, + "Successfully imported commitment for block {:?}", + commitment.commitment.block_number, + ); + + Ok(()) + } + } + + /// The current number of requests which have written to storage. + /// + /// If the `RequestCount` hits `MaxRequests`, no more calls will be allowed to the pallet until + /// the request capacity is increased. + /// + /// The `RequestCount` is decreased by one at the beginning of every block. This is to ensure + /// that the pallet can always make progress. + #[pallet::storage] + #[pallet::getter(fn request_count)] + pub type RequestCount, I: 'static = ()> = StorageValue<_, u32, ValueQuery>; + + /// High level info about the imported commitments. + /// + /// Contains the following info: + /// - best known block number of the bridged chain, finalized by BEEFY + /// - the head of the `ImportedBlockNumbers` ring buffer + #[pallet::storage] + pub type ImportedCommitmentsInfo, I: 'static = ()> = + StorageValue<_, ImportedCommitmentsInfoData>>; + + /// A ring buffer containing the block numbers of the commitments that we have imported, + /// ordered by the insertion time. + #[pallet::storage] + pub(super) type ImportedBlockNumbers, I: 'static = ()> = + StorageMap<_, Identity, u32, BridgedBlockNumber>; + + /// All the commitments that we have imported and haven't been pruned yet. + #[pallet::storage] + pub type ImportedCommitments, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, BridgedBlockNumber, ImportedCommitment>; + + /// The current BEEFY authority set at the bridged chain. + #[pallet::storage] + pub type CurrentAuthoritySetInfo, I: 'static = ()> = + StorageValue<_, BridgedBeefyAuthoritySetInfo, ValueQuery>; + + /// Optional pallet owner. + /// + /// Pallet owner has the right to halt all pallet operations and then resume it. If it is + /// `None`, then there are no direct ways to halt/resume pallet operations, but other + /// runtime methods may still be used to do that (i.e. `democracy::referendum` to update halt + /// flag directly or calling `halt_operations`). + #[pallet::storage] + pub type PalletOwner, I: 'static = ()> = + StorageValue<_, T::AccountId, OptionQuery>; + + /// The current operating mode of the pallet. + /// + /// Depending on the mode either all, or no transactions will be allowed. + #[pallet::storage] + pub type PalletOperatingMode, I: 'static = ()> = + StorageValue<_, BasicOperatingMode, ValueQuery>; + + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig, I: 'static = ()> { + /// Optional module owner account. + pub owner: Option, + /// Optional module initialization data. + pub init_data: Option>, + } + + #[pallet::genesis_build] + impl, I: 'static> BuildGenesisConfig for GenesisConfig { + fn build(&self) { + if let Some(ref owner) = self.owner { + >::put(owner); + } + + if let Some(init_data) = self.init_data.clone() { + initialize::(init_data) + .expect("invalid initialization data of BEEFY bridge pallet"); + } else { + // Since the bridge hasn't been initialized we shouldn't allow anyone to perform + // transactions. + >::put(BasicOperatingMode::Halted); + } + } + } + + #[pallet::error] + pub enum Error { + /// The pallet has not been initialized yet. + NotInitialized, + /// The pallet has already been initialized. + AlreadyInitialized, + /// Invalid initial authority set. + InvalidInitialAuthoritySet, + /// There are too many requests for the current window to handle. + TooManyRequests, + /// The imported commitment is older than the best commitment known to the pallet. + OldCommitment, + /// The commitment is signed by unknown validator set. + InvalidCommitmentValidatorSetId, + /// The id of the provided validator set is invalid. + InvalidValidatorSetId, + /// The number of signatures in the commitment is invalid. + InvalidCommitmentSignaturesLen, + /// The number of validator ids provided is invalid. + InvalidValidatorSetLen, + /// There aren't enough correct signatures in the commitment to finalize the block. + NotEnoughCorrectSignatures, + /// MMR root is missing from the commitment. + MmrRootMissingFromCommitment, + /// MMR proof verification has failed. + MmrProofVerificationFailed, + /// The validators are not matching the merkle tree root of the authority set. + InvalidValidatorSetRoot, + /// Error generated by the `OwnedBridgeModule` trait. + BridgeModule(bp_runtime::OwnedBridgeModuleError), + } + + /// Initialize pallet with given parameters. + pub(super) fn initialize, I: 'static>( + init_data: InitializationDataOf, + ) -> Result<(), Error> { + if init_data.authority_set.len == 0 { + return Err(Error::::InvalidInitialAuthoritySet) + } + CurrentAuthoritySetInfo::::put(init_data.authority_set); + + >::put(init_data.operating_mode); + ImportedCommitmentsInfo::::put(ImportedCommitmentsInfoData { + best_block_number: init_data.best_block_number, + next_block_number_index: 0, + }); + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bp_runtime::{BasicOperatingMode, OwnedBridgeModuleError}; + use bp_test_utils::generate_owned_bridge_module_tests; + use frame_support::{assert_noop, assert_ok, traits::Get}; + use mock::*; + use mock_chain::*; + use sp_consensus_beefy::mmr::BeefyAuthoritySet; + use sp_runtime::DispatchError; + + fn next_block() { + use frame_support::traits::OnInitialize; + + let current_number = frame_system::Pallet::::block_number(); + frame_system::Pallet::::set_block_number(current_number + 1); + let _ = Pallet::::on_initialize(current_number); + } + + fn import_header_chain(headers: Vec) { + for header in headers { + if header.commitment.is_some() { + assert_ok!(import_commitment(header)); + } + } + } + + #[test] + fn fails_to_initialize_if_already_initialized() { + run_test_with_initialize(32, || { + assert_noop!( + Pallet::::initialize( + RuntimeOrigin::root(), + InitializationData { + operating_mode: BasicOperatingMode::Normal, + best_block_number: 0, + authority_set: BeefyAuthoritySet { + id: 0, + len: 1, + keyset_commitment: [0u8; 32].into() + } + } + ), + Error::::AlreadyInitialized, + ); + }); + } + + #[test] + fn fails_to_initialize_if_authority_set_is_empty() { + run_test(|| { + assert_noop!( + Pallet::::initialize( + RuntimeOrigin::root(), + InitializationData { + operating_mode: BasicOperatingMode::Normal, + best_block_number: 0, + authority_set: BeefyAuthoritySet { + id: 0, + len: 0, + keyset_commitment: [0u8; 32].into() + } + } + ), + Error::::InvalidInitialAuthoritySet, + ); + }); + } + + #[test] + fn fails_to_import_commitment_if_halted() { + run_test_with_initialize(1, || { + assert_ok!(Pallet::::set_operating_mode( + RuntimeOrigin::root(), + BasicOperatingMode::Halted + )); + assert_noop!( + import_commitment(ChainBuilder::new(1).append_finalized_header().to_header()), + Error::::BridgeModule(OwnedBridgeModuleError::Halted), + ); + }) + } + + #[test] + fn fails_to_import_commitment_if_too_many_requests() { + run_test_with_initialize(1, || { + let max_requests = <::MaxRequests as Get>::get() as u64; + let mut chain = ChainBuilder::new(1); + for _ in 0..max_requests + 2 { + chain = chain.append_finalized_header(); + } + + // import `max_request` headers + for i in 0..max_requests { + assert_ok!(import_commitment(chain.header(i + 1))); + } + + // try to import next header: it fails because we are no longer accepting commitments + assert_noop!( + import_commitment(chain.header(max_requests + 1)), + Error::::TooManyRequests, + ); + + // when next block is "started", we allow import of next header + next_block(); + assert_ok!(import_commitment(chain.header(max_requests + 1))); + + // but we can't import two headers until next block and so on + assert_noop!( + import_commitment(chain.header(max_requests + 2)), + Error::::TooManyRequests, + ); + }) + } + + #[test] + fn fails_to_import_commitment_if_not_initialized() { + run_test(|| { + assert_noop!( + import_commitment(ChainBuilder::new(1).append_finalized_header().to_header()), + Error::::NotInitialized, + ); + }) + } + + #[test] + fn submit_commitment_works_with_long_chain_with_handoffs() { + run_test_with_initialize(3, || { + let chain = ChainBuilder::new(3) + .append_finalized_header() + .append_default_headers(16) // 2..17 + .append_finalized_header() // 18 + .append_default_headers(16) // 19..34 + .append_handoff_header(9) // 35 + .append_default_headers(8) // 36..43 + .append_finalized_header() // 44 + .append_default_headers(8) // 45..52 + .append_handoff_header(17) // 53 + .append_default_headers(4) // 54..57 + .append_finalized_header() // 58 + .append_default_headers(4); // 59..63 + import_header_chain(chain.to_chain()); + + assert_eq!( + ImportedCommitmentsInfo::::get().unwrap().best_block_number, + 58 + ); + assert_eq!(CurrentAuthoritySetInfo::::get().id, 2); + assert_eq!(CurrentAuthoritySetInfo::::get().len, 17); + + let imported_commitment = ImportedCommitments::::get(58).unwrap(); + assert_eq!( + imported_commitment, + bp_beefy::ImportedCommitment { + parent_number_and_hash: (57, chain.header(57).header.hash()), + mmr_root: chain.header(58).mmr_root, + }, + ); + }) + } + + #[test] + fn commitment_pruning_works() { + run_test_with_initialize(3, || { + let commitments_to_keep = >::CommitmentsToKeep::get(); + let commitments_to_import: Vec = ChainBuilder::new(3) + .append_finalized_headers(commitments_to_keep as usize + 2) + .to_chain(); + + // import exactly `CommitmentsToKeep` commitments + for index in 0..commitments_to_keep { + next_block(); + import_commitment(commitments_to_import[index as usize].clone()) + .expect("must succeed"); + assert_eq!( + ImportedCommitmentsInfo::::get().unwrap().next_block_number_index, + (index + 1) % commitments_to_keep + ); + } + + // ensure that all commitments are in the storage + assert_eq!( + ImportedCommitmentsInfo::::get().unwrap().best_block_number, + commitments_to_keep as TestBridgedBlockNumber + ); + assert_eq!( + ImportedCommitmentsInfo::::get().unwrap().next_block_number_index, + 0 + ); + for index in 0..commitments_to_keep { + assert!(ImportedCommitments::::get( + index as TestBridgedBlockNumber + 1 + ) + .is_some()); + assert_eq!( + ImportedBlockNumbers::::get(index), + Some(Into::into(index + 1)), + ); + } + + // import next commitment + next_block(); + import_commitment(commitments_to_import[commitments_to_keep as usize].clone()) + .expect("must succeed"); + assert_eq!( + ImportedCommitmentsInfo::::get().unwrap().next_block_number_index, + 1 + ); + assert!(ImportedCommitments::::get( + commitments_to_keep as TestBridgedBlockNumber + 1 + ) + .is_some()); + assert_eq!( + ImportedBlockNumbers::::get(0), + Some(Into::into(commitments_to_keep + 1)), + ); + // the side effect of the import is that the commitment#1 is pruned + assert!(ImportedCommitments::::get(1).is_none()); + + // import next commitment + next_block(); + import_commitment(commitments_to_import[commitments_to_keep as usize + 1].clone()) + .expect("must succeed"); + assert_eq!( + ImportedCommitmentsInfo::::get().unwrap().next_block_number_index, + 2 + ); + assert!(ImportedCommitments::::get( + commitments_to_keep as TestBridgedBlockNumber + 2 + ) + .is_some()); + assert_eq!( + ImportedBlockNumbers::::get(1), + Some(Into::into(commitments_to_keep + 2)), + ); + // the side effect of the import is that the commitment#2 is pruned + assert!(ImportedCommitments::::get(1).is_none()); + assert!(ImportedCommitments::::get(2).is_none()); + }); + } + + generate_owned_bridge_module_tests!(BasicOperatingMode::Normal, BasicOperatingMode::Halted); +} diff --git a/bridges/modules/beefy/src/mock.rs b/bridges/modules/beefy/src/mock.rs new file mode 100644 index 0000000000000..c99566b6b06d1 --- /dev/null +++ b/bridges/modules/beefy/src/mock.rs @@ -0,0 +1,193 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +use crate as beefy; +use crate::{ + utils::get_authorities_mmr_root, BridgedBeefyAuthoritySet, BridgedBeefyAuthoritySetInfo, + BridgedBeefyCommitmentHasher, BridgedBeefyMmrLeafExtra, BridgedBeefySignedCommitment, + BridgedMmrHash, BridgedMmrHashing, BridgedMmrProof, +}; + +use bp_beefy::{BeefyValidatorSignatureOf, ChainWithBeefy, Commitment, MmrDataOrHash}; +use bp_runtime::{BasicOperatingMode, Chain, ChainId}; +use codec::Encode; +use frame_support::{construct_runtime, derive_impl, weights::Weight}; +use sp_core::{sr25519::Signature, Pair}; +use sp_runtime::{ + testing::{Header, H256}, + traits::{BlakeTwo256, Hash}, +}; + +pub use sp_consensus_beefy::ecdsa_crypto::{AuthorityId as BeefyId, Pair as BeefyPair}; +use sp_core::crypto::Wraps; +use sp_runtime::traits::Keccak256; + +pub type TestAccountId = u64; +pub type TestBridgedBlockNumber = u64; +pub type TestBridgedBlockHash = H256; +pub type TestBridgedHeader = Header; +pub type TestBridgedAuthoritySetInfo = BridgedBeefyAuthoritySetInfo; +pub type TestBridgedValidatorSet = BridgedBeefyAuthoritySet; +pub type TestBridgedCommitment = BridgedBeefySignedCommitment; +pub type TestBridgedValidatorSignature = BeefyValidatorSignatureOf; +pub type TestBridgedCommitmentHasher = BridgedBeefyCommitmentHasher; +pub type TestBridgedMmrHashing = BridgedMmrHashing; +pub type TestBridgedMmrHash = BridgedMmrHash; +pub type TestBridgedBeefyMmrLeafExtra = BridgedBeefyMmrLeafExtra; +pub type TestBridgedMmrProof = BridgedMmrProof; +pub type TestBridgedRawMmrLeaf = sp_consensus_beefy::mmr::MmrLeaf< + TestBridgedBlockNumber, + TestBridgedBlockHash, + TestBridgedMmrHash, + TestBridgedBeefyMmrLeafExtra, +>; +pub type TestBridgedMmrNode = MmrDataOrHash; + +type Block = frame_system::mocking::MockBlock; + +construct_runtime! { + pub enum TestRuntime + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Beefy: beefy::{Pallet}, + } +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for TestRuntime { + type Block = Block; +} + +impl beefy::Config for TestRuntime { + type MaxRequests = frame_support::traits::ConstU32<16>; + type BridgedChain = TestBridgedChain; + type CommitmentsToKeep = frame_support::traits::ConstU32<16>; +} + +#[derive(Debug)] +pub struct TestBridgedChain; + +impl Chain for TestBridgedChain { + const ID: ChainId = *b"tbch"; + + type BlockNumber = TestBridgedBlockNumber; + type Hash = H256; + type Hasher = BlakeTwo256; + type Header = sp_runtime::testing::Header; + + type AccountId = TestAccountId; + type Balance = u64; + type Nonce = u64; + type Signature = Signature; + + fn max_extrinsic_size() -> u32 { + unreachable!() + } + fn max_extrinsic_weight() -> Weight { + unreachable!() + } +} + +impl ChainWithBeefy for TestBridgedChain { + type CommitmentHasher = Keccak256; + type MmrHashing = Keccak256; + type MmrHash = ::Output; + type BeefyMmrLeafExtra = (); + type AuthorityId = BeefyId; + type AuthorityIdToMerkleLeaf = pallet_beefy_mmr::BeefyEcdsaToEthereum; +} + +/// Run test within test runtime. +pub fn run_test(test: impl FnOnce() -> T) -> T { + sp_io::TestExternalities::new(Default::default()).execute_with(test) +} + +/// Initialize pallet and run test. +pub fn run_test_with_initialize(initial_validators_count: u32, test: impl FnOnce() -> T) -> T { + run_test(|| { + let validators = validator_ids(0, initial_validators_count); + let authority_set = authority_set_info(0, &validators); + + crate::Pallet::::initialize( + RuntimeOrigin::root(), + bp_beefy::InitializationData { + operating_mode: BasicOperatingMode::Normal, + best_block_number: 0, + authority_set, + }, + ) + .expect("initialization data is correct"); + + test() + }) +} + +/// Import given commitment. +pub fn import_commitment( + header: crate::mock_chain::HeaderAndCommitment, +) -> sp_runtime::DispatchResult { + crate::Pallet::::submit_commitment( + RuntimeOrigin::signed(1), + header + .commitment + .expect("thou shall not call import_commitment on header without commitment"), + header.validator_set, + Box::new(header.leaf), + header.leaf_proof, + ) +} + +pub fn validator_pairs(index: u32, count: u32) -> Vec { + (index..index + count) + .map(|index| { + let mut seed = [1u8; 32]; + seed[0..8].copy_from_slice(&(index as u64).encode()); + BeefyPair::from_seed(&seed) + }) + .collect() +} + +/// Return identifiers of validators, starting at given index. +pub fn validator_ids(index: u32, count: u32) -> Vec { + validator_pairs(index, count).into_iter().map(|pair| pair.public()).collect() +} + +pub fn authority_set_info(id: u64, validators: &[BeefyId]) -> TestBridgedAuthoritySetInfo { + let merkle_root = get_authorities_mmr_root::(validators.iter()); + + TestBridgedAuthoritySetInfo { id, len: validators.len() as u32, keyset_commitment: merkle_root } +} + +/// Sign BEEFY commitment. +pub fn sign_commitment( + commitment: Commitment, + validator_pairs: &[BeefyPair], + signature_count: usize, +) -> TestBridgedCommitment { + let total_validators = validator_pairs.len(); + let random_validators = + rand::seq::index::sample(&mut rand::thread_rng(), total_validators, signature_count); + + let commitment_hash = TestBridgedCommitmentHasher::hash(&commitment.encode()); + let mut signatures = vec![None; total_validators]; + for validator_idx in random_validators.iter() { + let validator = &validator_pairs[validator_idx]; + signatures[validator_idx] = + Some(validator.as_inner_ref().sign_prehashed(commitment_hash.as_fixed_bytes()).into()); + } + + TestBridgedCommitment { commitment, signatures } +} diff --git a/bridges/modules/beefy/src/mock_chain.rs b/bridges/modules/beefy/src/mock_chain.rs new file mode 100644 index 0000000000000..c83907f839568 --- /dev/null +++ b/bridges/modules/beefy/src/mock_chain.rs @@ -0,0 +1,299 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Utilities to build bridged chain and BEEFY+MMR structures. + +use crate::{ + mock::{ + sign_commitment, validator_pairs, BeefyPair, TestBridgedBlockNumber, TestBridgedCommitment, + TestBridgedHeader, TestBridgedMmrHash, TestBridgedMmrHashing, TestBridgedMmrNode, + TestBridgedMmrProof, TestBridgedRawMmrLeaf, TestBridgedValidatorSet, + TestBridgedValidatorSignature, TestRuntime, + }, + utils::get_authorities_mmr_root, +}; + +use bp_beefy::{BeefyPayload, Commitment, ValidatorSetId, MMR_ROOT_PAYLOAD_ID}; +use codec::Encode; +use pallet_mmr::NodeIndex; +use rand::Rng; +use sp_consensus_beefy::mmr::{BeefyNextAuthoritySet, MmrLeafVersion}; +use sp_core::Pair; +use sp_runtime::traits::{Hash, Header as HeaderT}; +use std::collections::HashMap; + +#[derive(Debug, Clone)] +pub struct HeaderAndCommitment { + pub header: TestBridgedHeader, + pub commitment: Option, + pub validator_set: TestBridgedValidatorSet, + pub leaf: TestBridgedRawMmrLeaf, + pub leaf_proof: TestBridgedMmrProof, + pub mmr_root: TestBridgedMmrHash, +} + +impl HeaderAndCommitment { + pub fn customize_signatures( + &mut self, + f: impl FnOnce(&mut Vec>), + ) { + if let Some(commitment) = &mut self.commitment { + f(&mut commitment.signatures); + } + } + + pub fn customize_commitment( + &mut self, + f: impl FnOnce(&mut Commitment), + validator_pairs: &[BeefyPair], + signature_count: usize, + ) { + if let Some(mut commitment) = self.commitment.take() { + f(&mut commitment.commitment); + self.commitment = + Some(sign_commitment(commitment.commitment, validator_pairs, signature_count)); + } + } +} + +pub struct ChainBuilder { + headers: Vec, + validator_set_id: ValidatorSetId, + validator_keys: Vec, + mmr: mmr_lib::MMR, +} + +struct BridgedMmrStorage { + nodes: HashMap, +} + +impl mmr_lib::MMRStore for BridgedMmrStorage { + fn get_elem(&self, pos: NodeIndex) -> mmr_lib::Result> { + Ok(self.nodes.get(&pos).cloned()) + } + + fn append(&mut self, pos: NodeIndex, elems: Vec) -> mmr_lib::Result<()> { + for (i, elem) in elems.into_iter().enumerate() { + self.nodes.insert(pos + i as NodeIndex, elem); + } + Ok(()) + } +} + +impl ChainBuilder { + /// Creates new chain builder with given validator set size. + pub fn new(initial_validators_count: u32) -> Self { + ChainBuilder { + headers: Vec::new(), + validator_set_id: 0, + validator_keys: validator_pairs(0, initial_validators_count), + mmr: mmr_lib::MMR::new(0, BridgedMmrStorage { nodes: HashMap::new() }), + } + } + + /// Get header with given number. + pub fn header(&self, number: TestBridgedBlockNumber) -> HeaderAndCommitment { + self.headers[number as usize - 1].clone() + } + + /// Returns single built header. + pub fn to_header(&self) -> HeaderAndCommitment { + assert_eq!(self.headers.len(), 1); + self.headers[0].clone() + } + + /// Returns built chain. + pub fn to_chain(&self) -> Vec { + self.headers.clone() + } + + /// Appends header, that has been finalized by BEEFY (so it has a linked signed commitment). + pub fn append_finalized_header(self) -> Self { + let next_validator_set_id = self.validator_set_id; + let next_validator_keys = self.validator_keys.clone(); + HeaderBuilder::with_chain(self, next_validator_set_id, next_validator_keys).finalize() + } + + /// Append multiple finalized headers at once. + pub fn append_finalized_headers(mut self, count: usize) -> Self { + for _ in 0..count { + self = self.append_finalized_header(); + } + self + } + + /// Appends header, that enacts new validator set. + /// + /// Such headers are explicitly finalized by BEEFY. + pub fn append_handoff_header(self, next_validators_len: u32) -> Self { + let new_validator_set_id = self.validator_set_id + 1; + let new_validator_pairs = + validator_pairs(rand::thread_rng().gen::() % (u32::MAX / 2), next_validators_len); + + HeaderBuilder::with_chain(self, new_validator_set_id, new_validator_pairs).finalize() + } + + /// Append several default header without commitment. + pub fn append_default_headers(mut self, count: usize) -> Self { + for _ in 0..count { + let next_validator_set_id = self.validator_set_id; + let next_validator_keys = self.validator_keys.clone(); + self = + HeaderBuilder::with_chain(self, next_validator_set_id, next_validator_keys).build() + } + self + } +} + +/// Custom header builder. +pub struct HeaderBuilder { + chain: ChainBuilder, + header: TestBridgedHeader, + leaf: TestBridgedRawMmrLeaf, + leaf_proof: Option, + next_validator_set_id: ValidatorSetId, + next_validator_keys: Vec, +} + +impl HeaderBuilder { + fn with_chain( + chain: ChainBuilder, + next_validator_set_id: ValidatorSetId, + next_validator_keys: Vec, + ) -> Self { + // we're starting with header#1, since header#0 is always finalized + let header_number = chain.headers.len() as TestBridgedBlockNumber + 1; + let header = TestBridgedHeader::new( + header_number, + Default::default(), + Default::default(), + chain.headers.last().map(|h| h.header.hash()).unwrap_or_default(), + Default::default(), + ); + + let next_validators = + next_validator_keys.iter().map(|pair| pair.public()).collect::>(); + let next_validators_mmr_root = + get_authorities_mmr_root::(next_validators.iter()); + let leaf = sp_consensus_beefy::mmr::MmrLeaf { + version: MmrLeafVersion::new(1, 0), + parent_number_and_hash: (header.number().saturating_sub(1), *header.parent_hash()), + beefy_next_authority_set: BeefyNextAuthoritySet { + id: next_validator_set_id, + len: next_validators.len() as u32, + keyset_commitment: next_validators_mmr_root, + }, + leaf_extra: (), + }; + + HeaderBuilder { + chain, + header, + leaf, + leaf_proof: None, + next_validator_keys, + next_validator_set_id, + } + } + + /// Customize generated proof of header MMR leaf. + /// + /// Can only be called once. + pub fn customize_proof( + mut self, + f: impl FnOnce(TestBridgedMmrProof) -> TestBridgedMmrProof, + ) -> Self { + assert!(self.leaf_proof.is_none()); + + let leaf_hash = TestBridgedMmrHashing::hash(&self.leaf.encode()); + let node = TestBridgedMmrNode::Hash(leaf_hash); + let leaf_position = self.chain.mmr.push(node).unwrap(); + + let proof = self.chain.mmr.gen_proof(vec![leaf_position]).unwrap(); + // genesis has no leaf => leaf index is header number minus 1 + let leaf_index = *self.header.number() - 1; + let leaf_count = *self.header.number(); + self.leaf_proof = Some(f(TestBridgedMmrProof { + leaf_indices: vec![leaf_index], + leaf_count, + items: proof.proof_items().iter().map(|i| i.hash()).collect(), + })); + + self + } + + /// Build header without commitment. + pub fn build(mut self) -> ChainBuilder { + if self.leaf_proof.is_none() { + self = self.customize_proof(|proof| proof); + } + + let validators = + self.chain.validator_keys.iter().map(|pair| pair.public()).collect::>(); + self.chain.headers.push(HeaderAndCommitment { + header: self.header, + commitment: None, + validator_set: TestBridgedValidatorSet::new(validators, self.chain.validator_set_id) + .unwrap(), + leaf: self.leaf, + leaf_proof: self.leaf_proof.expect("guaranteed by the customize_proof call above; qed"), + mmr_root: self.chain.mmr.get_root().unwrap().hash(), + }); + + self.chain.validator_set_id = self.next_validator_set_id; + self.chain.validator_keys = self.next_validator_keys; + + self.chain + } + + /// Build header with commitment. + pub fn finalize(self) -> ChainBuilder { + let validator_count = self.chain.validator_keys.len(); + let current_validator_set_id = self.chain.validator_set_id; + let current_validator_set_keys = self.chain.validator_keys.clone(); + let mut chain = self.build(); + + let last_header = chain.headers.last_mut().expect("added by append_header; qed"); + last_header.commitment = Some(sign_commitment( + Commitment { + payload: BeefyPayload::from_single_entry( + MMR_ROOT_PAYLOAD_ID, + chain.mmr.get_root().unwrap().hash().encode(), + ), + block_number: *last_header.header.number(), + validator_set_id: current_validator_set_id, + }, + ¤t_validator_set_keys, + validator_count * 2 / 3 + 1, + )); + + chain + } +} + +/// Default Merging & Hashing behavior for MMR. +pub struct BridgedMmrHashMerge; + +impl mmr_lib::Merge for BridgedMmrHashMerge { + type Item = TestBridgedMmrNode; + + fn merge(left: &Self::Item, right: &Self::Item) -> mmr_lib::Result { + let mut concat = left.hash().as_ref().to_vec(); + concat.extend_from_slice(right.hash().as_ref()); + + Ok(TestBridgedMmrNode::Hash(TestBridgedMmrHashing::hash(&concat))) + } +} diff --git a/bridges/modules/beefy/src/utils.rs b/bridges/modules/beefy/src/utils.rs new file mode 100644 index 0000000000000..ce7a116308d16 --- /dev/null +++ b/bridges/modules/beefy/src/utils.rs @@ -0,0 +1,361 @@ +use crate::{ + BridgedBeefyAuthorityId, BridgedBeefyAuthoritySet, BridgedBeefyAuthoritySetInfo, + BridgedBeefyMmrLeaf, BridgedBeefySignedCommitment, BridgedChain, BridgedMmrHash, + BridgedMmrHashing, BridgedMmrProof, Config, Error, LOG_TARGET, +}; +use bp_beefy::{merkle_root, verify_mmr_leaves_proof, BeefyAuthorityId, MmrDataOrHash}; +use codec::Encode; +use frame_support::ensure; +use sp_runtime::traits::{Convert, Hash}; +use sp_std::{vec, vec::Vec}; + +type BridgedMmrDataOrHash = MmrDataOrHash, BridgedBeefyMmrLeaf>; +/// A way to encode validator id to the BEEFY merkle tree leaf. +type BridgedBeefyAuthorityIdToMerkleLeaf = + bp_beefy::BeefyAuthorityIdToMerkleLeafOf>; + +/// Get the MMR root for a collection of validators. +pub(crate) fn get_authorities_mmr_root< + 'a, + T: Config, + I: 'static, + V: Iterator>, +>( + authorities: V, +) -> BridgedMmrHash { + let merkle_leafs = authorities + .cloned() + .map(BridgedBeefyAuthorityIdToMerkleLeaf::::convert) + .collect::>(); + merkle_root::, _>(merkle_leafs) +} + +fn verify_authority_set, I: 'static>( + authority_set_info: &BridgedBeefyAuthoritySetInfo, + authority_set: &BridgedBeefyAuthoritySet, +) -> Result<(), Error> { + ensure!(authority_set.id() == authority_set_info.id, Error::::InvalidValidatorSetId); + ensure!( + authority_set.len() == authority_set_info.len as usize, + Error::::InvalidValidatorSetLen + ); + + // Ensure that the authority set that signed the commitment is the expected one. + let root = get_authorities_mmr_root::(authority_set.validators().iter()); + ensure!(root == authority_set_info.keyset_commitment, Error::::InvalidValidatorSetRoot); + + Ok(()) +} + +/// Number of correct signatures, required from given validators set to accept signed +/// commitment. +/// +/// We're using 'conservative' approach here, where signatures of `2/3+1` validators are +/// required.. +pub(crate) fn signatures_required(validators_len: usize) -> usize { + validators_len - validators_len.saturating_sub(1) / 3 +} + +fn verify_signatures, I: 'static>( + commitment: &BridgedBeefySignedCommitment, + authority_set: &BridgedBeefyAuthoritySet, +) -> Result<(), Error> { + ensure!( + commitment.signatures.len() == authority_set.len(), + Error::::InvalidCommitmentSignaturesLen + ); + + // Ensure that the commitment was signed by enough authorities. + let msg = commitment.commitment.encode(); + let mut missing_signatures = signatures_required(authority_set.len()); + for (idx, (authority, maybe_sig)) in + authority_set.validators().iter().zip(commitment.signatures.iter()).enumerate() + { + if let Some(sig) = maybe_sig { + if authority.verify(sig, &msg) { + missing_signatures = missing_signatures.saturating_sub(1); + if missing_signatures == 0 { + break + } + } else { + log::debug!( + target: LOG_TARGET, + "Signed commitment contains incorrect signature of validator {} ({:?}): {:?}", + idx, + authority, + sig, + ); + } + } + } + ensure!(missing_signatures == 0, Error::::NotEnoughCorrectSignatures); + + Ok(()) +} + +/// Extract MMR root from commitment payload. +fn extract_mmr_root, I: 'static>( + commitment: &BridgedBeefySignedCommitment, +) -> Result, Error> { + commitment + .commitment + .payload + .get_decoded(&bp_beefy::MMR_ROOT_PAYLOAD_ID) + .ok_or(Error::MmrRootMissingFromCommitment) +} + +pub(crate) fn verify_commitment, I: 'static>( + commitment: &BridgedBeefySignedCommitment, + authority_set_info: &BridgedBeefyAuthoritySetInfo, + authority_set: &BridgedBeefyAuthoritySet, +) -> Result, Error> { + // Ensure that the commitment is signed by the best known BEEFY validator set. + ensure!( + commitment.commitment.validator_set_id == authority_set_info.id, + Error::::InvalidCommitmentValidatorSetId + ); + ensure!( + commitment.signatures.len() == authority_set_info.len as usize, + Error::::InvalidCommitmentSignaturesLen + ); + + verify_authority_set(authority_set_info, authority_set)?; + verify_signatures(commitment, authority_set)?; + + extract_mmr_root(commitment) +} + +/// Verify MMR proof of given leaf. +pub(crate) fn verify_beefy_mmr_leaf, I: 'static>( + mmr_leaf: &BridgedBeefyMmrLeaf, + mmr_proof: BridgedMmrProof, + mmr_root: BridgedMmrHash, +) -> Result<(), Error> { + let mmr_proof_leaf_count = mmr_proof.leaf_count; + let mmr_proof_length = mmr_proof.items.len(); + + // Verify the mmr proof for the provided leaf. + let mmr_leaf_hash = BridgedMmrHashing::::hash(&mmr_leaf.encode()); + verify_mmr_leaves_proof( + mmr_root, + vec![BridgedMmrDataOrHash::::Hash(mmr_leaf_hash)], + mmr_proof, + ) + .map_err(|e| { + log::error!( + target: LOG_TARGET, + "MMR proof of leaf {:?} (root: {:?}, leaf count: {}, len: {}) \ + verification has failed with error: {:?}", + mmr_leaf_hash, + mmr_root, + mmr_proof_leaf_count, + mmr_proof_length, + e, + ); + + Error::::MmrProofVerificationFailed + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{mock::*, mock_chain::*, *}; + use bp_beefy::{BeefyPayload, MMR_ROOT_PAYLOAD_ID}; + use frame_support::{assert_noop, assert_ok}; + use sp_consensus_beefy::ValidatorSet; + + #[test] + fn submit_commitment_checks_metadata() { + run_test_with_initialize(8, || { + // Fails if `commitment.commitment.validator_set_id` differs. + let mut header = ChainBuilder::new(8).append_finalized_header().to_header(); + header.customize_commitment( + |commitment| { + commitment.validator_set_id += 1; + }, + &validator_pairs(0, 8), + 6, + ); + assert_noop!( + import_commitment(header), + Error::::InvalidCommitmentValidatorSetId, + ); + + // Fails if `commitment.signatures.len()` differs. + let mut header = ChainBuilder::new(8).append_finalized_header().to_header(); + header.customize_signatures(|signatures| { + signatures.pop(); + }); + assert_noop!( + import_commitment(header), + Error::::InvalidCommitmentSignaturesLen, + ); + }); + } + + #[test] + fn submit_commitment_checks_validator_set() { + run_test_with_initialize(8, || { + // Fails if `ValidatorSet::id` differs. + let mut header = ChainBuilder::new(8).append_finalized_header().to_header(); + header.validator_set = ValidatorSet::new(validator_ids(0, 8), 1).unwrap(); + assert_noop!( + import_commitment(header), + Error::::InvalidValidatorSetId, + ); + + // Fails if `ValidatorSet::len()` differs. + let mut header = ChainBuilder::new(8).append_finalized_header().to_header(); + header.validator_set = ValidatorSet::new(validator_ids(0, 5), 0).unwrap(); + assert_noop!( + import_commitment(header), + Error::::InvalidValidatorSetLen, + ); + + // Fails if the validators differ. + let mut header = ChainBuilder::new(8).append_finalized_header().to_header(); + header.validator_set = ValidatorSet::new(validator_ids(3, 8), 0).unwrap(); + assert_noop!( + import_commitment(header), + Error::::InvalidValidatorSetRoot, + ); + }); + } + + #[test] + fn submit_commitment_checks_signatures() { + run_test_with_initialize(20, || { + // Fails when there aren't enough signatures. + let mut header = ChainBuilder::new(20).append_finalized_header().to_header(); + header.customize_signatures(|signatures| { + let first_signature_idx = signatures.iter().position(Option::is_some).unwrap(); + signatures[first_signature_idx] = None; + }); + assert_noop!( + import_commitment(header), + Error::::NotEnoughCorrectSignatures, + ); + + // Fails when there aren't enough correct signatures. + let mut header = ChainBuilder::new(20).append_finalized_header().to_header(); + header.customize_signatures(|signatures| { + let first_signature_idx = signatures.iter().position(Option::is_some).unwrap(); + let last_signature_idx = signatures.len() - + signatures.iter().rev().position(Option::is_some).unwrap() - + 1; + signatures[first_signature_idx] = signatures[last_signature_idx].clone(); + }); + assert_noop!( + import_commitment(header), + Error::::NotEnoughCorrectSignatures, + ); + + // Returns Ok(()) when there are enough signatures, even if some are incorrect. + let mut header = ChainBuilder::new(20).append_finalized_header().to_header(); + header.customize_signatures(|signatures| { + let first_signature_idx = signatures.iter().position(Option::is_some).unwrap(); + let first_missing_signature_idx = + signatures.iter().position(Option::is_none).unwrap(); + signatures[first_missing_signature_idx] = signatures[first_signature_idx].clone(); + }); + assert_ok!(import_commitment(header)); + }); + } + + #[test] + fn submit_commitment_checks_mmr_proof() { + run_test_with_initialize(1, || { + let validators = validator_pairs(0, 1); + + // Fails if leaf is not for parent. + let mut header = ChainBuilder::new(1).append_finalized_header().to_header(); + header.leaf.parent_number_and_hash.0 += 1; + assert_noop!( + import_commitment(header), + Error::::MmrProofVerificationFailed, + ); + + // Fails if mmr proof is incorrect. + let mut header = ChainBuilder::new(1).append_finalized_header().to_header(); + header.leaf_proof.leaf_indices[0] += 1; + assert_noop!( + import_commitment(header), + Error::::MmrProofVerificationFailed, + ); + + // Fails if mmr root is incorrect. + let mut header = ChainBuilder::new(1).append_finalized_header().to_header(); + // Replace MMR root with zeroes. + header.customize_commitment( + |commitment| { + commitment.payload = + BeefyPayload::from_single_entry(MMR_ROOT_PAYLOAD_ID, [0u8; 32].encode()); + }, + &validators, + 1, + ); + assert_noop!( + import_commitment(header), + Error::::MmrProofVerificationFailed, + ); + }); + } + + #[test] + fn submit_commitment_extracts_mmr_root() { + run_test_with_initialize(1, || { + let validators = validator_pairs(0, 1); + + // Fails if there is no mmr root in the payload. + let mut header = ChainBuilder::new(1).append_finalized_header().to_header(); + // Remove MMR root from the payload. + header.customize_commitment( + |commitment| { + commitment.payload = BeefyPayload::from_single_entry(*b"xy", vec![]); + }, + &validators, + 1, + ); + assert_noop!( + import_commitment(header), + Error::::MmrRootMissingFromCommitment, + ); + + // Fails if mmr root can't be decoded. + let mut header = ChainBuilder::new(1).append_finalized_header().to_header(); + // MMR root is a 32-byte array and we have replaced it with single byte + header.customize_commitment( + |commitment| { + commitment.payload = + BeefyPayload::from_single_entry(MMR_ROOT_PAYLOAD_ID, vec![42]); + }, + &validators, + 1, + ); + assert_noop!( + import_commitment(header), + Error::::MmrRootMissingFromCommitment, + ); + }); + } + + #[test] + fn submit_commitment_stores_valid_data() { + run_test_with_initialize(20, || { + let header = ChainBuilder::new(20).append_handoff_header(30).to_header(); + assert_ok!(import_commitment(header.clone())); + + assert_eq!(ImportedCommitmentsInfo::::get().unwrap().best_block_number, 1); + assert_eq!(CurrentAuthoritySetInfo::::get().id, 1); + assert_eq!(CurrentAuthoritySetInfo::::get().len, 30); + assert_eq!( + ImportedCommitments::::get(1).unwrap(), + bp_beefy::ImportedCommitment { + parent_number_and_hash: (0, [0; 32].into()), + mmr_root: header.mmr_root, + }, + ); + }); + } +} diff --git a/bridges/modules/grandpa/Cargo.toml b/bridges/modules/grandpa/Cargo.toml index 0db1827211a05..b3deefc879231 100644 --- a/bridges/modules/grandpa/Cargo.toml +++ b/bridges/modules/grandpa/Cargo.toml @@ -25,20 +25,20 @@ bp-header-chain = { path = "../../primitives/header-chain", default-features = f # Substrate Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../substrate/frame/system", default-features = false } -sp-consensus-grandpa = { path = "../../../substrate/primitives/consensus/grandpa", default-features = false, features = ["serde"] } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false, features = ["serde"] } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } -sp-trie = { path = "../../../substrate/primitives/trie", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false, features = ["serde"] } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false, features = ["serde"] } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-trie = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } # Optional Benchmarking Dependencies bp-test-utils = { path = "../../primitives/test-utils", default-features = false, optional = true } -frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false, optional = true } [dev-dependencies] -sp-core = { path = "../../../substrate/primitives/core" } -sp-io = { path = "../../../substrate/primitives/io" } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sp-io = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } [features] default = ["std"] diff --git a/bridges/modules/messages/Cargo.toml b/bridges/modules/messages/Cargo.toml index df5b92db7402b..24ad437be09da 100644 --- a/bridges/modules/messages/Cargo.toml +++ b/bridges/modules/messages/Cargo.toml @@ -23,16 +23,16 @@ bp-runtime = { path = "../../primitives/runtime", default-features = false } # Substrate Dependencies -frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } -frame-support = { path = "../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../substrate/frame/system", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false, optional = true } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [dev-dependencies] bp-test-utils = { path = "../../primitives/test-utils" } -pallet-balances = { path = "../../../substrate/frame/balances" } -sp-io = { path = "../../../substrate/primitives/io" } +pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sp-io = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } [features] default = ["std"] diff --git a/bridges/modules/parachains/Cargo.toml b/bridges/modules/parachains/Cargo.toml index 35213be0674a8..6352b21b8f8b5 100644 --- a/bridges/modules/parachains/Cargo.toml +++ b/bridges/modules/parachains/Cargo.toml @@ -25,18 +25,18 @@ pallet-bridge-grandpa = { path = "../grandpa", default-features = false } # Substrate Dependencies -frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } -frame-support = { path = "../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../substrate/frame/system", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } -sp-trie = { path = "../../../substrate/primitives/trie", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false, optional = true } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-trie = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [dev-dependencies] bp-header-chain = { path = "../../primitives/header-chain" } bp-test-utils = { path = "../../primitives/test-utils" } -sp-core = { path = "../../../substrate/primitives/core" } -sp-io = { path = "../../../substrate/primitives/io" } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sp-io = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } [features] default = ["std"] diff --git a/bridges/modules/relayers/Cargo.toml b/bridges/modules/relayers/Cargo.toml index e2b7aca92249c..ae57e36f7fb1e 100644 --- a/bridges/modules/relayers/Cargo.toml +++ b/bridges/modules/relayers/Cargo.toml @@ -24,18 +24,18 @@ pallet-bridge-messages = { path = "../messages", default-features = false } # Substrate Dependencies -frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } -frame-support = { path = "../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../substrate/frame/system", default-features = false } -sp-arithmetic = { path = "../../../substrate/primitives/arithmetic", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false, optional = true } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-arithmetic = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [dev-dependencies] bp-runtime = { path = "../../primitives/runtime" } -pallet-balances = { path = "../../../substrate/frame/balances" } -sp-io = { path = "../../../substrate/primitives/io" } -sp-runtime = { path = "../../../substrate/primitives/runtime" } +pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sp-io = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } [features] default = ["std"] diff --git a/bridges/modules/xcm-bridge-hub-router/Cargo.toml b/bridges/modules/xcm-bridge-hub-router/Cargo.toml index 06f2a339bed9d..af130c5e7b37e 100644 --- a/bridges/modules/xcm-bridge-hub-router/Cargo.toml +++ b/bridges/modules/xcm-bridge-hub-router/Cargo.toml @@ -21,21 +21,21 @@ bp-xcm-bridge-hub-router = { path = "../../primitives/xcm-bridge-hub-router", de # Substrate Dependencies -frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } -frame-support = { path = "../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../substrate/frame/system", default-features = false } -sp-core = { path = "../../../substrate/primitives/core", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false, optional = true } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } # Polkadot Dependencies -xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false } -xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/xcm-builder", default-features = false } +xcm = { package = "staging-xcm", git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +xcm-builder = { package = "staging-xcm-builder", git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [dev-dependencies] -sp-io = { path = "../../../substrate/primitives/io" } -sp-std = { path = "../../../substrate/primitives/std" } +sp-io = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } [features] default = ["std"] diff --git a/bridges/modules/xcm-bridge-hub/Cargo.toml b/bridges/modules/xcm-bridge-hub/Cargo.toml index 4483a3790900f..d7e5625309142 100644 --- a/bridges/modules/xcm-bridge-hub/Cargo.toml +++ b/bridges/modules/xcm-bridge-hub/Cargo.toml @@ -23,21 +23,21 @@ pallet-bridge-messages = { path = "../messages", default-features = false } bridge-runtime-common = { path = "../../bin/runtime-common", default-features = false } # Substrate Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../substrate/frame/system", default-features = false } -sp-core = { path = "../../../substrate/primitives/core", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } # Polkadot Dependencies -xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false } -xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/xcm-builder", default-features = false } -xcm-executor = { package = "staging-xcm-executor", path = "../../../polkadot/xcm/xcm-executor", default-features = false } +xcm = { package = "staging-xcm", git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +xcm-builder = { package = "staging-xcm-builder", git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +xcm-executor = { package = "staging-xcm-executor", git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [dev-dependencies] bp-header-chain = { path = "../../primitives/header-chain" } -pallet-balances = { path = "../../../substrate/frame/balances" } -sp-io = { path = "../../../substrate/primitives/io" } +pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sp-io = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } [features] default = ["std"] diff --git a/bridges/primitives/beefy/Cargo.toml b/bridges/primitives/beefy/Cargo.toml new file mode 100644 index 0000000000000..f1992e59b709a --- /dev/null +++ b/bridges/primitives/beefy/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "bp-beefy" +description = "Primitives of pallet-bridge-beefy module." +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +repository.workspace = true +publish = false + +[lints] +workspace = true + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["bit-vec", "derive"] } +scale-info = { version = "2.11.1", default-features = false, features = ["bit-vec", "derive"] } +serde = { default-features = false, features = ["alloc", "derive"], workspace = true } + +# Bridge Dependencies + +bp-runtime = { path = "../runtime", default-features = false } + +# Substrate Dependencies + +binary-merkle-tree = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-consensus-beefy = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +pallet-beefy-mmr = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +pallet-mmr = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } + +[features] +default = ["std"] +std = [ + "binary-merkle-tree/std", + "bp-runtime/std", + "codec/std", + "frame-support/std", + "pallet-beefy-mmr/std", + "pallet-mmr/std", + "scale-info/std", + "serde/std", + "sp-consensus-beefy/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/bridges/primitives/beefy/src/lib.rs b/bridges/primitives/beefy/src/lib.rs new file mode 100644 index 0000000000000..0441781e79a66 --- /dev/null +++ b/bridges/primitives/beefy/src/lib.rs @@ -0,0 +1,151 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Primitives that are used to interact with BEEFY bridge pallet. + +#![cfg_attr(not(feature = "std"), no_std)] +#![warn(missing_docs)] + +pub use binary_merkle_tree::merkle_root; +pub use pallet_beefy_mmr::BeefyEcdsaToEthereum; +pub use pallet_mmr::{ + primitives::{DataOrHash as MmrDataOrHash, Proof as MmrProof}, + verify_leaves_proof as verify_mmr_leaves_proof, +}; +pub use sp_consensus_beefy::{ + ecdsa_crypto::{ + AuthorityId as EcdsaValidatorId, AuthoritySignature as EcdsaValidatorSignature, + }, + known_payloads::MMR_ROOT_ID as MMR_ROOT_PAYLOAD_ID, + mmr::{BeefyAuthoritySet, MmrLeafVersion}, + BeefyAuthorityId, Commitment, Payload as BeefyPayload, SignedCommitment, ValidatorSet, + ValidatorSetId, BEEFY_ENGINE_ID, +}; + +use bp_runtime::{BasicOperatingMode, BlockNumberOf, Chain, HashOf}; +use codec::{Decode, Encode}; +use frame_support::Parameter; +use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; +use sp_runtime::{ + traits::{Convert, MaybeSerializeDeserialize}, + RuntimeAppPublic, RuntimeDebug, +}; +use sp_std::prelude::*; + +/// Substrate-based chain with BEEFY && MMR pallets deployed. +/// +/// Both BEEFY and MMR pallets and their clients may be configured to use different +/// primitives. Some of types can be configured in low-level pallets, but are constrained +/// when BEEFY+MMR bundle is used. +pub trait ChainWithBeefy: Chain { + /// The hashing algorithm used to compute the digest of the BEEFY commitment. + /// + /// Corresponds to the hashing algorithm, used by `sc_consensus_beefy::BeefyKeystore`. + type CommitmentHasher: sp_runtime::traits::Hash; + + /// The hashing algorithm used to build the MMR. + /// + /// The same algorithm is also used to compute merkle roots in BEEFY + /// (e.g. validator addresses root in leaf data). + /// + /// Corresponds to the `Hashing` field of the `pallet-mmr` configuration. + type MmrHashing: sp_runtime::traits::Hash; + + /// The output type of the hashing algorithm used to build the MMR. + /// + /// This type is actually stored in the MMR. + + /// Corresponds to the `Hash` field of the `pallet-mmr` configuration. + type MmrHash: sp_std::hash::Hash + + Parameter + + Copy + + AsRef<[u8]> + + Default + + MaybeSerializeDeserialize + + PartialOrd; + + /// The type expected for the MMR leaf extra data. + type BeefyMmrLeafExtra: Parameter; + + /// A way to identify a BEEFY validator. + /// + /// Corresponds to the `BeefyId` field of the `pallet-beefy` configuration. + type AuthorityId: BeefyAuthorityId + Parameter; + + /// A way to convert validator id to its raw representation in the BEEFY merkle tree. + /// + /// Corresponds to the `BeefyAuthorityToMerkleLeaf` field of the `pallet-beefy-mmr` + /// configuration. + type AuthorityIdToMerkleLeaf: Convert>; +} + +/// BEEFY validator id used by given Substrate chain. +pub type BeefyAuthorityIdOf = ::AuthorityId; +/// BEEFY validator set, containing both validator identifiers and the numeric set id. +pub type BeefyAuthoritySetOf = ValidatorSet>; +/// BEEFY authority set, containing both validator identifiers and the numeric set id. +pub type BeefyAuthoritySetInfoOf = sp_consensus_beefy::mmr::BeefyAuthoritySet>; +/// BEEFY validator signature used by given Substrate chain. +pub type BeefyValidatorSignatureOf = + <::AuthorityId as RuntimeAppPublic>::Signature; +/// Signed BEEFY commitment used by given Substrate chain. +pub type BeefySignedCommitmentOf = + SignedCommitment, BeefyValidatorSignatureOf>; +/// Hash algorithm, used to compute the digest of the BEEFY commitment before signing it. +pub type BeefyCommitmentHasher = ::CommitmentHasher; +/// Hash algorithm used in Beefy MMR construction by given Substrate chain. +pub type MmrHashingOf = ::MmrHashing; +/// Hash type, used in MMR construction by given Substrate chain. +pub type MmrHashOf = ::MmrHash; +/// BEEFY MMR proof type used by the given Substrate chain. +pub type MmrProofOf = MmrProof>; +/// The type of the MMR leaf extra data used by the given Substrate chain. +pub type BeefyMmrLeafExtraOf = ::BeefyMmrLeafExtra; +/// A way to convert a validator id to its raw representation in the BEEFY merkle tree, used by +/// the given Substrate chain. +pub type BeefyAuthorityIdToMerkleLeafOf = ::AuthorityIdToMerkleLeaf; +/// Actual type of leafs in the BEEFY MMR. +pub type BeefyMmrLeafOf = sp_consensus_beefy::mmr::MmrLeaf< + BlockNumberOf, + HashOf, + MmrHashOf, + BeefyMmrLeafExtraOf, +>; + +/// Data required for initializing the BEEFY pallet. +/// +/// Provides the initial context that the bridge needs in order to know +/// where to start the sync process from. +#[derive(Encode, Decode, RuntimeDebug, PartialEq, Clone, TypeInfo, Serialize, Deserialize)] +pub struct InitializationData { + /// Pallet operating mode. + pub operating_mode: BasicOperatingMode, + /// Number of the best block, finalized by BEEFY. + pub best_block_number: BlockNumber, + /// BEEFY authority set that will be finalizing descendants of the `best_beefy_block_number` + /// block. + pub authority_set: BeefyAuthoritySet, +} + +/// Basic data, stored by the pallet for every imported commitment. +#[derive(Encode, Decode, RuntimeDebug, PartialEq, TypeInfo)] +pub struct ImportedCommitment { + /// Block number and hash of the finalized block parent. + pub parent_number_and_hash: (BlockNumber, BlockHash), + /// MMR root at the imported block. + pub mmr_root: MmrHash, +} diff --git a/bridges/primitives/header-chain/Cargo.toml b/bridges/primitives/header-chain/Cargo.toml index f7a61a9ff32bd..f38d754544bba 100644 --- a/bridges/primitives/header-chain/Cargo.toml +++ b/bridges/primitives/header-chain/Cargo.toml @@ -22,11 +22,11 @@ bp-runtime = { path = "../runtime", default-features = false } # Substrate Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -sp-core = { path = "../../../substrate/primitives/core", default-features = false, features = ["serde"] } -sp-consensus-grandpa = { path = "../../../substrate/primitives/consensus/grandpa", default-features = false, features = ["serde"] } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false, features = ["serde"] } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false, features = ["serde"] } +sp-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false, features = ["serde"] } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false, features = ["serde"] } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [dev-dependencies] bp-test-utils = { path = "../test-utils" } diff --git a/bridges/primitives/messages/Cargo.toml b/bridges/primitives/messages/Cargo.toml index d41acfb9d3286..8bacff709eb4e 100644 --- a/bridges/primitives/messages/Cargo.toml +++ b/bridges/primitives/messages/Cargo.toml @@ -22,9 +22,9 @@ bp-header-chain = { path = "../header-chain", default-features = false } # Substrate Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -sp-core = { path = "../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [dev-dependencies] hex = "0.4" diff --git a/bridges/primitives/parachains/Cargo.toml b/bridges/primitives/parachains/Cargo.toml index 2e7000b86a5e4..1606dbfcd6423 100644 --- a/bridges/primitives/parachains/Cargo.toml +++ b/bridges/primitives/parachains/Cargo.toml @@ -23,10 +23,10 @@ bp-runtime = { path = "../runtime", default-features = false } # Substrate dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -sp-core = { path = "../../../substrate/primitives/core", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [features] default = ["std"] diff --git a/bridges/primitives/polkadot-core/Cargo.toml b/bridges/primitives/polkadot-core/Cargo.toml index 53b1e574cb199..b85586405ff32 100644 --- a/bridges/primitives/polkadot-core/Cargo.toml +++ b/bridges/primitives/polkadot-core/Cargo.toml @@ -23,11 +23,11 @@ bp-runtime = { path = "../runtime", default-features = false } # Substrate Based Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../substrate/frame/system", default-features = false } -sp-core = { path = "../../../substrate/primitives/core", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [dev-dependencies] hex = "0.4" diff --git a/bridges/primitives/relayers/Cargo.toml b/bridges/primitives/relayers/Cargo.toml index 1be7f1dc6ebd3..46bc034ef1154 100644 --- a/bridges/primitives/relayers/Cargo.toml +++ b/bridges/primitives/relayers/Cargo.toml @@ -21,9 +21,9 @@ bp-runtime = { path = "../runtime", default-features = false } # Substrate Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [dev-dependencies] hex = "0.4" diff --git a/bridges/primitives/runtime/Cargo.toml b/bridges/primitives/runtime/Cargo.toml index cca9c21a608d7..258b576828665 100644 --- a/bridges/primitives/runtime/Cargo.toml +++ b/bridges/primitives/runtime/Cargo.toml @@ -21,14 +21,14 @@ serde = { features = ["alloc", "derive"], workspace = true } # Substrate Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../substrate/frame/system", default-features = false } -sp-core = { path = "../../../substrate/primitives/core", default-features = false } -sp-io = { path = "../../../substrate/primitives/io", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false, features = ["serde"] } -sp-state-machine = { path = "../../../substrate/primitives/state-machine", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } -sp-trie = { path = "../../../substrate/primitives/trie", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-io = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false, features = ["serde"] } +sp-state-machine = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-trie = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } trie-db = { version = "0.28.0", default-features = false } [dev-dependencies] diff --git a/bridges/primitives/test-utils/Cargo.toml b/bridges/primitives/test-utils/Cargo.toml index d314c38683cdb..b46868a0a599c 100644 --- a/bridges/primitives/test-utils/Cargo.toml +++ b/bridges/primitives/test-utils/Cargo.toml @@ -18,12 +18,12 @@ bp-runtime = { path = "../runtime", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } ed25519-dalek = { version = "2.1", default-features = false } finality-grandpa = { version = "0.16.2", default-features = false } -sp-application-crypto = { path = "../../../substrate/primitives/application-crypto", default-features = false } -sp-consensus-grandpa = { path = "../../../substrate/primitives/consensus/grandpa", default-features = false } -sp-core = { path = "../../../substrate/primitives/core", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false } -sp-trie = { path = "../../../substrate/primitives/trie", default-features = false } +sp-application-crypto = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-trie = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [features] default = ["std"] diff --git a/bridges/primitives/xcm-bridge-hub-router/Cargo.toml b/bridges/primitives/xcm-bridge-hub-router/Cargo.toml index 94eece16d5797..c3fe409b68c20 100644 --- a/bridges/primitives/xcm-bridge-hub-router/Cargo.toml +++ b/bridges/primitives/xcm-bridge-hub-router/Cargo.toml @@ -15,8 +15,8 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = scale-info = { version = "2.11.1", default-features = false, features = ["bit-vec", "derive"] } # Substrate Dependencies -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } -sp-core = { path = "../../../substrate/primitives/core", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [features] default = ["std"] diff --git a/bridges/primitives/xcm-bridge-hub/Cargo.toml b/bridges/primitives/xcm-bridge-hub/Cargo.toml index 27881bc99d1f8..9043071002a91 100644 --- a/bridges/primitives/xcm-bridge-hub/Cargo.toml +++ b/bridges/primitives/xcm-bridge-hub/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] # Substrate Dependencies -sp-std = { path = "../../../substrate/primitives/std", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [features] default = ["std"] diff --git a/bridges/relays/client-substrate/Cargo.toml b/bridges/relays/client-substrate/Cargo.toml new file mode 100644 index 0000000000000..85ebce1f9b97f --- /dev/null +++ b/bridges/relays/client-substrate/Cargo.toml @@ -0,0 +1,61 @@ +[package] +name = "relay-substrate-client" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +repository.workspace = true +publish = false + +[lints] +workspace = true + +[dependencies] +async-std = { version = "1.9.0", features = ["attributes"] } +async-trait = "0.1.79" +codec = { package = "parity-scale-codec", version = "3.6.1" } +futures = "0.3.30" +jsonrpsee = { version = "0.22", features = ["macros", "ws-client"] } +log = { workspace = true } +num-traits = "0.2" +rand = "0.8.5" +scale-info = { version = "2.11.1", features = ["derive"] } +tokio = { version = "1.37", features = ["rt-multi-thread"] } +thiserror = { workspace = true } + +# Bridge dependencies + +bp-header-chain = { path = "../../primitives/header-chain" } +bp-messages = { path = "../../primitives/messages" } +bp-polkadot-core = { path = "../../primitives/polkadot-core" } +bp-runtime = { path = "../../primitives/runtime" } +pallet-bridge-messages = { path = "../../modules/messages" } +finality-relay = { path = "../finality" } +relay-utils = { path = "../utils" } + +# Substrate Dependencies + +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +pallet-utility = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sc-chain-spec = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sc-rpc-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sc-transaction-pool-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sp-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sp-rpc = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sp-trie = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sp-version = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } + +# Polkadot Dependencies + +xcm = { package = "staging-xcm", git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } + +[features] +default = [] +test-helpers = [] diff --git a/bridges/relays/client-substrate/src/calls.rs b/bridges/relays/client-substrate/src/calls.rs new file mode 100644 index 0000000000000..71b9ec84aca30 --- /dev/null +++ b/bridges/relays/client-substrate/src/calls.rs @@ -0,0 +1,59 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Basic runtime calls. + +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_std::{boxed::Box, vec::Vec}; + +use xcm::{VersionedLocation, VersionedXcm}; + +/// A minimized version of `frame-system::Call` that can be used without a runtime. +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +#[allow(non_camel_case_types)] +pub enum SystemCall { + /// `frame-system::Call::remark` + #[codec(index = 1)] + remark(Vec), +} + +/// A minimized version of `pallet-utility::Call` that can be used without a runtime. +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +#[allow(non_camel_case_types)] +pub enum UtilityCall { + /// `pallet-utility::Call::batch_all` + #[codec(index = 2)] + batch_all(Vec), +} + +/// A minimized version of `pallet-sudo::Call` that can be used without a runtime. +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +#[allow(non_camel_case_types)] +pub enum SudoCall { + /// `pallet-sudo::Call::sudo` + #[codec(index = 0)] + sudo(Box), +} + +/// A minimized version of `pallet-xcm::Call`, that can be used without a runtime. +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +#[allow(non_camel_case_types)] +pub enum XcmCall { + /// `pallet-xcm::Call::send` + #[codec(index = 0)] + send(Box, Box>), +} diff --git a/bridges/relays/client-substrate/src/chain.rs b/bridges/relays/client-substrate/src/chain.rs new file mode 100644 index 0000000000000..2aba5f5674d97 --- /dev/null +++ b/bridges/relays/client-substrate/src/chain.rs @@ -0,0 +1,286 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +use crate::calls::UtilityCall; + +use crate::SimpleRuntimeVersion; +use bp_header_chain::ChainWithGrandpa as ChainWithGrandpaBase; +use bp_messages::ChainWithMessages as ChainWithMessagesBase; +use bp_runtime::{ + Chain as ChainBase, EncodedOrDecodedCall, HashOf, Parachain as ParachainBase, TransactionEra, + TransactionEraOf, UnderlyingChainProvider, +}; +use codec::{Codec, Decode, Encode}; +use jsonrpsee::core::{DeserializeOwned, Serialize}; +use num_traits::Zero; +use sc_transaction_pool_api::TransactionStatus; +use scale_info::TypeInfo; +use sp_core::{storage::StorageKey, Pair}; +use sp_runtime::{ + generic::SignedBlock, + traits::{Block as BlockT, Member}, + ConsensusEngineId, EncodedJustification, +}; +use std::{fmt::Debug, time::Duration}; + +/// Substrate-based chain from minimal relay-client point of view. +pub trait Chain: ChainBase + Clone { + /// Chain name. + const NAME: &'static str; + /// Name of the runtime API method that is returning best known finalized header number + /// and hash (as tuple). + /// + /// Keep in mind that this method is normally provided by the other chain, which is + /// bridged with this chain. + const BEST_FINALIZED_HEADER_ID_METHOD: &'static str; + + /// Average block interval. + /// + /// How often blocks are produced on that chain. It's suggested to set this value + /// to match the block time of the chain. + const AVERAGE_BLOCK_INTERVAL: Duration; + + /// Block type. + type SignedBlock: Member + Serialize + DeserializeOwned + BlockWithJustification; + /// The aggregated `Call` type. + type Call: Clone + Codec + Debug + Send + Sync; +} + +/// Bridge-supported network definition. +/// +/// Used to abstract away CLI commands. +pub trait ChainWithRuntimeVersion: Chain { + /// Current version of the chain runtime, known to relay. + /// + /// can be `None` if relay is not going to submit transactions to that chain. + const RUNTIME_VERSION: Option; +} + +/// Substrate-based relay chain that supports parachains. +/// +/// We assume that the parachains are supported using `runtime_parachains::paras` pallet. +pub trait RelayChain: Chain { + /// Name of the `runtime_parachains::paras` pallet in the runtime of this chain. + const PARAS_PALLET_NAME: &'static str; +} + +/// Substrate-based chain that is using direct GRANDPA finality from minimal relay-client point of +/// view. +/// +/// Keep in mind that parachains are relying on relay chain GRANDPA, so they should not implement +/// this trait. +pub trait ChainWithGrandpa: Chain + ChainWithGrandpaBase { + /// Name of the runtime API method that is returning the GRANDPA info associated with the + /// headers accepted by the `submit_finality_proofs` extrinsic in the queried block. + /// + /// Keep in mind that this method is normally provided by the other chain, which is + /// bridged with this chain. + const SYNCED_HEADERS_GRANDPA_INFO_METHOD: &'static str; + + /// The type of the key owner proof used by the grandpa engine. + type KeyOwnerProof: Decode + TypeInfo + Send; +} + +/// Substrate-based parachain from minimal relay-client point of view. +pub trait Parachain: Chain + ParachainBase {} + +impl Parachain for T where T: UnderlyingChainProvider + Chain + ParachainBase {} + +/// Substrate-based chain with messaging support from minimal relay-client point of view. +pub trait ChainWithMessages: Chain + ChainWithMessagesBase { + // TODO (https://github.com/paritytech/parity-bridges-common/issues/1692): check all the names + // after the issue is fixed - all names must be changed + + /// Name of the bridge relayers pallet (used in `construct_runtime` macro call) that is deployed + /// at some other chain to bridge with this `ChainWithMessages`. + /// + /// We assume that all chains that are bridging with this `ChainWithMessages` are using + /// the same name. + const WITH_CHAIN_RELAYERS_PALLET_NAME: Option<&'static str>; + + /// Name of the `ToOutboundLaneApi::message_details` runtime API method. + /// The method is provided by the runtime that is bridged with this `ChainWithMessages`. + const TO_CHAIN_MESSAGE_DETAILS_METHOD: &'static str; + + /// Name of the `FromInboundLaneApi::message_details` runtime API method. + /// The method is provided by the runtime that is bridged with this `ChainWithMessages`. + const FROM_CHAIN_MESSAGE_DETAILS_METHOD: &'static str; +} + +/// Call type used by the chain. +pub type CallOf = ::Call; +/// Transaction status of the chain. +pub type TransactionStatusOf = TransactionStatus, HashOf>; + +/// Substrate-based chain with `AccountData` generic argument of `frame_system::AccountInfo` set to +/// the `pallet_balances::AccountData`. +pub trait ChainWithBalances: Chain { + /// Return runtime storage key for getting `frame_system::AccountInfo` of given account. + fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey; +} + +/// SCALE-encoded extrinsic. +pub type EncodedExtrinsic = Vec; + +/// Block with justification. +pub trait BlockWithJustification
{ + /// Return block header. + fn header(&self) -> Header; + /// Return encoded block extrinsics. + fn extrinsics(&self) -> Vec; + /// Return block justification, if known. + fn justification(&self, engine_id: ConsensusEngineId) -> Option<&EncodedJustification>; +} + +/// Transaction before it is signed. +#[derive(Clone, Debug, PartialEq)] +pub struct UnsignedTransaction { + /// Runtime call of this transaction. + pub call: EncodedOrDecodedCall, + /// Transaction nonce. + pub nonce: C::Nonce, + /// Tip included into transaction. + pub tip: C::Balance, + /// Transaction era used by the chain. + pub era: TransactionEraOf, +} + +impl UnsignedTransaction { + /// Create new unsigned transaction with given call, nonce, era and zero tip. + pub fn new(call: EncodedOrDecodedCall, nonce: C::Nonce) -> Self { + Self { call, nonce, era: TransactionEra::Immortal, tip: Zero::zero() } + } + + /// Convert to the transaction of the other compatible chain. + pub fn switch_chain(self) -> UnsignedTransaction + where + Other: Chain< + Nonce = C::Nonce, + Balance = C::Balance, + BlockNumber = C::BlockNumber, + Hash = C::Hash, + >, + { + UnsignedTransaction { + call: EncodedOrDecodedCall::Encoded(self.call.into_encoded()), + nonce: self.nonce, + tip: self.tip, + era: self.era, + } + } + + /// Set transaction tip. + #[must_use] + pub fn tip(mut self, tip: C::Balance) -> Self { + self.tip = tip; + self + } + + /// Set transaction era. + #[must_use] + pub fn era(mut self, era: TransactionEraOf) -> Self { + self.era = era; + self + } +} + +/// Account key pair used by transactions signing scheme. +pub type AccountKeyPairOf = ::AccountKeyPair; + +/// Substrate-based chain transactions signing scheme. +pub trait ChainWithTransactions: Chain { + /// Type of key pairs used to sign transactions. + type AccountKeyPair: Pair + Clone + Send + Sync; + /// Signed transaction. + type SignedTransaction: Clone + Debug + Codec + Send + 'static; + + /// Create transaction for given runtime call, signed by given account. + fn sign_transaction( + param: SignParam, + unsigned: UnsignedTransaction, + ) -> Result + where + Self: Sized; +} + +/// Sign transaction parameters +pub struct SignParam { + /// Version of the runtime specification. + pub spec_version: u32, + /// Transaction version + pub transaction_version: u32, + /// Hash of the genesis block. + pub genesis_hash: HashOf, + /// Signer account + pub signer: AccountKeyPairOf, +} + +impl BlockWithJustification for SignedBlock { + fn header(&self) -> Block::Header { + self.block.header().clone() + } + + fn extrinsics(&self) -> Vec { + self.block.extrinsics().iter().map(Encode::encode).collect() + } + + fn justification(&self, engine_id: ConsensusEngineId) -> Option<&EncodedJustification> { + self.justifications.as_ref().and_then(|j| j.get(engine_id)) + } +} + +/// Trait that provides functionality defined inside `pallet-utility` +pub trait UtilityPallet { + /// Create batch call from given calls vector. + fn build_batch_call(calls: Vec) -> C::Call; +} + +/// Structure that implements `UtilityPalletProvider` based on a full runtime. +pub struct FullRuntimeUtilityPallet { + _phantom: std::marker::PhantomData, +} + +impl UtilityPallet for FullRuntimeUtilityPallet +where + C: Chain, + R: pallet_utility::Config, + ::RuntimeCall: From>, +{ + fn build_batch_call(calls: Vec) -> C::Call { + pallet_utility::Call::batch_all { calls }.into() + } +} + +/// Structure that implements `UtilityPalletProvider` based on a call conversion. +pub struct MockedRuntimeUtilityPallet { + _phantom: std::marker::PhantomData, +} + +impl UtilityPallet for MockedRuntimeUtilityPallet +where + C: Chain, + C::Call: From>, +{ + fn build_batch_call(calls: Vec) -> C::Call { + UtilityCall::batch_all(calls).into() + } +} + +/// Substrate-based chain that uses `pallet-utility`. +pub trait ChainWithUtilityPallet: Chain { + /// The utility pallet provider. + type UtilityPallet: UtilityPallet; +} diff --git a/bridges/relays/client-substrate/src/client.rs b/bridges/relays/client-substrate/src/client.rs new file mode 100644 index 0000000000000..afbda8599b2aa --- /dev/null +++ b/bridges/relays/client-substrate/src/client.rs @@ -0,0 +1,990 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Substrate node client. + +use crate::{ + chain::{Chain, ChainWithTransactions}, + guard::Environment, + rpc::{ + SubstrateAuthorClient, SubstrateChainClient, SubstrateFinalityClient, + SubstrateFrameSystemClient, SubstrateStateClient, SubstrateSystemClient, + }, + transaction_stall_timeout, AccountKeyPairOf, ChainWithGrandpa, ConnectionParams, Error, HashOf, + HeaderIdOf, Result, SignParam, TransactionTracker, UnsignedTransaction, +}; + +use async_std::sync::{Arc, Mutex, RwLock}; +use async_trait::async_trait; +use bp_runtime::{HeaderIdProvider, StorageDoubleMapKeyProvider, StorageMapKeyProvider}; +use codec::{Decode, Encode}; +use frame_support::weights::Weight; +use futures::{SinkExt, StreamExt}; +use jsonrpsee::{ + core::DeserializeOwned, + ws_client::{WsClient as RpcClient, WsClientBuilder as RpcClientBuilder}, +}; +use num_traits::{Saturating, Zero}; +use pallet_transaction_payment::RuntimeDispatchInfo; +use relay_utils::{relay_loop::RECONNECT_DELAY, STALL_TIMEOUT}; +use sp_core::{ + storage::{StorageData, StorageKey}, + Bytes, Hasher, Pair, +}; +use sp_runtime::{ + traits::Header as HeaderT, + transaction_validity::{TransactionSource, TransactionValidity}, +}; +use sp_trie::StorageProof; +use sp_version::RuntimeVersion; +use std::{cmp::Ordering, future::Future}; + +const SUB_API_GRANDPA_AUTHORITIES: &str = "GrandpaApi_grandpa_authorities"; +const SUB_API_GRANDPA_GENERATE_KEY_OWNERSHIP_PROOF: &str = + "GrandpaApi_generate_key_ownership_proof"; +const SUB_API_TXPOOL_VALIDATE_TRANSACTION: &str = "TaggedTransactionQueue_validate_transaction"; +const SUB_API_TX_PAYMENT_QUERY_INFO: &str = "TransactionPaymentApi_query_info"; +const MAX_SUBSCRIPTION_CAPACITY: usize = 4096; + +/// The difference between best block number and number of its ancestor, that is enough +/// for us to consider that ancestor an "ancient" block with dropped state. +/// +/// The relay does not assume that it is connected to the archive node, so it always tries +/// to use the best available chain state. But sometimes it still may use state of some +/// old block. If the state of that block is already dropped, relay will see errors when +/// e.g. it tries to prove something. +/// +/// By default Substrate-based nodes are storing state for last 256 blocks. We'll use +/// half of this value. +pub const ANCIENT_BLOCK_THRESHOLD: u32 = 128; + +/// Returns `true` if we think that the state is already discarded for given block. +pub fn is_ancient_block + PartialOrd + Saturating>(block: N, best: N) -> bool { + best.saturating_sub(block) >= N::from(ANCIENT_BLOCK_THRESHOLD) +} + +/// Opaque justifications subscription type. +pub struct Subscription(pub(crate) Mutex>>); + +/// Opaque GRANDPA authorities set. +pub type OpaqueGrandpaAuthoritiesSet = Vec; + +/// A simple runtime version. It only includes the `spec_version` and `transaction_version`. +#[derive(Copy, Clone, Debug)] +pub struct SimpleRuntimeVersion { + /// Version of the runtime specification. + pub spec_version: u32, + /// All existing dispatches are fully compatible when this number doesn't change. + pub transaction_version: u32, +} + +impl SimpleRuntimeVersion { + /// Create a new instance of `SimpleRuntimeVersion` from a `RuntimeVersion`. + pub const fn from_runtime_version(runtime_version: &RuntimeVersion) -> Self { + Self { + spec_version: runtime_version.spec_version, + transaction_version: runtime_version.transaction_version, + } + } +} + +/// Chain runtime version in client +#[derive(Copy, Clone, Debug)] +pub enum ChainRuntimeVersion { + /// Auto query from chain. + Auto, + /// Custom runtime version, defined by user. + Custom(SimpleRuntimeVersion), +} + +/// Substrate client type. +/// +/// Cloning `Client` is a cheap operation that only clones internal references. Different +/// clones of the same client are guaranteed to use the same references. +pub struct Client { + // Lock order: `submit_signed_extrinsic_lock`, `data` + /// Client connection params. + params: Arc, + /// Saved chain runtime version. + chain_runtime_version: ChainRuntimeVersion, + /// If several tasks are submitting their transactions simultaneously using + /// `submit_signed_extrinsic` method, they may get the same transaction nonce. So one of + /// transactions will be rejected from the pool. This lock is here to prevent situations like + /// that. + submit_signed_extrinsic_lock: Arc>, + /// Genesis block hash. + genesis_hash: HashOf, + /// Shared dynamic data. + data: Arc>, +} + +/// Client data, shared by all `Client` clones. +struct ClientData { + /// Tokio runtime handle. + tokio: Arc, + /// Substrate RPC client. + client: Arc, +} + +/// Already encoded value. +struct PreEncoded(Vec); + +impl Encode for PreEncoded { + fn encode(&self) -> Vec { + self.0.clone() + } +} + +#[async_trait] +impl relay_utils::relay_loop::Client for Client { + type Error = Error; + + async fn reconnect(&mut self) -> Result<()> { + let mut data = self.data.write().await; + let (tokio, client) = Self::build_client(&self.params).await?; + data.tokio = tokio; + data.client = client; + Ok(()) + } +} + +impl Clone for Client { + fn clone(&self) -> Self { + Client { + params: self.params.clone(), + chain_runtime_version: self.chain_runtime_version, + submit_signed_extrinsic_lock: self.submit_signed_extrinsic_lock.clone(), + genesis_hash: self.genesis_hash, + data: self.data.clone(), + } + } +} + +impl std::fmt::Debug for Client { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.debug_struct("Client").field("genesis_hash", &self.genesis_hash).finish() + } +} + +impl Client { + /// Returns client that is able to call RPCs on Substrate node over websocket connection. + /// + /// This function will keep connecting to given Substrate node until connection is established + /// and is functional. If attempt fail, it will wait for `RECONNECT_DELAY` and retry again. + pub async fn new(params: ConnectionParams) -> Self { + let params = Arc::new(params); + loop { + match Self::try_connect(params.clone()).await { + Ok(client) => return client, + Err(error) => log::error!( + target: "bridge", + "Failed to connect to {} node: {:?}. Going to retry in {}s", + C::NAME, + error, + RECONNECT_DELAY.as_secs(), + ), + } + + async_std::task::sleep(RECONNECT_DELAY).await; + } + } + + /// Try to connect to Substrate node over websocket. Returns Substrate RPC client if connection + /// has been established or error otherwise. + pub async fn try_connect(params: Arc) -> Result { + let (tokio, client) = Self::build_client(¶ms).await?; + + let number: C::BlockNumber = Zero::zero(); + let genesis_hash_client = client.clone(); + let genesis_hash = tokio + .spawn(async move { + SubstrateChainClient::::block_hash(&*genesis_hash_client, Some(number)).await + }) + .await??; + + let chain_runtime_version = params.chain_runtime_version; + let mut client = Self { + params, + chain_runtime_version, + submit_signed_extrinsic_lock: Arc::new(Mutex::new(())), + genesis_hash, + data: Arc::new(RwLock::new(ClientData { tokio, client })), + }; + Self::ensure_correct_runtime_version(&mut client, chain_runtime_version).await?; + Ok(client) + } + + // Check runtime version to understand if we need are connected to expected version, or we + // need to wait for upgrade, we need to abort immediately. + async fn ensure_correct_runtime_version>( + env: &mut E, + expected: ChainRuntimeVersion, + ) -> Result<()> { + // we are only interested if version mode is bundled or passed using CLI + let expected = match expected { + ChainRuntimeVersion::Auto => return Ok(()), + ChainRuntimeVersion::Custom(expected) => expected, + }; + + // we need to wait if actual version is < than expected, we are OK of versions are the + // same and we need to abort if actual version is > than expected + let actual = SimpleRuntimeVersion::from_runtime_version(&env.runtime_version().await?); + match actual.spec_version.cmp(&expected.spec_version) { + Ordering::Less => + Err(Error::WaitingForRuntimeUpgrade { chain: C::NAME.into(), expected, actual }), + Ordering::Equal => Ok(()), + Ordering::Greater => { + log::error!( + target: "bridge", + "The {} client is configured to use runtime version {expected:?} and actual \ + version is {actual:?}. Aborting", + C::NAME, + ); + env.abort().await; + Err(Error::Custom("Aborted".into())) + }, + } + } + + /// Build client to use in connection. + async fn build_client( + params: &ConnectionParams, + ) -> Result<(Arc, Arc)> { + let tokio = tokio::runtime::Runtime::new()?; + + let uri = match params.uri { + Some(ref uri) => uri.clone(), + None => { + format!( + "{}://{}:{}{}", + if params.secure { "wss" } else { "ws" }, + params.host, + params.port, + match params.path { + Some(ref path) => format!("/{}", path), + None => String::new(), + }, + ) + }, + }; + log::info!(target: "bridge", "Connecting to {} node at {}", C::NAME, uri); + + let client = tokio + .spawn(async move { + RpcClientBuilder::default() + .max_buffer_capacity_per_subscription(MAX_SUBSCRIPTION_CAPACITY) + .build(&uri) + .await + }) + .await??; + + Ok((Arc::new(tokio), Arc::new(client))) + } +} + +impl Client { + /// Return simple runtime version, only include `spec_version` and `transaction_version`. + pub async fn simple_runtime_version(&self) -> Result { + Ok(match &self.chain_runtime_version { + ChainRuntimeVersion::Auto => { + let runtime_version = self.runtime_version().await?; + SimpleRuntimeVersion::from_runtime_version(&runtime_version) + }, + ChainRuntimeVersion::Custom(version) => *version, + }) + } + + /// Returns true if client is connected to at least one peer and is in synced state. + pub async fn ensure_synced(&self) -> Result<()> { + self.jsonrpsee_execute(|client| async move { + let health = SubstrateSystemClient::::health(&*client).await?; + let is_synced = !health.is_syncing && (!health.should_have_peers || health.peers > 0); + if is_synced { + Ok(()) + } else { + Err(Error::ClientNotSynced(health)) + } + }) + .await + } + + /// Return hash of the genesis block. + pub fn genesis_hash(&self) -> &C::Hash { + &self.genesis_hash + } + + /// Return hash of the best finalized block. + pub async fn best_finalized_header_hash(&self) -> Result { + self.jsonrpsee_execute(|client| async move { + Ok(SubstrateChainClient::::finalized_head(&*client).await?) + }) + .await + .map_err(|e| Error::FailedToReadBestFinalizedHeaderHash { + chain: C::NAME.into(), + error: e.boxed(), + }) + } + + /// Return number of the best finalized block. + pub async fn best_finalized_header_number(&self) -> Result { + Ok(*self.best_finalized_header().await?.number()) + } + + /// Return header of the best finalized block. + pub async fn best_finalized_header(&self) -> Result { + self.header_by_hash(self.best_finalized_header_hash().await?).await + } + + /// Returns the best Substrate header. + pub async fn best_header(&self) -> Result + where + C::Header: DeserializeOwned, + { + self.jsonrpsee_execute(|client| async move { + Ok(SubstrateChainClient::::header(&*client, None).await?) + }) + .await + .map_err(|e| Error::FailedToReadBestHeader { chain: C::NAME.into(), error: e.boxed() }) + } + + /// Get a Substrate block from its hash. + pub async fn get_block(&self, block_hash: Option) -> Result { + self.jsonrpsee_execute(move |client| async move { + Ok(SubstrateChainClient::::block(&*client, block_hash).await?) + }) + .await + } + + /// Get a Substrate header by its hash. + pub async fn header_by_hash(&self, block_hash: C::Hash) -> Result + where + C::Header: DeserializeOwned, + { + self.jsonrpsee_execute(move |client| async move { + Ok(SubstrateChainClient::::header(&*client, Some(block_hash)).await?) + }) + .await + .map_err(|e| Error::FailedToReadHeaderByHash { + chain: C::NAME.into(), + hash: format!("{block_hash}"), + error: e.boxed(), + }) + } + + /// Get a Substrate block hash by its number. + pub async fn block_hash_by_number(&self, number: C::BlockNumber) -> Result { + self.jsonrpsee_execute(move |client| async move { + Ok(SubstrateChainClient::::block_hash(&*client, Some(number)).await?) + }) + .await + } + + /// Get a Substrate header by its number. + pub async fn header_by_number(&self, block_number: C::BlockNumber) -> Result + where + C::Header: DeserializeOwned, + { + let block_hash = Self::block_hash_by_number(self, block_number).await?; + let header_by_hash = Self::header_by_hash(self, block_hash).await?; + Ok(header_by_hash) + } + + /// Return runtime version. + pub async fn runtime_version(&self) -> Result { + self.jsonrpsee_execute(move |client| async move { + Ok(SubstrateStateClient::::runtime_version(&*client).await?) + }) + .await + } + + /// Read value from runtime storage. + pub async fn storage_value( + &self, + storage_key: StorageKey, + block_hash: Option, + ) -> Result> { + self.raw_storage_value(storage_key, block_hash) + .await? + .map(|encoded_value| { + T::decode(&mut &encoded_value.0[..]).map_err(Error::ResponseParseFailed) + }) + .transpose() + } + + /// Read `MapStorage` value from runtime storage. + pub async fn storage_map_value( + &self, + pallet_prefix: &str, + key: &T::Key, + block_hash: Option, + ) -> Result> { + let storage_key = T::final_key(pallet_prefix, key); + + self.raw_storage_value(storage_key, block_hash) + .await? + .map(|encoded_value| { + T::Value::decode(&mut &encoded_value.0[..]).map_err(Error::ResponseParseFailed) + }) + .transpose() + } + + /// Read `DoubleMapStorage` value from runtime storage. + pub async fn storage_double_map_value( + &self, + pallet_prefix: &str, + key1: &T::Key1, + key2: &T::Key2, + block_hash: Option, + ) -> Result> { + let storage_key = T::final_key(pallet_prefix, key1, key2); + + self.raw_storage_value(storage_key, block_hash) + .await? + .map(|encoded_value| { + T::Value::decode(&mut &encoded_value.0[..]).map_err(Error::ResponseParseFailed) + }) + .transpose() + } + + /// Read raw value from runtime storage. + pub async fn raw_storage_value( + &self, + storage_key: StorageKey, + block_hash: Option, + ) -> Result> { + let cloned_storage_key = storage_key.clone(); + self.jsonrpsee_execute(move |client| async move { + Ok(SubstrateStateClient::::storage(&*client, storage_key.clone(), block_hash) + .await?) + }) + .await + .map_err(|e| Error::FailedToReadRuntimeStorageValue { + chain: C::NAME.into(), + key: cloned_storage_key, + error: e.boxed(), + }) + } + + /// Get the nonce of the given Substrate account. + /// + /// Note: It's the caller's responsibility to make sure `account` is a valid SS58 address. + pub async fn next_account_index(&self, account: C::AccountId) -> Result { + self.jsonrpsee_execute(move |client| async move { + Ok(SubstrateFrameSystemClient::::account_next_index(&*client, account).await?) + }) + .await + } + + /// Submit unsigned extrinsic for inclusion in a block. + /// + /// Note: The given transaction needs to be SCALE encoded beforehand. + pub async fn submit_unsigned_extrinsic(&self, transaction: Bytes) -> Result { + // one last check that the transaction is valid. Most of checks happen in the relay loop and + // it is the "final" check before submission. + let best_header_hash = self.best_header().await?.hash(); + self.validate_transaction(best_header_hash, PreEncoded(transaction.0.clone())) + .await + .map_err(|e| { + log::error!(target: "bridge", "Pre-submit {} transaction validation failed: {:?}", C::NAME, e); + e + })??; + + self.jsonrpsee_execute(move |client| async move { + let tx_hash = SubstrateAuthorClient::::submit_extrinsic(&*client, transaction) + .await + .map_err(|e| { + log::error!(target: "bridge", "Failed to send transaction to {} node: {:?}", C::NAME, e); + e + })?; + log::trace!(target: "bridge", "Sent transaction to {} node: {:?}", C::NAME, tx_hash); + Ok(tx_hash) + }) + .await + } + + async fn build_sign_params(&self, signer: AccountKeyPairOf) -> Result> + where + C: ChainWithTransactions, + { + let runtime_version = self.simple_runtime_version().await?; + Ok(SignParam:: { + spec_version: runtime_version.spec_version, + transaction_version: runtime_version.transaction_version, + genesis_hash: self.genesis_hash, + signer, + }) + } + + /// Submit an extrinsic signed by given account. + /// + /// All calls of this method are synchronized, so there can't be more than one active + /// `submit_signed_extrinsic()` call. This guarantees that no nonces collision may happen + /// if all client instances are clones of the same initial `Client`. + /// + /// Note: The given transaction needs to be SCALE encoded beforehand. + pub async fn submit_signed_extrinsic( + &self, + signer: &AccountKeyPairOf, + prepare_extrinsic: impl FnOnce(HeaderIdOf, C::Nonce) -> Result> + + Send + + 'static, + ) -> Result + where + C: ChainWithTransactions, + C::AccountId: From<::Public>, + { + let _guard = self.submit_signed_extrinsic_lock.lock().await; + let transaction_nonce = self.next_account_index(signer.public().into()).await?; + let best_header = self.best_header().await?; + let signing_data = self.build_sign_params(signer.clone()).await?; + + // By using parent of best block here, we are protecing again best-block reorganizations. + // E.g. transaction may have been submitted when the best block was `A[num=100]`. Then it + // has been changed to `B[num=100]`. Hash of `A` has been included into transaction + // signature payload. So when signature will be checked, the check will fail and transaction + // will be dropped from the pool. + let best_header_id = best_header.parent_id().unwrap_or_else(|| best_header.id()); + + let extrinsic = prepare_extrinsic(best_header_id, transaction_nonce)?; + let signed_extrinsic = C::sign_transaction(signing_data, extrinsic)?.encode(); + + // one last check that the transaction is valid. Most of checks happen in the relay loop and + // it is the "final" check before submission. + self.validate_transaction(best_header_id.1, PreEncoded(signed_extrinsic.clone())) + .await + .map_err(|e| { + log::error!(target: "bridge", "Pre-submit {} transaction validation failed: {:?}", C::NAME, e); + e + })??; + + self.jsonrpsee_execute(move |client| async move { + let tx_hash = + SubstrateAuthorClient::::submit_extrinsic(&*client, Bytes(signed_extrinsic)) + .await + .map_err(|e| { + log::error!(target: "bridge", "Failed to send transaction to {} node: {:?}", C::NAME, e); + e + })?; + log::trace!(target: "bridge", "Sent transaction to {} node: {:?}", C::NAME, tx_hash); + Ok(tx_hash) + }) + .await + } + + /// Does exactly the same as `submit_signed_extrinsic`, but keeps watching for extrinsic status + /// after submission. + pub async fn submit_and_watch_signed_extrinsic( + &self, + signer: &AccountKeyPairOf, + prepare_extrinsic: impl FnOnce(HeaderIdOf, C::Nonce) -> Result> + + Send + + 'static, + ) -> Result> + where + C: ChainWithTransactions, + C::AccountId: From<::Public>, + { + let self_clone = self.clone(); + let signing_data = self.build_sign_params(signer.clone()).await?; + let _guard = self.submit_signed_extrinsic_lock.lock().await; + let transaction_nonce = self.next_account_index(signer.public().into()).await?; + let best_header = self.best_header().await?; + let best_header_id = best_header.id(); + + let extrinsic = prepare_extrinsic(best_header_id, transaction_nonce)?; + let stall_timeout = transaction_stall_timeout( + extrinsic.era.mortality_period(), + C::AVERAGE_BLOCK_INTERVAL, + STALL_TIMEOUT, + ); + let signed_extrinsic = C::sign_transaction(signing_data, extrinsic)?.encode(); + + // one last check that the transaction is valid. Most of checks happen in the relay loop and + // it is the "final" check before submission. + self.validate_transaction(best_header_id.1, PreEncoded(signed_extrinsic.clone())) + .await + .map_err(|e| { + log::error!(target: "bridge", "Pre-submit {} transaction validation failed: {:?}", C::NAME, e); + e + })??; + + let (sender, receiver) = futures::channel::mpsc::channel(MAX_SUBSCRIPTION_CAPACITY); + let (tracker, subscription) = self + .jsonrpsee_execute(move |client| async move { + let tx_hash = C::Hasher::hash(&signed_extrinsic); + let subscription = SubstrateAuthorClient::::submit_and_watch_extrinsic( + &*client, + Bytes(signed_extrinsic), + ) + .await + .map_err(|e| { + log::error!(target: "bridge", "Failed to send transaction to {} node: {:?}", C::NAME, e); + e + })?; + log::trace!(target: "bridge", "Sent transaction to {} node: {:?}", C::NAME, tx_hash); + let tracker = TransactionTracker::new( + self_clone, + stall_timeout, + tx_hash, + Subscription(Mutex::new(receiver)), + ); + Ok((tracker, subscription)) + }) + .await?; + self.data.read().await.tokio.spawn(Subscription::background_worker( + C::NAME.into(), + "extrinsic".into(), + subscription, + sender, + )); + Ok(tracker) + } + + /// Returns pending extrinsics from transaction pool. + pub async fn pending_extrinsics(&self) -> Result> { + self.jsonrpsee_execute(move |client| async move { + Ok(SubstrateAuthorClient::::pending_extrinsics(&*client).await?) + }) + .await + } + + /// Validate transaction at given block state. + pub async fn validate_transaction( + &self, + at_block: C::Hash, + transaction: SignedTransaction, + ) -> Result { + self.jsonrpsee_execute(move |client| async move { + let call = SUB_API_TXPOOL_VALIDATE_TRANSACTION.to_string(); + let data = Bytes((TransactionSource::External, transaction, at_block).encode()); + + let encoded_response = + SubstrateStateClient::::call(&*client, call, data, Some(at_block)).await?; + let validity = TransactionValidity::decode(&mut &encoded_response.0[..]) + .map_err(Error::ResponseParseFailed)?; + + Ok(validity) + }) + .await + } + + /// Returns weight of the given transaction. + pub async fn extimate_extrinsic_weight( + &self, + transaction: SignedTransaction, + ) -> Result { + self.jsonrpsee_execute(move |client| async move { + let transaction_len = transaction.encoded_size() as u32; + + let call = SUB_API_TX_PAYMENT_QUERY_INFO.to_string(); + let data = Bytes((transaction, transaction_len).encode()); + + let encoded_response = + SubstrateStateClient::::call(&*client, call, data, None).await?; + let dispatch_info = + RuntimeDispatchInfo::::decode(&mut &encoded_response.0[..]) + .map_err(Error::ResponseParseFailed)?; + + Ok(dispatch_info.weight) + }) + .await + } + + /// Get the GRANDPA authority set at given block. + pub async fn grandpa_authorities_set( + &self, + block: C::Hash, + ) -> Result { + self.jsonrpsee_execute(move |client| async move { + let call = SUB_API_GRANDPA_AUTHORITIES.to_string(); + let data = Bytes(Vec::new()); + + let encoded_response = + SubstrateStateClient::::call(&*client, call, data, Some(block)).await?; + let authority_list = encoded_response.0; + + Ok(authority_list) + }) + .await + } + + /// Execute runtime call at given block, provided the input and output types. + /// It also performs the input encode and output decode. + pub async fn typed_state_call( + &self, + method_name: String, + input: Input, + at_block: Option, + ) -> Result { + let encoded_output = self + .state_call(method_name.clone(), Bytes(input.encode()), at_block) + .await + .map_err(|e| Error::ErrorExecutingRuntimeCall { + chain: C::NAME.into(), + method: method_name, + error: e.boxed(), + })?; + Output::decode(&mut &encoded_output.0[..]).map_err(Error::ResponseParseFailed) + } + + /// Execute runtime call at given block. + pub async fn state_call( + &self, + method: String, + data: Bytes, + at_block: Option, + ) -> Result { + self.jsonrpsee_execute(move |client| async move { + SubstrateStateClient::::call(&*client, method, data, at_block) + .await + .map_err(Into::into) + }) + .await + } + + /// Returns storage proof of given storage keys. + pub async fn prove_storage( + &self, + keys: Vec, + at_block: C::Hash, + ) -> Result { + self.jsonrpsee_execute(move |client| async move { + SubstrateStateClient::::prove_storage(&*client, keys, Some(at_block)) + .await + .map(|proof| { + StorageProof::new(proof.proof.into_iter().map(|b| b.0).collect::>()) + }) + .map_err(Into::into) + }) + .await + } + + /// Return `tokenDecimals` property from the set of chain properties. + pub async fn token_decimals(&self) -> Result> { + self.jsonrpsee_execute(move |client| async move { + let system_properties = SubstrateSystemClient::::properties(&*client).await?; + Ok(system_properties.get("tokenDecimals").and_then(|v| v.as_u64())) + }) + .await + } + + /// Return new finality justifications stream. + pub async fn subscribe_finality_justifications>( + &self, + ) -> Result> { + let subscription = self + .jsonrpsee_execute(move |client| async move { + Ok(FC::subscribe_justifications(&client).await?) + }) + .await?; + let (sender, receiver) = futures::channel::mpsc::channel(MAX_SUBSCRIPTION_CAPACITY); + self.data.read().await.tokio.spawn(Subscription::background_worker( + C::NAME.into(), + "justification".into(), + subscription, + sender, + )); + Ok(Subscription(Mutex::new(receiver))) + } + + /// Generates a proof of key ownership for the given authority in the given set. + pub async fn generate_grandpa_key_ownership_proof( + &self, + at: HashOf, + set_id: sp_consensus_grandpa::SetId, + authority_id: sp_consensus_grandpa::AuthorityId, + ) -> Result> + where + C: ChainWithGrandpa, + { + self.typed_state_call( + SUB_API_GRANDPA_GENERATE_KEY_OWNERSHIP_PROOF.into(), + (set_id, authority_id), + Some(at), + ) + .await + } + + /// Execute jsonrpsee future in tokio context. + async fn jsonrpsee_execute(&self, make_jsonrpsee_future: MF) -> Result + where + MF: FnOnce(Arc) -> F + Send + 'static, + F: Future> + Send + 'static, + T: Send + 'static, + { + let data = self.data.read().await; + let client = data.client.clone(); + data.tokio.spawn(make_jsonrpsee_future(client)).await? + } + + /// Returns `true` if version guard can be started. + /// + /// There's no reason to run version guard when version mode is set to `Auto`. It can + /// lead to relay shutdown when chain is upgraded, even though we have explicitly + /// said that we don't want to shutdown. + pub fn can_start_version_guard(&self) -> bool { + !matches!(self.chain_runtime_version, ChainRuntimeVersion::Auto) + } +} + +impl Subscription { + /// Consumes subscription and returns future statuses stream. + pub fn into_stream(self) -> impl futures::Stream { + futures::stream::unfold(self, |this| async { + let item = this.0.lock().await.next().await.unwrap_or(None); + item.map(|i| (i, this)) + }) + } + + /// Return next item from the subscription. + pub async fn next(&self) -> Result> { + let mut receiver = self.0.lock().await; + let item = receiver.next().await; + Ok(item.unwrap_or(None)) + } + + /// Background worker that is executed in tokio context as `jsonrpsee` requires. + async fn background_worker( + chain_name: String, + item_type: String, + mut subscription: jsonrpsee::core::client::Subscription, + mut sender: futures::channel::mpsc::Sender>, + ) { + loop { + match subscription.next().await { + Some(Ok(item)) => + if sender.send(Some(item)).await.is_err() { + break + }, + Some(Err(e)) => { + log::trace!( + target: "bridge", + "{} {} subscription stream has returned '{:?}'. Stream needs to be restarted.", + chain_name, + item_type, + e, + ); + let _ = sender.send(None).await; + break + }, + None => { + log::trace!( + target: "bridge", + "{} {} subscription stream has returned None. Stream needs to be restarted.", + chain_name, + item_type, + ); + let _ = sender.send(None).await; + break + }, + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{guard::tests::TestEnvironment, test_chain::TestChain}; + use futures::{channel::mpsc::unbounded, FutureExt}; + + async fn run_ensure_correct_runtime_version( + expected: ChainRuntimeVersion, + actual: RuntimeVersion, + ) -> Result<()> { + let ( + (mut runtime_version_tx, runtime_version_rx), + (slept_tx, _slept_rx), + (aborted_tx, mut aborted_rx), + ) = (unbounded(), unbounded(), unbounded()); + runtime_version_tx.send(actual).await.unwrap(); + let mut env = TestEnvironment { runtime_version_rx, slept_tx, aborted_tx }; + + let ensure_correct_runtime_version = + Client::::ensure_correct_runtime_version(&mut env, expected).boxed(); + let aborted = aborted_rx.next().map(|_| Err(Error::Custom("".into()))).boxed(); + futures::pin_mut!(ensure_correct_runtime_version, aborted); + futures::future::select(ensure_correct_runtime_version, aborted) + .await + .into_inner() + .0 + } + + #[async_std::test] + async fn ensure_correct_runtime_version_works() { + // when we are configured to use auto version + assert!(matches!( + run_ensure_correct_runtime_version( + ChainRuntimeVersion::Auto, + RuntimeVersion { + spec_version: 100, + transaction_version: 100, + ..Default::default() + }, + ) + .await, + Ok(()), + )); + // when actual == expected + assert!(matches!( + run_ensure_correct_runtime_version( + ChainRuntimeVersion::Custom(SimpleRuntimeVersion { + spec_version: 100, + transaction_version: 100 + }), + RuntimeVersion { + spec_version: 100, + transaction_version: 100, + ..Default::default() + }, + ) + .await, + Ok(()), + )); + // when actual spec version < expected spec version + assert!(matches!( + run_ensure_correct_runtime_version( + ChainRuntimeVersion::Custom(SimpleRuntimeVersion { + spec_version: 100, + transaction_version: 100 + }), + RuntimeVersion { spec_version: 99, transaction_version: 100, ..Default::default() }, + ) + .await, + Err(Error::WaitingForRuntimeUpgrade { + expected: SimpleRuntimeVersion { spec_version: 100, transaction_version: 100 }, + actual: SimpleRuntimeVersion { spec_version: 99, transaction_version: 100 }, + .. + }), + )); + // when actual spec version > expected spec version + assert!(matches!( + run_ensure_correct_runtime_version( + ChainRuntimeVersion::Custom(SimpleRuntimeVersion { + spec_version: 100, + transaction_version: 100 + }), + RuntimeVersion { + spec_version: 101, + transaction_version: 100, + ..Default::default() + }, + ) + .await, + Err(Error::Custom(_)), + )); + } +} diff --git a/bridges/relays/client-substrate/src/error.rs b/bridges/relays/client-substrate/src/error.rs new file mode 100644 index 0000000000000..0b44668181887 --- /dev/null +++ b/bridges/relays/client-substrate/src/error.rs @@ -0,0 +1,165 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Substrate node RPC errors. + +use crate::SimpleRuntimeVersion; +use bp_polkadot_core::parachains::ParaId; +use jsonrpsee::core::ClientError as RpcError; +use relay_utils::MaybeConnectionError; +use sc_rpc_api::system::Health; +use sp_core::storage::StorageKey; +use sp_runtime::transaction_validity::TransactionValidityError; +use thiserror::Error; + +/// Result type used by Substrate client. +pub type Result = std::result::Result; + +/// Errors that can occur only when interacting with +/// a Substrate node through RPC. +#[derive(Error, Debug)] +pub enum Error { + /// IO error. + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + /// An error that can occur when making a request to + /// an JSON-RPC server. + #[error("RPC error: {0}")] + RpcError(#[from] RpcError), + /// The response from the server could not be SCALE decoded. + #[error("Response parse failed: {0}")] + ResponseParseFailed(#[from] codec::Error), + /// Account does not exist on the chain. + #[error("Account does not exist on the chain.")] + AccountDoesNotExist, + /// Runtime storage is missing some mandatory value. + #[error("Mandatory storage value is missing from the runtime storage.")] + MissingMandatoryStorageValue, + /// Required parachain head is not present at the relay chain. + #[error("Parachain {0:?} head {1} is missing from the relay chain storage.")] + MissingRequiredParachainHead(ParaId, u64), + /// Failed to find finality proof for the given header. + #[error("Failed to find finality proof for header {0}.")] + FinalityProofNotFound(u64), + /// The client we're connected to is not synced, so we can't rely on its state. + #[error("Substrate client is not synced {0}.")] + ClientNotSynced(Health), + /// Failed to read best finalized header hash from given chain. + #[error("Failed to read best finalized header hash of {chain}: {error:?}.")] + FailedToReadBestFinalizedHeaderHash { + /// Name of the chain where the error has happened. + chain: String, + /// Underlying error. + error: Box, + }, + /// Failed to read best finalized header from given chain. + #[error("Failed to read best header of {chain}: {error:?}.")] + FailedToReadBestHeader { + /// Name of the chain where the error has happened. + chain: String, + /// Underlying error. + error: Box, + }, + /// Failed to read header by hash from given chain. + #[error("Failed to read header {hash} of {chain}: {error:?}.")] + FailedToReadHeaderByHash { + /// Name of the chain where the error has happened. + chain: String, + /// Hash of the header we've tried to read. + hash: String, + /// Underlying error. + error: Box, + }, + /// Failed to execute runtime call at given chain. + #[error("Failed to execute runtime call {method} at {chain}: {error:?}.")] + ErrorExecutingRuntimeCall { + /// Name of the chain where the error has happened. + chain: String, + /// Runtime method name. + method: String, + /// Underlying error. + error: Box, + }, + /// Failed to read sotrage value at given chain. + #[error("Failed to read storage value {key:?} at {chain}: {error:?}.")] + FailedToReadRuntimeStorageValue { + /// Name of the chain where the error has happened. + chain: String, + /// Runtime storage key + key: StorageKey, + /// Underlying error. + error: Box, + }, + /// The bridge pallet is halted and all transactions will be rejected. + #[error("Bridge pallet is halted.")] + BridgePalletIsHalted, + /// The bridge pallet is not yet initialized and all transactions will be rejected. + #[error("Bridge pallet is not initialized.")] + BridgePalletIsNotInitialized, + /// There's no best head of the parachain at the `pallet-bridge-parachains` at the target side. + #[error("No head of the ParaId({0}) at the bridge parachains pallet at {1}.")] + NoParachainHeadAtTarget(u32, String), + /// An error has happened when we have tried to parse storage proof. + #[error("Error when parsing storage proof: {0:?}.")] + StorageProofError(bp_runtime::StorageProofError), + /// The Substrate transaction is invalid. + #[error("Substrate transaction is invalid: {0:?}")] + TransactionInvalid(#[from] TransactionValidityError), + /// The client is configured to use newer runtime version than the connected chain uses. + /// The client will keep waiting until chain is upgraded to given version. + #[error("Waiting for {chain} runtime upgrade: expected {expected:?} actual {actual:?}")] + WaitingForRuntimeUpgrade { + /// Name of the chain where the error has happened. + chain: String, + /// Expected runtime version. + expected: SimpleRuntimeVersion, + /// Actual runtime version. + actual: SimpleRuntimeVersion, + }, + /// Custom logic error. + #[error("{0}")] + Custom(String), +} + +impl From for Error { + fn from(error: tokio::task::JoinError) -> Self { + Error::Custom(format!("Failed to wait tokio task: {error}")) + } +} + +impl Error { + /// Box the error. + pub fn boxed(self) -> Box { + Box::new(self) + } +} + +impl MaybeConnectionError for Error { + fn is_connection_error(&self) -> bool { + match *self { + Error::RpcError(RpcError::Transport(_)) | + Error::RpcError(RpcError::RestartNeeded(_)) | + Error::ClientNotSynced(_) => true, + Error::FailedToReadBestFinalizedHeaderHash { ref error, .. } => + error.is_connection_error(), + Error::FailedToReadBestHeader { ref error, .. } => error.is_connection_error(), + Error::FailedToReadHeaderByHash { ref error, .. } => error.is_connection_error(), + Error::ErrorExecutingRuntimeCall { ref error, .. } => error.is_connection_error(), + Error::FailedToReadRuntimeStorageValue { ref error, .. } => error.is_connection_error(), + _ => false, + } + } +} diff --git a/bridges/relays/client-substrate/src/guard.rs b/bridges/relays/client-substrate/src/guard.rs new file mode 100644 index 0000000000000..47454892cd039 --- /dev/null +++ b/bridges/relays/client-substrate/src/guard.rs @@ -0,0 +1,196 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Pallet provides a set of guard functions that are running in background threads +//! and are aborting process if some condition fails. + +use crate::{error::Error, Chain, Client}; + +use async_trait::async_trait; +use sp_version::RuntimeVersion; +use std::{ + fmt::Display, + time::{Duration, Instant}, +}; + +/// Guards environment. +#[async_trait] +pub trait Environment: Send + Sync + 'static { + /// Error type. + type Error: Display + Send + Sync + 'static; + + /// Return current runtime version. + async fn runtime_version(&mut self) -> Result; + + /// Return current time. + fn now(&self) -> Instant { + Instant::now() + } + + /// Sleep given amount of time. + async fn sleep(&mut self, duration: Duration) { + async_std::task::sleep(duration).await + } + + /// Abort current process. Called when guard condition check fails. + async fn abort(&mut self) { + std::process::abort(); + } +} + +/// Abort when runtime spec version is different from specified. +pub fn abort_on_spec_version_change( + mut env: impl Environment, + expected_spec_version: u32, +) { + async_std::task::spawn(async move { + log::info!( + target: "bridge-guard", + "Starting spec_version guard for {}. Expected spec_version: {}", + C::NAME, + expected_spec_version, + ); + + loop { + let actual_spec_version = env.runtime_version().await; + match actual_spec_version { + Ok(version) if version.spec_version == expected_spec_version => (), + Ok(version) => { + log::error!( + target: "bridge-guard", + "{} runtime spec version has changed from {} to {}. Aborting relay", + C::NAME, + expected_spec_version, + version.spec_version, + ); + + env.abort().await; + }, + Err(error) => log::warn!( + target: "bridge-guard", + "Failed to read {} runtime version: {}. Relay may need to be stopped manually", + C::NAME, + error, + ), + } + + env.sleep(conditions_check_delay::()).await; + } + }); +} + +/// Delay between conditions check. +fn conditions_check_delay() -> Duration { + C::AVERAGE_BLOCK_INTERVAL * (10 + rand::random::() % 10) +} + +#[async_trait] +impl Environment for Client { + type Error = Error; + + async fn runtime_version(&mut self) -> Result { + Client::::runtime_version(self).await + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use crate::test_chain::TestChain; + use futures::{ + channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}, + future::FutureExt, + stream::StreamExt, + SinkExt, + }; + + pub struct TestEnvironment { + pub runtime_version_rx: UnboundedReceiver, + pub slept_tx: UnboundedSender<()>, + pub aborted_tx: UnboundedSender<()>, + } + + #[async_trait] + impl Environment for TestEnvironment { + type Error = Error; + + async fn runtime_version(&mut self) -> Result { + Ok(self.runtime_version_rx.next().await.unwrap_or_default()) + } + + async fn sleep(&mut self, _duration: Duration) { + let _ = self.slept_tx.send(()).await; + } + + async fn abort(&mut self) { + let _ = self.aborted_tx.send(()).await; + // simulate process abort :) + async_std::task::sleep(Duration::from_secs(60)).await; + } + } + + #[test] + fn aborts_when_spec_version_is_changed() { + async_std::task::block_on(async { + let ( + (mut runtime_version_tx, runtime_version_rx), + (slept_tx, mut slept_rx), + (aborted_tx, mut aborted_rx), + ) = (unbounded(), unbounded(), unbounded()); + abort_on_spec_version_change( + TestEnvironment { runtime_version_rx, slept_tx, aborted_tx }, + 0, + ); + + // client responds with wrong version + runtime_version_tx + .send(RuntimeVersion { spec_version: 42, ..Default::default() }) + .await + .unwrap(); + + // then the `abort` function is called + aborted_rx.next().await; + // and we do not reach the `sleep` function call + assert!(slept_rx.next().now_or_never().is_none()); + }); + } + + #[test] + fn does_not_aborts_when_spec_version_is_unchanged() { + async_std::task::block_on(async { + let ( + (mut runtime_version_tx, runtime_version_rx), + (slept_tx, mut slept_rx), + (aborted_tx, mut aborted_rx), + ) = (unbounded(), unbounded(), unbounded()); + abort_on_spec_version_change( + TestEnvironment { runtime_version_rx, slept_tx, aborted_tx }, + 42, + ); + + // client responds with the same version + runtime_version_tx + .send(RuntimeVersion { spec_version: 42, ..Default::default() }) + .await + .unwrap(); + + // then the `sleep` function is called + slept_rx.next().await; + // and the `abort` function is not called + assert!(aborted_rx.next().now_or_never().is_none()); + }); + } +} diff --git a/bridges/relays/client-substrate/src/lib.rs b/bridges/relays/client-substrate/src/lib.rs new file mode 100644 index 0000000000000..d5b8d4dcced2d --- /dev/null +++ b/bridges/relays/client-substrate/src/lib.rs @@ -0,0 +1,101 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Tools to interact with Substrate node using RPC methods. + +#![warn(missing_docs)] + +mod chain; +mod client; +mod error; +mod rpc; +mod sync_header; +mod transaction_tracker; + +pub mod calls; +pub mod guard; +pub mod metrics; +pub mod test_chain; + +use std::time::Duration; + +pub use crate::{ + chain::{ + AccountKeyPairOf, BlockWithJustification, CallOf, Chain, ChainWithBalances, + ChainWithGrandpa, ChainWithMessages, ChainWithRuntimeVersion, ChainWithTransactions, + ChainWithUtilityPallet, FullRuntimeUtilityPallet, MockedRuntimeUtilityPallet, Parachain, + RelayChain, SignParam, TransactionStatusOf, UnsignedTransaction, UtilityPallet, + }, + client::{ + is_ancient_block, ChainRuntimeVersion, Client, OpaqueGrandpaAuthoritiesSet, + SimpleRuntimeVersion, Subscription, ANCIENT_BLOCK_THRESHOLD, + }, + error::{Error, Result}, + rpc::{SubstrateBeefyFinalityClient, SubstrateFinalityClient, SubstrateGrandpaFinalityClient}, + sync_header::SyncHeader, + transaction_tracker::TransactionTracker, +}; +pub use bp_runtime::{ + AccountIdOf, AccountPublicOf, BalanceOf, BlockNumberOf, Chain as ChainBase, HashOf, HeaderIdOf, + HeaderOf, NonceOf, Parachain as ParachainBase, SignatureOf, TransactionEra, TransactionEraOf, + UnderlyingChainProvider, +}; + +/// Substrate-over-websocket connection params. +#[derive(Debug, Clone)] +pub struct ConnectionParams { + /// Websocket endpoint URL. Overrides all other URL components (`host`, `port`, `path` and + /// `secure`). + pub uri: Option, + /// Websocket server host name. + pub host: String, + /// Websocket server TCP port. + pub port: u16, + /// Websocket endpoint path at server. + pub path: Option, + /// Use secure websocket connection. + pub secure: bool, + /// Defined chain runtime version + pub chain_runtime_version: ChainRuntimeVersion, +} + +impl Default for ConnectionParams { + fn default() -> Self { + ConnectionParams { + uri: None, + host: "localhost".into(), + port: 9944, + path: None, + secure: false, + chain_runtime_version: ChainRuntimeVersion::Auto, + } + } +} + +/// Returns stall timeout for relay loop. +/// +/// Relay considers himself stalled if he has submitted transaction to the node, but it has not +/// been mined for this period. +pub fn transaction_stall_timeout( + mortality_period: Option, + average_block_interval: Duration, + default_stall_timeout: Duration, +) -> Duration { + // 1 extra block for transaction to reach the pool && 1 for relayer to awake after it is mined + mortality_period + .map(|mortality_period| average_block_interval.saturating_mul(mortality_period + 1 + 1)) + .unwrap_or(default_stall_timeout) +} diff --git a/bridges/relays/client-substrate/src/metrics/float_storage_value.rs b/bridges/relays/client-substrate/src/metrics/float_storage_value.rs new file mode 100644 index 0000000000000..7bb92693b38d2 --- /dev/null +++ b/bridges/relays/client-substrate/src/metrics/float_storage_value.rs @@ -0,0 +1,133 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +use crate::{chain::Chain, client::Client, Error as SubstrateError}; + +use async_std::sync::{Arc, RwLock}; +use async_trait::async_trait; +use codec::Decode; +use num_traits::One; +use relay_utils::metrics::{ + metric_name, register, F64SharedRef, Gauge, Metric, PrometheusError, Registry, + StandaloneMetric, F64, +}; +use sp_core::storage::{StorageData, StorageKey}; +use sp_runtime::{traits::UniqueSaturatedInto, FixedPointNumber, FixedU128}; +use std::{marker::PhantomData, time::Duration}; + +/// Storage value update interval (in blocks). +const UPDATE_INTERVAL_IN_BLOCKS: u32 = 5; + +/// Fied-point storage value and the way it is decoded from the raw storage value. +pub trait FloatStorageValue: 'static + Clone + Send + Sync { + /// Type of the value. + type Value: FixedPointNumber; + /// Try to decode value from the raw storage value. + fn decode( + &self, + maybe_raw_value: Option, + ) -> Result, SubstrateError>; +} + +/// Implementation of `FloatStorageValue` that expects encoded `FixedU128` value and returns `1` if +/// value is missing from the storage. +#[derive(Clone, Debug, Default)] +pub struct FixedU128OrOne; + +impl FloatStorageValue for FixedU128OrOne { + type Value = FixedU128; + + fn decode( + &self, + maybe_raw_value: Option, + ) -> Result, SubstrateError> { + maybe_raw_value + .map(|raw_value| { + FixedU128::decode(&mut &raw_value.0[..]) + .map_err(SubstrateError::ResponseParseFailed) + .map(Some) + }) + .unwrap_or_else(|| Ok(Some(FixedU128::one()))) + } +} + +/// Metric that represents fixed-point runtime storage value as float gauge. +#[derive(Clone, Debug)] +pub struct FloatStorageValueMetric { + value_converter: V, + client: Client, + storage_key: StorageKey, + metric: Gauge, + shared_value_ref: F64SharedRef, + _phantom: PhantomData, +} + +impl FloatStorageValueMetric { + /// Create new metric. + pub fn new( + value_converter: V, + client: Client, + storage_key: StorageKey, + name: String, + help: String, + ) -> Result { + let shared_value_ref = Arc::new(RwLock::new(None)); + Ok(FloatStorageValueMetric { + value_converter, + client, + storage_key, + metric: Gauge::new(metric_name(None, &name), help)?, + shared_value_ref, + _phantom: Default::default(), + }) + } + + /// Get shared reference to metric value. + pub fn shared_value_ref(&self) -> F64SharedRef { + self.shared_value_ref.clone() + } +} + +impl Metric for FloatStorageValueMetric { + fn register(&self, registry: &Registry) -> Result<(), PrometheusError> { + register(self.metric.clone(), registry).map(drop) + } +} + +#[async_trait] +impl StandaloneMetric for FloatStorageValueMetric { + fn update_interval(&self) -> Duration { + C::AVERAGE_BLOCK_INTERVAL * UPDATE_INTERVAL_IN_BLOCKS + } + + async fn update(&self) { + let value = self + .client + .raw_storage_value(self.storage_key.clone(), None) + .await + .and_then(|maybe_storage_value| { + self.value_converter.decode(maybe_storage_value).map(|maybe_fixed_point_value| { + maybe_fixed_point_value.map(|fixed_point_value| { + fixed_point_value.into_inner().unique_saturated_into() as f64 / + V::Value::DIV.unique_saturated_into() as f64 + }) + }) + }) + .map_err(|e| e.to_string()); + relay_utils::metrics::set_gauge_value(&self.metric, value.clone()); + *self.shared_value_ref.write().await = value.ok().and_then(|x| x); + } +} diff --git a/bridges/relays/client-substrate/src/metrics/mod.rs b/bridges/relays/client-substrate/src/metrics/mod.rs new file mode 100644 index 0000000000000..fe200e2d3dca7 --- /dev/null +++ b/bridges/relays/client-substrate/src/metrics/mod.rs @@ -0,0 +1,21 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Contains several Substrate-specific metrics that may be exposed by relay. + +pub use float_storage_value::{FixedU128OrOne, FloatStorageValue, FloatStorageValueMetric}; + +mod float_storage_value; diff --git a/bridges/relays/client-substrate/src/rpc.rs b/bridges/relays/client-substrate/src/rpc.rs new file mode 100644 index 0000000000000..60c29cdeb5c77 --- /dev/null +++ b/bridges/relays/client-substrate/src/rpc.rs @@ -0,0 +1,176 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! The most generic Substrate node RPC interface. + +use async_trait::async_trait; + +use crate::{Chain, ChainWithGrandpa, TransactionStatusOf}; + +use jsonrpsee::{ + core::{client::Subscription, ClientError}, + proc_macros::rpc, + ws_client::WsClient, +}; +use pallet_transaction_payment_rpc_runtime_api::FeeDetails; +use sc_rpc_api::{state::ReadProof, system::Health}; +use sp_core::{ + storage::{StorageData, StorageKey}, + Bytes, +}; +use sp_rpc::number::NumberOrHex; +use sp_version::RuntimeVersion; + +/// RPC methods of Substrate `system` namespace, that we are using. +#[rpc(client, client_bounds(C: Chain), namespace = "system")] +pub(crate) trait SubstrateSystem { + /// Return node health. + #[method(name = "health")] + async fn health(&self) -> RpcResult; + /// Return system properties. + #[method(name = "properties")] + async fn properties(&self) -> RpcResult; +} + +/// RPC methods of Substrate `chain` namespace, that we are using. +#[rpc(client, client_bounds(C: Chain), namespace = "chain")] +pub(crate) trait SubstrateChain { + /// Get block hash by its number. + #[method(name = "getBlockHash")] + async fn block_hash(&self, block_number: Option) -> RpcResult; + /// Return block header by its hash. + #[method(name = "getHeader")] + async fn header(&self, block_hash: Option) -> RpcResult; + /// Return best finalized block hash. + #[method(name = "getFinalizedHead")] + async fn finalized_head(&self) -> RpcResult; + /// Return signed block (with justifications) by its hash. + #[method(name = "getBlock")] + async fn block(&self, block_hash: Option) -> RpcResult; +} + +/// RPC methods of Substrate `author` namespace, that we are using. +#[rpc(client, client_bounds(C: Chain), namespace = "author")] +pub(crate) trait SubstrateAuthor { + /// Submit extrinsic to the transaction pool. + #[method(name = "submitExtrinsic")] + async fn submit_extrinsic(&self, extrinsic: Bytes) -> RpcResult; + /// Return vector of pending extrinsics from the transaction pool. + #[method(name = "pendingExtrinsics")] + async fn pending_extrinsics(&self) -> RpcResult>; + /// Submit and watch for extrinsic state. + #[subscription(name = "submitAndWatchExtrinsic", unsubscribe = "unwatchExtrinsic", item = TransactionStatusOf)] + async fn submit_and_watch_extrinsic(&self, extrinsic: Bytes); +} + +/// RPC methods of Substrate `state` namespace, that we are using. +#[rpc(client, client_bounds(C: Chain), namespace = "state")] +pub(crate) trait SubstrateState { + /// Get current runtime version. + #[method(name = "getRuntimeVersion")] + async fn runtime_version(&self) -> RpcResult; + /// Call given runtime method. + #[method(name = "call")] + async fn call( + &self, + method: String, + data: Bytes, + at_block: Option, + ) -> RpcResult; + /// Get value of the runtime storage. + #[method(name = "getStorage")] + async fn storage( + &self, + key: StorageKey, + at_block: Option, + ) -> RpcResult>; + /// Get proof of the runtime storage value. + #[method(name = "getReadProof")] + async fn prove_storage( + &self, + keys: Vec, + hash: Option, + ) -> RpcResult>; +} + +/// RPC methods that we are using for a certain finality gadget. +#[async_trait] +pub trait SubstrateFinalityClient { + /// Subscribe to finality justifications. + async fn subscribe_justifications( + client: &WsClient, + ) -> Result, ClientError>; +} + +/// RPC methods of Substrate `grandpa` namespace, that we are using. +#[rpc(client, client_bounds(C: ChainWithGrandpa), namespace = "grandpa")] +pub(crate) trait SubstrateGrandpa { + /// Subscribe to GRANDPA justifications. + #[subscription(name = "subscribeJustifications", unsubscribe = "unsubscribeJustifications", item = Bytes)] + async fn subscribe_justifications(&self); +} + +/// RPC finality methods of Substrate `grandpa` namespace, that we are using. +pub struct SubstrateGrandpaFinalityClient; +#[async_trait] +impl SubstrateFinalityClient for SubstrateGrandpaFinalityClient { + async fn subscribe_justifications( + client: &WsClient, + ) -> Result, ClientError> { + SubstrateGrandpaClient::::subscribe_justifications(client).await + } +} + +// TODO: Use `ChainWithBeefy` instead of `Chain` after #1606 is merged +/// RPC methods of Substrate `beefy` namespace, that we are using. +#[rpc(client, client_bounds(C: Chain), namespace = "beefy")] +pub(crate) trait SubstrateBeefy { + /// Subscribe to BEEFY justifications. + #[subscription(name = "subscribeJustifications", unsubscribe = "unsubscribeJustifications", item = Bytes)] + async fn subscribe_justifications(&self); +} + +/// RPC finality methods of Substrate `beefy` namespace, that we are using. +pub struct SubstrateBeefyFinalityClient; +// TODO: Use `ChainWithBeefy` instead of `Chain` after #1606 is merged +#[async_trait] +impl SubstrateFinalityClient for SubstrateBeefyFinalityClient { + async fn subscribe_justifications( + client: &WsClient, + ) -> Result, ClientError> { + SubstrateBeefyClient::::subscribe_justifications(client).await + } +} + +/// RPC methods of Substrate `system` frame pallet, that we are using. +#[rpc(client, client_bounds(C: Chain), namespace = "system")] +pub(crate) trait SubstrateFrameSystem { + /// Return index of next account transaction. + #[method(name = "accountNextIndex")] + async fn account_next_index(&self, account_id: C::AccountId) -> RpcResult; +} + +/// RPC methods of Substrate `pallet_transaction_payment` frame pallet, that we are using. +#[rpc(client, client_bounds(C: Chain), namespace = "payment")] +pub(crate) trait SubstrateTransactionPayment { + /// Query transaction fee details. + #[method(name = "queryFeeDetails")] + async fn fee_details( + &self, + extrinsic: Bytes, + at_block: Option, + ) -> RpcResult>; +} diff --git a/bridges/relays/client-substrate/src/sync_header.rs b/bridges/relays/client-substrate/src/sync_header.rs new file mode 100644 index 0000000000000..fdfd1f22ce9ed --- /dev/null +++ b/bridges/relays/client-substrate/src/sync_header.rs @@ -0,0 +1,61 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +use bp_header_chain::ConsensusLogReader; +use finality_relay::SourceHeader as FinalitySourceHeader; +use sp_runtime::traits::Header as HeaderT; + +/// Generic wrapper for `sp_runtime::traits::Header` based headers, that +/// implements `finality_relay::SourceHeader` and may be used in headers sync directly. +#[derive(Clone, Debug, PartialEq)] +pub struct SyncHeader
(Header); + +impl
SyncHeader
{ + /// Extracts wrapped header from self. + pub fn into_inner(self) -> Header { + self.0 + } +} + +impl
std::ops::Deref for SyncHeader
{ + type Target = Header; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl
From
for SyncHeader
{ + fn from(header: Header) -> Self { + Self(header) + } +} + +impl FinalitySourceHeader + for SyncHeader
+{ + fn hash(&self) -> Header::Hash { + self.0.hash() + } + + fn number(&self) -> Header::Number { + *self.0.number() + } + + fn is_mandatory(&self) -> bool { + R::schedules_authorities_change(self.digest()) + } +} diff --git a/bridges/relays/client-substrate/src/test_chain.rs b/bridges/relays/client-substrate/src/test_chain.rs new file mode 100644 index 0000000000000..77240d15884f4 --- /dev/null +++ b/bridges/relays/client-substrate/src/test_chain.rs @@ -0,0 +1,132 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Pallet provides a set of guard functions that are running in background threads +//! and are aborting process if some condition fails. + +//! Test chain implementation to use in tests. + +#![cfg(any(feature = "test-helpers", test))] + +use crate::{Chain, ChainWithBalances, ChainWithMessages}; +use bp_messages::{ChainWithMessages as ChainWithMessagesBase, MessageNonce}; +use bp_runtime::ChainId; +use frame_support::weights::Weight; +use std::time::Duration; + +/// Chain that may be used in tests. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TestChain; + +impl bp_runtime::Chain for TestChain { + const ID: ChainId = *b"test"; + + type BlockNumber = u32; + type Hash = sp_core::H256; + type Hasher = sp_runtime::traits::BlakeTwo256; + type Header = sp_runtime::generic::Header; + + type AccountId = u32; + type Balance = u32; + type Nonce = u32; + type Signature = sp_runtime::testing::TestSignature; + + fn max_extrinsic_size() -> u32 { + 100000 + } + + fn max_extrinsic_weight() -> Weight { + unreachable!() + } +} + +impl Chain for TestChain { + const NAME: &'static str = "Test"; + const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = "TestMethod"; + const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_millis(0); + + type SignedBlock = sp_runtime::generic::SignedBlock< + sp_runtime::generic::Block, + >; + type Call = (); +} + +impl ChainWithBalances for TestChain { + fn account_info_storage_key(_account_id: &u32) -> sp_core::storage::StorageKey { + unreachable!() + } +} + +impl ChainWithMessagesBase for TestChain { + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "Test"; + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 0; + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 0; +} + +impl ChainWithMessages for TestChain { + const WITH_CHAIN_RELAYERS_PALLET_NAME: Option<&'static str> = None; + const TO_CHAIN_MESSAGE_DETAILS_METHOD: &'static str = "TestMessagesDetailsMethod"; + const FROM_CHAIN_MESSAGE_DETAILS_METHOD: &'static str = "TestFromMessagesDetailsMethod"; +} + +/// Primitives-level parachain that may be used in tests. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TestParachainBase; + +impl bp_runtime::Chain for TestParachainBase { + const ID: ChainId = *b"tstp"; + + type BlockNumber = u32; + type Hash = sp_core::H256; + type Hasher = sp_runtime::traits::BlakeTwo256; + type Header = sp_runtime::generic::Header; + + type AccountId = u32; + type Balance = u32; + type Nonce = u32; + type Signature = sp_runtime::testing::TestSignature; + + fn max_extrinsic_size() -> u32 { + unreachable!() + } + + fn max_extrinsic_weight() -> Weight { + unreachable!() + } +} + +impl bp_runtime::Parachain for TestParachainBase { + const PARACHAIN_ID: u32 = 1000; +} + +/// Parachain that may be used in tests. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TestParachain; + +impl bp_runtime::UnderlyingChainProvider for TestParachain { + type Chain = TestParachainBase; +} + +impl Chain for TestParachain { + const NAME: &'static str = "TestParachain"; + const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = "TestParachainMethod"; + const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_millis(0); + + type SignedBlock = sp_runtime::generic::SignedBlock< + sp_runtime::generic::Block, + >; + type Call = (); +} diff --git a/bridges/relays/client-substrate/src/transaction_tracker.rs b/bridges/relays/client-substrate/src/transaction_tracker.rs new file mode 100644 index 0000000000000..00375768c45c2 --- /dev/null +++ b/bridges/relays/client-substrate/src/transaction_tracker.rs @@ -0,0 +1,447 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Helper for tracking transaction invalidation events. + +use crate::{Chain, Client, Error, HashOf, HeaderIdOf, Subscription, TransactionStatusOf}; + +use async_trait::async_trait; +use futures::{future::Either, Future, FutureExt, Stream, StreamExt}; +use relay_utils::{HeaderId, TrackedTransactionStatus}; +use sp_runtime::traits::Header as _; +use std::time::Duration; + +/// Transaction tracker environment. +#[async_trait] +pub trait Environment: Send + Sync { + /// Returns header id by its hash. + async fn header_id_by_hash(&self, hash: HashOf) -> Result, Error>; +} + +#[async_trait] +impl Environment for Client { + async fn header_id_by_hash(&self, hash: HashOf) -> Result, Error> { + self.header_by_hash(hash).await.map(|h| HeaderId(*h.number(), hash)) + } +} + +/// Substrate transaction tracker implementation. +/// +/// Substrate node provides RPC API to submit and watch for transaction events. This way +/// we may know when transaction is included into block, finalized or rejected. There are +/// some edge cases, when we can't fully trust this mechanism - e.g. transaction may broadcasted +/// and then dropped out of node transaction pool (some other cases are also possible - node +/// restarts, connection lost, ...). Then we can't know for sure - what is currently happening +/// with our transaction. Is the transaction really lost? Is it still alive on the chain network? +/// +/// We have several options to handle such cases: +/// +/// 1) hope that the transaction is still alive and wait for its mining until it is spoiled; +/// +/// 2) assume that the transaction is lost and resubmit another transaction instantly; +/// +/// 3) wait for some time (if transaction is mortal - then until block where it dies; if it is +/// immortal - then for some time that we assume is long enough to mine it) and assume that it is +/// lost. +/// +/// This struct implements third option as it seems to be the most optimal. +pub struct TransactionTracker { + environment: E, + transaction_hash: HashOf, + stall_timeout: Duration, + subscription: Subscription>, +} + +impl> TransactionTracker { + /// Create transaction tracker. + pub fn new( + environment: E, + stall_timeout: Duration, + transaction_hash: HashOf, + subscription: Subscription>, + ) -> Self { + Self { environment, stall_timeout, transaction_hash, subscription } + } + + /// Wait for final transaction status and return it along with last known internal invalidation + /// status. + async fn do_wait( + self, + wait_for_stall_timeout: impl Future, + wait_for_stall_timeout_rest: impl Future, + ) -> (TrackedTransactionStatus>, Option>>) { + // sometimes we want to wait for the rest of the stall timeout even if + // `wait_for_invalidation` has been "select"ed first => it is shared + let wait_for_invalidation = watch_transaction_status::<_, C, _>( + self.environment, + self.transaction_hash, + self.subscription.into_stream(), + ); + futures::pin_mut!(wait_for_stall_timeout, wait_for_invalidation); + + match futures::future::select(wait_for_stall_timeout, wait_for_invalidation).await { + Either::Left((_, _)) => { + log::trace!( + target: "bridge", + "{} transaction {:?} is considered lost after timeout (no status response from the node)", + C::NAME, + self.transaction_hash, + ); + + (TrackedTransactionStatus::Lost, None) + }, + Either::Right((invalidation_status, _)) => match invalidation_status { + InvalidationStatus::Finalized(at_block) => + (TrackedTransactionStatus::Finalized(at_block), Some(invalidation_status)), + InvalidationStatus::Invalid => + (TrackedTransactionStatus::Lost, Some(invalidation_status)), + InvalidationStatus::Lost => { + // wait for the rest of stall timeout - this way we'll be sure that the + // transaction is actually dead if it has been crafted properly + wait_for_stall_timeout_rest.await; + // if someone is still watching for our transaction, then we're reporting + // an error here (which is treated as "transaction lost") + log::trace!( + target: "bridge", + "{} transaction {:?} is considered lost after timeout", + C::NAME, + self.transaction_hash, + ); + + (TrackedTransactionStatus::Lost, Some(invalidation_status)) + }, + }, + } + } +} + +#[async_trait] +impl> relay_utils::TransactionTracker for TransactionTracker { + type HeaderId = HeaderIdOf; + + async fn wait(self) -> TrackedTransactionStatus> { + let wait_for_stall_timeout = async_std::task::sleep(self.stall_timeout).shared(); + let wait_for_stall_timeout_rest = wait_for_stall_timeout.clone(); + self.do_wait(wait_for_stall_timeout, wait_for_stall_timeout_rest).await.0 + } +} + +/// Transaction invalidation status. +/// +/// Note that in places where the `TransactionTracker` is used, the finalization event will be +/// ignored - relay loops are detecting the mining/finalization using their own +/// techniques. That's why we're using `InvalidationStatus` here. +#[derive(Debug, PartialEq)] +enum InvalidationStatus { + /// Transaction has been included into block and finalized at given block. + Finalized(BlockId), + /// Transaction has been invalidated. + Invalid, + /// We have lost track of transaction status. + Lost, +} + +/// Watch for transaction status until transaction is finalized or we lose track of its status. +async fn watch_transaction_status< + E: Environment, + C: Chain, + S: Stream>, +>( + environment: E, + transaction_hash: HashOf, + subscription: S, +) -> InvalidationStatus> { + futures::pin_mut!(subscription); + + loop { + match subscription.next().await { + Some(TransactionStatusOf::::Finalized((block_hash, _))) => { + // the only "successful" outcome of this method is when the block with transaction + // has been finalized + log::trace!( + target: "bridge", + "{} transaction {:?} has been finalized at block: {:?}", + C::NAME, + transaction_hash, + block_hash, + ); + + let header_id = match environment.header_id_by_hash(block_hash).await { + Ok(header_id) => header_id, + Err(e) => { + log::error!( + target: "bridge", + "Failed to read header {:?} when watching for {} transaction {:?}: {:?}", + block_hash, + C::NAME, + transaction_hash, + e, + ); + // that's the best option we have here + return InvalidationStatus::Lost + }, + }; + return InvalidationStatus::Finalized(header_id) + }, + Some(TransactionStatusOf::::Invalid) => { + // if node says that the transaction is invalid, there are still chances that + // it is not actually invalid - e.g. if the block where transaction has been + // revalidated is retracted and transaction (at some other node pool) becomes + // valid again on other fork. But let's assume that the chances of this event + // are almost zero - there's a lot of things that must happen for this to be the + // case. + log::trace!( + target: "bridge", + "{} transaction {:?} has been invalidated", + C::NAME, + transaction_hash, + ); + return InvalidationStatus::Invalid + }, + Some(TransactionStatusOf::::Future) | + Some(TransactionStatusOf::::Ready) | + Some(TransactionStatusOf::::Broadcast(_)) => { + // nothing important (for us) has happened + }, + Some(TransactionStatusOf::::InBlock(block_hash)) => { + // TODO: read matching system event (ExtrinsicSuccess or ExtrinsicFailed), log it + // here and use it later (on finality) for reporting invalid transaction + // https://github.com/paritytech/parity-bridges-common/issues/1464 + log::trace!( + target: "bridge", + "{} transaction {:?} has been included in block: {:?}", + C::NAME, + transaction_hash, + block_hash, + ); + }, + Some(TransactionStatusOf::::Retracted(block_hash)) => { + log::trace!( + target: "bridge", + "{} transaction {:?} at block {:?} has been retracted", + C::NAME, + transaction_hash, + block_hash, + ); + }, + Some(TransactionStatusOf::::FinalityTimeout(block_hash)) => { + // finality is lagging? let's wait a bit more and report a stall + log::trace!( + target: "bridge", + "{} transaction {:?} block {:?} has not been finalized for too long", + C::NAME, + transaction_hash, + block_hash, + ); + return InvalidationStatus::Lost + }, + Some(TransactionStatusOf::::Usurped(new_transaction_hash)) => { + // this may be result of our transaction resubmitter work or some manual + // intervention. In both cases - let's start stall timeout, because the meaning + // of transaction may have changed + log::trace!( + target: "bridge", + "{} transaction {:?} has been usurped by new transaction: {:?}", + C::NAME, + transaction_hash, + new_transaction_hash, + ); + return InvalidationStatus::Lost + }, + Some(TransactionStatusOf::::Dropped) => { + // the transaction has been removed from the pool because of its limits. Let's wait + // a bit and report a stall + log::trace!( + target: "bridge", + "{} transaction {:?} has been dropped from the pool", + C::NAME, + transaction_hash, + ); + return InvalidationStatus::Lost + }, + None => { + // the status of transaction is unknown to us (the subscription has been closed?). + // Let's wait a bit and report a stall + return InvalidationStatus::Lost + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_chain::TestChain; + use futures::{FutureExt, SinkExt}; + use sc_transaction_pool_api::TransactionStatus; + + struct TestEnvironment(Result, Error>); + + #[async_trait] + impl Environment for TestEnvironment { + async fn header_id_by_hash( + &self, + _hash: HashOf, + ) -> Result, Error> { + self.0.as_ref().map_err(|_| Error::BridgePalletIsNotInitialized).cloned() + } + } + + async fn on_transaction_status( + status: TransactionStatus, HashOf>, + ) -> Option<( + TrackedTransactionStatus>, + InvalidationStatus>, + )> { + let (mut sender, receiver) = futures::channel::mpsc::channel(1); + let tx_tracker = TransactionTracker::::new( + TestEnvironment(Ok(HeaderId(0, Default::default()))), + Duration::from_secs(0), + Default::default(), + Subscription(async_std::sync::Mutex::new(receiver)), + ); + + let wait_for_stall_timeout = futures::future::pending(); + let wait_for_stall_timeout_rest = futures::future::ready(()); + sender.send(Some(status)).await.unwrap(); + tx_tracker + .do_wait(wait_for_stall_timeout, wait_for_stall_timeout_rest) + .now_or_never() + .map(|(ts, is)| (ts, is.unwrap())) + } + + #[async_std::test] + async fn returns_finalized_on_finalized() { + assert_eq!( + on_transaction_status(TransactionStatus::Finalized(Default::default())).await, + Some(( + TrackedTransactionStatus::Finalized(Default::default()), + InvalidationStatus::Finalized(Default::default()) + )), + ); + } + + #[async_std::test] + async fn returns_lost_on_finalized_and_environment_error() { + assert_eq!( + watch_transaction_status::<_, TestChain, _>( + TestEnvironment(Err(Error::BridgePalletIsNotInitialized)), + Default::default(), + futures::stream::iter([TransactionStatus::Finalized(Default::default())]) + ) + .now_or_never(), + Some(InvalidationStatus::Lost), + ); + } + + #[async_std::test] + async fn returns_invalid_on_invalid() { + assert_eq!( + on_transaction_status(TransactionStatus::Invalid).await, + Some((TrackedTransactionStatus::Lost, InvalidationStatus::Invalid)), + ); + } + + #[async_std::test] + async fn waits_on_future() { + assert_eq!(on_transaction_status(TransactionStatus::Future).await, None,); + } + + #[async_std::test] + async fn waits_on_ready() { + assert_eq!(on_transaction_status(TransactionStatus::Ready).await, None,); + } + + #[async_std::test] + async fn waits_on_broadcast() { + assert_eq!( + on_transaction_status(TransactionStatus::Broadcast(Default::default())).await, + None, + ); + } + + #[async_std::test] + async fn waits_on_in_block() { + assert_eq!( + on_transaction_status(TransactionStatus::InBlock(Default::default())).await, + None, + ); + } + + #[async_std::test] + async fn waits_on_retracted() { + assert_eq!( + on_transaction_status(TransactionStatus::Retracted(Default::default())).await, + None, + ); + } + + #[async_std::test] + async fn lost_on_finality_timeout() { + assert_eq!( + on_transaction_status(TransactionStatus::FinalityTimeout(Default::default())).await, + Some((TrackedTransactionStatus::Lost, InvalidationStatus::Lost)), + ); + } + + #[async_std::test] + async fn lost_on_usurped() { + assert_eq!( + on_transaction_status(TransactionStatus::Usurped(Default::default())).await, + Some((TrackedTransactionStatus::Lost, InvalidationStatus::Lost)), + ); + } + + #[async_std::test] + async fn lost_on_dropped() { + assert_eq!( + on_transaction_status(TransactionStatus::Dropped).await, + Some((TrackedTransactionStatus::Lost, InvalidationStatus::Lost)), + ); + } + + #[async_std::test] + async fn lost_on_subscription_error() { + assert_eq!( + watch_transaction_status::<_, TestChain, _>( + TestEnvironment(Ok(HeaderId(0, Default::default()))), + Default::default(), + futures::stream::iter([]) + ) + .now_or_never(), + Some(InvalidationStatus::Lost), + ); + } + + #[async_std::test] + async fn lost_on_timeout_when_waiting_for_invalidation_status() { + let (_sender, receiver) = futures::channel::mpsc::channel(1); + let tx_tracker = TransactionTracker::::new( + TestEnvironment(Ok(HeaderId(0, Default::default()))), + Duration::from_secs(0), + Default::default(), + Subscription(async_std::sync::Mutex::new(receiver)), + ); + + let wait_for_stall_timeout = futures::future::ready(()).shared(); + let wait_for_stall_timeout_rest = wait_for_stall_timeout.clone(); + let wait_result = tx_tracker + .do_wait(wait_for_stall_timeout, wait_for_stall_timeout_rest) + .now_or_never(); + + assert_eq!(wait_result, Some((TrackedTransactionStatus::Lost, None))); + } +} diff --git a/bridges/relays/equivocation/Cargo.toml b/bridges/relays/equivocation/Cargo.toml new file mode 100644 index 0000000000000..e7146e05f479c --- /dev/null +++ b/bridges/relays/equivocation/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "equivocation-detector" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +repository.workspace = true +description = "Equivocation detector" +publish = false + +[lints] +workspace = true + +[dependencies] +async-std = { version = "1.9.0", features = ["attributes"] } +async-trait = "0.1.79" +bp-header-chain = { path = "../../primitives/header-chain" } +finality-relay = { path = "../finality" } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +futures = "0.3.30" +log = { workspace = true } +num-traits = "0.2" +relay-utils = { path = "../utils" } diff --git a/bridges/relays/equivocation/src/block_checker.rs b/bridges/relays/equivocation/src/block_checker.rs new file mode 100644 index 0000000000000..c8131e5b9796f --- /dev/null +++ b/bridges/relays/equivocation/src/block_checker.rs @@ -0,0 +1,471 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +use crate::{ + handle_client_error, reporter::EquivocationsReporter, EquivocationDetectionPipeline, + EquivocationReportingContext, HeaderFinalityInfo, SourceClient, TargetClient, +}; + +use bp_header_chain::{FinalityProof, FindEquivocations as FindEquivocationsT}; +use finality_relay::FinalityProofsBuf; +use futures::future::{BoxFuture, FutureExt}; +use num_traits::Saturating; + +/// First step in the block checking state machine. +/// +/// Getting the finality info associated to the source headers synced with the target chain +/// at the specified block. +#[cfg_attr(test, derive(Debug, PartialEq))] +pub struct ReadSyncedHeaders { + pub target_block_num: P::TargetNumber, +} + +impl ReadSyncedHeaders

{ + pub async fn next>( + self, + target_client: &mut TC, + ) -> Result, Self> { + match target_client.synced_headers_finality_info(self.target_block_num).await { + Ok(synced_headers) => + Ok(ReadContext { target_block_num: self.target_block_num, synced_headers }), + Err(e) => { + log::error!( + target: "bridge", + "Could not get {} headers synced to {} at block {}: {e:?}", + P::SOURCE_NAME, + P::TARGET_NAME, + self.target_block_num + ); + + // Reconnect target client in case of a connection error. + handle_client_error(target_client, e).await; + + Err(self) + }, + } + } +} + +/// Second step in the block checking state machine. +/// +/// Reading the equivocation reporting context from the target chain. +#[cfg_attr(test, derive(Debug))] +pub struct ReadContext { + target_block_num: P::TargetNumber, + synced_headers: Vec>, +} + +impl ReadContext

{ + pub async fn next>( + self, + target_client: &mut TC, + ) -> Result>, Self> { + match EquivocationReportingContext::try_read_from_target::( + target_client, + self.target_block_num.saturating_sub(1.into()), + ) + .await + { + Ok(Some(context)) => Ok(Some(FindEquivocations { + target_block_num: self.target_block_num, + synced_headers: self.synced_headers, + context, + })), + Ok(None) => Ok(None), + Err(e) => { + log::error!( + target: "bridge", + "Could not read {} `EquivocationReportingContext` from {} at block {}: {e:?}", + P::SOURCE_NAME, + P::TARGET_NAME, + self.target_block_num.saturating_sub(1.into()), + ); + + // Reconnect target client in case of a connection error. + handle_client_error(target_client, e).await; + + Err(self) + }, + } + } +} + +/// Third step in the block checking state machine. +/// +/// Searching for equivocations in the source headers synced with the target chain. +#[cfg_attr(test, derive(Debug))] +pub struct FindEquivocations { + target_block_num: P::TargetNumber, + synced_headers: Vec>, + context: EquivocationReportingContext

, +} + +impl FindEquivocations

{ + pub fn next( + mut self, + finality_proofs_buf: &mut FinalityProofsBuf

, + ) -> Vec> { + let mut result = vec![]; + for synced_header in self.synced_headers { + match P::EquivocationsFinder::find_equivocations( + &self.context.synced_verification_context, + &synced_header.finality_proof, + finality_proofs_buf.buf().as_slice(), + ) { + Ok(equivocations) => + if !equivocations.is_empty() { + result.push(ReportEquivocations { + source_block_hash: self.context.synced_header_hash, + equivocations, + }) + }, + Err(e) => { + log::error!( + target: "bridge", + "Could not search for equivocations in the finality proof \ + for source header {:?} synced at target block {}: {e:?}", + synced_header.finality_proof.target_header_hash(), + self.target_block_num + ); + }, + }; + + finality_proofs_buf.prune(synced_header.finality_proof.target_header_number(), None); + self.context.update(synced_header); + } + + result + } +} + +/// Fourth step in the block checking state machine. +/// +/// Reporting the detected equivocations (if any). +#[cfg_attr(test, derive(Debug))] +pub struct ReportEquivocations { + source_block_hash: P::Hash, + equivocations: Vec, +} + +impl ReportEquivocations

{ + pub async fn next>( + mut self, + source_client: &mut SC, + reporter: &mut EquivocationsReporter<'_, P, SC>, + ) -> Result<(), Self> { + let mut unprocessed_equivocations = vec![]; + for equivocation in self.equivocations { + match reporter + .submit_report(source_client, self.source_block_hash, equivocation.clone()) + .await + { + Ok(_) => {}, + Err(e) => { + log::error!( + target: "bridge", + "Could not submit equivocation report to {} for {equivocation:?}: {e:?}", + P::SOURCE_NAME, + ); + + // Mark the equivocation as unprocessed + unprocessed_equivocations.push(equivocation); + // Reconnect source client in case of a connection error. + handle_client_error(source_client, e).await; + }, + } + } + + self.equivocations = unprocessed_equivocations; + if !self.equivocations.is_empty() { + return Err(self) + } + + Ok(()) + } +} + +/// Block checking state machine. +#[cfg_attr(test, derive(Debug))] +pub enum BlockChecker { + ReadSyncedHeaders(ReadSyncedHeaders

), + ReadContext(ReadContext

), + ReportEquivocations(Vec>), +} + +impl BlockChecker

{ + pub fn new(target_block_num: P::TargetNumber) -> Self { + Self::ReadSyncedHeaders(ReadSyncedHeaders { target_block_num }) + } + + pub fn run<'a, SC: SourceClient

, TC: TargetClient

>( + self, + source_client: &'a mut SC, + target_client: &'a mut TC, + finality_proofs_buf: &'a mut FinalityProofsBuf

, + reporter: &'a mut EquivocationsReporter, + ) -> BoxFuture<'a, Result<(), Self>> { + async move { + match self { + Self::ReadSyncedHeaders(state) => { + let read_context = + state.next(target_client).await.map_err(Self::ReadSyncedHeaders)?; + Self::ReadContext(read_context) + .run(source_client, target_client, finality_proofs_buf, reporter) + .await + }, + Self::ReadContext(state) => { + let maybe_find_equivocations = + state.next(target_client).await.map_err(Self::ReadContext)?; + let find_equivocations = match maybe_find_equivocations { + Some(find_equivocations) => find_equivocations, + None => return Ok(()), + }; + Self::ReportEquivocations(find_equivocations.next(finality_proofs_buf)) + .run(source_client, target_client, finality_proofs_buf, reporter) + .await + }, + Self::ReportEquivocations(state) => { + let mut failures = vec![]; + for report_equivocations in state { + if let Err(failure) = + report_equivocations.next(source_client, reporter).await + { + failures.push(failure); + } + } + + if !failures.is_empty() { + return Err(Self::ReportEquivocations(failures)) + } + + Ok(()) + }, + } + } + .boxed() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::*; + use std::collections::HashMap; + + impl PartialEq for ReadContext { + fn eq(&self, other: &Self) -> bool { + self.target_block_num == other.target_block_num && + self.synced_headers == other.synced_headers + } + } + + impl PartialEq for FindEquivocations { + fn eq(&self, other: &Self) -> bool { + self.target_block_num == other.target_block_num && + self.synced_headers == other.synced_headers && + self.context == other.context + } + } + + impl PartialEq for ReportEquivocations { + fn eq(&self, other: &Self) -> bool { + self.source_block_hash == other.source_block_hash && + self.equivocations == other.equivocations + } + } + + impl PartialEq for BlockChecker { + fn eq(&self, _other: &Self) -> bool { + matches!(self, _other) + } + } + + #[async_std::test] + async fn block_checker_works() { + let mut source_client = TestSourceClient { ..Default::default() }; + let mut target_client = TestTargetClient { + best_synced_header_hash: HashMap::from([(9, Ok(Some(5)))]), + finality_verification_context: HashMap::from([( + 9, + Ok(TestFinalityVerificationContext { check_equivocations: true }), + )]), + synced_headers_finality_info: HashMap::from([( + 10, + Ok(vec![ + new_header_finality_info(6, None), + new_header_finality_info(7, Some(false)), + new_header_finality_info(8, None), + new_header_finality_info(9, Some(true)), + new_header_finality_info(10, None), + new_header_finality_info(11, None), + new_header_finality_info(12, None), + ]), + )]), + ..Default::default() + }; + let mut reporter = + EquivocationsReporter::::new(); + + let block_checker = BlockChecker::new(10); + assert!(block_checker + .run( + &mut source_client, + &mut target_client, + &mut FinalityProofsBuf::new(vec![ + TestFinalityProof(6, vec!["6-1"]), + TestFinalityProof(7, vec![]), + TestFinalityProof(8, vec!["8-1"]), + TestFinalityProof(9, vec!["9-1"]), + TestFinalityProof(10, vec![]), + TestFinalityProof(11, vec!["11-1", "11-2"]), + TestFinalityProof(12, vec!["12-1"]) + ]), + &mut reporter + ) + .await + .is_ok()); + assert_eq!( + *source_client.reported_equivocations.lock().unwrap(), + HashMap::from([(5, vec!["6-1"]), (9, vec!["11-1", "11-2", "12-1"])]) + ); + } + + #[async_std::test] + async fn block_checker_works_with_empty_context() { + let mut target_client = TestTargetClient { + best_synced_header_hash: HashMap::from([(9, Ok(None))]), + finality_verification_context: HashMap::from([( + 9, + Ok(TestFinalityVerificationContext { check_equivocations: true }), + )]), + synced_headers_finality_info: HashMap::from([( + 10, + Ok(vec![new_header_finality_info(6, None)]), + )]), + ..Default::default() + }; + let mut source_client = TestSourceClient { ..Default::default() }; + let mut reporter = + EquivocationsReporter::::new(); + + let block_checker = BlockChecker::new(10); + assert!(block_checker + .run( + &mut source_client, + &mut target_client, + &mut FinalityProofsBuf::new(vec![TestFinalityProof(6, vec!["6-1"])]), + &mut reporter + ) + .await + .is_ok()); + assert_eq!(*source_client.reported_equivocations.lock().unwrap(), HashMap::default()); + } + + #[async_std::test] + async fn read_synced_headers_handles_errors() { + let mut target_client = TestTargetClient { + synced_headers_finality_info: HashMap::from([ + (10, Err(TestClientError::NonConnection)), + (11, Err(TestClientError::Connection)), + ]), + ..Default::default() + }; + let mut source_client = TestSourceClient { ..Default::default() }; + let mut reporter = + EquivocationsReporter::::new(); + + // NonConnection error + let block_checker = BlockChecker::new(10); + assert_eq!( + block_checker + .run( + &mut source_client, + &mut target_client, + &mut FinalityProofsBuf::new(vec![]), + &mut reporter + ) + .await, + Err(BlockChecker::ReadSyncedHeaders(ReadSyncedHeaders { target_block_num: 10 })) + ); + assert_eq!(target_client.num_reconnects, 0); + + // Connection error + let block_checker = BlockChecker::new(11); + assert_eq!( + block_checker + .run( + &mut source_client, + &mut target_client, + &mut FinalityProofsBuf::new(vec![]), + &mut reporter + ) + .await, + Err(BlockChecker::ReadSyncedHeaders(ReadSyncedHeaders { target_block_num: 11 })) + ); + assert_eq!(target_client.num_reconnects, 1); + } + + #[async_std::test] + async fn read_context_handles_errors() { + let mut target_client = TestTargetClient { + synced_headers_finality_info: HashMap::from([(10, Ok(vec![])), (11, Ok(vec![]))]), + best_synced_header_hash: HashMap::from([ + (9, Err(TestClientError::NonConnection)), + (10, Err(TestClientError::Connection)), + ]), + ..Default::default() + }; + let mut source_client = TestSourceClient { ..Default::default() }; + let mut reporter = + EquivocationsReporter::::new(); + + // NonConnection error + let block_checker = BlockChecker::new(10); + assert_eq!( + block_checker + .run( + &mut source_client, + &mut target_client, + &mut FinalityProofsBuf::new(vec![]), + &mut reporter + ) + .await, + Err(BlockChecker::ReadContext(ReadContext { + target_block_num: 10, + synced_headers: vec![] + })) + ); + assert_eq!(target_client.num_reconnects, 0); + + // Connection error + let block_checker = BlockChecker::new(11); + assert_eq!( + block_checker + .run( + &mut source_client, + &mut target_client, + &mut FinalityProofsBuf::new(vec![]), + &mut reporter + ) + .await, + Err(BlockChecker::ReadContext(ReadContext { + target_block_num: 11, + synced_headers: vec![] + })) + ); + assert_eq!(target_client.num_reconnects, 1); + } +} diff --git a/bridges/relays/equivocation/src/equivocation_loop.rs b/bridges/relays/equivocation/src/equivocation_loop.rs new file mode 100644 index 0000000000000..dfc4af0d4f62b --- /dev/null +++ b/bridges/relays/equivocation/src/equivocation_loop.rs @@ -0,0 +1,308 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +use crate::{ + handle_client_error, reporter::EquivocationsReporter, EquivocationDetectionPipeline, + SourceClient, TargetClient, +}; + +use crate::block_checker::BlockChecker; +use finality_relay::{FinalityProofsBuf, FinalityProofsStream}; +use futures::{select_biased, FutureExt}; +use num_traits::Saturating; +use relay_utils::{metrics::MetricsParams, FailedClient}; +use std::{future::Future, time::Duration}; + +/// Equivocations detection loop state. +struct EquivocationDetectionLoop< + P: EquivocationDetectionPipeline, + SC: SourceClient

, + TC: TargetClient

, +> { + source_client: SC, + target_client: TC, + + from_block_num: Option, + until_block_num: Option, + + reporter: EquivocationsReporter<'static, P, SC>, + + finality_proofs_stream: FinalityProofsStream, + finality_proofs_buf: FinalityProofsBuf

, +} + +impl, TC: TargetClient

> + EquivocationDetectionLoop +{ + async fn ensure_finality_proofs_stream(&mut self) { + match self.finality_proofs_stream.ensure_stream(&self.source_client).await { + Ok(_) => {}, + Err(e) => { + log::error!( + target: "bridge", + "Could not connect to the {} `FinalityProofsStream`: {e:?}", + P::SOURCE_NAME, + ); + + // Reconnect to the source client if needed + handle_client_error(&mut self.source_client, e).await; + }, + } + } + + async fn best_finalized_target_block_number(&mut self) -> Option { + match self.target_client.best_finalized_header_number().await { + Ok(block_num) => Some(block_num), + Err(e) => { + log::error!( + target: "bridge", + "Could not read best finalized header number from {}: {e:?}", + P::TARGET_NAME, + ); + + // Reconnect target client and move on + handle_client_error(&mut self.target_client, e).await; + + None + }, + } + } + + async fn do_run(&mut self, tick: Duration, exit_signal: impl Future) { + let exit_signal = exit_signal.fuse(); + futures::pin_mut!(exit_signal); + + loop { + // Make sure that we are connected to the source finality proofs stream. + self.ensure_finality_proofs_stream().await; + // Check the status of the pending equivocation reports + self.reporter.process_pending_reports().await; + + // Update blocks range. + if let Some(block_number) = self.best_finalized_target_block_number().await { + self.from_block_num.get_or_insert(block_number); + self.until_block_num = Some(block_number); + } + let (from, until) = match (self.from_block_num, self.until_block_num) { + (Some(from), Some(until)) => (from, until), + _ => continue, + }; + + // Check the available blocks + let mut current_block_number = from; + while current_block_number <= until { + self.finality_proofs_buf.fill(&mut self.finality_proofs_stream); + let block_checker = BlockChecker::new(current_block_number); + let _ = block_checker + .run( + &mut self.source_client, + &mut self.target_client, + &mut self.finality_proofs_buf, + &mut self.reporter, + ) + .await; + current_block_number = current_block_number.saturating_add(1.into()); + } + self.from_block_num = Some(current_block_number); + + select_biased! { + _ = exit_signal => return, + _ = async_std::task::sleep(tick).fuse() => {}, + } + } + } + + pub async fn run( + source_client: SC, + target_client: TC, + tick: Duration, + exit_signal: impl Future, + ) -> Result<(), FailedClient> { + let mut equivocation_detection_loop = Self { + source_client, + target_client, + from_block_num: None, + until_block_num: None, + reporter: EquivocationsReporter::::new(), + finality_proofs_stream: FinalityProofsStream::new(), + finality_proofs_buf: FinalityProofsBuf::new(vec![]), + }; + + equivocation_detection_loop.do_run(tick, exit_signal).await; + Ok(()) + } +} + +/// Spawn the equivocations detection loop. +pub async fn run( + source_client: impl SourceClient

, + target_client: impl TargetClient

, + tick: Duration, + metrics_params: MetricsParams, + exit_signal: impl Future + 'static + Send, +) -> Result<(), relay_utils::Error> { + let exit_signal = exit_signal.shared(); + relay_utils::relay_loop(source_client, target_client) + .with_metrics(metrics_params) + .expose() + .await? + .run( + format!("{}_to_{}_EquivocationDetection", P::SOURCE_NAME, P::TARGET_NAME), + move |source_client, target_client, _metrics| { + EquivocationDetectionLoop::run( + source_client, + target_client, + tick, + exit_signal.clone(), + ) + }, + ) + .await +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::*; + use futures::{channel::mpsc::UnboundedSender, StreamExt}; + use std::{ + collections::{HashMap, VecDeque}, + sync::{Arc, Mutex}, + }; + + fn best_finalized_header_number( + best_finalized_headers: &Mutex>>, + exit_sender: &UnboundedSender<()>, + ) -> Result { + let mut best_finalized_headers = best_finalized_headers.lock().unwrap(); + let result = best_finalized_headers.pop_front().unwrap(); + if best_finalized_headers.is_empty() { + exit_sender.unbounded_send(()).unwrap(); + } + result + } + + #[async_std::test] + async fn multiple_blocks_are_checked_correctly() { + let best_finalized_headers = Arc::new(Mutex::new(VecDeque::from([Ok(10), Ok(12), Ok(13)]))); + let (exit_sender, exit_receiver) = futures::channel::mpsc::unbounded(); + + let source_client = TestSourceClient { + finality_proofs: Arc::new(Mutex::new(vec![ + TestFinalityProof(2, vec!["2-1"]), + TestFinalityProof(3, vec!["3-1", "3-2"]), + TestFinalityProof(4, vec!["4-1"]), + TestFinalityProof(5, vec!["5-1"]), + TestFinalityProof(6, vec!["6-1", "6-2"]), + TestFinalityProof(7, vec!["7-1", "7-2"]), + ])), + ..Default::default() + }; + let reported_equivocations = source_client.reported_equivocations.clone(); + let target_client = TestTargetClient { + best_finalized_header_number: Arc::new(move || { + best_finalized_header_number(&best_finalized_headers, &exit_sender) + }), + best_synced_header_hash: HashMap::from([ + (9, Ok(Some(1))), + (10, Ok(Some(3))), + (11, Ok(Some(5))), + (12, Ok(Some(6))), + ]), + finality_verification_context: HashMap::from([ + (9, Ok(TestFinalityVerificationContext { check_equivocations: true })), + (10, Ok(TestFinalityVerificationContext { check_equivocations: true })), + (11, Ok(TestFinalityVerificationContext { check_equivocations: false })), + (12, Ok(TestFinalityVerificationContext { check_equivocations: true })), + ]), + synced_headers_finality_info: HashMap::from([ + ( + 10, + Ok(vec![new_header_finality_info(2, None), new_header_finality_info(3, None)]), + ), + ( + 11, + Ok(vec![ + new_header_finality_info(4, None), + new_header_finality_info(5, Some(false)), + ]), + ), + (12, Ok(vec![new_header_finality_info(6, None)])), + (13, Ok(vec![new_header_finality_info(7, None)])), + ]), + ..Default::default() + }; + + assert!(run::( + source_client, + target_client, + Duration::from_secs(0), + MetricsParams { address: None, registry: Default::default() }, + exit_receiver.into_future().map(|(_, _)| ()), + ) + .await + .is_ok()); + assert_eq!( + *reported_equivocations.lock().unwrap(), + HashMap::from([ + (1, vec!["2-1", "3-1", "3-2"]), + (3, vec!["4-1", "5-1"]), + (6, vec!["7-1", "7-2"]) + ]) + ); + } + + #[async_std::test] + async fn blocks_following_error_are_checked_correctly() { + let best_finalized_headers = Mutex::new(VecDeque::from([Ok(10), Ok(11)])); + let (exit_sender, exit_receiver) = futures::channel::mpsc::unbounded(); + + let source_client = TestSourceClient { + finality_proofs: Arc::new(Mutex::new(vec![ + TestFinalityProof(2, vec!["2-1"]), + TestFinalityProof(3, vec!["3-1"]), + ])), + ..Default::default() + }; + let reported_equivocations = source_client.reported_equivocations.clone(); + let target_client = TestTargetClient { + best_finalized_header_number: Arc::new(move || { + best_finalized_header_number(&best_finalized_headers, &exit_sender) + }), + best_synced_header_hash: HashMap::from([(9, Ok(Some(1))), (10, Ok(Some(2)))]), + finality_verification_context: HashMap::from([ + (9, Ok(TestFinalityVerificationContext { check_equivocations: true })), + (10, Ok(TestFinalityVerificationContext { check_equivocations: true })), + ]), + synced_headers_finality_info: HashMap::from([ + (10, Err(TestClientError::NonConnection)), + (11, Ok(vec![new_header_finality_info(3, None)])), + ]), + ..Default::default() + }; + + assert!(run::( + source_client, + target_client, + Duration::from_secs(0), + MetricsParams { address: None, registry: Default::default() }, + exit_receiver.into_future().map(|(_, _)| ()), + ) + .await + .is_ok()); + assert_eq!(*reported_equivocations.lock().unwrap(), HashMap::from([(2, vec!["3-1"]),])); + } +} diff --git a/bridges/relays/equivocation/src/lib.rs b/bridges/relays/equivocation/src/lib.rs new file mode 100644 index 0000000000000..56a71ef3bc63c --- /dev/null +++ b/bridges/relays/equivocation/src/lib.rs @@ -0,0 +1,137 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +mod block_checker; +mod equivocation_loop; +mod mock; +mod reporter; + +use async_trait::async_trait; +use bp_header_chain::{FinalityProof, FindEquivocations}; +use finality_relay::{FinalityPipeline, SourceClientBase}; +use relay_utils::{relay_loop::Client as RelayClient, MaybeConnectionError, TransactionTracker}; +use std::{fmt::Debug, time::Duration}; + +pub use equivocation_loop::run; + +#[cfg(not(test))] +const RECONNECT_DELAY: Duration = relay_utils::relay_loop::RECONNECT_DELAY; +#[cfg(test)] +const RECONNECT_DELAY: Duration = mock::TEST_RECONNECT_DELAY; + +pub trait EquivocationDetectionPipeline: FinalityPipeline { + /// Block number of the target chain. + type TargetNumber: relay_utils::BlockNumberBase; + /// The context needed for validating finality proofs. + type FinalityVerificationContext: Debug + Send; + /// The type of the equivocation proof. + type EquivocationProof: Clone + Debug + Send + Sync; + /// The equivocations finder. + type EquivocationsFinder: FindEquivocations< + Self::FinalityProof, + Self::FinalityVerificationContext, + Self::EquivocationProof, + >; +} + +type HeaderFinalityInfo

= bp_header_chain::HeaderFinalityInfo< +

::FinalityProof, +

::FinalityVerificationContext, +>; + +/// Source client used in equivocation detection loop. +#[async_trait] +pub trait SourceClient: SourceClientBase

{ + /// Transaction tracker to track submitted transactions. + type TransactionTracker: TransactionTracker; + + /// Report equivocation. + async fn report_equivocation( + &self, + at: P::Hash, + equivocation: P::EquivocationProof, + ) -> Result; +} + +/// Target client used in equivocation detection loop. +#[async_trait] +pub trait TargetClient: RelayClient { + /// Get the best finalized header number. + async fn best_finalized_header_number(&self) -> Result; + + /// Get the hash of the best source header known by the target at the provided block number. + async fn best_synced_header_hash( + &self, + at: P::TargetNumber, + ) -> Result, Self::Error>; + + /// Get the data stored by the target at the specified block for validating source finality + /// proofs. + async fn finality_verification_context( + &self, + at: P::TargetNumber, + ) -> Result; + + /// Get the finality info associated to the source headers synced with the target chain at the + /// specified block. + async fn synced_headers_finality_info( + &self, + at: P::TargetNumber, + ) -> Result>, Self::Error>; +} + +/// The context needed for finding equivocations inside finality proofs and reporting them. +#[derive(Debug, PartialEq)] +struct EquivocationReportingContext { + pub synced_header_hash: P::Hash, + pub synced_verification_context: P::FinalityVerificationContext, +} + +impl EquivocationReportingContext

{ + /// Try to get the `EquivocationReportingContext` used by the target chain + /// at the provided block. + pub async fn try_read_from_target>( + target_client: &TC, + at: P::TargetNumber, + ) -> Result, TC::Error> { + let maybe_best_synced_header_hash = target_client.best_synced_header_hash(at).await?; + Ok(match maybe_best_synced_header_hash { + Some(best_synced_header_hash) => Some(EquivocationReportingContext { + synced_header_hash: best_synced_header_hash, + synced_verification_context: target_client + .finality_verification_context(at) + .await?, + }), + None => None, + }) + } + + /// Update with the new context introduced by the `HeaderFinalityInfo

` if any. + pub fn update(&mut self, info: HeaderFinalityInfo

) { + if let Some(new_verification_context) = info.new_verification_context { + self.synced_header_hash = info.finality_proof.target_header_hash(); + self.synced_verification_context = new_verification_context; + } + } +} + +async fn handle_client_error(client: &mut C, e: C::Error) { + if e.is_connection_error() { + client.reconnect_until_success(RECONNECT_DELAY).await; + } else { + async_std::task::sleep(RECONNECT_DELAY).await; + } +} diff --git a/bridges/relays/equivocation/src/mock.rs b/bridges/relays/equivocation/src/mock.rs new file mode 100644 index 0000000000000..ced5c6f358065 --- /dev/null +++ b/bridges/relays/equivocation/src/mock.rs @@ -0,0 +1,285 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +#![cfg(test)] + +use crate::{EquivocationDetectionPipeline, HeaderFinalityInfo, SourceClient, TargetClient}; +use async_trait::async_trait; +use bp_header_chain::{FinalityProof, FindEquivocations}; +use finality_relay::{FinalityPipeline, SourceClientBase}; +use futures::{Stream, StreamExt}; +use relay_utils::{ + relay_loop::Client as RelayClient, HeaderId, MaybeConnectionError, TrackedTransactionStatus, + TransactionTracker, +}; +use std::{ + collections::HashMap, + pin::Pin, + sync::{Arc, Mutex}, + time::Duration, +}; + +pub type TestSourceHashAndNumber = u64; +pub type TestTargetNumber = u64; +pub type TestEquivocationProof = &'static str; + +pub const TEST_RECONNECT_DELAY: Duration = Duration::from_secs(0); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TestFinalityProof(pub TestSourceHashAndNumber, pub Vec); + +impl FinalityProof for TestFinalityProof { + fn target_header_hash(&self) -> TestSourceHashAndNumber { + self.0 + } + + fn target_header_number(&self) -> TestSourceHashAndNumber { + self.0 + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct TestEquivocationDetectionPipeline; + +impl FinalityPipeline for TestEquivocationDetectionPipeline { + const SOURCE_NAME: &'static str = "TestSource"; + const TARGET_NAME: &'static str = "TestTarget"; + + type Hash = TestSourceHashAndNumber; + type Number = TestSourceHashAndNumber; + type FinalityProof = TestFinalityProof; +} + +#[derive(Clone, Debug, PartialEq)] +pub struct TestFinalityVerificationContext { + pub check_equivocations: bool, +} + +pub struct TestEquivocationsFinder; + +impl FindEquivocations + for TestEquivocationsFinder +{ + type Error = (); + + fn find_equivocations( + verification_context: &TestFinalityVerificationContext, + synced_proof: &TestFinalityProof, + source_proofs: &[TestFinalityProof], + ) -> Result, Self::Error> { + if verification_context.check_equivocations { + // Get the equivocations from the source proofs, in order to make sure + // that they are correctly provided. + if let Some(proof) = source_proofs.iter().find(|proof| proof.0 == synced_proof.0) { + return Ok(proof.1.clone()) + } + } + + Ok(vec![]) + } +} + +impl EquivocationDetectionPipeline for TestEquivocationDetectionPipeline { + type TargetNumber = TestTargetNumber; + type FinalityVerificationContext = TestFinalityVerificationContext; + type EquivocationProof = TestEquivocationProof; + type EquivocationsFinder = TestEquivocationsFinder; +} + +#[derive(Debug, Clone)] +pub enum TestClientError { + Connection, + NonConnection, +} + +impl MaybeConnectionError for TestClientError { + fn is_connection_error(&self) -> bool { + match self { + TestClientError::Connection => true, + TestClientError::NonConnection => false, + } + } +} + +#[derive(Clone)] +pub struct TestSourceClient { + pub num_reconnects: u32, + pub finality_proofs: Arc>>, + pub reported_equivocations: + Arc>>>, +} + +impl Default for TestSourceClient { + fn default() -> Self { + Self { + num_reconnects: 0, + finality_proofs: Arc::new(Mutex::new(vec![])), + reported_equivocations: Arc::new(Mutex::new(Default::default())), + } + } +} + +#[async_trait] +impl RelayClient for TestSourceClient { + type Error = TestClientError; + + async fn reconnect(&mut self) -> Result<(), Self::Error> { + self.num_reconnects += 1; + + Ok(()) + } +} + +#[async_trait] +impl SourceClientBase for TestSourceClient { + type FinalityProofsStream = Pin + 'static + Send>>; + + async fn finality_proofs(&self) -> Result { + let finality_proofs = std::mem::take(&mut *self.finality_proofs.lock().unwrap()); + Ok(futures::stream::iter(finality_proofs).boxed()) + } +} + +#[derive(Clone, Debug)] +pub struct TestTransactionTracker( + pub TrackedTransactionStatus>, +); + +impl Default for TestTransactionTracker { + fn default() -> TestTransactionTracker { + TestTransactionTracker(TrackedTransactionStatus::Finalized(Default::default())) + } +} + +#[async_trait] +impl TransactionTracker for TestTransactionTracker { + type HeaderId = HeaderId; + + async fn wait( + self, + ) -> TrackedTransactionStatus> { + self.0 + } +} + +#[async_trait] +impl SourceClient for TestSourceClient { + type TransactionTracker = TestTransactionTracker; + + async fn report_equivocation( + &self, + at: TestSourceHashAndNumber, + equivocation: TestEquivocationProof, + ) -> Result { + self.reported_equivocations + .lock() + .unwrap() + .entry(at) + .or_default() + .push(equivocation); + + Ok(TestTransactionTracker::default()) + } +} + +#[derive(Clone)] +pub struct TestTargetClient { + pub num_reconnects: u32, + pub best_finalized_header_number: + Arc Result + Send + Sync>, + pub best_synced_header_hash: + HashMap, TestClientError>>, + pub finality_verification_context: + HashMap>, + pub synced_headers_finality_info: HashMap< + TestTargetNumber, + Result>, TestClientError>, + >, +} + +impl Default for TestTargetClient { + fn default() -> Self { + Self { + num_reconnects: 0, + best_finalized_header_number: Arc::new(|| Ok(0)), + best_synced_header_hash: Default::default(), + finality_verification_context: Default::default(), + synced_headers_finality_info: Default::default(), + } + } +} + +#[async_trait] +impl RelayClient for TestTargetClient { + type Error = TestClientError; + + async fn reconnect(&mut self) -> Result<(), Self::Error> { + self.num_reconnects += 1; + + Ok(()) + } +} + +#[async_trait] +impl TargetClient for TestTargetClient { + async fn best_finalized_header_number(&self) -> Result { + (self.best_finalized_header_number)() + } + + async fn best_synced_header_hash( + &self, + at: TestTargetNumber, + ) -> Result, Self::Error> { + self.best_synced_header_hash + .get(&at) + .unwrap_or(&Err(TestClientError::NonConnection)) + .clone() + } + + async fn finality_verification_context( + &self, + at: TestTargetNumber, + ) -> Result { + self.finality_verification_context + .get(&at) + .unwrap_or(&Err(TestClientError::NonConnection)) + .clone() + } + + async fn synced_headers_finality_info( + &self, + at: TestTargetNumber, + ) -> Result>, Self::Error> { + self.synced_headers_finality_info + .get(&at) + .unwrap_or(&Err(TestClientError::NonConnection)) + .clone() + } +} + +pub fn new_header_finality_info( + source_hdr: TestSourceHashAndNumber, + check_following_equivocations: Option, +) -> HeaderFinalityInfo { + HeaderFinalityInfo:: { + finality_proof: TestFinalityProof(source_hdr, vec![]), + new_verification_context: check_following_equivocations.map( + |check_following_equivocations| TestFinalityVerificationContext { + check_equivocations: check_following_equivocations, + }, + ), + } +} diff --git a/bridges/relays/equivocation/src/reporter.rs b/bridges/relays/equivocation/src/reporter.rs new file mode 100644 index 0000000000000..9c4642383d164 --- /dev/null +++ b/bridges/relays/equivocation/src/reporter.rs @@ -0,0 +1,129 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Helper struct used for submitting finality reports and tracking their status. + +use crate::{EquivocationDetectionPipeline, SourceClient}; + +use futures::FutureExt; +use relay_utils::{TrackedTransactionFuture, TrackedTransactionStatus, TransactionTracker}; +use std::{ + future::poll_fn, + task::{Context, Poll}, +}; + +pub struct EquivocationsReporter<'a, P: EquivocationDetectionPipeline, SC: SourceClient

> { + pending_reports: Vec>, +} + +impl<'a, P: EquivocationDetectionPipeline, SC: SourceClient

> EquivocationsReporter<'a, P, SC> { + pub fn new() -> Self { + Self { pending_reports: vec![] } + } + + /// Submit a `report_equivocation()` transaction to the source chain. + /// + /// We store the transaction tracker for future monitoring. + pub async fn submit_report( + &mut self, + source_client: &SC, + at: P::Hash, + equivocation: P::EquivocationProof, + ) -> Result<(), SC::Error> { + let pending_report = source_client.report_equivocation(at, equivocation).await?; + self.pending_reports.push(pending_report.wait()); + + Ok(()) + } + + fn do_process_pending_reports(&mut self, cx: &mut Context<'_>) -> Poll<()> { + self.pending_reports.retain_mut(|pending_report| { + match pending_report.poll_unpin(cx) { + Poll::Ready(tx_status) => { + match tx_status { + TrackedTransactionStatus::Lost => { + log::error!(target: "bridge", "Equivocation report tx was lost"); + }, + TrackedTransactionStatus::Finalized(id) => { + log::error!(target: "bridge", "Equivocation report tx was finalized in source block {id:?}"); + }, + } + + // The future was processed. Drop it. + false + }, + Poll::Pending => { + // The future is still pending. Retain it. + true + }, + } + }); + + Poll::Ready(()) + } + + /// Iterate through all the pending `report_equivocation()` transactions + /// and log the ones that finished. + pub async fn process_pending_reports(&mut self) { + poll_fn(|cx| self.do_process_pending_reports(cx)).await + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::*; + use relay_utils::HeaderId; + use std::sync::Mutex; + + #[async_std::test] + async fn process_pending_reports_works() { + let polled_reports = Mutex::new(vec![]); + let finished_reports = Mutex::new(vec![]); + + let mut reporter = + EquivocationsReporter:: { + pending_reports: vec![ + Box::pin(async { + polled_reports.lock().unwrap().push(1); + finished_reports.lock().unwrap().push(1); + TrackedTransactionStatus::Finalized(HeaderId(1, 1)) + }), + Box::pin(async { + polled_reports.lock().unwrap().push(2); + finished_reports.lock().unwrap().push(2); + TrackedTransactionStatus::Finalized(HeaderId(2, 2)) + }), + Box::pin(async { + polled_reports.lock().unwrap().push(3); + std::future::pending::<()>().await; + finished_reports.lock().unwrap().push(3); + TrackedTransactionStatus::Finalized(HeaderId(3, 3)) + }), + Box::pin(async { + polled_reports.lock().unwrap().push(4); + finished_reports.lock().unwrap().push(4); + TrackedTransactionStatus::Finalized(HeaderId(4, 4)) + }), + ], + }; + + reporter.process_pending_reports().await; + assert_eq!(*polled_reports.lock().unwrap(), vec![1, 2, 3, 4]); + assert_eq!(*finished_reports.lock().unwrap(), vec![1, 2, 4]); + assert_eq!(reporter.pending_reports.len(), 1); + } +} diff --git a/bridges/relays/finality/Cargo.toml b/bridges/relays/finality/Cargo.toml new file mode 100644 index 0000000000000..5ee4b10fa638f --- /dev/null +++ b/bridges/relays/finality/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "finality-relay" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +repository.workspace = true +description = "Finality proofs relay" +publish = false + +[lints] +workspace = true + +[dependencies] +async-std = "1.9.0" +async-trait = "0.1.79" +backoff = "0.4" +bp-header-chain = { path = "../../primitives/header-chain" } +futures = "0.3.30" +log = { workspace = true } +num-traits = "0.2" +relay-utils = { path = "../utils" } + +[dev-dependencies] +parking_lot = "0.12.1" diff --git a/bridges/relays/finality/README.md b/bridges/relays/finality/README.md new file mode 100644 index 0000000000000..92e765cea0e50 --- /dev/null +++ b/bridges/relays/finality/README.md @@ -0,0 +1,60 @@ +# GRANDPA Finality Relay + +The finality relay is able to work with different finality engines. In the modern Substrate world they are GRANDPA +and BEEFY. Let's talk about GRANDPA here, because BEEFY relay and bridge BEEFY pallet are in development. + +In general, the relay works as follows: it connects to the source and target chain. The source chain must have the +[GRANDPA gadget](https://github.com/paritytech/finality-grandpa) running (so it can't be a parachain). The target +chain must have the [bridge GRANDPA pallet](../../modules/grandpa/) deployed at its runtime. The relay subscribes +to the GRANDPA finality notifications at the source chain and when the new justification is received, it is submitted +to the pallet at the target chain. + +Apart from that, the relay is watching for every source header that is missing at target. If it finds the missing +mandatory header (header that is changing the current GRANDPA validators set), it submits the justification for +this header. The case when the source node can't return the mandatory justification is considered a fatal error, +because the pallet can't proceed without it. + +More: [GRANDPA Finality Relay Sequence Diagram](../../docs/grandpa-finality-relay.html). + +## How to Use the Finality Relay + +The most important trait is the [`FinalitySyncPipeline`](./src/lib.rs), which defines the basic primitives of the +source chain (like block hash and number) and the type of finality proof (GRANDPA justification or MMR proof). Once +that is defined, there are two other traits - [`SourceClient`](./src/finality_loop.rs) and +[`TargetClient`](./src/finality_loop.rs). + +The `SourceClient` represents the Substrate node client that connects to the source chain. The client needs to +be able to return the best finalized header number, finalized header and its finality proof and the stream of +finality proofs. + +The `TargetClient` implementation must be able to craft finality delivery transaction and submit it to the target +node. The transaction is then tracked by the relay until it is mined and finalized. + +The main entrypoint for the crate is the [`run` function](./src/finality_loop.rs), which takes source and target +clients and [`FinalitySyncParams`](./src/finality_loop.rs) parameters. The most important parameter is the +`only_mandatory_headers` - it is set to `true`, the relay will only submit mandatory headers. Since transactions +with mandatory headers are fee-free, the cost of running such relay is zero (in terms of fees). + +## Finality Relay Metrics + +Finality relay provides several metrics. Metrics names depend on names of source and target chains. The list below +shows metrics names for Rococo (source chain) to BridgeHubWestend (target chain) finality relay. For other +chains, simply change chain names. So the metrics are: + +- `Rococo_to_BridgeHubWestend_Sync_best_source_block_number` - returns best finalized source chain (Rococo) block + number, known to the relay. + If relay is running in [on-demand mode](../bin-substrate/src/cli/relay_headers_and_messages/), the + number may not match (it may be far behind) the actual best finalized number; + +- `Rococo_to_BridgeHubWestend_Sync_best_source_at_target_block_number` - returns best finalized source chain (Rococo) + block number that is known to the bridge GRANDPA pallet at the target chain. + +- `Rococo_to_BridgeHubWestend_Sync_is_source_and_source_at_target_using_different_forks` - if this metrics is set + to `1`, then the best source chain header known to the target chain doesn't match the same-number-header + at the source chain. It means that the GRANDPA validators set has crafted the duplicate justification + and it has been submitted to the target chain. + Normally (if majority of validators are honest and if you're running finality relay without large breaks) + this shall not happen and the metric will have `0` value. + +If relay operates properly, you should see that the `Rococo_to_BridgeHubWestend_Sync_best_source_at_target_block_number` +tries to reach the `Rococo_to_BridgeHubWestend_Sync_best_source_block_number`. And the latter one always increases. diff --git a/bridges/relays/finality/src/base.rs b/bridges/relays/finality/src/base.rs new file mode 100644 index 0000000000000..4253468eaace1 --- /dev/null +++ b/bridges/relays/finality/src/base.rs @@ -0,0 +1,51 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +use async_trait::async_trait; +use bp_header_chain::FinalityProof; +use futures::Stream; +use relay_utils::relay_loop::Client as RelayClient; +use std::fmt::Debug; + +/// Base finality pipeline. +pub trait FinalityPipeline: 'static + Clone + Debug + Send + Sync { + /// Name of the finality proofs source. + const SOURCE_NAME: &'static str; + /// Name of the finality proofs target. + const TARGET_NAME: &'static str; + + /// Synced headers are identified by this hash. + type Hash: Eq + Clone + Copy + Send + Sync + Debug; + /// Synced headers are identified by this number. + type Number: relay_utils::BlockNumberBase; + /// Finality proof type. + type FinalityProof: FinalityProof; +} + +/// Source client used in finality related loops. +#[async_trait] +pub trait SourceClientBase: RelayClient { + /// Stream of new finality proofs. The stream is allowed to miss proofs for some + /// headers, even if those headers are mandatory. + type FinalityProofsStream: Stream + Send + Unpin; + + /// Subscribe to new finality proofs. + async fn finality_proofs(&self) -> Result; +} + +/// Target client used in finality related loops. +#[async_trait] +pub trait TargetClientBase: RelayClient {} diff --git a/bridges/relays/finality/src/finality_loop.rs b/bridges/relays/finality/src/finality_loop.rs new file mode 100644 index 0000000000000..e31d8a708122d --- /dev/null +++ b/bridges/relays/finality/src/finality_loop.rs @@ -0,0 +1,698 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! The loop basically reads all missing headers and their finality proofs from the source client. +//! The proof for the best possible header is then submitted to the target node. The only exception +//! is the mandatory headers, which we always submit to the target node. For such headers, we +//! assume that the persistent proof either exists, or will eventually become available. + +use crate::{sync_loop_metrics::SyncLoopMetrics, Error, FinalitySyncPipeline, SourceHeader}; + +use crate::{ + base::SourceClientBase, + finality_proofs::{FinalityProofsBuf, FinalityProofsStream}, + headers::{JustifiedHeader, JustifiedHeaderSelector}, +}; +use async_trait::async_trait; +use backoff::{backoff::Backoff, ExponentialBackoff}; +use futures::{future::Fuse, select, Future, FutureExt}; +use num_traits::Saturating; +use relay_utils::{ + metrics::MetricsParams, relay_loop::Client as RelayClient, retry_backoff, FailedClient, + HeaderId, MaybeConnectionError, TrackedTransactionStatus, TransactionTracker, +}; +use std::{ + fmt::Debug, + time::{Duration, Instant}, +}; + +/// Finality proof synchronization loop parameters. +#[derive(Debug, Clone)] +pub struct FinalitySyncParams { + /// Interval at which we check updates on both clients. Normally should be larger than + /// `min(source_block_time, target_block_time)`. + /// + /// This parameter may be used to limit transactions rate. Increase the value && you'll get + /// infrequent updates => sparse headers => potential slow down of bridge applications, but + /// pallet storage won't be super large. Decrease the value to near `source_block_time` and + /// you'll get transaction for (almost) every block of the source chain => all source headers + /// will be known to the target chain => bridge applications will run faster, but pallet + /// storage may explode (but if pruning is there, then it's fine). + pub tick: Duration, + /// Number of finality proofs to keep in internal buffer between loop iterations. + /// + /// While in "major syncing" state, we still read finality proofs from the stream. They're + /// stored in the internal buffer between loop iterations. When we're close to the tip of the + /// chain, we may meet finality delays if headers are not finalized frequently. So instead of + /// waiting for next finality proof to appear in the stream, we may use existing proof from + /// that buffer. + pub recent_finality_proofs_limit: usize, + /// Timeout before we treat our transactions as lost and restart the whole sync process. + pub stall_timeout: Duration, + /// If true, only mandatory headers are relayed. + pub only_mandatory_headers: bool, +} + +/// Source client used in finality synchronization loop. +#[async_trait] +pub trait SourceClient: SourceClientBase

{ + /// Get best finalized block number. + async fn best_finalized_block_number(&self) -> Result; + + /// Get canonical header and its finality proof by number. + async fn header_and_finality_proof( + &self, + number: P::Number, + ) -> Result<(P::Header, Option), Self::Error>; +} + +/// Target client used in finality synchronization loop. +#[async_trait] +pub trait TargetClient: RelayClient { + /// Transaction tracker to track submitted transactions. + type TransactionTracker: TransactionTracker; + + /// Get best finalized source block number. + async fn best_finalized_source_block_id( + &self, + ) -> Result, Self::Error>; + + /// Submit header finality proof. + async fn submit_finality_proof( + &self, + header: P::Header, + proof: P::FinalityProof, + ) -> Result; +} + +/// Return prefix that will be used by default to expose Prometheus metrics of the finality proofs +/// sync loop. +pub fn metrics_prefix() -> String { + format!("{}_to_{}_Sync", P::SOURCE_NAME, P::TARGET_NAME) +} + +pub struct SyncInfo { + pub best_number_at_source: P::Number, + pub best_number_at_target: P::Number, + pub is_using_same_fork: bool, +} + +impl SyncInfo

{ + /// Checks if both clients are on the same fork. + async fn is_on_same_fork>( + source_client: &SC, + id_at_target: &HeaderId, + ) -> Result { + let header_at_source = source_client.header_and_finality_proof(id_at_target.0).await?.0; + let header_hash_at_source = header_at_source.hash(); + Ok(if id_at_target.1 == header_hash_at_source { + true + } else { + log::error!( + target: "bridge", + "Source node ({}) and pallet at target node ({}) have different headers at the same height {:?}: \ + at-source {:?} vs at-target {:?}", + P::SOURCE_NAME, + P::TARGET_NAME, + id_at_target.0, + header_hash_at_source, + id_at_target.1, + ); + + false + }) + } + + async fn new, TC: TargetClient

>( + source_client: &SC, + target_client: &TC, + ) -> Result> { + let best_number_at_source = + source_client.best_finalized_block_number().await.map_err(Error::Source)?; + let best_id_at_target = + target_client.best_finalized_source_block_id().await.map_err(Error::Target)?; + let best_number_at_target = best_id_at_target.0; + + let is_using_same_fork = Self::is_on_same_fork(source_client, &best_id_at_target) + .await + .map_err(Error::Source)?; + + Ok(Self { best_number_at_source, best_number_at_target, is_using_same_fork }) + } + + fn update_metrics(&self, metrics_sync: &Option) { + if let Some(metrics_sync) = metrics_sync { + metrics_sync.update_best_block_at_source(self.best_number_at_source); + metrics_sync.update_best_block_at_target(self.best_number_at_target); + metrics_sync.update_using_same_fork(self.is_using_same_fork); + } + } + + pub fn num_headers(&self) -> P::Number { + self.best_number_at_source.saturating_sub(self.best_number_at_target) + } +} + +/// Information about transaction that we have submitted. +#[derive(Debug, Clone)] +pub struct Transaction { + /// Submitted transaction tracker. + tracker: Tracker, + /// The number of the header we have submitted. + header_number: Number, +} + +impl Transaction { + pub async fn submit< + P: FinalitySyncPipeline, + TC: TargetClient, + >( + target_client: &TC, + header: P::Header, + justification: P::FinalityProof, + ) -> Result { + let header_number = header.number(); + log::debug!( + target: "bridge", + "Going to submit finality proof of {} header #{:?} to {}", + P::SOURCE_NAME, + header_number, + P::TARGET_NAME, + ); + + let tracker = target_client.submit_finality_proof(header, justification).await?; + Ok(Transaction { tracker, header_number }) + } + + async fn track< + P: FinalitySyncPipeline, + SC: SourceClient

, + TC: TargetClient

, + >( + self, + target_client: TC, + ) -> Result<(), Error> { + match self.tracker.wait().await { + TrackedTransactionStatus::Finalized(_) => { + // The transaction has been finalized, but it may have been finalized in the + // "failed" state. So let's check if the block number was actually updated. + target_client + .best_finalized_source_block_id() + .await + .map_err(Error::Target) + .and_then(|best_id_at_target| { + if self.header_number > best_id_at_target.0 { + return Err(Error::ProofSubmissionTxFailed { + submitted_number: self.header_number, + best_number_at_target: best_id_at_target.0, + }) + } + Ok(()) + }) + }, + TrackedTransactionStatus::Lost => Err(Error::ProofSubmissionTxLost), + } + } +} + +/// Finality synchronization loop state. +struct FinalityLoop, TC: TargetClient

> { + source_client: SC, + target_client: TC, + + sync_params: FinalitySyncParams, + metrics_sync: Option, + + progress: (Instant, Option), + retry_backoff: ExponentialBackoff, + finality_proofs_stream: FinalityProofsStream, + finality_proofs_buf: FinalityProofsBuf

, + best_submitted_number: Option, +} + +impl, TC: TargetClient

> FinalityLoop { + pub fn new( + source_client: SC, + target_client: TC, + sync_params: FinalitySyncParams, + metrics_sync: Option, + ) -> Self { + Self { + source_client, + target_client, + sync_params, + metrics_sync, + progress: (Instant::now(), None), + retry_backoff: retry_backoff(), + finality_proofs_stream: FinalityProofsStream::new(), + finality_proofs_buf: FinalityProofsBuf::new(vec![]), + best_submitted_number: None, + } + } + + fn update_progress(&mut self, info: &SyncInfo

) { + let (prev_time, prev_best_number_at_target) = self.progress; + let now = Instant::now(); + + let needs_update = now - prev_time > Duration::from_secs(10) || + prev_best_number_at_target + .map(|prev_best_number_at_target| { + info.best_number_at_target.saturating_sub(prev_best_number_at_target) > + 10.into() + }) + .unwrap_or(true); + + if !needs_update { + return + } + + log::info!( + target: "bridge", + "Synced {:?} of {:?} headers", + info.best_number_at_target, + info.best_number_at_source, + ); + + self.progress = (now, Some(info.best_number_at_target)) + } + + pub async fn select_header_to_submit( + &mut self, + info: &SyncInfo

, + ) -> Result>, Error> { + // to see that the loop is progressing + log::trace!( + target: "bridge", + "Considering range of headers ({}; {}]", + info.best_number_at_target, + info.best_number_at_source + ); + + // read missing headers + let selector = JustifiedHeaderSelector::new::(&self.source_client, info).await?; + // if we see that the header schedules GRANDPA change, we need to submit it + if self.sync_params.only_mandatory_headers { + return Ok(selector.select_mandatory()) + } + + // all headers that are missing from the target client are non-mandatory + // => even if we have already selected some header and its persistent finality proof, + // we may try to select better header by reading non-persistent proofs from the stream + self.finality_proofs_buf.fill(&mut self.finality_proofs_stream); + let maybe_justified_header = selector.select(&self.finality_proofs_buf); + + // remove obsolete 'recent' finality proofs + keep its size under certain limit + let oldest_finality_proof_to_keep = maybe_justified_header + .as_ref() + .map(|justified_header| justified_header.number()) + .unwrap_or(info.best_number_at_target); + self.finality_proofs_buf.prune( + oldest_finality_proof_to_keep, + Some(self.sync_params.recent_finality_proofs_limit), + ); + + Ok(maybe_justified_header) + } + + pub async fn run_iteration( + &mut self, + ) -> Result< + Option>, + Error, + > { + // read best source headers ids from source and target nodes + let info = SyncInfo::new(&self.source_client, &self.target_client).await?; + info.update_metrics(&self.metrics_sync); + self.update_progress(&info); + + // if we have already submitted header, then we just need to wait for it + // if we're waiting too much, then we believe our transaction has been lost and restart sync + if Some(info.best_number_at_target) < self.best_submitted_number { + return Ok(None) + } + + // submit new header if we have something new + match self.select_header_to_submit(&info).await? { + Some(header) => { + let transaction = + Transaction::submit(&self.target_client, header.header, header.proof) + .await + .map_err(Error::Target)?; + self.best_submitted_number = Some(transaction.header_number); + Ok(Some(transaction)) + }, + None => Ok(None), + } + } + + async fn ensure_finality_proofs_stream(&mut self) -> Result<(), FailedClient> { + if let Err(e) = self.finality_proofs_stream.ensure_stream(&self.source_client).await { + if e.is_connection_error() { + return Err(FailedClient::Source) + } + } + + Ok(()) + } + + /// Run finality relay loop until connection to one of nodes is lost. + async fn run_until_connection_lost( + &mut self, + exit_signal: impl Future, + ) -> Result<(), FailedClient> { + self.ensure_finality_proofs_stream().await?; + let proof_submission_tx_tracker = Fuse::terminated(); + let exit_signal = exit_signal.fuse(); + futures::pin_mut!(exit_signal, proof_submission_tx_tracker); + + loop { + // run loop iteration + let next_tick = match self.run_iteration().await { + Ok(Some(tx)) => { + proof_submission_tx_tracker + .set(tx.track::(self.target_client.clone()).fuse()); + self.retry_backoff.reset(); + self.sync_params.tick + }, + Ok(None) => { + self.retry_backoff.reset(); + self.sync_params.tick + }, + Err(error) => { + log::error!(target: "bridge", "Finality sync loop iteration has failed with error: {:?}", error); + error.fail_if_connection_error()?; + self.retry_backoff + .next_backoff() + .unwrap_or(relay_utils::relay_loop::RECONNECT_DELAY) + }, + }; + self.ensure_finality_proofs_stream().await?; + + // wait till exit signal, or new source block + select! { + proof_submission_result = proof_submission_tx_tracker => { + if let Err(e) = proof_submission_result { + log::error!( + target: "bridge", + "Finality sync proof submission tx to {} has failed with error: {:?}.", + P::TARGET_NAME, + e, + ); + self.best_submitted_number = None; + e.fail_if_connection_error()?; + } + }, + _ = async_std::task::sleep(next_tick).fuse() => {}, + _ = exit_signal => return Ok(()), + } + } + } + + pub async fn run( + source_client: SC, + target_client: TC, + sync_params: FinalitySyncParams, + metrics_sync: Option, + exit_signal: impl Future, + ) -> Result<(), FailedClient> { + let mut finality_loop = Self::new(source_client, target_client, sync_params, metrics_sync); + finality_loop.run_until_connection_lost(exit_signal).await + } +} + +/// Run finality proofs synchronization loop. +pub async fn run( + source_client: impl SourceClient

, + target_client: impl TargetClient

, + sync_params: FinalitySyncParams, + metrics_params: MetricsParams, + exit_signal: impl Future + 'static + Send, +) -> Result<(), relay_utils::Error> { + let exit_signal = exit_signal.shared(); + relay_utils::relay_loop(source_client, target_client) + .with_metrics(metrics_params) + .loop_metric(SyncLoopMetrics::new( + Some(&metrics_prefix::

()), + "source", + "source_at_target", + )?)? + .expose() + .await? + .run(metrics_prefix::

(), move |source_client, target_client, metrics| { + FinalityLoop::run( + source_client, + target_client, + sync_params.clone(), + metrics, + exit_signal.clone(), + ) + }) + .await +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::mock::*; + use futures::{FutureExt, StreamExt}; + use parking_lot::Mutex; + use relay_utils::{FailedClient, HeaderId, TrackedTransactionStatus}; + use std::{collections::HashMap, sync::Arc}; + + fn prepare_test_clients( + exit_sender: futures::channel::mpsc::UnboundedSender<()>, + state_function: impl Fn(&mut ClientsData) -> bool + Send + Sync + 'static, + source_headers: HashMap)>, + ) -> (TestSourceClient, TestTargetClient) { + let internal_state_function: Arc = + Arc::new(move |data| { + if state_function(data) { + exit_sender.unbounded_send(()).unwrap(); + } + }); + let clients_data = Arc::new(Mutex::new(ClientsData { + source_best_block_number: 10, + source_headers, + source_proofs: vec![TestFinalityProof(12), TestFinalityProof(14)], + + target_best_block_id: HeaderId(5, 5), + target_headers: vec![], + target_transaction_tracker: TestTransactionTracker( + TrackedTransactionStatus::Finalized(Default::default()), + ), + })); + ( + TestSourceClient { + on_method_call: internal_state_function.clone(), + data: clients_data.clone(), + }, + TestTargetClient { on_method_call: internal_state_function, data: clients_data }, + ) + } + + fn test_sync_params() -> FinalitySyncParams { + FinalitySyncParams { + tick: Duration::from_secs(0), + recent_finality_proofs_limit: 1024, + stall_timeout: Duration::from_secs(1), + only_mandatory_headers: false, + } + } + + fn run_sync_loop( + state_function: impl Fn(&mut ClientsData) -> bool + Send + Sync + 'static, + ) -> (ClientsData, Result<(), FailedClient>) { + let (exit_sender, exit_receiver) = futures::channel::mpsc::unbounded(); + let (source_client, target_client) = prepare_test_clients( + exit_sender, + state_function, + vec![ + (5, (TestSourceHeader(false, 5, 5), None)), + (6, (TestSourceHeader(false, 6, 6), None)), + (7, (TestSourceHeader(false, 7, 7), Some(TestFinalityProof(7)))), + (8, (TestSourceHeader(true, 8, 8), Some(TestFinalityProof(8)))), + (9, (TestSourceHeader(false, 9, 9), Some(TestFinalityProof(9)))), + (10, (TestSourceHeader(false, 10, 10), None)), + ] + .into_iter() + .collect(), + ); + let sync_params = test_sync_params(); + + let clients_data = source_client.data.clone(); + let result = async_std::task::block_on(FinalityLoop::run( + source_client, + target_client, + sync_params, + None, + exit_receiver.into_future().map(|(_, _)| ()), + )); + + let clients_data = clients_data.lock().clone(); + (clients_data, result) + } + + #[test] + fn finality_sync_loop_works() { + let (client_data, result) = run_sync_loop(|data| { + // header#7 has persistent finality proof, but it isn't mandatory => it isn't submitted, + // because header#8 has persistent finality proof && it is mandatory => it is submitted + // header#9 has persistent finality proof, but it isn't mandatory => it is submitted, + // because there are no more persistent finality proofs + // + // once this ^^^ is done, we generate more blocks && read proof for blocks 12 and 14 + // from the stream + if data.target_best_block_id.0 == 9 { + data.source_best_block_number = 14; + data.source_headers.insert(11, (TestSourceHeader(false, 11, 11), None)); + data.source_headers + .insert(12, (TestSourceHeader(false, 12, 12), Some(TestFinalityProof(12)))); + data.source_headers.insert(13, (TestSourceHeader(false, 13, 13), None)); + data.source_headers + .insert(14, (TestSourceHeader(false, 14, 14), Some(TestFinalityProof(14)))); + } + // once this ^^^ is done, we generate more blocks && read persistent proof for block 16 + if data.target_best_block_id.0 == 14 { + data.source_best_block_number = 17; + data.source_headers.insert(15, (TestSourceHeader(false, 15, 15), None)); + data.source_headers + .insert(16, (TestSourceHeader(false, 16, 16), Some(TestFinalityProof(16)))); + data.source_headers.insert(17, (TestSourceHeader(false, 17, 17), None)); + } + + data.target_best_block_id.0 == 16 + }); + + assert_eq!(result, Ok(())); + assert_eq!( + client_data.target_headers, + vec![ + // before adding 11..14: finality proof for mandatory header#8 + (TestSourceHeader(true, 8, 8), TestFinalityProof(8)), + // before adding 11..14: persistent finality proof for non-mandatory header#9 + (TestSourceHeader(false, 9, 9), TestFinalityProof(9)), + // after adding 11..14: ephemeral finality proof for non-mandatory header#14 + (TestSourceHeader(false, 14, 14), TestFinalityProof(14)), + // after adding 15..17: persistent finality proof for non-mandatory header#16 + (TestSourceHeader(false, 16, 16), TestFinalityProof(16)), + ], + ); + } + + fn run_only_mandatory_headers_mode_test( + only_mandatory_headers: bool, + has_mandatory_headers: bool, + ) -> Option> { + let (exit_sender, _) = futures::channel::mpsc::unbounded(); + let (source_client, target_client) = prepare_test_clients( + exit_sender, + |_| false, + vec![ + (6, (TestSourceHeader(false, 6, 6), Some(TestFinalityProof(6)))), + (7, (TestSourceHeader(false, 7, 7), Some(TestFinalityProof(7)))), + (8, (TestSourceHeader(has_mandatory_headers, 8, 8), Some(TestFinalityProof(8)))), + (9, (TestSourceHeader(false, 9, 9), Some(TestFinalityProof(9)))), + (10, (TestSourceHeader(false, 10, 10), Some(TestFinalityProof(10)))), + ] + .into_iter() + .collect(), + ); + async_std::task::block_on(async { + let mut finality_loop = FinalityLoop::new( + source_client, + target_client, + FinalitySyncParams { + tick: Duration::from_secs(0), + recent_finality_proofs_limit: 0, + stall_timeout: Duration::from_secs(0), + only_mandatory_headers, + }, + None, + ); + let info = SyncInfo { + best_number_at_source: 10, + best_number_at_target: 5, + is_using_same_fork: true, + }; + finality_loop.select_header_to_submit(&info).await.unwrap() + }) + } + + #[test] + fn select_header_to_submit_skips_non_mandatory_headers_when_only_mandatory_headers_are_required( + ) { + assert_eq!(run_only_mandatory_headers_mode_test(true, false), None); + assert_eq!( + run_only_mandatory_headers_mode_test(false, false), + Some(JustifiedHeader { + header: TestSourceHeader(false, 10, 10), + proof: TestFinalityProof(10) + }), + ); + } + + #[test] + fn select_header_to_submit_selects_mandatory_headers_when_only_mandatory_headers_are_required() + { + assert_eq!( + run_only_mandatory_headers_mode_test(true, true), + Some(JustifiedHeader { + header: TestSourceHeader(true, 8, 8), + proof: TestFinalityProof(8) + }), + ); + assert_eq!( + run_only_mandatory_headers_mode_test(false, true), + Some(JustifiedHeader { + header: TestSourceHeader(true, 8, 8), + proof: TestFinalityProof(8) + }), + ); + } + + #[test] + fn different_forks_at_source_and_at_target_are_detected() { + let (exit_sender, _exit_receiver) = futures::channel::mpsc::unbounded(); + let (source_client, target_client) = prepare_test_clients( + exit_sender, + |_| false, + vec![ + (5, (TestSourceHeader(false, 5, 42), None)), + (6, (TestSourceHeader(false, 6, 6), None)), + (7, (TestSourceHeader(false, 7, 7), None)), + (8, (TestSourceHeader(false, 8, 8), None)), + (9, (TestSourceHeader(false, 9, 9), None)), + (10, (TestSourceHeader(false, 10, 10), None)), + ] + .into_iter() + .collect(), + ); + + let metrics_sync = SyncLoopMetrics::new(None, "source", "target").unwrap(); + async_std::task::block_on(async { + let mut finality_loop = FinalityLoop::new( + source_client, + target_client, + test_sync_params(), + Some(metrics_sync.clone()), + ); + finality_loop.run_iteration().await.unwrap() + }); + + assert!(!metrics_sync.is_using_same_fork()); + } +} diff --git a/bridges/relays/finality/src/finality_proofs.rs b/bridges/relays/finality/src/finality_proofs.rs new file mode 100644 index 0000000000000..e78cf8d62790d --- /dev/null +++ b/bridges/relays/finality/src/finality_proofs.rs @@ -0,0 +1,222 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +use crate::{base::SourceClientBase, FinalityPipeline}; + +use bp_header_chain::FinalityProof; +use futures::{FutureExt, Stream, StreamExt}; +use std::pin::Pin; + +/// Source finality proofs stream that may be restarted. +#[derive(Default)] +pub struct FinalityProofsStream> { + /// The underlying stream. + stream: Option>>, +} + +impl> FinalityProofsStream { + pub fn new() -> Self { + Self { stream: None } + } + + pub fn from_stream(stream: SC::FinalityProofsStream) -> Self { + Self { stream: Some(Box::pin(stream)) } + } + + fn next(&mut self) -> Option<::Item> { + let stream = match &mut self.stream { + Some(stream) => stream, + None => return None, + }; + + match stream.next().now_or_never() { + Some(Some(finality_proof)) => Some(finality_proof), + Some(None) => { + self.stream = None; + None + }, + None => None, + } + } + + pub async fn ensure_stream(&mut self, source_client: &SC) -> Result<(), SC::Error> { + if self.stream.is_none() { + log::warn!(target: "bridge", "{} finality proofs stream is being started / restarted", + P::SOURCE_NAME); + + let stream = source_client.finality_proofs().await.map_err(|error| { + log::error!( + target: "bridge", + "Failed to subscribe to {} justifications: {:?}", + P::SOURCE_NAME, + error, + ); + + error + })?; + self.stream = Some(Box::pin(stream)); + } + + Ok(()) + } +} + +/// Source finality proofs buffer. +pub struct FinalityProofsBuf { + /// Proofs buffer. Ordered by target header number. + buf: Vec, +} + +impl FinalityProofsBuf

{ + pub fn new(buf: Vec) -> Self { + Self { buf } + } + + pub fn buf(&self) -> &Vec { + &self.buf + } + + pub fn fill>(&mut self, stream: &mut FinalityProofsStream) { + let mut proofs_count = 0; + let mut first_header_number = None; + let mut last_header_number = None; + while let Some(finality_proof) = stream.next() { + let target_header_number = finality_proof.target_header_number(); + first_header_number.get_or_insert(target_header_number); + last_header_number = Some(target_header_number); + proofs_count += 1; + + self.buf.push(finality_proof); + } + + if proofs_count != 0 { + log::trace!( + target: "bridge", + "Read {} finality proofs from {} finality stream for headers in range [{:?}; {:?}]", + proofs_count, + P::SOURCE_NAME, + first_header_number, + last_header_number, + ); + } + } + + /// Prune all finality proofs that target header numbers older than `first_to_keep`. + pub fn prune(&mut self, first_to_keep: P::Number, maybe_buf_limit: Option) { + let first_to_keep_idx = self + .buf + .binary_search_by_key(&first_to_keep, |hdr| hdr.target_header_number()) + .map(|idx| idx + 1) + .unwrap_or_else(|idx| idx); + let buf_limit_idx = match maybe_buf_limit { + Some(buf_limit) => self.buf.len().saturating_sub(buf_limit), + None => 0, + }; + + self.buf = self.buf.split_off(std::cmp::max(first_to_keep_idx, buf_limit_idx)); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::*; + + #[test] + fn finality_proofs_buf_fill_works() { + // when stream is currently empty, nothing is changed + let mut finality_proofs_buf = + FinalityProofsBuf:: { buf: vec![TestFinalityProof(1)] }; + let mut stream = + FinalityProofsStream::::from_stream( + Box::pin(futures::stream::pending()), + ); + finality_proofs_buf.fill(&mut stream); + assert_eq!(finality_proofs_buf.buf, vec![TestFinalityProof(1)]); + assert!(stream.stream.is_some()); + + // when stream has entry with target, it is added to the recent proofs container + let mut stream = + FinalityProofsStream::::from_stream( + Box::pin( + futures::stream::iter(vec![TestFinalityProof(4)]) + .chain(futures::stream::pending()), + ), + ); + finality_proofs_buf.fill(&mut stream); + assert_eq!(finality_proofs_buf.buf, vec![TestFinalityProof(1), TestFinalityProof(4)]); + assert!(stream.stream.is_some()); + + // when stream has ended, we'll need to restart it + let mut stream = + FinalityProofsStream::::from_stream( + Box::pin(futures::stream::empty()), + ); + finality_proofs_buf.fill(&mut stream); + assert_eq!(finality_proofs_buf.buf, vec![TestFinalityProof(1), TestFinalityProof(4)]); + assert!(stream.stream.is_none()); + } + + #[test] + fn finality_proofs_buf_prune_works() { + let original_finality_proofs_buf: Vec< + ::FinalityProof, + > = vec![ + TestFinalityProof(10), + TestFinalityProof(13), + TestFinalityProof(15), + TestFinalityProof(17), + TestFinalityProof(19), + ] + .into_iter() + .collect(); + + // when there's proof for justified header in the vec + let mut finality_proofs_buf = FinalityProofsBuf:: { + buf: original_finality_proofs_buf.clone(), + }; + finality_proofs_buf.prune(10, None); + assert_eq!(&original_finality_proofs_buf[1..], finality_proofs_buf.buf,); + + // when there are no proof for justified header in the vec + let mut finality_proofs_buf = FinalityProofsBuf:: { + buf: original_finality_proofs_buf.clone(), + }; + finality_proofs_buf.prune(11, None); + assert_eq!(&original_finality_proofs_buf[1..], finality_proofs_buf.buf,); + + // when there are too many entries after initial prune && they also need to be pruned + let mut finality_proofs_buf = FinalityProofsBuf:: { + buf: original_finality_proofs_buf.clone(), + }; + finality_proofs_buf.prune(10, Some(2)); + assert_eq!(&original_finality_proofs_buf[3..], finality_proofs_buf.buf,); + + // when last entry is pruned + let mut finality_proofs_buf = FinalityProofsBuf:: { + buf: original_finality_proofs_buf.clone(), + }; + finality_proofs_buf.prune(19, Some(2)); + assert_eq!(&original_finality_proofs_buf[5..], finality_proofs_buf.buf,); + + // when post-last entry is pruned + let mut finality_proofs_buf = FinalityProofsBuf:: { + buf: original_finality_proofs_buf.clone(), + }; + finality_proofs_buf.prune(20, Some(2)); + assert_eq!(&original_finality_proofs_buf[5..], finality_proofs_buf.buf,); + } +} diff --git a/bridges/relays/finality/src/headers.rs b/bridges/relays/finality/src/headers.rs new file mode 100644 index 0000000000000..91f7cd0378ecd --- /dev/null +++ b/bridges/relays/finality/src/headers.rs @@ -0,0 +1,238 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +use crate::{ + finality_loop::SyncInfo, finality_proofs::FinalityProofsBuf, Error, FinalitySyncPipeline, + SourceClient, SourceHeader, TargetClient, +}; + +use bp_header_chain::FinalityProof; +use std::cmp::Ordering; + +/// Unjustified headers container. Ordered by header number. +pub type UnjustifiedHeaders = Vec; + +#[derive(Debug)] +#[cfg_attr(test, derive(Clone, PartialEq))] +pub struct JustifiedHeader { + pub header: P::Header, + pub proof: P::FinalityProof, +} + +impl JustifiedHeader

{ + pub fn number(&self) -> P::Number { + self.header.number() + } +} + +/// Finality proof that has been selected by the `read_missing_headers` function. +pub enum JustifiedHeaderSelector { + /// Mandatory header and its proof has been selected. We shall submit proof for this header. + Mandatory(JustifiedHeader

), + /// Regular header and its proof has been selected. We may submit this proof, or proof for + /// some better header. + Regular(UnjustifiedHeaders, JustifiedHeader

), + /// We haven't found any missing header with persistent proof at the target client. + None(UnjustifiedHeaders), +} + +impl JustifiedHeaderSelector

{ + pub(crate) async fn new, TC: TargetClient

>( + source_client: &SC, + info: &SyncInfo

, + ) -> Result> { + let mut unjustified_headers = Vec::new(); + let mut maybe_justified_header = None; + + let mut header_number = info.best_number_at_target + 1.into(); + while header_number <= info.best_number_at_source { + let (header, maybe_proof) = source_client + .header_and_finality_proof(header_number) + .await + .map_err(Error::Source)?; + + match (header.is_mandatory(), maybe_proof) { + (true, Some(proof)) => { + log::trace!(target: "bridge", "Header {:?} is mandatory", header_number); + return Ok(Self::Mandatory(JustifiedHeader { header, proof })) + }, + (true, None) => return Err(Error::MissingMandatoryFinalityProof(header.number())), + (false, Some(proof)) => { + log::trace!(target: "bridge", "Header {:?} has persistent finality proof", header_number); + unjustified_headers.clear(); + maybe_justified_header = Some(JustifiedHeader { header, proof }); + }, + (false, None) => { + unjustified_headers.push(header); + }, + } + + header_number = header_number + 1.into(); + } + + log::trace!( + target: "bridge", + "Read {} {} headers. Selected finality proof for header: {:?}", + info.num_headers(), + P::SOURCE_NAME, + maybe_justified_header.as_ref().map(|justified_header| &justified_header.header), + ); + + Ok(match maybe_justified_header { + Some(justified_header) => Self::Regular(unjustified_headers, justified_header), + None => Self::None(unjustified_headers), + }) + } + + pub fn select_mandatory(self) -> Option> { + match self { + JustifiedHeaderSelector::Mandatory(header) => Some(header), + _ => None, + } + } + + pub fn select(self, buf: &FinalityProofsBuf

) -> Option> { + let (unjustified_headers, maybe_justified_header) = match self { + JustifiedHeaderSelector::Mandatory(justified_header) => return Some(justified_header), + JustifiedHeaderSelector::Regular(unjustified_headers, justified_header) => + (unjustified_headers, Some(justified_header)), + JustifiedHeaderSelector::None(unjustified_headers) => (unjustified_headers, None), + }; + + let mut finality_proofs_iter = buf.buf().iter().rev(); + let mut maybe_finality_proof = finality_proofs_iter.next(); + + let mut unjustified_headers_iter = unjustified_headers.iter().rev(); + let mut maybe_unjustified_header = unjustified_headers_iter.next(); + + while let (Some(finality_proof), Some(unjustified_header)) = + (maybe_finality_proof, maybe_unjustified_header) + { + match finality_proof.target_header_number().cmp(&unjustified_header.number()) { + Ordering::Equal => { + log::trace!( + target: "bridge", + "Managed to improve selected {} finality proof {:?} to {:?}.", + P::SOURCE_NAME, + maybe_justified_header.as_ref().map(|justified_header| justified_header.number()), + finality_proof.target_header_number() + ); + return Some(JustifiedHeader { + header: unjustified_header.clone(), + proof: finality_proof.clone(), + }) + }, + Ordering::Less => maybe_unjustified_header = unjustified_headers_iter.next(), + Ordering::Greater => { + maybe_finality_proof = finality_proofs_iter.next(); + }, + } + } + + log::trace!( + target: "bridge", + "Could not improve selected {} finality proof {:?}.", + P::SOURCE_NAME, + maybe_justified_header.as_ref().map(|justified_header| justified_header.number()) + ); + maybe_justified_header + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::*; + + #[test] + fn select_better_recent_finality_proof_works() { + // if there are no unjustified headers, nothing is changed + let finality_proofs_buf = + FinalityProofsBuf::::new(vec![TestFinalityProof(5)]); + let justified_header = + JustifiedHeader { header: TestSourceHeader(false, 2, 2), proof: TestFinalityProof(2) }; + let selector = JustifiedHeaderSelector::Regular(vec![], justified_header.clone()); + assert_eq!(selector.select(&finality_proofs_buf), Some(justified_header)); + + // if there are no buffered finality proofs, nothing is changed + let finality_proofs_buf = FinalityProofsBuf::::new(vec![]); + let justified_header = + JustifiedHeader { header: TestSourceHeader(false, 2, 2), proof: TestFinalityProof(2) }; + let selector = JustifiedHeaderSelector::Regular( + vec![TestSourceHeader(false, 5, 5)], + justified_header.clone(), + ); + assert_eq!(selector.select(&finality_proofs_buf), Some(justified_header)); + + // if there's no intersection between recent finality proofs and unjustified headers, + // nothing is changed + let finality_proofs_buf = FinalityProofsBuf::::new(vec![ + TestFinalityProof(1), + TestFinalityProof(4), + ]); + let justified_header = + JustifiedHeader { header: TestSourceHeader(false, 2, 2), proof: TestFinalityProof(2) }; + let selector = JustifiedHeaderSelector::Regular( + vec![TestSourceHeader(false, 9, 9), TestSourceHeader(false, 10, 10)], + justified_header.clone(), + ); + assert_eq!(selector.select(&finality_proofs_buf), Some(justified_header)); + + // if there's intersection between recent finality proofs and unjustified headers, but there + // are no proofs in this intersection, nothing is changed + let finality_proofs_buf = FinalityProofsBuf::::new(vec![ + TestFinalityProof(7), + TestFinalityProof(11), + ]); + let justified_header = + JustifiedHeader { header: TestSourceHeader(false, 2, 2), proof: TestFinalityProof(2) }; + let selector = JustifiedHeaderSelector::Regular( + vec![ + TestSourceHeader(false, 8, 8), + TestSourceHeader(false, 9, 9), + TestSourceHeader(false, 10, 10), + ], + justified_header.clone(), + ); + assert_eq!(selector.select(&finality_proofs_buf), Some(justified_header)); + + // if there's intersection between recent finality proofs and unjustified headers and + // there's a proof in this intersection: + // - this better (last from intersection) proof is selected; + // - 'obsolete' unjustified headers are pruned. + let finality_proofs_buf = FinalityProofsBuf::::new(vec![ + TestFinalityProof(7), + TestFinalityProof(9), + ]); + let justified_header = + JustifiedHeader { header: TestSourceHeader(false, 2, 2), proof: TestFinalityProof(2) }; + let selector = JustifiedHeaderSelector::Regular( + vec![ + TestSourceHeader(false, 8, 8), + TestSourceHeader(false, 9, 9), + TestSourceHeader(false, 10, 10), + ], + justified_header, + ); + assert_eq!( + selector.select(&finality_proofs_buf), + Some(JustifiedHeader { + header: TestSourceHeader(false, 9, 9), + proof: TestFinalityProof(9) + }) + ); + } +} diff --git a/bridges/relays/finality/src/lib.rs b/bridges/relays/finality/src/lib.rs new file mode 100644 index 0000000000000..3579e68e1ef9c --- /dev/null +++ b/bridges/relays/finality/src/lib.rs @@ -0,0 +1,91 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! This crate has single entrypoint to run synchronization loop that is built around finality +//! proofs, as opposed to headers synchronization loop, which is built around headers. The headers +//! are still submitted to the target node, but are treated as auxiliary data as we are not trying +//! to submit all source headers to the target node. + +pub use crate::{ + base::{FinalityPipeline, SourceClientBase}, + finality_loop::{metrics_prefix, run, FinalitySyncParams, SourceClient, TargetClient}, + finality_proofs::{FinalityProofsBuf, FinalityProofsStream}, + sync_loop_metrics::SyncLoopMetrics, +}; + +use bp_header_chain::ConsensusLogReader; +use relay_utils::{FailedClient, MaybeConnectionError}; +use std::fmt::Debug; + +mod base; +mod finality_loop; +mod finality_proofs; +mod headers; +mod mock; +mod sync_loop_metrics; + +/// Finality proofs synchronization pipeline. +pub trait FinalitySyncPipeline: FinalityPipeline { + /// A reader that can extract the consensus log from the header digest and interpret it. + type ConsensusLogReader: ConsensusLogReader; + /// Type of header that we're syncing. + type Header: SourceHeader; +} + +/// Header that we're receiving from source node. +pub trait SourceHeader: Clone + Debug + PartialEq + Send + Sync { + /// Returns hash of header. + fn hash(&self) -> Hash; + /// Returns number of header. + fn number(&self) -> Number; + /// Returns true if this header needs to be submitted to target node. + fn is_mandatory(&self) -> bool; +} + +/// Error that may happen inside finality synchronization loop. +#[derive(Debug)] +enum Error { + /// Source client request has failed with given error. + Source(SourceError), + /// Target client request has failed with given error. + Target(TargetError), + /// Finality proof for mandatory header is missing from the source node. + MissingMandatoryFinalityProof(P::Number), + /// `submit_finality_proof` transaction failed + ProofSubmissionTxFailed { + #[allow(dead_code)] + submitted_number: P::Number, + #[allow(dead_code)] + best_number_at_target: P::Number, + }, + /// `submit_finality_proof` transaction lost + ProofSubmissionTxLost, +} + +impl Error +where + P: FinalitySyncPipeline, + SourceError: MaybeConnectionError, + TargetError: MaybeConnectionError, +{ + fn fail_if_connection_error(&self) -> Result<(), FailedClient> { + match *self { + Error::Source(ref error) if error.is_connection_error() => Err(FailedClient::Source), + Error::Target(ref error) if error.is_connection_error() => Err(FailedClient::Target), + _ => Ok(()), + } + } +} diff --git a/bridges/relays/finality/src/mock.rs b/bridges/relays/finality/src/mock.rs new file mode 100644 index 0000000000000..e3ec4e4d0d47a --- /dev/null +++ b/bridges/relays/finality/src/mock.rs @@ -0,0 +1,213 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Tests for finality synchronization loop. + +#![cfg(test)] + +use crate::{ + base::SourceClientBase, + finality_loop::{SourceClient, TargetClient}, + FinalityPipeline, FinalitySyncPipeline, SourceHeader, +}; + +use async_trait::async_trait; +use bp_header_chain::{FinalityProof, GrandpaConsensusLogReader}; +use futures::{Stream, StreamExt}; +use parking_lot::Mutex; +use relay_utils::{ + relay_loop::Client as RelayClient, HeaderId, MaybeConnectionError, TrackedTransactionStatus, + TransactionTracker, +}; +use std::{collections::HashMap, pin::Pin, sync::Arc}; + +type IsMandatory = bool; +pub type TestNumber = u64; +type TestHash = u64; + +#[derive(Clone, Debug)] +pub struct TestTransactionTracker(pub TrackedTransactionStatus>); + +impl Default for TestTransactionTracker { + fn default() -> TestTransactionTracker { + TestTransactionTracker(TrackedTransactionStatus::Finalized(Default::default())) + } +} + +#[async_trait] +impl TransactionTracker for TestTransactionTracker { + type HeaderId = HeaderId; + + async fn wait(self) -> TrackedTransactionStatus> { + self.0 + } +} + +#[derive(Debug, Clone)] +pub enum TestError { + NonConnection, +} + +impl MaybeConnectionError for TestError { + fn is_connection_error(&self) -> bool { + false + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct TestFinalitySyncPipeline; + +impl FinalityPipeline for TestFinalitySyncPipeline { + const SOURCE_NAME: &'static str = "TestSource"; + const TARGET_NAME: &'static str = "TestTarget"; + + type Hash = TestHash; + type Number = TestNumber; + type FinalityProof = TestFinalityProof; +} + +impl FinalitySyncPipeline for TestFinalitySyncPipeline { + type ConsensusLogReader = GrandpaConsensusLogReader; + type Header = TestSourceHeader; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TestSourceHeader(pub IsMandatory, pub TestNumber, pub TestHash); + +impl SourceHeader> + for TestSourceHeader +{ + fn hash(&self) -> TestHash { + self.2 + } + + fn number(&self) -> TestNumber { + self.1 + } + + fn is_mandatory(&self) -> bool { + self.0 + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TestFinalityProof(pub TestNumber); + +impl FinalityProof for TestFinalityProof { + fn target_header_hash(&self) -> TestHash { + Default::default() + } + + fn target_header_number(&self) -> TestNumber { + self.0 + } +} + +#[derive(Debug, Clone, Default)] +pub struct ClientsData { + pub source_best_block_number: TestNumber, + pub source_headers: HashMap)>, + pub source_proofs: Vec, + + pub target_best_block_id: HeaderId, + pub target_headers: Vec<(TestSourceHeader, TestFinalityProof)>, + pub target_transaction_tracker: TestTransactionTracker, +} + +#[derive(Clone)] +pub struct TestSourceClient { + pub on_method_call: Arc, + pub data: Arc>, +} + +#[async_trait] +impl RelayClient for TestSourceClient { + type Error = TestError; + + async fn reconnect(&mut self) -> Result<(), TestError> { + unreachable!() + } +} + +#[async_trait] +impl SourceClientBase for TestSourceClient { + type FinalityProofsStream = Pin + 'static + Send>>; + + async fn finality_proofs(&self) -> Result { + let mut data = self.data.lock(); + (self.on_method_call)(&mut data); + Ok(futures::stream::iter(data.source_proofs.clone()).boxed()) + } +} + +#[async_trait] +impl SourceClient for TestSourceClient { + async fn best_finalized_block_number(&self) -> Result { + let mut data = self.data.lock(); + (self.on_method_call)(&mut data); + Ok(data.source_best_block_number) + } + + async fn header_and_finality_proof( + &self, + number: TestNumber, + ) -> Result<(TestSourceHeader, Option), TestError> { + let mut data = self.data.lock(); + (self.on_method_call)(&mut data); + data.source_headers.get(&number).cloned().ok_or(TestError::NonConnection) + } +} + +#[derive(Clone)] +pub struct TestTargetClient { + pub on_method_call: Arc, + pub data: Arc>, +} + +#[async_trait] +impl RelayClient for TestTargetClient { + type Error = TestError; + + async fn reconnect(&mut self) -> Result<(), TestError> { + unreachable!() + } +} + +#[async_trait] +impl TargetClient for TestTargetClient { + type TransactionTracker = TestTransactionTracker; + + async fn best_finalized_source_block_id( + &self, + ) -> Result, TestError> { + let mut data = self.data.lock(); + (self.on_method_call)(&mut data); + Ok(data.target_best_block_id) + } + + async fn submit_finality_proof( + &self, + header: TestSourceHeader, + proof: TestFinalityProof, + ) -> Result { + let mut data = self.data.lock(); + (self.on_method_call)(&mut data); + data.target_best_block_id = HeaderId(header.number(), header.hash()); + data.target_headers.push((header, proof)); + (self.on_method_call)(&mut data); + Ok(data.target_transaction_tracker.clone()) + } +} diff --git a/bridges/relays/finality/src/sync_loop_metrics.rs b/bridges/relays/finality/src/sync_loop_metrics.rs new file mode 100644 index 0000000000000..4da1df811f6ec --- /dev/null +++ b/bridges/relays/finality/src/sync_loop_metrics.rs @@ -0,0 +1,95 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Metrics for headers synchronization relay loop. + +use relay_utils::{ + metrics::{metric_name, register, IntGauge, Metric, PrometheusError, Registry}, + UniqueSaturatedInto, +}; + +/// Headers sync metrics. +#[derive(Clone)] +pub struct SyncLoopMetrics { + /// Best syncing header at the source. + best_source_block_number: IntGauge, + /// Best syncing header at the target. + best_target_block_number: IntGauge, + /// Flag that has `0` value when best source headers at the source node and at-target-chain + /// are matching and `1` otherwise. + using_different_forks: IntGauge, +} + +impl SyncLoopMetrics { + /// Create and register headers loop metrics. + pub fn new( + prefix: Option<&str>, + at_source_chain_label: &str, + at_target_chain_label: &str, + ) -> Result { + Ok(SyncLoopMetrics { + best_source_block_number: IntGauge::new( + metric_name(prefix, &format!("best_{at_source_chain_label}_block_number")), + format!("Best block number at the {at_source_chain_label}"), + )?, + best_target_block_number: IntGauge::new( + metric_name(prefix, &format!("best_{at_target_chain_label}_block_number")), + format!("Best block number at the {at_target_chain_label}"), + )?, + using_different_forks: IntGauge::new( + metric_name(prefix, &format!("is_{at_source_chain_label}_and_{at_target_chain_label}_using_different_forks")), + "Whether the best finalized source block at target node is different (value 1) from the \ + corresponding block at the source node", + )?, + }) + } + + /// Returns current value of the using-same-fork flag. + #[cfg(test)] + pub(crate) fn is_using_same_fork(&self) -> bool { + self.using_different_forks.get() == 0 + } + + /// Update best block number at source. + pub fn update_best_block_at_source>( + &self, + source_best_number: Number, + ) { + self.best_source_block_number.set(source_best_number.unique_saturated_into()); + } + + /// Update best block number at target. + pub fn update_best_block_at_target>( + &self, + target_best_number: Number, + ) { + self.best_target_block_number.set(target_best_number.unique_saturated_into()); + } + + /// Update using-same-fork flag. + pub fn update_using_same_fork(&self, using_same_fork: bool) { + self.using_different_forks.set((!using_same_fork).into()) + } +} + +impl Metric for SyncLoopMetrics { + fn register(&self, registry: &Registry) -> Result<(), PrometheusError> { + register(self.best_source_block_number.clone(), registry)?; + register(self.best_target_block_number.clone(), registry)?; + register(self.using_different_forks.clone(), registry)?; + Ok(()) + } +} diff --git a/bridges/relays/lib-substrate-relay/Cargo.toml b/bridges/relays/lib-substrate-relay/Cargo.toml new file mode 100644 index 0000000000000..7e7e774d7253b --- /dev/null +++ b/bridges/relays/lib-substrate-relay/Cargo.toml @@ -0,0 +1,62 @@ +[package] +name = "substrate-relay-helper" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +repository.workspace = true +publish = false + +[lints] +workspace = true + +[dependencies] +anyhow = "1.0" +async-std = "1.9.0" +async-trait = "0.1.79" +codec = { package = "parity-scale-codec", version = "3.6.1" } +futures = "0.3.30" +hex = "0.4" +log = { workspace = true } +num-traits = "0.2" +rbtag = "0.3" +structopt = "0.3" +strum = { version = "0.26.2", features = ["derive"] } +thiserror = { workspace = true } + +# Bridge dependencies + +bp-header-chain = { path = "../../primitives/header-chain" } +bp-parachains = { path = "../../primitives/parachains" } +bp-polkadot-core = { path = "../../primitives/polkadot-core" } +bp-relayers = { path = "../../primitives/relayers" } +bridge-runtime-common = { path = "../../bin/runtime-common" } + +equivocation-detector = { path = "../equivocation" } +finality-grandpa = { version = "0.16.2" } +finality-relay = { path = "../finality" } +parachains-relay = { path = "../parachains" } +relay-utils = { path = "../utils" } +messages-relay = { path = "../messages" } +relay-substrate-client = { path = "../client-substrate" } + +pallet-bridge-grandpa = { path = "../../modules/grandpa" } +pallet-bridge-messages = { path = "../../modules/messages" } +pallet-bridge-parachains = { path = "../../modules/parachains" } + +bp-runtime = { path = "../../primitives/runtime" } +bp-messages = { path = "../../primitives/messages" } + +# Substrate Dependencies + +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sp-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } + +[dev-dependencies] +pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +relay-substrate-client = { path = "../client-substrate", features = ["test-helpers"] } diff --git a/bridges/relays/lib-substrate-relay/src/cli/bridge.rs b/bridges/relays/lib-substrate-relay/src/cli/bridge.rs new file mode 100644 index 0000000000000..316f59a2b0c86 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/cli/bridge.rs @@ -0,0 +1,110 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Basic traits for exposing bridges in the CLI. + +use crate::{ + equivocation::SubstrateEquivocationDetectionPipeline, + finality::SubstrateFinalitySyncPipeline, + messages_lane::{MessagesRelayLimits, SubstrateMessageLane}, + parachains::SubstrateParachainsPipeline, +}; +use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber}; +use relay_substrate_client::{ + Chain, ChainWithRuntimeVersion, ChainWithTransactions, Parachain, RelayChain, +}; + +/// Minimal bridge representation that can be used from the CLI. +/// It connects a source chain to a target chain. +pub trait CliBridgeBase: Sized { + /// The source chain. + type Source: Chain + ChainWithRuntimeVersion; + /// The target chain. + type Target: ChainWithTransactions + ChainWithRuntimeVersion; +} + +/// Bridge representation that can be used from the CLI for relaying headers +/// from a relay chain to a relay chain. +pub trait RelayToRelayHeadersCliBridge: CliBridgeBase { + /// Finality proofs synchronization pipeline. + type Finality: SubstrateFinalitySyncPipeline< + SourceChain = Self::Source, + TargetChain = Self::Target, + >; +} + +/// Convenience trait that adds bounds to `CliBridgeBase`. +pub trait RelayToRelayEquivocationDetectionCliBridgeBase: CliBridgeBase { + /// The source chain with extra bounds. + type BoundedSource: ChainWithTransactions; +} + +impl RelayToRelayEquivocationDetectionCliBridgeBase for T +where + T: CliBridgeBase, + T::Source: ChainWithTransactions, +{ + type BoundedSource = T::Source; +} + +/// Bridge representation that can be used from the CLI for detecting equivocations +/// in the headers synchronized from a relay chain to a relay chain. +pub trait RelayToRelayEquivocationDetectionCliBridge: + RelayToRelayEquivocationDetectionCliBridgeBase +{ + /// Equivocation detection pipeline. + type Equivocation: SubstrateEquivocationDetectionPipeline< + SourceChain = Self::Source, + TargetChain = Self::Target, + >; +} + +/// Bridge representation that can be used from the CLI for relaying headers +/// from a parachain to a relay chain. +pub trait ParachainToRelayHeadersCliBridge: CliBridgeBase +where + Self::Source: Parachain, +{ + /// The `CliBridgeBase` type represents the parachain in this situation. + /// We need to add an extra type for the relay chain. + type SourceRelay: Chain + + ChainWithRuntimeVersion + + RelayChain; + /// Finality proofs synchronization pipeline (source parachain -> target). + type ParachainFinality: SubstrateParachainsPipeline< + SourceRelayChain = Self::SourceRelay, + SourceParachain = Self::Source, + TargetChain = Self::Target, + >; + /// Finality proofs synchronization pipeline (source relay chain -> target). + type RelayFinality: SubstrateFinalitySyncPipeline< + SourceChain = Self::SourceRelay, + TargetChain = Self::Target, + >; +} + +/// Bridge representation that can be used from the CLI for relaying messages. +pub trait MessagesCliBridge: CliBridgeBase { + /// The Source -> Destination messages synchronization pipeline. + type MessagesLane: SubstrateMessageLane; + + /// Optional messages delivery transaction limits that the messages relay is going + /// to use. If it returns `None`, limits are estimated using `TransactionPayment` API + /// at the target chain. + fn maybe_messages_limits() -> Option { + None + } +} diff --git a/bridges/relays/lib-substrate-relay/src/cli/chain_schema.rs b/bridges/relays/lib-substrate-relay/src/cli/chain_schema.rs new file mode 100644 index 0000000000000..6246bdbf01515 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/cli/chain_schema.rs @@ -0,0 +1,261 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Primitives related to chain CLI options. + +use relay_substrate_client::{AccountKeyPairOf, ChainWithTransactions}; +use structopt::StructOpt; +use strum::{EnumString, VariantNames}; + +use relay_substrate_client::{ChainRuntimeVersion, ChainWithRuntimeVersion, SimpleRuntimeVersion}; + +use crate::TransactionParams; + +#[doc = "Runtime version params."] +#[derive(StructOpt, Debug, PartialEq, Eq, Clone, Copy, EnumString, VariantNames)] +pub enum RuntimeVersionType { + /// Auto query version from chain + Auto, + /// Custom `spec_version` and `transaction_version` + Custom, + /// Read version from bundle dependencies directly. + Bundle, +} + +/// Create chain-specific set of runtime version parameters. +#[macro_export] +macro_rules! declare_chain_runtime_version_params_cli_schema { + ($chain:ident, $chain_prefix:ident) => { + bp_runtime::paste::item! { + #[doc = $chain " runtime version params."] + #[derive(StructOpt, Debug, PartialEq, Eq, Clone, Copy)] + pub struct [<$chain RuntimeVersionParams>] { + #[doc = "The type of runtime version for chain " $chain] + #[structopt(long, default_value = "Bundle")] + pub [<$chain_prefix _version_mode>]: RuntimeVersionType, + #[doc = "The custom sepc_version for chain " $chain] + #[structopt(long)] + pub [<$chain_prefix _spec_version>]: Option, + #[doc = "The custom transaction_version for chain " $chain] + #[structopt(long)] + pub [<$chain_prefix _transaction_version>]: Option, + } + + impl [<$chain RuntimeVersionParams>] { + /// Converts self into `ChainRuntimeVersion`. + pub fn into_runtime_version( + self, + bundle_runtime_version: Option, + ) -> anyhow::Result { + Ok(match self.[<$chain_prefix _version_mode>] { + RuntimeVersionType::Auto => ChainRuntimeVersion::Auto, + RuntimeVersionType::Custom => { + let custom_spec_version = self.[<$chain_prefix _spec_version>] + .ok_or_else(|| anyhow::Error::msg(format!("The {}-spec-version is required when choose custom mode", stringify!($chain_prefix))))?; + let custom_transaction_version = self.[<$chain_prefix _transaction_version>] + .ok_or_else(|| anyhow::Error::msg(format!("The {}-transaction-version is required when choose custom mode", stringify!($chain_prefix))))?; + ChainRuntimeVersion::Custom( + SimpleRuntimeVersion { + spec_version: custom_spec_version, + transaction_version: custom_transaction_version + } + ) + }, + RuntimeVersionType::Bundle => match bundle_runtime_version { + Some(runtime_version) => ChainRuntimeVersion::Custom(runtime_version), + None => { + return Err(anyhow::format_err!("Cannot use bundled runtime version of {}: it is not known to the relay", stringify!($chain_prefix))); + } + }, + }) + } + } + } + }; +} + +/// Create chain-specific set of runtime version parameters. +#[macro_export] +macro_rules! declare_chain_connection_params_cli_schema { + ($chain:ident, $chain_prefix:ident) => { + bp_runtime::paste::item! { + // TODO: https://github.com/paritytech/parity-bridges-common/issues/2909 + // remove all obsolete arguments (separate URI components) + + #[doc = $chain " connection params."] + #[derive(StructOpt, Debug, PartialEq, Eq, Clone)] + pub struct [<$chain ConnectionParams>] { + #[doc = "WS endpoint of " $chain ": full URI. Overrides all other connection string components (host, port, path, secure)."] + #[structopt(long)] + pub [<$chain_prefix _uri>]: Option, + #[doc = "WS endpoint of " $chain ": host component."] + #[structopt(long, default_value = "127.0.0.1")] + pub [<$chain_prefix _host>]: String, + #[doc = "WS endpoint of " $chain ": port component."] + #[structopt(long, default_value = "9944")] + pub [<$chain_prefix _port>]: u16, + #[doc = "WS endpoint of " $chain ": path component."] + #[structopt(long)] + pub [<$chain_prefix _path>]: Option, + #[doc = "Use secure websocket connection."] + #[structopt(long)] + pub [<$chain_prefix _secure>]: bool, + #[doc = "Custom runtime version"] + #[structopt(flatten)] + pub [<$chain_prefix _runtime_version>]: [<$chain RuntimeVersionParams>], + } + + impl [<$chain ConnectionParams>] { + /// Convert connection params into Substrate client. + #[allow(dead_code)] + pub async fn into_client( + self, + ) -> anyhow::Result> { + let chain_runtime_version = self + .[<$chain_prefix _runtime_version>] + .into_runtime_version(Chain::RUNTIME_VERSION)?; + Ok(relay_substrate_client::Client::new(relay_substrate_client::ConnectionParams { + uri: self.[<$chain_prefix _uri>], + host: self.[<$chain_prefix _host>], + port: self.[<$chain_prefix _port>], + path: self.[<$chain_prefix _path>], + secure: self.[<$chain_prefix _secure>], + chain_runtime_version, + }) + .await + ) + } + } + } + }; +} + +/// Create chain-specific set of signing parameters. +#[macro_export] +macro_rules! declare_chain_signing_params_cli_schema { + ($chain:ident, $chain_prefix:ident) => { + bp_runtime::paste::item! { + #[doc = $chain " signing params."] + #[derive(StructOpt, Debug, PartialEq, Eq, Clone)] + pub struct [<$chain SigningParams>] { + #[doc = "The SURI of secret key to use when transactions are submitted to the " $chain " node."] + #[structopt(long)] + pub [<$chain_prefix _signer>]: Option, + #[doc = "The password for the SURI of secret key to use when transactions are submitted to the " $chain " node."] + #[structopt(long)] + pub [<$chain_prefix _signer_password>]: Option, + + #[doc = "Path to the file, that contains SURI of secret key to use when transactions are submitted to the " $chain " node. Can be overridden with " $chain_prefix "_signer option."] + #[structopt(long)] + pub [<$chain_prefix _signer_file>]: Option, + #[doc = "Path to the file, that password for the SURI of secret key to use when transactions are submitted to the " $chain " node. Can be overridden with " $chain_prefix "_signer_password option."] + #[structopt(long)] + pub [<$chain_prefix _signer_password_file>]: Option, + + #[doc = "Transactions mortality period, in blocks. MUST be a power of two in [4; 65536] range. MAY NOT be larger than `BlockHashCount` parameter of the chain system module."] + #[structopt(long)] + pub [<$chain_prefix _transactions_mortality>]: Option, + } + + impl [<$chain SigningParams>] { + /// Return transactions mortality. + #[allow(dead_code)] + pub fn transactions_mortality(&self) -> anyhow::Result> { + self.[<$chain_prefix _transactions_mortality>] + .map(|transactions_mortality| { + if !(4..=65536).contains(&transactions_mortality) + || !transactions_mortality.is_power_of_two() + { + Err(anyhow::format_err!( + "Transactions mortality {} is not a power of two in a [4; 65536] range", + transactions_mortality, + )) + } else { + Ok(transactions_mortality) + } + }) + .transpose() + } + + /// Parse signing params into chain-specific KeyPair. + #[allow(dead_code)] + pub fn to_keypair(&self) -> anyhow::Result> { + let suri = match (self.[<$chain_prefix _signer>].as_ref(), self.[<$chain_prefix _signer_file>].as_ref()) { + (Some(suri), _) => suri.to_owned(), + (None, Some(suri_file)) => std::fs::read_to_string(suri_file) + .map_err(|err| anyhow::format_err!( + "Failed to read SURI from file {:?}: {}", + suri_file, + err, + ))?, + (None, None) => return Err(anyhow::format_err!( + "One of options must be specified: '{}' or '{}'", + stringify!([<$chain_prefix _signer>]), + stringify!([<$chain_prefix _signer_file>]), + )), + }; + + let suri_password = match ( + self.[<$chain_prefix _signer_password>].as_ref(), + self.[<$chain_prefix _signer_password_file>].as_ref(), + ) { + (Some(suri_password), _) => Some(suri_password.to_owned()), + (None, Some(suri_password_file)) => std::fs::read_to_string(suri_password_file) + .map(Some) + .map_err(|err| anyhow::format_err!( + "Failed to read SURI password from file {:?}: {}", + suri_password_file, + err, + ))?, + _ => None, + }; + + use sp_core::crypto::Pair; + + AccountKeyPairOf::::from_string( + &suri, + suri_password.as_deref() + ).map_err(|e| anyhow::format_err!("{:?}", e)) + } + + /// Return transaction parameters. + #[allow(dead_code)] + pub fn transaction_params( + &self, + ) -> anyhow::Result>> { + Ok(TransactionParams { + mortality: self.transactions_mortality()?, + signer: self.to_keypair::()?, + }) + } + } + } + }; +} + +/// Create chain-specific set of configuration objects: connection parameters, +/// signing parameters and bridge initialization parameters. +#[macro_export] +macro_rules! declare_chain_cli_schema { + ($chain:ident, $chain_prefix:ident) => { + $crate::declare_chain_runtime_version_params_cli_schema!($chain, $chain_prefix); + $crate::declare_chain_connection_params_cli_schema!($chain, $chain_prefix); + $crate::declare_chain_signing_params_cli_schema!($chain, $chain_prefix); + }; +} + +declare_chain_cli_schema!(Source, source); +declare_chain_cli_schema!(Target, target); diff --git a/bridges/relays/lib-substrate-relay/src/cli/detect_equivocations.rs b/bridges/relays/lib-substrate-relay/src/cli/detect_equivocations.rs new file mode 100644 index 0000000000000..b98e41b2a43e4 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/cli/detect_equivocations.rs @@ -0,0 +1,65 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Primitives for exposing the equivocation detection functionality in the CLI. + +use crate::{ + cli::{bridge::*, chain_schema::*, PrometheusParams}, + equivocation, + equivocation::SubstrateEquivocationDetectionPipeline, +}; + +use async_trait::async_trait; +use relay_substrate_client::ChainWithTransactions; +use structopt::StructOpt; + +/// Start equivocation detection loop. +#[derive(StructOpt)] +pub struct DetectEquivocationsParams { + #[structopt(flatten)] + source: SourceConnectionParams, + #[structopt(flatten)] + source_sign: SourceSigningParams, + #[structopt(flatten)] + target: TargetConnectionParams, + #[structopt(flatten)] + prometheus_params: PrometheusParams, +} + +/// Trait used for starting the equivocation detection loop between 2 chains. +#[async_trait] +pub trait EquivocationsDetector: RelayToRelayEquivocationDetectionCliBridge +where + Self::Source: ChainWithTransactions, +{ + /// Start the equivocation detection loop. + async fn start(data: DetectEquivocationsParams) -> anyhow::Result<()> { + let source_client = data.source.into_client::().await?; + Self::Equivocation::start_relay_guards( + &source_client, + source_client.can_start_version_guard(), + ) + .await?; + + equivocation::run::( + source_client, + data.target.into_client::().await?, + data.source_sign.transaction_params::()?, + data.prometheus_params.into_metrics_params()?, + ) + .await + } +} diff --git a/bridges/relays/lib-substrate-relay/src/cli/init_bridge.rs b/bridges/relays/lib-substrate-relay/src/cli/init_bridge.rs new file mode 100644 index 0000000000000..bf7c86437ba77 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/cli/init_bridge.rs @@ -0,0 +1,85 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Primitives for exposing the bridge initialization functionality in the CLI. + +use async_trait::async_trait; +use codec::Encode; + +use crate::{ + cli::{bridge::CliBridgeBase, chain_schema::*}, + finality_base::engine::Engine, +}; +use bp_runtime::Chain as ChainBase; +use relay_substrate_client::{AccountKeyPairOf, Chain, UnsignedTransaction}; +use sp_core::Pair; +use structopt::StructOpt; + +/// Bridge initialization params. +#[derive(StructOpt)] +pub struct InitBridgeParams { + #[structopt(flatten)] + source: SourceConnectionParams, + #[structopt(flatten)] + target: TargetConnectionParams, + #[structopt(flatten)] + target_sign: TargetSigningParams, + /// Generates all required data, but does not submit extrinsic + #[structopt(long)] + dry_run: bool, +} + +/// Trait used for bridge initializing. +#[async_trait] +pub trait BridgeInitializer: CliBridgeBase +where + ::AccountId: From< as Pair>::Public>, +{ + /// The finality engine used by the source chain. + type Engine: Engine; + + /// Get the encoded call to init the bridge. + fn encode_init_bridge( + init_data: >::InitializationData, + ) -> ::Call; + + /// Initialize the bridge. + async fn init_bridge(data: InitBridgeParams) -> anyhow::Result<()> { + let source_client = data.source.into_client::().await?; + let target_client = data.target.into_client::().await?; + let target_sign = data.target_sign.to_keypair::()?; + let dry_run = data.dry_run; + + crate::finality::initialize::initialize::( + source_client, + target_client.clone(), + target_sign, + move |transaction_nonce, initialization_data| { + let call = Self::encode_init_bridge(initialization_data); + log::info!( + target: "bridge", + "Initialize bridge call encoded as hex string: {:?}", + format!("0x{}", hex::encode(call.encode())) + ); + Ok(UnsignedTransaction::new(call.into(), transaction_nonce)) + }, + dry_run, + ) + .await; + + Ok(()) + } +} diff --git a/bridges/relays/lib-substrate-relay/src/cli/mod.rs b/bridges/relays/lib-substrate-relay/src/cli/mod.rs new file mode 100644 index 0000000000000..0dd0d5474b3a5 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/cli/mod.rs @@ -0,0 +1,192 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Deal with CLI args of substrate-to-substrate relay. + +use codec::{Decode, Encode}; +use rbtag::BuildInfo; +use structopt::StructOpt; +use strum::{EnumString, VariantNames}; + +use bp_messages::LaneId; + +pub mod bridge; +pub mod chain_schema; +pub mod detect_equivocations; +pub mod init_bridge; +pub mod relay_headers; +pub mod relay_headers_and_messages; +pub mod relay_messages; +pub mod relay_parachains; + +/// The target that will be used when publishing logs related to this pallet. +pub const LOG_TARGET: &str = "bridge"; + +/// Lane id. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HexLaneId(pub [u8; 4]); + +impl From for LaneId { + fn from(lane_id: HexLaneId) -> LaneId { + LaneId(lane_id.0) + } +} + +impl std::str::FromStr for HexLaneId { + type Err = hex::FromHexError; + + fn from_str(s: &str) -> Result { + let mut lane_id = [0u8; 4]; + hex::decode_to_slice(s, &mut lane_id)?; + Ok(HexLaneId(lane_id)) + } +} + +/// Nicer formatting for raw bytes vectors. +#[derive(Default, Encode, Decode, PartialEq, Eq)] +pub struct HexBytes(pub Vec); + +impl std::str::FromStr for HexBytes { + type Err = hex::FromHexError; + + fn from_str(s: &str) -> Result { + Ok(Self(hex::decode(s)?)) + } +} + +impl std::fmt::Debug for HexBytes { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(fmt, "0x{self}") + } +} + +impl std::fmt::Display for HexBytes { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(fmt, "{}", hex::encode(&self.0)) + } +} + +/// Prometheus metrics params. +#[derive(Clone, Debug, PartialEq, StructOpt)] +pub struct PrometheusParams { + /// Do not expose a Prometheus metric endpoint. + #[structopt(long)] + pub no_prometheus: bool, + /// Expose Prometheus endpoint at given interface. + #[structopt(long, default_value = "127.0.0.1")] + pub prometheus_host: String, + /// Expose Prometheus endpoint at given port. + #[structopt(long, default_value = "9616")] + pub prometheus_port: u16, +} + +/// Struct to get git commit info and build time. +#[derive(BuildInfo)] +struct SubstrateRelayBuildInfo; + +impl SubstrateRelayBuildInfo { + /// Get git commit in form ``. + pub fn get_git_commit() -> String { + // on gitlab we use images without git installed, so we can't use `rbtag` there + // locally we don't have `CI_*` env variables, so we can't rely on them + // => we are using `CI_*` env variables or else `rbtag` + let maybe_sha_from_ci = option_env!("CI_COMMIT_SHORT_SHA"); + maybe_sha_from_ci + .map(|short_sha| { + // we assume that on CI the copy is always clean + format!("{short_sha}-clean") + }) + .unwrap_or_else(|| SubstrateRelayBuildInfo.get_build_commit().into()) + } +} + +impl PrometheusParams { + /// Tries to convert CLI metrics params into metrics params, used by the relay. + pub fn into_metrics_params(self) -> anyhow::Result { + let metrics_address = if !self.no_prometheus { + Some(relay_utils::metrics::MetricsAddress { + host: self.prometheus_host, + port: self.prometheus_port, + }) + } else { + None + }; + + let relay_version = option_env!("CARGO_PKG_VERSION").unwrap_or("unknown"); + let relay_commit = SubstrateRelayBuildInfo::get_git_commit(); + relay_utils::metrics::MetricsParams::new( + metrics_address, + relay_version.into(), + relay_commit, + ) + .map_err(|e| anyhow::format_err!("{:?}", e)) + } +} + +/// Either explicit or maximal allowed value. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ExplicitOrMaximal { + /// User has explicitly specified argument value. + Explicit(V), + /// Maximal allowed value for this argument. + Maximal, +} + +impl std::str::FromStr for ExplicitOrMaximal +where + V::Err: std::fmt::Debug, +{ + type Err = String; + + fn from_str(s: &str) -> Result { + if s.to_lowercase() == "max" { + return Ok(ExplicitOrMaximal::Maximal) + } + + V::from_str(s) + .map(ExplicitOrMaximal::Explicit) + .map_err(|e| format!("Failed to parse '{e:?}'. Expected 'max' or explicit value")) + } +} + +#[doc = "Runtime version params."] +#[derive(StructOpt, Debug, PartialEq, Eq, Clone, Copy, EnumString, VariantNames)] +pub enum RuntimeVersionType { + /// Auto query version from chain + Auto, + /// Custom `spec_version` and `transaction_version` + Custom, + /// Read version from bundle dependencies directly. + Bundle, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn hex_bytes_display_matches_from_str_for_clap() { + // given + let hex = HexBytes(vec![1, 2, 3, 4]); + let display = format!("{hex}"); + + // when + let hex2: HexBytes = display.parse().unwrap(); + + // then + assert_eq!(hex.0, hex2.0); + } +} diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers.rs new file mode 100644 index 0000000000000..90558ed461383 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers.rs @@ -0,0 +1,76 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Primitives for exposing the headers relaying functionality in the CLI. + +use async_trait::async_trait; +use structopt::StructOpt; + +use relay_utils::metrics::{GlobalMetrics, StandaloneMetric}; + +use crate::{ + cli::{bridge::*, chain_schema::*, PrometheusParams}, + finality::SubstrateFinalitySyncPipeline, +}; + +/// Chain headers relaying params. +#[derive(StructOpt)] +pub struct RelayHeadersParams { + /// If passed, only mandatory headers (headers that are changing the GRANDPA authorities set) + /// are relayed. + #[structopt(long)] + only_mandatory_headers: bool, + #[structopt(flatten)] + source: SourceConnectionParams, + #[structopt(flatten)] + target: TargetConnectionParams, + #[structopt(flatten)] + target_sign: TargetSigningParams, + #[structopt(flatten)] + prometheus_params: PrometheusParams, +} + +/// Trait used for relaying headers between 2 chains. +#[async_trait] +pub trait HeadersRelayer: RelayToRelayHeadersCliBridge { + /// Relay headers. + async fn relay_headers(data: RelayHeadersParams) -> anyhow::Result<()> { + let source_client = data.source.into_client::().await?; + let target_client = data.target.into_client::().await?; + let target_transactions_mortality = data.target_sign.target_transactions_mortality; + let target_sign = data.target_sign.to_keypair::()?; + + let metrics_params: relay_utils::metrics::MetricsParams = + data.prometheus_params.into_metrics_params()?; + GlobalMetrics::new()?.register_and_spawn(&metrics_params.registry)?; + + let target_transactions_params = crate::TransactionParams { + signer: target_sign, + mortality: target_transactions_mortality, + }; + Self::Finality::start_relay_guards(&target_client, target_client.can_start_version_guard()) + .await?; + + crate::finality::run::( + source_client, + target_client, + data.only_mandatory_headers, + target_transactions_params, + metrics_params, + ) + .await + } +} diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs new file mode 100644 index 0000000000000..27e9f1c21ba0d --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs @@ -0,0 +1,492 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Complex 2-ways headers+messages relays support. +//! +//! To add new complex relay between `ChainA` and `ChainB`, you must: +//! +//! 1) ensure that there's a `declare_chain_cli_schema!(...)` for both chains. +//! 2) add `declare_chain_to_chain_bridge_schema!(...)` or +//! `declare_chain_to_parachain_bridge_schema` for the bridge. +//! 3) declare a new struct for the added bridge and implement the `Full2WayBridge` trait for it. + +#[macro_use] +pub mod parachain_to_parachain; +#[macro_use] +pub mod relay_to_relay; +#[macro_use] +pub mod relay_to_parachain; + +use async_trait::async_trait; +use std::{marker::PhantomData, sync::Arc}; +use structopt::StructOpt; + +use futures::{FutureExt, TryFutureExt}; + +use crate::{ + cli::{bridge::MessagesCliBridge, HexLaneId, PrometheusParams}, + messages_lane::{MessagesRelayLimits, MessagesRelayParams}, + on_demand::OnDemandRelay, + TaggedAccount, TransactionParams, +}; +use bp_messages::LaneId; +use bp_runtime::BalanceOf; +use relay_substrate_client::{ + AccountIdOf, AccountKeyPairOf, Chain, ChainWithBalances, ChainWithMessages, + ChainWithRuntimeVersion, ChainWithTransactions, Client, +}; +use relay_utils::metrics::MetricsParams; +use sp_core::Pair; + +/// Parameters that have the same names across all bridges. +#[derive(Debug, PartialEq, StructOpt)] +pub struct HeadersAndMessagesSharedParams { + /// Hex-encoded lane identifiers that should be served by the complex relay. + #[structopt(long, default_value = "00000000")] + pub lane: Vec, + /// If passed, only mandatory headers (headers that are changing the GRANDPA authorities set) + /// are relayed. + #[structopt(long)] + pub only_mandatory_headers: bool, + #[structopt(flatten)] + /// Prometheus metrics params. + pub prometheus_params: PrometheusParams, +} + +/// Bridge parameters, shared by all bridge types. +pub struct Full2WayBridgeCommonParams< + Left: ChainWithTransactions + ChainWithRuntimeVersion, + Right: ChainWithTransactions + ChainWithRuntimeVersion, +> { + /// Shared parameters. + pub shared: HeadersAndMessagesSharedParams, + /// Parameters of the left chain. + pub left: BridgeEndCommonParams, + /// Parameters of the right chain. + pub right: BridgeEndCommonParams, + + /// Common metric parameters. + pub metrics_params: MetricsParams, +} + +impl< + Left: ChainWithTransactions + ChainWithRuntimeVersion, + Right: ChainWithTransactions + ChainWithRuntimeVersion, + > Full2WayBridgeCommonParams +{ + /// Creates new bridge parameters from its components. + pub fn new>( + shared: HeadersAndMessagesSharedParams, + left: BridgeEndCommonParams, + right: BridgeEndCommonParams, + ) -> anyhow::Result { + // Create metrics registry. + let metrics_params = shared.prometheus_params.clone().into_metrics_params()?; + let metrics_params = relay_utils::relay_metrics(metrics_params).into_params(); + + Ok(Self { shared, left, right, metrics_params }) + } +} + +/// Parameters that are associated with one side of the bridge. +pub struct BridgeEndCommonParams { + /// Chain client. + pub client: Client, + /// Params used for sending transactions to the chain. + pub tx_params: TransactionParams>, + /// Accounts, which balances are exposed as metrics by the relay process. + pub accounts: Vec>>, +} + +/// All data of the bidirectional complex relay. +pub struct FullBridge< + 'a, + Source: ChainWithTransactions + ChainWithRuntimeVersion, + Target: ChainWithTransactions + ChainWithRuntimeVersion, + Bridge: MessagesCliBridge, +> { + source: &'a mut BridgeEndCommonParams, + target: &'a mut BridgeEndCommonParams, + metrics_params: &'a MetricsParams, + _phantom_data: PhantomData, +} + +impl< + 'a, + Source: ChainWithTransactions + ChainWithRuntimeVersion, + Target: ChainWithTransactions + ChainWithRuntimeVersion, + Bridge: MessagesCliBridge, + > FullBridge<'a, Source, Target, Bridge> +where + AccountIdOf: From< as Pair>::Public>, + AccountIdOf: From< as Pair>::Public>, + BalanceOf: TryFrom> + Into, +{ + /// Construct complex relay given it components. + fn new( + source: &'a mut BridgeEndCommonParams, + target: &'a mut BridgeEndCommonParams, + metrics_params: &'a MetricsParams, + ) -> Self { + Self { source, target, metrics_params, _phantom_data: Default::default() } + } + + /// Returns message relay parameters. + fn messages_relay_params( + &self, + source_to_target_headers_relay: Arc>, + target_to_source_headers_relay: Arc>, + lane_id: LaneId, + maybe_limits: Option, + ) -> MessagesRelayParams { + MessagesRelayParams { + source_client: self.source.client.clone(), + source_transaction_params: self.source.tx_params.clone(), + target_client: self.target.client.clone(), + target_transaction_params: self.target.tx_params.clone(), + source_to_target_headers_relay: Some(source_to_target_headers_relay), + target_to_source_headers_relay: Some(target_to_source_headers_relay), + lane_id, + limits: maybe_limits, + metrics_params: self.metrics_params.clone().disable(), + } + } +} + +/// Base portion of the bidirectional complex relay. +/// +/// This main purpose of extracting this trait is that in different relays the implementation +/// of `start_on_demand_headers_relayers` method will be different. But the number of +/// implementations is limited to relay <> relay, parachain <> relay and parachain <> parachain. +/// This trait allows us to reuse these implementations in different bridges. +#[async_trait] +pub trait Full2WayBridgeBase: Sized + Send + Sync { + /// The CLI params for the bridge. + type Params; + /// The left relay chain. + type Left: ChainWithTransactions + ChainWithRuntimeVersion; + /// The right destination chain (it can be a relay or a parachain). + type Right: ChainWithTransactions + ChainWithRuntimeVersion; + + /// Reference to common relay parameters. + fn common(&self) -> &Full2WayBridgeCommonParams; + + /// Mutable reference to common relay parameters. + fn mut_common(&mut self) -> &mut Full2WayBridgeCommonParams; + + /// Start on-demand headers relays. + async fn start_on_demand_headers_relayers( + &mut self, + ) -> anyhow::Result<( + Arc>, + Arc>, + )>; +} + +/// Bidirectional complex relay. +#[async_trait] +pub trait Full2WayBridge: Sized + Sync +where + AccountIdOf: From< as Pair>::Public>, + AccountIdOf: From< as Pair>::Public>, + BalanceOf: TryFrom> + Into, + BalanceOf: TryFrom> + Into, +{ + /// Base portion of the bidirectional complex relay. + type Base: Full2WayBridgeBase; + + /// The left relay chain. + type Left: ChainWithTransactions + + ChainWithBalances + + ChainWithMessages + + ChainWithRuntimeVersion; + /// The right relay chain. + type Right: ChainWithTransactions + + ChainWithBalances + + ChainWithMessages + + ChainWithRuntimeVersion; + + /// Left to Right bridge. + type L2R: MessagesCliBridge; + /// Right to Left bridge + type R2L: MessagesCliBridge; + + /// Construct new bridge. + fn new(params: ::Params) -> anyhow::Result; + + /// Reference to the base relay portion. + fn base(&self) -> &Self::Base; + + /// Mutable reference to the base relay portion. + fn mut_base(&mut self) -> &mut Self::Base; + + /// Creates and returns Left to Right complex relay. + fn left_to_right(&mut self) -> FullBridge { + let common = self.mut_base().mut_common(); + FullBridge::<_, _, Self::L2R>::new( + &mut common.left, + &mut common.right, + &common.metrics_params, + ) + } + + /// Creates and returns Right to Left complex relay. + fn right_to_left(&mut self) -> FullBridge { + let common = self.mut_base().mut_common(); + FullBridge::<_, _, Self::R2L>::new( + &mut common.right, + &mut common.left, + &common.metrics_params, + ) + } + + /// Start complex relay. + async fn run(&mut self) -> anyhow::Result<()> { + // Register standalone metrics. + { + let common = self.mut_base().mut_common(); + common.left.accounts.push(TaggedAccount::Messages { + id: common.left.tx_params.signer.public().into(), + bridged_chain: Self::Right::NAME.to_string(), + }); + common.right.accounts.push(TaggedAccount::Messages { + id: common.right.tx_params.signer.public().into(), + bridged_chain: Self::Left::NAME.to_string(), + }); + } + + // start on-demand header relays + let (left_to_right_on_demand_headers, right_to_left_on_demand_headers) = + self.mut_base().start_on_demand_headers_relayers().await?; + + // add balance-related metrics + let lanes = self + .base() + .common() + .shared + .lane + .iter() + .cloned() + .map(Into::into) + .collect::>(); + { + let common = self.mut_base().mut_common(); + crate::messages_metrics::add_relay_balances_metrics::<_, Self::Right>( + common.left.client.clone(), + &common.metrics_params, + &common.left.accounts, + &lanes, + ) + .await?; + crate::messages_metrics::add_relay_balances_metrics::<_, Self::Left>( + common.right.client.clone(), + &common.metrics_params, + &common.right.accounts, + &lanes, + ) + .await?; + } + + // Need 2x capacity since we consider both directions for each lane + let mut message_relays = Vec::with_capacity(lanes.len() * 2); + for lane in lanes { + let left_to_right_messages = crate::messages_lane::run::< + ::MessagesLane, + >(self.left_to_right().messages_relay_params( + left_to_right_on_demand_headers.clone(), + right_to_left_on_demand_headers.clone(), + lane, + Self::L2R::maybe_messages_limits(), + )) + .map_err(|e| anyhow::format_err!("{}", e)) + .boxed(); + message_relays.push(left_to_right_messages); + + let right_to_left_messages = crate::messages_lane::run::< + ::MessagesLane, + >(self.right_to_left().messages_relay_params( + right_to_left_on_demand_headers.clone(), + left_to_right_on_demand_headers.clone(), + lane, + Self::R2L::maybe_messages_limits(), + )) + .map_err(|e| anyhow::format_err!("{}", e)) + .boxed(); + message_relays.push(right_to_left_messages); + } + + relay_utils::relay_metrics(self.base().common().metrics_params.clone()) + .expose() + .await + .map_err(|e| anyhow::format_err!("{}", e))?; + + futures::future::select_all(message_relays).await.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{cli::chain_schema::RuntimeVersionType, declare_chain_cli_schema}; + + use relay_substrate_client::{ChainRuntimeVersion, Parachain, SimpleRuntimeVersion}; + + #[test] + // We need `#[allow(dead_code)]` because some of the methods generated by the macros + // are not used. + #[allow(dead_code)] + fn should_parse_parachain_to_parachain_options() { + // Chains. + declare_chain_cli_schema!(Kusama, kusama); + declare_chain_cli_schema!(BridgeHubKusama, bridge_hub_kusama); + declare_chain_cli_schema!(Polkadot, polkadot); + declare_chain_cli_schema!(BridgeHubPolkadot, bridge_hub_polkadot); + // Means to override signers of different layer transactions. + declare_chain_cli_schema!( + KusamaHeadersToBridgeHubPolkadot, + kusama_headers_to_bridge_hub_polkadot + ); + declare_chain_cli_schema!( + KusamaParachainsToBridgeHubPolkadot, + kusama_parachains_to_bridge_hub_polkadot + ); + declare_chain_cli_schema!( + PolkadotHeadersToBridgeHubKusama, + polkadot_headers_to_bridge_hub_kusama + ); + declare_chain_cli_schema!( + PolkadotParachainsToBridgeHubKusama, + polkadot_parachains_to_bridge_hub_kusama + ); + // Bridges. + declare_parachain_to_parachain_bridge_schema!( + BridgeHubKusama, + Kusama, + BridgeHubPolkadot, + Polkadot + ); + + let res = BridgeHubKusamaBridgeHubPolkadotHeadersAndMessages::from_iter(vec![ + "bridge-hub-kusama-bridge-hub-polkadot-headers-and-messages", + "--bridge-hub-kusama-host", + "bridge-hub-kusama-node-collator1", + "--bridge-hub-kusama-port", + "9944", + "--bridge-hub-kusama-signer", + "//Iden", + "--bridge-hub-kusama-transactions-mortality", + "64", + "--kusama-host", + "kusama-alice", + "--kusama-port", + "9944", + "--bridge-hub-polkadot-host", + "bridge-hub-polkadot-collator1", + "--bridge-hub-polkadot-port", + "9944", + "--bridge-hub-polkadot-signer", + "//George", + "--bridge-hub-polkadot-transactions-mortality", + "64", + "--polkadot-host", + "polkadot-alice", + "--polkadot-port", + "9944", + "--lane", + "00000000", + "--prometheus-host", + "0.0.0.0", + ]); + + // then + assert_eq!( + res, + BridgeHubKusamaBridgeHubPolkadotHeadersAndMessages { + shared: HeadersAndMessagesSharedParams { + lane: vec![HexLaneId([0x00, 0x00, 0x00, 0x00])], + only_mandatory_headers: false, + prometheus_params: PrometheusParams { + no_prometheus: false, + prometheus_host: "0.0.0.0".into(), + prometheus_port: 9616, + }, + }, + left: BridgeHubKusamaConnectionParams { + bridge_hub_kusama_uri: None, + bridge_hub_kusama_host: "bridge-hub-kusama-node-collator1".into(), + bridge_hub_kusama_port: 9944, + bridge_hub_kusama_path: None, + bridge_hub_kusama_secure: false, + bridge_hub_kusama_runtime_version: BridgeHubKusamaRuntimeVersionParams { + bridge_hub_kusama_version_mode: RuntimeVersionType::Bundle, + bridge_hub_kusama_spec_version: None, + bridge_hub_kusama_transaction_version: None, + }, + }, + left_sign: BridgeHubKusamaSigningParams { + bridge_hub_kusama_signer: Some("//Iden".into()), + bridge_hub_kusama_signer_password: None, + bridge_hub_kusama_signer_file: None, + bridge_hub_kusama_signer_password_file: None, + bridge_hub_kusama_transactions_mortality: Some(64), + }, + left_relay: KusamaConnectionParams { + kusama_uri: None, + kusama_host: "kusama-alice".into(), + kusama_port: 9944, + kusama_path: None, + kusama_secure: false, + kusama_runtime_version: KusamaRuntimeVersionParams { + kusama_version_mode: RuntimeVersionType::Bundle, + kusama_spec_version: None, + kusama_transaction_version: None, + }, + }, + right: BridgeHubPolkadotConnectionParams { + bridge_hub_polkadot_uri: None, + bridge_hub_polkadot_host: "bridge-hub-polkadot-collator1".into(), + bridge_hub_polkadot_port: 9944, + bridge_hub_polkadot_path: None, + bridge_hub_polkadot_secure: false, + bridge_hub_polkadot_runtime_version: BridgeHubPolkadotRuntimeVersionParams { + bridge_hub_polkadot_version_mode: RuntimeVersionType::Bundle, + bridge_hub_polkadot_spec_version: None, + bridge_hub_polkadot_transaction_version: None, + }, + }, + right_sign: BridgeHubPolkadotSigningParams { + bridge_hub_polkadot_signer: Some("//George".into()), + bridge_hub_polkadot_signer_password: None, + bridge_hub_polkadot_signer_file: None, + bridge_hub_polkadot_signer_password_file: None, + bridge_hub_polkadot_transactions_mortality: Some(64), + }, + right_relay: PolkadotConnectionParams { + polkadot_uri: None, + polkadot_host: "polkadot-alice".into(), + polkadot_port: 9944, + polkadot_path: None, + polkadot_secure: false, + polkadot_runtime_version: PolkadotRuntimeVersionParams { + polkadot_version_mode: RuntimeVersionType::Bundle, + polkadot_spec_version: None, + polkadot_transaction_version: None, + }, + }, + } + ); + } +} diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/parachain_to_parachain.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/parachain_to_parachain.rs new file mode 100644 index 0000000000000..76accfa290506 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/parachain_to_parachain.rs @@ -0,0 +1,217 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Parachain to parachain relayer CLI primitives. + +use async_trait::async_trait; +use std::sync::Arc; + +use crate::{ + cli::{ + bridge::{CliBridgeBase, MessagesCliBridge, ParachainToRelayHeadersCliBridge}, + relay_headers_and_messages::{Full2WayBridgeBase, Full2WayBridgeCommonParams}, + }, + finality::SubstrateFinalitySyncPipeline, + on_demand::{ + headers::OnDemandHeadersRelay, parachains::OnDemandParachainsRelay, OnDemandRelay, + }, +}; +use bp_polkadot_core::parachains::ParaHash; +use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber}; +use relay_substrate_client::{ + AccountIdOf, AccountKeyPairOf, Chain, ChainWithRuntimeVersion, ChainWithTransactions, Client, + Parachain, +}; +use sp_core::Pair; + +/// A base relay between two parachain from different consensus systems. +/// +/// Such relay starts 2 messages relay. It also starts 2 on-demand header relays and 2 on-demand +/// parachain heads relay. +pub struct ParachainToParachainBridge< + L2R: MessagesCliBridge + ParachainToRelayHeadersCliBridge, + R2L: MessagesCliBridge + ParachainToRelayHeadersCliBridge, +> where + ::Source: Parachain, + ::Source: Parachain, +{ + /// Parameters that are shared by all bridge types. + pub common: + Full2WayBridgeCommonParams<::Target, ::Target>, + /// Client of the left relay chain. + pub left_relay: Client<::SourceRelay>, + /// Client of the right relay chain. + pub right_relay: Client<::SourceRelay>, +} + +/// Create set of configuration objects specific to parachain-to-parachain relayer. +#[macro_export] +macro_rules! declare_parachain_to_parachain_bridge_schema { + // left-parachain, relay-chain-of-left-parachain, right-parachain, relay-chain-of-right-parachain + ($left_parachain:ident, $left_chain:ident, $right_parachain:ident, $right_chain:ident) => { + bp_runtime::paste::item! { + #[doc = $left_parachain ", " $left_chain ", " $right_parachain " and " $right_chain " headers+parachains+messages relay params."] + #[derive(Debug, PartialEq, StructOpt)] + pub struct [<$left_parachain $right_parachain HeadersAndMessages>] { + // shared parameters + #[structopt(flatten)] + shared: HeadersAndMessagesSharedParams, + + #[structopt(flatten)] + left: [<$left_parachain ConnectionParams>], + // default signer, which is always used to sign messages relay transactions on the left chain + #[structopt(flatten)] + left_sign: [<$left_parachain SigningParams>], + + #[structopt(flatten)] + left_relay: [<$left_chain ConnectionParams>], + + #[structopt(flatten)] + right: [<$right_parachain ConnectionParams>], + // default signer, which is always used to sign messages relay transactions on the right chain + #[structopt(flatten)] + right_sign: [<$right_parachain SigningParams>], + + #[structopt(flatten)] + right_relay: [<$right_chain ConnectionParams>], + } + + impl [<$left_parachain $right_parachain HeadersAndMessages>] { + async fn into_bridge< + Left: ChainWithTransactions + ChainWithRuntimeVersion + Parachain, + LeftRelay: ChainWithRuntimeVersion, + Right: ChainWithTransactions + ChainWithRuntimeVersion + Parachain, + RightRelay: ChainWithRuntimeVersion, + L2R: $crate::cli::bridge::CliBridgeBase + + MessagesCliBridge + + $crate::cli::bridge::ParachainToRelayHeadersCliBridge, + R2L: $crate::cli::bridge::CliBridgeBase + + MessagesCliBridge + + $crate::cli::bridge::ParachainToRelayHeadersCliBridge, + >( + self, + ) -> anyhow::Result<$crate::cli::relay_headers_and_messages::parachain_to_parachain::ParachainToParachainBridge> { + Ok($crate::cli::relay_headers_and_messages::parachain_to_parachain::ParachainToParachainBridge { + common: Full2WayBridgeCommonParams::new::( + self.shared, + BridgeEndCommonParams { + client: self.left.into_client::().await?, + tx_params: self.left_sign.transaction_params::()?, + accounts: vec![], + }, + BridgeEndCommonParams { + client: self.right.into_client::().await?, + tx_params: self.right_sign.transaction_params::()?, + accounts: vec![], + }, + )?, + left_relay: self.left_relay.into_client::().await?, + right_relay: self.right_relay.into_client::().await?, + }) + } + } + } + }; +} + +#[async_trait] +impl< + Left: Chain + ChainWithTransactions + ChainWithRuntimeVersion + Parachain, + Right: Chain + ChainWithTransactions + ChainWithRuntimeVersion + Parachain, + LeftRelay: Chain + + ChainWithRuntimeVersion, + RightRelay: Chain + + ChainWithRuntimeVersion, + L2R: CliBridgeBase + + MessagesCliBridge + + ParachainToRelayHeadersCliBridge, + R2L: CliBridgeBase + + MessagesCliBridge + + ParachainToRelayHeadersCliBridge, + > Full2WayBridgeBase for ParachainToParachainBridge +where + AccountIdOf: From< as Pair>::Public>, + AccountIdOf: From< as Pair>::Public>, +{ + type Params = ParachainToParachainBridge; + type Left = Left; + type Right = Right; + + fn common(&self) -> &Full2WayBridgeCommonParams { + &self.common + } + + fn mut_common(&mut self) -> &mut Full2WayBridgeCommonParams { + &mut self.common + } + + async fn start_on_demand_headers_relayers( + &mut self, + ) -> anyhow::Result<( + Arc>, + Arc>, + )> { + ::RelayFinality::start_relay_guards( + &self.common.right.client, + self.common.right.client.can_start_version_guard(), + ) + .await?; + ::RelayFinality::start_relay_guards( + &self.common.left.client, + self.common.left.client.can_start_version_guard(), + ) + .await?; + + let left_relay_to_right_on_demand_headers = + OnDemandHeadersRelay::<::RelayFinality>::new( + self.left_relay.clone(), + self.common.right.client.clone(), + self.common.right.tx_params.clone(), + self.common.shared.only_mandatory_headers, + Some(self.common.metrics_params.clone()), + ); + let right_relay_to_left_on_demand_headers = + OnDemandHeadersRelay::<::RelayFinality>::new( + self.right_relay.clone(), + self.common.left.client.clone(), + self.common.left.tx_params.clone(), + self.common.shared.only_mandatory_headers, + Some(self.common.metrics_params.clone()), + ); + + let left_to_right_on_demand_parachains = OnDemandParachainsRelay::< + ::ParachainFinality, + >::new( + self.left_relay.clone(), + self.common.right.client.clone(), + self.common.right.tx_params.clone(), + Arc::new(left_relay_to_right_on_demand_headers), + ); + let right_to_left_on_demand_parachains = OnDemandParachainsRelay::< + ::ParachainFinality, + >::new( + self.right_relay.clone(), + self.common.left.client.clone(), + self.common.left.tx_params.clone(), + Arc::new(right_relay_to_left_on_demand_headers), + ); + + Ok(( + Arc::new(left_to_right_on_demand_parachains), + Arc::new(right_to_left_on_demand_parachains), + )) + } +} diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_parachain.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_parachain.rs new file mode 100644 index 0000000000000..b75ac3e60c269 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_parachain.rs @@ -0,0 +1,199 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Relay chain to parachain relayer CLI primitives. + +use async_trait::async_trait; +use std::sync::Arc; + +use crate::{ + cli::{ + bridge::{ + CliBridgeBase, MessagesCliBridge, ParachainToRelayHeadersCliBridge, + RelayToRelayHeadersCliBridge, + }, + relay_headers_and_messages::{Full2WayBridgeBase, Full2WayBridgeCommonParams}, + }, + finality::SubstrateFinalitySyncPipeline, + on_demand::{ + headers::OnDemandHeadersRelay, parachains::OnDemandParachainsRelay, OnDemandRelay, + }, +}; +use bp_polkadot_core::parachains::ParaHash; +use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber}; +use relay_substrate_client::{ + AccountIdOf, AccountKeyPairOf, Chain, ChainWithRuntimeVersion, ChainWithTransactions, Client, + Parachain, +}; +use sp_core::Pair; + +/// A base relay between standalone (relay) chain and a parachain from another consensus system. +/// +/// Such relay starts 2 messages relay. It also starts 2 on-demand header relays and 1 on-demand +/// parachain heads relay. +pub struct RelayToParachainBridge< + L2R: MessagesCliBridge + RelayToRelayHeadersCliBridge, + R2L: MessagesCliBridge + ParachainToRelayHeadersCliBridge, +> where + ::Source: Parachain, +{ + /// Parameters that are shared by all bridge types. + pub common: + Full2WayBridgeCommonParams<::Target, ::Target>, + /// Client of the right relay chain. + pub right_relay: Client<::SourceRelay>, +} + +/// Create set of configuration objects specific to relay-to-parachain relayer. +#[macro_export] +macro_rules! declare_relay_to_parachain_bridge_schema { + // chain, parachain, relay-chain-of-parachain + ($left_chain:ident, $right_parachain:ident, $right_chain:ident) => { + bp_runtime::paste::item! { + #[doc = $left_chain ", " $right_parachain " and " $right_chain " headers+parachains+messages relay params."] + #[derive(Debug, PartialEq, StructOpt)] + pub struct [<$left_chain $right_parachain HeadersAndMessages>] { + // shared parameters + #[structopt(flatten)] + shared: HeadersAndMessagesSharedParams, + + #[structopt(flatten)] + left: [<$left_chain ConnectionParams>], + // default signer, which is always used to sign messages relay transactions on the left chain + #[structopt(flatten)] + left_sign: [<$left_chain SigningParams>], + + #[structopt(flatten)] + right: [<$right_parachain ConnectionParams>], + // default signer, which is always used to sign messages relay transactions on the right chain + #[structopt(flatten)] + right_sign: [<$right_parachain SigningParams>], + + #[structopt(flatten)] + right_relay: [<$right_chain ConnectionParams>], + } + + impl [<$left_chain $right_parachain HeadersAndMessages>] { + async fn into_bridge< + Left: ChainWithTransactions + ChainWithRuntimeVersion, + Right: ChainWithTransactions + ChainWithRuntimeVersion + Parachain, + RightRelay: ChainWithRuntimeVersion, + L2R: CliBridgeBase + MessagesCliBridge + RelayToRelayHeadersCliBridge, + R2L: CliBridgeBase + + MessagesCliBridge + + ParachainToRelayHeadersCliBridge, + >( + self, + ) -> anyhow::Result> { + Ok(RelayToParachainBridge { + common: Full2WayBridgeCommonParams::new::( + self.shared, + BridgeEndCommonParams { + client: self.left.into_client::().await?, + tx_params: self.left_sign.transaction_params::()?, + accounts: vec![], + }, + BridgeEndCommonParams { + client: self.right.into_client::().await?, + tx_params: self.right_sign.transaction_params::()?, + accounts: vec![], + }, + )?, + right_relay: self.right_relay.into_client::().await?, + }) + } + } + } + }; +} + +#[async_trait] +impl< + Left: ChainWithTransactions + ChainWithRuntimeVersion, + Right: Chain + ChainWithTransactions + ChainWithRuntimeVersion + Parachain, + RightRelay: Chain + + ChainWithRuntimeVersion, + L2R: CliBridgeBase + + MessagesCliBridge + + RelayToRelayHeadersCliBridge, + R2L: CliBridgeBase + + MessagesCliBridge + + ParachainToRelayHeadersCliBridge, + > Full2WayBridgeBase for RelayToParachainBridge +where + AccountIdOf: From< as Pair>::Public>, + AccountIdOf: From< as Pair>::Public>, +{ + type Params = RelayToParachainBridge; + type Left = Left; + type Right = Right; + + fn common(&self) -> &Full2WayBridgeCommonParams { + &self.common + } + + fn mut_common(&mut self) -> &mut Full2WayBridgeCommonParams { + &mut self.common + } + + async fn start_on_demand_headers_relayers( + &mut self, + ) -> anyhow::Result<( + Arc>, + Arc>, + )> { + ::Finality::start_relay_guards( + &self.common.right.client, + self.common.right.client.can_start_version_guard(), + ) + .await?; + ::RelayFinality::start_relay_guards( + &self.common.left.client, + self.common.left.client.can_start_version_guard(), + ) + .await?; + + let left_to_right_on_demand_headers = + OnDemandHeadersRelay::<::Finality>::new( + self.common.left.client.clone(), + self.common.right.client.clone(), + self.common.right.tx_params.clone(), + self.common.shared.only_mandatory_headers, + None, + ); + let right_relay_to_left_on_demand_headers = + OnDemandHeadersRelay::<::RelayFinality>::new( + self.right_relay.clone(), + self.common.left.client.clone(), + self.common.left.tx_params.clone(), + self.common.shared.only_mandatory_headers, + Some(self.common.metrics_params.clone()), + ); + let right_to_left_on_demand_parachains = OnDemandParachainsRelay::< + ::ParachainFinality, + >::new( + self.right_relay.clone(), + self.common.left.client.clone(), + self.common.left.tx_params.clone(), + Arc::new(right_relay_to_left_on_demand_headers), + ); + + Ok(( + Arc::new(left_to_right_on_demand_headers), + Arc::new(right_to_left_on_demand_parachains), + )) + } +} diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_relay.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_relay.rs new file mode 100644 index 0000000000000..b397ff50a20a6 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_relay.rs @@ -0,0 +1,169 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +// we don't have any relay/standalone <> relay/standalone chain bridges, but we may need it in a +// future +#![allow(unused_macros)] + +//! Relay chain to Relay chain relayer CLI primitives. + +use async_trait::async_trait; +use std::sync::Arc; + +use crate::{ + cli::{ + bridge::{CliBridgeBase, MessagesCliBridge, RelayToRelayHeadersCliBridge}, + relay_headers_and_messages::{Full2WayBridgeBase, Full2WayBridgeCommonParams}, + }, + finality::SubstrateFinalitySyncPipeline, + on_demand::{headers::OnDemandHeadersRelay, OnDemandRelay}, +}; +use relay_substrate_client::{ + AccountIdOf, AccountKeyPairOf, ChainWithRuntimeVersion, ChainWithTransactions, +}; +use sp_core::Pair; + +/// A base relay between two standalone (relay) chains. +/// +/// Such relay starts 2 messages relay and 2 on-demand header relays. +pub struct RelayToRelayBridge< + L2R: MessagesCliBridge + RelayToRelayHeadersCliBridge, + R2L: MessagesCliBridge + RelayToRelayHeadersCliBridge, +> { + /// Parameters that are shared by all bridge types. + pub common: + Full2WayBridgeCommonParams<::Target, ::Target>, +} + +/// Create set of configuration objects specific to relay-to-relay relayer. +macro_rules! declare_relay_to_relay_bridge_schema { + ($left_chain:ident, $right_chain:ident) => { + bp_runtime::paste::item! { + #[doc = $left_chain " and " $right_chain " headers+messages relay params."] + #[derive(Debug, PartialEq, StructOpt)] + pub struct [<$left_chain $right_chain HeadersAndMessages>] { + #[structopt(flatten)] + shared: HeadersAndMessagesSharedParams, + + #[structopt(flatten)] + left: [<$left_chain ConnectionParams>], + // default signer, which is always used to sign messages relay transactions on the left chain + #[structopt(flatten)] + left_sign: [<$left_chain SigningParams>], + + #[structopt(flatten)] + right: [<$right_chain ConnectionParams>], + #[structopt(flatten)] + // default signer, which is always used to sign messages relay transactions on the right chain + right_sign: [<$right_chain SigningParams>], + } + + impl [<$left_chain $right_chain HeadersAndMessages>] { + async fn into_bridge< + Left: ChainWithTransactions + CliChain, + Right: ChainWithTransactions + CliChain, + L2R: CliBridgeBase + MessagesCliBridge + RelayToRelayHeadersCliBridge, + R2L: CliBridgeBase + MessagesCliBridge + RelayToRelayHeadersCliBridge, + >( + self, + ) -> anyhow::Result> { + Ok(RelayToRelayBridge { + common: Full2WayBridgeCommonParams::new::( + self.shared, + BridgeEndCommonParams { + client: self.left.into_client::().await?, + tx_params: self.left_sign.transaction_params::()?, + accounts: vec![], + }, + BridgeEndCommonParams { + client: self.right.into_client::().await?, + tx_params: self.right_sign.transaction_params::()?, + accounts: vec![], + }, + )?, + right_to_left_transaction_params: self.left_sign.transaction_params::(), + left_to_right_transaction_params: self.right_sign.transaction_params::(), + }) + } + } + } + }; +} + +#[async_trait] +impl< + Left: ChainWithTransactions + ChainWithRuntimeVersion, + Right: ChainWithTransactions + ChainWithRuntimeVersion, + L2R: CliBridgeBase + + MessagesCliBridge + + RelayToRelayHeadersCliBridge, + R2L: CliBridgeBase + + MessagesCliBridge + + RelayToRelayHeadersCliBridge, + > Full2WayBridgeBase for RelayToRelayBridge +where + AccountIdOf: From< as Pair>::Public>, + AccountIdOf: From< as Pair>::Public>, +{ + type Params = RelayToRelayBridge; + type Left = Left; + type Right = Right; + + fn common(&self) -> &Full2WayBridgeCommonParams { + &self.common + } + + fn mut_common(&mut self) -> &mut Full2WayBridgeCommonParams { + &mut self.common + } + + async fn start_on_demand_headers_relayers( + &mut self, + ) -> anyhow::Result<( + Arc>, + Arc>, + )> { + ::Finality::start_relay_guards( + &self.common.right.client, + self.common.right.client.can_start_version_guard(), + ) + .await?; + ::Finality::start_relay_guards( + &self.common.left.client, + self.common.left.client.can_start_version_guard(), + ) + .await?; + + let left_to_right_on_demand_headers = + OnDemandHeadersRelay::<::Finality>::new( + self.common.left.client.clone(), + self.common.right.client.clone(), + self.common.right.tx_params.clone(), + self.common.shared.only_mandatory_headers, + None, + ); + let right_to_left_on_demand_headers = + OnDemandHeadersRelay::<::Finality>::new( + self.common.right.client.clone(), + self.common.left.client.clone(), + self.common.left.tx_params.clone(), + self.common.shared.only_mandatory_headers, + None, + ); + + Ok((Arc::new(left_to_right_on_demand_headers), Arc::new(right_to_left_on_demand_headers))) + } +} diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs new file mode 100644 index 0000000000000..b672bd4f9b868 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs @@ -0,0 +1,89 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Primitives for exposing the messages relaying functionality in the CLI. + +use crate::{ + cli::{bridge::*, chain_schema::*, HexLaneId, PrometheusParams}, + messages_lane::MessagesRelayParams, + TransactionParams, +}; + +use async_trait::async_trait; +use sp_core::Pair; +use structopt::StructOpt; + +use relay_substrate_client::{ + AccountIdOf, AccountKeyPairOf, BalanceOf, ChainWithRuntimeVersion, ChainWithTransactions, +}; + +/// Messages relaying params. +#[derive(StructOpt)] +pub struct RelayMessagesParams { + /// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`. + #[structopt(long, default_value = "00000000")] + lane: HexLaneId, + #[structopt(flatten)] + source: SourceConnectionParams, + #[structopt(flatten)] + source_sign: SourceSigningParams, + #[structopt(flatten)] + target: TargetConnectionParams, + #[structopt(flatten)] + target_sign: TargetSigningParams, + #[structopt(flatten)] + prometheus_params: PrometheusParams, +} + +/// Trait used for relaying messages between 2 chains. +#[async_trait] +pub trait MessagesRelayer: MessagesCliBridge +where + Self::Source: ChainWithTransactions + ChainWithRuntimeVersion, + AccountIdOf: From< as Pair>::Public>, + AccountIdOf: From< as Pair>::Public>, + BalanceOf: TryFrom>, +{ + /// Start relaying messages. + async fn relay_messages(data: RelayMessagesParams) -> anyhow::Result<()> { + let source_client = data.source.into_client::().await?; + let source_sign = data.source_sign.to_keypair::()?; + let source_transactions_mortality = data.source_sign.transactions_mortality()?; + let target_client = data.target.into_client::().await?; + let target_sign = data.target_sign.to_keypair::()?; + let target_transactions_mortality = data.target_sign.transactions_mortality()?; + + crate::messages_lane::run::(MessagesRelayParams { + source_client, + source_transaction_params: TransactionParams { + signer: source_sign, + mortality: source_transactions_mortality, + }, + target_client, + target_transaction_params: TransactionParams { + signer: target_sign, + mortality: target_transactions_mortality, + }, + source_to_target_headers_relay: None, + target_to_source_headers_relay: None, + lane_id: data.lane.into(), + limits: Self::maybe_messages_limits(), + metrics_params: data.prometheus_params.into_metrics_params()?, + }) + .await + .map_err(|e| anyhow::format_err!("{}", e)) + } +} diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs new file mode 100644 index 0000000000000..e5a52349469bb --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs @@ -0,0 +1,91 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Primitives for exposing the parachains finality relaying functionality in the CLI. + +use async_std::sync::Mutex; +use async_trait::async_trait; +use parachains_relay::parachains_loop::{AvailableHeader, SourceClient, TargetClient}; +use relay_substrate_client::Parachain; +use relay_utils::metrics::{GlobalMetrics, StandaloneMetric}; +use std::sync::Arc; +use structopt::StructOpt; + +use crate::{ + cli::{ + bridge::{CliBridgeBase, ParachainToRelayHeadersCliBridge}, + chain_schema::*, + PrometheusParams, + }, + parachains::{source::ParachainsSource, target::ParachainsTarget, ParachainsPipelineAdapter}, + TransactionParams, +}; + +/// Parachains heads relaying params. +#[derive(StructOpt)] +pub struct RelayParachainsParams { + #[structopt(flatten)] + source: SourceConnectionParams, + #[structopt(flatten)] + target: TargetConnectionParams, + #[structopt(flatten)] + target_sign: TargetSigningParams, + #[structopt(flatten)] + prometheus_params: PrometheusParams, +} + +/// Trait used for relaying parachains finality between 2 chains. +#[async_trait] +pub trait ParachainsRelayer: ParachainToRelayHeadersCliBridge +where + ParachainsSource: + SourceClient>, + ParachainsTarget: + TargetClient>, + ::Source: Parachain, +{ + /// Start relaying parachains finality. + async fn relay_parachains(data: RelayParachainsParams) -> anyhow::Result<()> { + let source_client = data.source.into_client::().await?; + let source_client = ParachainsSource::::new( + source_client, + Arc::new(Mutex::new(AvailableHeader::Missing)), + ); + + let target_transaction_params = TransactionParams { + signer: data.target_sign.to_keypair::()?, + mortality: data.target_sign.target_transactions_mortality, + }; + let target_client = data.target.into_client::().await?; + let target_client = ParachainsTarget::::new( + target_client.clone(), + target_transaction_params, + ); + + let metrics_params: relay_utils::metrics::MetricsParams = + data.prometheus_params.into_metrics_params()?; + GlobalMetrics::new()?.register_and_spawn(&metrics_params.registry)?; + + parachains_relay::parachains_loop::run( + source_client, + target_client, + metrics_params, + futures::future::pending(), + ) + .await + .map_err(|e| anyhow::format_err!("{}", e)) + } +} diff --git a/bridges/relays/lib-substrate-relay/src/equivocation/mod.rs b/bridges/relays/lib-substrate-relay/src/equivocation/mod.rs new file mode 100644 index 0000000000000..f6d58cbaa4ab4 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/equivocation/mod.rs @@ -0,0 +1,223 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Types and functions intended to ease adding of new Substrate -> Substrate +//! equivocation detection pipelines. + +mod source; +mod target; + +use crate::{ + equivocation::{source::SubstrateEquivocationSource, target::SubstrateEquivocationTarget}, + finality_base::{engine::Engine, SubstrateFinalityPipeline, SubstrateFinalityProof}, + TransactionParams, +}; + +use async_trait::async_trait; +use bp_runtime::{AccountIdOf, BlockNumberOf, HashOf}; +use equivocation_detector::EquivocationDetectionPipeline; +use finality_relay::FinalityPipeline; +use pallet_grandpa::{Call as GrandpaCall, Config as GrandpaConfig}; +use relay_substrate_client::{AccountKeyPairOf, CallOf, Chain, ChainWithTransactions, Client}; +use relay_utils::metrics::MetricsParams; +use sp_core::Pair; +use sp_runtime::traits::{Block, Header}; +use std::marker::PhantomData; + +/// Convenience trait that adds bounds to `SubstrateEquivocationDetectionPipeline`. +pub trait BaseSubstrateEquivocationDetectionPipeline: + SubstrateFinalityPipeline +{ + /// Bounded `SubstrateFinalityPipeline::SourceChain`. + type BoundedSourceChain: ChainWithTransactions; + + /// Bounded `AccountIdOf`. + type BoundedSourceChainAccountId: From< as Pair>::Public> + + Send; +} + +impl BaseSubstrateEquivocationDetectionPipeline for T +where + T: SubstrateFinalityPipeline, + T::SourceChain: ChainWithTransactions, + AccountIdOf: From< as Pair>::Public>, +{ + type BoundedSourceChain = T::SourceChain; + type BoundedSourceChainAccountId = AccountIdOf; +} + +/// Substrate -> Substrate equivocation detection pipeline. +#[async_trait] +pub trait SubstrateEquivocationDetectionPipeline: + BaseSubstrateEquivocationDetectionPipeline +{ + /// How the `report_equivocation` call is built ? + type ReportEquivocationCallBuilder: ReportEquivocationCallBuilder; + + /// Add relay guards if required. + async fn start_relay_guards( + source_client: &Client, + enable_version_guard: bool, + ) -> relay_substrate_client::Result<()> { + if enable_version_guard { + relay_substrate_client::guard::abort_on_spec_version_change( + source_client.clone(), + source_client.simple_runtime_version().await?.spec_version, + ); + } + Ok(()) + } +} + +type FinalityProoffOf

= <

::FinalityEngine as Engine< +

::SourceChain, +>>::FinalityProof; +type FinalityVerificationContextfOf

= + <

::FinalityEngine as Engine< +

::SourceChain, + >>::FinalityVerificationContext; +/// The type of the equivocation proof used by the `SubstrateEquivocationDetectionPipeline` +pub type EquivocationProofOf

= <

::FinalityEngine as Engine< +

::SourceChain, +>>::EquivocationProof; +type EquivocationsFinderOf

= <

::FinalityEngine as Engine< +

::SourceChain, +>>::EquivocationsFinder; +/// The type of the key owner proof used by the `SubstrateEquivocationDetectionPipeline` +pub type KeyOwnerProofOf

= <

::FinalityEngine as Engine< +

::SourceChain, +>>::KeyOwnerProof; + +/// Adapter that allows a `SubstrateEquivocationDetectionPipeline` to act as an +/// `EquivocationDetectionPipeline`. +#[derive(Clone, Debug)] +pub struct EquivocationDetectionPipelineAdapter { + _phantom: PhantomData

, +} + +impl FinalityPipeline + for EquivocationDetectionPipelineAdapter

+{ + const SOURCE_NAME: &'static str = P::SourceChain::NAME; + const TARGET_NAME: &'static str = P::TargetChain::NAME; + + type Hash = HashOf; + type Number = BlockNumberOf; + type FinalityProof = SubstrateFinalityProof

; +} + +impl EquivocationDetectionPipeline + for EquivocationDetectionPipelineAdapter

+{ + type TargetNumber = BlockNumberOf; + type FinalityVerificationContext = FinalityVerificationContextfOf

; + type EquivocationProof = EquivocationProofOf

; + type EquivocationsFinder = EquivocationsFinderOf

; +} + +/// Different ways of building `report_equivocation` calls. +pub trait ReportEquivocationCallBuilder { + /// Build a `report_equivocation` call to be executed on the source chain. + fn build_report_equivocation_call( + equivocation_proof: EquivocationProofOf

, + key_owner_proof: KeyOwnerProofOf

, + ) -> CallOf; +} + +/// Building the `report_equivocation` call when having direct access to the target chain runtime. +pub struct DirectReportGrandpaEquivocationCallBuilder { + _phantom: PhantomData<(P, R)>, +} + +impl ReportEquivocationCallBuilder

for DirectReportGrandpaEquivocationCallBuilder +where + P: SubstrateEquivocationDetectionPipeline, + P::FinalityEngine: Engine< + P::SourceChain, + EquivocationProof = sp_consensus_grandpa::EquivocationProof< + HashOf, + BlockNumberOf, + >, + >, + R: frame_system::Config> + + GrandpaConfig>, + ::Header: Header>, + CallOf: From>, +{ + fn build_report_equivocation_call( + equivocation_proof: EquivocationProofOf

, + key_owner_proof: KeyOwnerProofOf

, + ) -> CallOf { + GrandpaCall::::report_equivocation { + equivocation_proof: Box::new(equivocation_proof), + key_owner_proof, + } + .into() + } +} + +/// Macro that generates `ReportEquivocationCallBuilder` implementation for the case where +/// we only have access to the mocked version of the source chain runtime. +#[rustfmt::skip] +#[macro_export] +macro_rules! generate_report_equivocation_call_builder { + ($pipeline:ident, $mocked_builder:ident, $grandpa:path, $report_equivocation:path) => { + pub struct $mocked_builder; + + impl $crate::equivocation::ReportEquivocationCallBuilder<$pipeline> + for $mocked_builder + { + fn build_report_equivocation_call( + equivocation_proof: $crate::equivocation::EquivocationProofOf<$pipeline>, + key_owner_proof: $crate::equivocation::KeyOwnerProofOf<$pipeline>, + ) -> relay_substrate_client::CallOf< + <$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::SourceChain + > { + bp_runtime::paste::item! { + $grandpa($report_equivocation { + equivocation_proof: Box::new(equivocation_proof), + key_owner_proof: key_owner_proof + }) + } + } + } + }; +} + +/// Run Substrate-to-Substrate equivocations detection loop. +pub async fn run( + source_client: Client, + target_client: Client, + source_transaction_params: TransactionParams>, + metrics_params: MetricsParams, +) -> anyhow::Result<()> { + log::info!( + target: "bridge", + "Starting {} -> {} equivocations detection loop", + P::SourceChain::NAME, + P::TargetChain::NAME, + ); + + equivocation_detector::run( + SubstrateEquivocationSource::

::new(source_client, source_transaction_params), + SubstrateEquivocationTarget::

::new(target_client), + P::TargetChain::AVERAGE_BLOCK_INTERVAL, + metrics_params, + futures::future::pending(), + ) + .await + .map_err(|e| anyhow::format_err!("{}", e)) +} diff --git a/bridges/relays/lib-substrate-relay/src/equivocation/source.rs b/bridges/relays/lib-substrate-relay/src/equivocation/source.rs new file mode 100644 index 0000000000000..a0c7dcf5cbc32 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/equivocation/source.rs @@ -0,0 +1,109 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Default generic implementation of equivocation source for basic Substrate client. + +use crate::{ + equivocation::{ + EquivocationDetectionPipelineAdapter, EquivocationProofOf, ReportEquivocationCallBuilder, + SubstrateEquivocationDetectionPipeline, + }, + finality_base::{engine::Engine, finality_proofs, SubstrateFinalityProofsStream}, + TransactionParams, +}; + +use async_trait::async_trait; +use bp_runtime::{HashOf, TransactionEra}; +use equivocation_detector::SourceClient; +use finality_relay::SourceClientBase; +use relay_substrate_client::{ + AccountKeyPairOf, Client, Error, TransactionTracker, UnsignedTransaction, +}; +use relay_utils::relay_loop::Client as RelayClient; + +/// Substrate node as equivocation source. +pub struct SubstrateEquivocationSource { + client: Client, + transaction_params: TransactionParams>, +} + +impl SubstrateEquivocationSource

{ + /// Create new instance of `SubstrateEquivocationSource`. + pub fn new( + client: Client, + transaction_params: TransactionParams>, + ) -> Self { + Self { client, transaction_params } + } +} + +impl Clone for SubstrateEquivocationSource

{ + fn clone(&self) -> Self { + Self { client: self.client.clone(), transaction_params: self.transaction_params.clone() } + } +} + +#[async_trait] +impl RelayClient for SubstrateEquivocationSource

{ + type Error = Error; + + async fn reconnect(&mut self) -> Result<(), Error> { + self.client.reconnect().await + } +} + +#[async_trait] +impl + SourceClientBase> for SubstrateEquivocationSource

+{ + type FinalityProofsStream = SubstrateFinalityProofsStream

; + + async fn finality_proofs(&self) -> Result { + finality_proofs::

(&self.client).await + } +} + +#[async_trait] +impl + SourceClient> for SubstrateEquivocationSource

+{ + type TransactionTracker = TransactionTracker>; + + async fn report_equivocation( + &self, + at: HashOf, + equivocation: EquivocationProofOf

, + ) -> Result { + let key_owner_proof = + P::FinalityEngine::generate_source_key_ownership_proof(&self.client, at, &equivocation) + .await?; + + let mortality = self.transaction_params.mortality; + let call = P::ReportEquivocationCallBuilder::build_report_equivocation_call( + equivocation, + key_owner_proof, + ); + self.client + .submit_and_watch_signed_extrinsic( + &self.transaction_params.signer, + move |best_block_id, transaction_nonce| { + Ok(UnsignedTransaction::new(call.into(), transaction_nonce) + .era(TransactionEra::new(best_block_id, mortality))) + }, + ) + .await + } +} diff --git a/bridges/relays/lib-substrate-relay/src/equivocation/target.rs b/bridges/relays/lib-substrate-relay/src/equivocation/target.rs new file mode 100644 index 0000000000000..6eee2ab91d45b --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/equivocation/target.rs @@ -0,0 +1,111 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Default generic implementation of equivocation source for basic Substrate client. + +use crate::{ + equivocation::{ + EquivocationDetectionPipelineAdapter, FinalityProoffOf, FinalityVerificationContextfOf, + SubstrateEquivocationDetectionPipeline, + }, + finality_base::{best_synced_header_id, engine::Engine}, +}; + +use async_trait::async_trait; +use bp_header_chain::HeaderFinalityInfo; +use bp_runtime::{BlockNumberOf, HashOf}; +use equivocation_detector::TargetClient; +use relay_substrate_client::{Client, Error}; +use relay_utils::relay_loop::Client as RelayClient; +use sp_runtime::traits::Header; +use std::marker::PhantomData; + +/// Substrate node as equivocation source. +pub struct SubstrateEquivocationTarget { + client: Client, + + _phantom: PhantomData

, +} + +impl SubstrateEquivocationTarget

{ + /// Create new instance of `SubstrateEquivocationTarget`. + pub fn new(client: Client) -> Self { + Self { client, _phantom: Default::default() } + } +} + +impl Clone for SubstrateEquivocationTarget

{ + fn clone(&self) -> Self { + Self { client: self.client.clone(), _phantom: Default::default() } + } +} + +#[async_trait] +impl RelayClient for SubstrateEquivocationTarget

{ + type Error = Error; + + async fn reconnect(&mut self) -> Result<(), Error> { + self.client.reconnect().await + } +} + +#[async_trait] +impl + TargetClient> for SubstrateEquivocationTarget

+{ + async fn best_finalized_header_number( + &self, + ) -> Result, Self::Error> { + self.client.best_finalized_header_number().await + } + + async fn best_synced_header_hash( + &self, + at: BlockNumberOf, + ) -> Result>, Self::Error> { + Ok(best_synced_header_id::( + &self.client, + self.client.header_by_number(at).await?.hash(), + ) + .await? + .map(|id| id.hash())) + } + + async fn finality_verification_context( + &self, + at: BlockNumberOf, + ) -> Result, Self::Error> { + P::FinalityEngine::finality_verification_context( + &self.client, + self.client.header_by_number(at).await?.hash(), + ) + .await + } + + async fn synced_headers_finality_info( + &self, + at: BlockNumberOf, + ) -> Result< + Vec, FinalityVerificationContextfOf

>>, + Self::Error, + > { + P::FinalityEngine::synced_headers_finality_info( + &self.client, + self.client.header_by_number(at).await?.hash(), + ) + .await + } +} diff --git a/bridges/relays/lib-substrate-relay/src/error.rs b/bridges/relays/lib-substrate-relay/src/error.rs new file mode 100644 index 0000000000000..2ebd9130f3912 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/error.rs @@ -0,0 +1,63 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Relay errors. + +use relay_substrate_client as client; +use sp_consensus_grandpa::AuthorityList; +use sp_runtime::traits::MaybeDisplay; +use std::fmt::Debug; +use thiserror::Error; + +/// Relay errors. +#[derive(Error, Debug)] +pub enum Error { + /// Failed to submit signed extrinsic from to the target chain. + #[error("Failed to submit {0} transaction: {1:?}")] + SubmitTransaction(&'static str, client::Error), + /// Failed subscribe to justification stream of the source chain. + #[error("Failed to subscribe to {0} justifications: {1:?}")] + Subscribe(&'static str, client::Error), + /// Failed subscribe to read justification from the source chain (client error). + #[error("Failed to read {0} justification from the stream: {1}")] + ReadJustification(&'static str, client::Error), + /// Failed subscribe to read justification from the source chain (stream ended). + #[error("Failed to read {0} justification from the stream: stream has ended unexpectedly")] + ReadJustificationStreamEnded(&'static str), + /// Failed subscribe to decode justification from the source chain. + #[error("Failed to decode {0} justification: {1:?}")] + DecodeJustification(&'static str, codec::Error), + /// GRANDPA authorities read from the source chain are invalid. + #[error("Read invalid {0} authorities set: {1:?}")] + ReadInvalidAuthorities(&'static str, AuthorityList), + /// Failed to guess initial GRANDPA authorities at the given header of the source chain. + #[error("Failed to guess initial {0} GRANDPA authorities set id: checked all possible ids in range [0; {1}]")] + GuessInitialAuthorities(&'static str, HeaderNumber), + /// Failed to retrieve GRANDPA authorities at the given header from the source chain. + #[error("Failed to retrive {0} GRANDPA authorities set at header {1}: {2:?}")] + RetrieveAuthorities(&'static str, Hash, client::Error), + /// Failed to decode GRANDPA authorities at the given header of the source chain. + #[error("Failed to decode {0} GRANDPA authorities set at header {1}: {2:?}")] + DecodeAuthorities(&'static str, Hash, codec::Error), + /// Failed to retrieve header by the hash from the source chain. + #[error("Failed to retrieve {0} header with hash {1}: {2:?}")] + RetrieveHeader(&'static str, Hash, client::Error), + /// Failed to submit signed extrinsic from to the target chain. + #[error( + "Failed to retrieve `is_initialized` flag of the with-{0} finality pallet at {1}: {2:?}" + )] + IsInitializedRetrieve(&'static str, &'static str, client::Error), +} diff --git a/bridges/relays/lib-substrate-relay/src/finality/initialize.rs b/bridges/relays/lib-substrate-relay/src/finality/initialize.rs new file mode 100644 index 0000000000000..5dde46c39dd67 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/finality/initialize.rs @@ -0,0 +1,163 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Initialize Substrate -> Substrate finality bridge. +//! +//! Initialization is a transaction that calls `initialize()` function of the +//! finality pallet (GRANDPA/BEEFY/...). This transaction brings initial header +//! and authorities set from source to target chain. The finality sync starts +//! with this header. + +use crate::{error::Error, finality_base::engine::Engine}; +use sp_core::Pair; + +use bp_runtime::HeaderIdOf; +use relay_substrate_client::{ + AccountKeyPairOf, Chain, ChainWithTransactions, Client, Error as SubstrateError, + UnsignedTransaction, +}; +use relay_utils::{TrackedTransactionStatus, TransactionTracker}; +use sp_runtime::traits::Header as HeaderT; + +/// Submit headers-bridge initialization transaction. +pub async fn initialize< + E: Engine, + SourceChain: Chain, + TargetChain: ChainWithTransactions, + F, +>( + source_client: Client, + target_client: Client, + target_signer: AccountKeyPairOf, + prepare_initialize_transaction: F, + dry_run: bool, +) where + F: FnOnce( + TargetChain::Nonce, + E::InitializationData, + ) -> Result, SubstrateError> + + Send + + 'static, + TargetChain::AccountId: From<::Public>, +{ + let result = do_initialize::( + source_client, + target_client, + target_signer, + prepare_initialize_transaction, + dry_run, + ) + .await; + + match result { + Ok(Some(tx_status)) => match tx_status { + TrackedTransactionStatus::Lost => { + log::error!( + target: "bridge", + "Failed to execute {}-headers bridge initialization transaction on {}: {:?}.", + SourceChain::NAME, + TargetChain::NAME, + tx_status + ) + }, + TrackedTransactionStatus::Finalized(_) => { + log::info!( + target: "bridge", + "Successfully executed {}-headers bridge initialization transaction on {}: {:?}.", + SourceChain::NAME, + TargetChain::NAME, + tx_status + ) + }, + }, + Ok(None) => (), + Err(err) => log::error!( + target: "bridge", + "Failed to submit {}-headers bridge initialization transaction to {}: {:?}", + SourceChain::NAME, + TargetChain::NAME, + err, + ), + } +} + +/// Craft and submit initialization transaction, returning any error that may occur. +async fn do_initialize< + E: Engine, + SourceChain: Chain, + TargetChain: ChainWithTransactions, + F, +>( + source_client: Client, + target_client: Client, + target_signer: AccountKeyPairOf, + prepare_initialize_transaction: F, + dry_run: bool, +) -> Result< + Option>>, + Error::Number>, +> +where + F: FnOnce( + TargetChain::Nonce, + E::InitializationData, + ) -> Result, SubstrateError> + + Send + + 'static, + TargetChain::AccountId: From<::Public>, +{ + let is_initialized = E::is_initialized(&target_client) + .await + .map_err(|e| Error::IsInitializedRetrieve(SourceChain::NAME, TargetChain::NAME, e))?; + if is_initialized { + log::info!( + target: "bridge", + "{}-headers bridge at {} is already initialized. Skipping", + SourceChain::NAME, + TargetChain::NAME, + ); + if !dry_run { + return Ok(None) + } + } + + let initialization_data = E::prepare_initialization_data(source_client).await?; + log::info!( + target: "bridge", + "Prepared initialization data for {}-headers bridge at {}: {:?}", + SourceChain::NAME, + TargetChain::NAME, + initialization_data, + ); + + let tx_status = target_client + .submit_and_watch_signed_extrinsic(&target_signer, move |_, transaction_nonce| { + let tx = prepare_initialize_transaction(transaction_nonce, initialization_data); + if dry_run { + Err(SubstrateError::Custom( + "Not submitting extrinsic in `dry-run` mode!".to_string(), + )) + } else { + tx + } + }) + .await + .map_err(|err| Error::SubmitTransaction(TargetChain::NAME, err))? + .wait() + .await; + + Ok(Some(tx_status)) +} diff --git a/bridges/relays/lib-substrate-relay/src/finality/mod.rs b/bridges/relays/lib-substrate-relay/src/finality/mod.rs new file mode 100644 index 0000000000000..206f628b143b8 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/finality/mod.rs @@ -0,0 +1,270 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Types and functions intended to ease adding of new Substrate -> Substrate +//! finality proofs synchronization pipelines. + +use crate::{ + finality::{source::SubstrateFinalitySource, target::SubstrateFinalityTarget}, + finality_base::{engine::Engine, SubstrateFinalityPipeline, SubstrateFinalityProof}, + TransactionParams, +}; + +use async_trait::async_trait; +use bp_header_chain::justification::{GrandpaJustification, JustificationVerificationContext}; +use finality_relay::{FinalityPipeline, FinalitySyncPipeline}; +use pallet_bridge_grandpa::{Call as BridgeGrandpaCall, Config as BridgeGrandpaConfig}; +use relay_substrate_client::{ + transaction_stall_timeout, AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain, + ChainWithTransactions, Client, HashOf, HeaderOf, SyncHeader, +}; +use relay_utils::metrics::MetricsParams; +use sp_core::Pair; +use std::{fmt::Debug, marker::PhantomData}; + +pub mod initialize; +pub mod source; +pub mod target; + +/// Default limit of recent finality proofs. +/// +/// Finality delay of 4096 blocks is unlikely to happen in practice in +/// Substrate+GRANDPA based chains (good to know). +pub(crate) const RECENT_FINALITY_PROOFS_LIMIT: usize = 4096; + +/// Convenience trait that adds bounds to `SubstrateFinalitySyncPipeline`. +pub trait BaseSubstrateFinalitySyncPipeline: + SubstrateFinalityPipeline +{ + /// Bounded `SubstrateFinalityPipeline::TargetChain`. + type BoundedTargetChain: ChainWithTransactions; + + /// Bounded `AccountIdOf`. + type BoundedTargetChainAccountId: From< as Pair>::Public> + + Send; +} + +impl BaseSubstrateFinalitySyncPipeline for T +where + T: SubstrateFinalityPipeline, + T::TargetChain: ChainWithTransactions, + AccountIdOf: From< as Pair>::Public>, +{ + type BoundedTargetChain = T::TargetChain; + type BoundedTargetChainAccountId = AccountIdOf; +} + +/// Substrate -> Substrate finality proofs synchronization pipeline. +#[async_trait] +pub trait SubstrateFinalitySyncPipeline: BaseSubstrateFinalitySyncPipeline { + /// How submit finality proof call is built? + type SubmitFinalityProofCallBuilder: SubmitFinalityProofCallBuilder; + + /// Add relay guards if required. + async fn start_relay_guards( + target_client: &Client, + enable_version_guard: bool, + ) -> relay_substrate_client::Result<()> { + if enable_version_guard { + relay_substrate_client::guard::abort_on_spec_version_change( + target_client.clone(), + target_client.simple_runtime_version().await?.spec_version, + ); + } + Ok(()) + } +} + +/// Adapter that allows all `SubstrateFinalitySyncPipeline` to act as `FinalitySyncPipeline`. +#[derive(Clone, Debug)] +pub struct FinalitySyncPipelineAdapter { + _phantom: PhantomData

, +} + +impl FinalityPipeline for FinalitySyncPipelineAdapter

{ + const SOURCE_NAME: &'static str = P::SourceChain::NAME; + const TARGET_NAME: &'static str = P::TargetChain::NAME; + + type Hash = HashOf; + type Number = BlockNumberOf; + type FinalityProof = SubstrateFinalityProof

; +} + +impl FinalitySyncPipeline for FinalitySyncPipelineAdapter

{ + type ConsensusLogReader = >::ConsensusLogReader; + type Header = SyncHeader>; +} + +/// Different ways of building `submit_finality_proof` calls. +pub trait SubmitFinalityProofCallBuilder { + /// Given source chain header, its finality proof and the current authority set id, build call + /// of `submit_finality_proof` function of bridge GRANDPA module at the target chain. + fn build_submit_finality_proof_call( + header: SyncHeader>, + proof: SubstrateFinalityProof

, + context: <

::FinalityEngine as Engine>::FinalityVerificationContext, + ) -> CallOf; +} + +/// Building `submit_finality_proof` call when you have direct access to the target +/// chain runtime. +pub struct DirectSubmitGrandpaFinalityProofCallBuilder { + _phantom: PhantomData<(P, R, I)>, +} + +impl SubmitFinalityProofCallBuilder

+ for DirectSubmitGrandpaFinalityProofCallBuilder +where + P: SubstrateFinalitySyncPipeline, + R: BridgeGrandpaConfig, + I: 'static, + R::BridgedChain: bp_runtime::Chain

>, + CallOf: From>, + P::FinalityEngine: Engine< + P::SourceChain, + FinalityProof = GrandpaJustification>, + FinalityVerificationContext = JustificationVerificationContext, + >, +{ + fn build_submit_finality_proof_call( + header: SyncHeader>, + proof: GrandpaJustification>, + _context: JustificationVerificationContext, + ) -> CallOf { + BridgeGrandpaCall::::submit_finality_proof { + finality_target: Box::new(header.into_inner()), + justification: proof, + } + .into() + } +} + +/// Macro that generates `SubmitFinalityProofCallBuilder` implementation for the case when +/// you only have an access to the mocked version of target chain runtime. In this case you +/// should provide "name" of the call variant for the bridge GRANDPA calls and the "name" of +/// the variant for the `submit_finality_proof` call within that first option. +#[rustfmt::skip] +#[macro_export] +macro_rules! generate_submit_finality_proof_call_builder { + ($pipeline:ident, $mocked_builder:ident, $bridge_grandpa:path, $submit_finality_proof:path) => { + pub struct $mocked_builder; + + impl $crate::finality::SubmitFinalityProofCallBuilder<$pipeline> + for $mocked_builder + { + fn build_submit_finality_proof_call( + header: relay_substrate_client::SyncHeader< + relay_substrate_client::HeaderOf< + <$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::SourceChain + > + >, + proof: bp_header_chain::justification::GrandpaJustification< + relay_substrate_client::HeaderOf< + <$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::SourceChain + > + >, + _context: bp_header_chain::justification::JustificationVerificationContext, + ) -> relay_substrate_client::CallOf< + <$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::TargetChain + > { + bp_runtime::paste::item! { + $bridge_grandpa($submit_finality_proof { + finality_target: Box::new(header.into_inner()), + justification: proof + }) + } + } + } + }; +} + +/// Macro that generates `SubmitFinalityProofCallBuilder` implementation for the case when +/// you only have an access to the mocked version of target chain runtime. In this case you +/// should provide "name" of the call variant for the bridge GRANDPA calls and the "name" of +/// the variant for the `submit_finality_proof_ex` call within that first option. +#[rustfmt::skip] +#[macro_export] +macro_rules! generate_submit_finality_proof_ex_call_builder { + ($pipeline:ident, $mocked_builder:ident, $bridge_grandpa:path, $submit_finality_proof:path) => { + pub struct $mocked_builder; + + impl $crate::finality::SubmitFinalityProofCallBuilder<$pipeline> + for $mocked_builder + { + fn build_submit_finality_proof_call( + header: relay_substrate_client::SyncHeader< + relay_substrate_client::HeaderOf< + <$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::SourceChain + > + >, + proof: bp_header_chain::justification::GrandpaJustification< + relay_substrate_client::HeaderOf< + <$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::SourceChain + > + >, + context: bp_header_chain::justification::JustificationVerificationContext, + ) -> relay_substrate_client::CallOf< + <$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::TargetChain + > { + bp_runtime::paste::item! { + $bridge_grandpa($submit_finality_proof { + finality_target: Box::new(header.into_inner()), + justification: proof, + current_set_id: context.authority_set_id + }) + } + } + } + }; +} + +/// Run Substrate-to-Substrate finality sync loop. +pub async fn run( + source_client: Client, + target_client: Client, + only_mandatory_headers: bool, + transaction_params: TransactionParams>, + metrics_params: MetricsParams, +) -> anyhow::Result<()> { + log::info!( + target: "bridge", + "Starting {} -> {} finality proof relay", + P::SourceChain::NAME, + P::TargetChain::NAME, + ); + + finality_relay::run( + SubstrateFinalitySource::

::new(source_client, None), + SubstrateFinalityTarget::

::new(target_client, transaction_params.clone()), + finality_relay::FinalitySyncParams { + tick: std::cmp::max( + P::SourceChain::AVERAGE_BLOCK_INTERVAL, + P::TargetChain::AVERAGE_BLOCK_INTERVAL, + ), + recent_finality_proofs_limit: RECENT_FINALITY_PROOFS_LIMIT, + stall_timeout: transaction_stall_timeout( + transaction_params.mortality, + P::TargetChain::AVERAGE_BLOCK_INTERVAL, + relay_utils::STALL_TIMEOUT, + ), + only_mandatory_headers, + }, + metrics_params, + futures::future::pending(), + ) + .await + .map_err(|e| anyhow::format_err!("{}", e)) +} diff --git a/bridges/relays/lib-substrate-relay/src/finality/source.rs b/bridges/relays/lib-substrate-relay/src/finality/source.rs new file mode 100644 index 0000000000000..c94af6108957a --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/finality/source.rs @@ -0,0 +1,259 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Default generic implementation of finality source for basic Substrate client. + +use crate::{ + finality::{FinalitySyncPipelineAdapter, SubstrateFinalitySyncPipeline}, + finality_base::{ + engine::Engine, finality_proofs, SubstrateFinalityProof, SubstrateFinalityProofsStream, + }, +}; + +use async_std::sync::{Arc, Mutex}; +use async_trait::async_trait; +use bp_header_chain::FinalityProof; +use codec::Decode; +use finality_relay::{SourceClient, SourceClientBase}; +use futures::{ + select, + stream::{try_unfold, Stream, StreamExt, TryStreamExt}, +}; +use num_traits::One; +use relay_substrate_client::{BlockNumberOf, BlockWithJustification, Client, Error, HeaderOf}; +use relay_utils::{relay_loop::Client as RelayClient, UniqueSaturatedInto}; + +/// Shared updatable reference to the maximal header number that we want to sync from the source. +pub type RequiredHeaderNumberRef = Arc::BlockNumber>>; + +/// Substrate node as finality source. +pub struct SubstrateFinalitySource { + client: Client, + maximal_header_number: Option>, +} + +impl SubstrateFinalitySource

{ + /// Create new headers source using given client. + pub fn new( + client: Client, + maximal_header_number: Option>, + ) -> Self { + SubstrateFinalitySource { client, maximal_header_number } + } + + /// Returns reference to the underlying RPC client. + pub fn client(&self) -> &Client { + &self.client + } + + /// Returns best finalized block number. + pub async fn on_chain_best_finalized_block_number( + &self, + ) -> Result, Error> { + // we **CAN** continue to relay finality proofs if source node is out of sync, because + // target node may be missing proofs that are already available at the source + self.client.best_finalized_header_number().await + } + + /// Return header and its justification of the given block or its descendant that + /// has a GRANDPA justification. + /// + /// This method is optimized for cases when `block_number` is close to the best finalized + /// chain block. + pub async fn prove_block_finality( + &self, + block_number: BlockNumberOf, + ) -> Result< + (relay_substrate_client::SyncHeader>, SubstrateFinalityProof

), + Error, + > { + // first, subscribe to proofs + let next_persistent_proof = + self.persistent_proofs_stream(block_number + One::one()).await?.fuse(); + let next_ephemeral_proof = self.ephemeral_proofs_stream(block_number).await?.fuse(); + + // in perfect world we'll need to return justfication for the requested `block_number` + let (header, maybe_proof) = self.header_and_finality_proof(block_number).await?; + if let Some(proof) = maybe_proof { + return Ok((header, proof)) + } + + // otherwise we don't care which header to return, so let's select first + futures::pin_mut!(next_persistent_proof, next_ephemeral_proof); + loop { + select! { + maybe_header_and_proof = next_persistent_proof.next() => match maybe_header_and_proof { + Some(header_and_proof) => return header_and_proof, + None => continue, + }, + maybe_header_and_proof = next_ephemeral_proof.next() => match maybe_header_and_proof { + Some(header_and_proof) => return header_and_proof, + None => continue, + }, + complete => return Err(Error::FinalityProofNotFound(block_number.unique_saturated_into())) + } + } + } + + /// Returns stream of headers and their persistent proofs, starting from given block. + async fn persistent_proofs_stream( + &self, + block_number: BlockNumberOf, + ) -> Result< + impl Stream< + Item = Result< + ( + relay_substrate_client::SyncHeader>, + SubstrateFinalityProof

, + ), + Error, + >, + >, + Error, + > { + let client = self.client.clone(); + let best_finalized_block_number = client.best_finalized_header_number().await?; + Ok(try_unfold((client, block_number), move |(client, current_block_number)| async move { + // if we've passed the `best_finalized_block_number`, we no longer need persistent + // justifications + if current_block_number > best_finalized_block_number { + return Ok(None) + } + + let (header, maybe_proof) = + header_and_finality_proof::

(&client, current_block_number).await?; + let next_block_number = current_block_number + One::one(); + let next_state = (client, next_block_number); + + Ok(Some((maybe_proof.map(|proof| (header, proof)), next_state))) + }) + .try_filter_map(|maybe_result| async { Ok(maybe_result) })) + } + + /// Returns stream of headers and their ephemeral proofs, starting from given block. + async fn ephemeral_proofs_stream( + &self, + block_number: BlockNumberOf, + ) -> Result< + impl Stream< + Item = Result< + ( + relay_substrate_client::SyncHeader>, + SubstrateFinalityProof

, + ), + Error, + >, + >, + Error, + > { + let client = self.client.clone(); + Ok(self.finality_proofs().await?.map(Ok).try_filter_map(move |proof| { + let client = client.clone(); + async move { + if proof.target_header_number() < block_number { + return Ok(None) + } + + let header = client.header_by_number(proof.target_header_number()).await?; + Ok(Some((header.into(), proof))) + } + })) + } +} + +impl Clone for SubstrateFinalitySource

{ + fn clone(&self) -> Self { + SubstrateFinalitySource { + client: self.client.clone(), + maximal_header_number: self.maximal_header_number.clone(), + } + } +} + +#[async_trait] +impl RelayClient for SubstrateFinalitySource

{ + type Error = Error; + + async fn reconnect(&mut self) -> Result<(), Error> { + self.client.reconnect().await + } +} + +#[async_trait] +impl SourceClientBase> + for SubstrateFinalitySource

+{ + type FinalityProofsStream = SubstrateFinalityProofsStream

; + + async fn finality_proofs(&self) -> Result { + finality_proofs::

(&self.client).await + } +} + +#[async_trait] +impl SourceClient> + for SubstrateFinalitySource

+{ + async fn best_finalized_block_number(&self) -> Result, Error> { + let mut finalized_header_number = self.on_chain_best_finalized_block_number().await?; + // never return block number larger than requested. This way we'll never sync headers + // past `maximal_header_number` + if let Some(ref maximal_header_number) = self.maximal_header_number { + let maximal_header_number = *maximal_header_number.lock().await; + if finalized_header_number > maximal_header_number { + finalized_header_number = maximal_header_number; + } + } + Ok(finalized_header_number) + } + + async fn header_and_finality_proof( + &self, + number: BlockNumberOf, + ) -> Result< + ( + relay_substrate_client::SyncHeader>, + Option>, + ), + Error, + > { + header_and_finality_proof::

(&self.client, number).await + } +} + +async fn header_and_finality_proof( + client: &Client, + number: BlockNumberOf, +) -> Result< + ( + relay_substrate_client::SyncHeader>, + Option>, + ), + Error, +> { + let header_hash = client.block_hash_by_number(number).await?; + let signed_block = client.get_block(Some(header_hash)).await?; + + let justification = signed_block + .justification(P::FinalityEngine::ID) + .map(|raw_justification| { + SubstrateFinalityProof::

::decode(&mut raw_justification.as_slice()) + }) + .transpose() + .map_err(Error::ResponseParseFailed)?; + + Ok((signed_block.header().into(), justification)) +} diff --git a/bridges/relays/lib-substrate-relay/src/finality/target.rs b/bridges/relays/lib-substrate-relay/src/finality/target.rs new file mode 100644 index 0000000000000..18464d523f4f6 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/finality/target.rs @@ -0,0 +1,130 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Substrate client as Substrate finality proof target. + +use crate::{ + finality::{ + FinalitySyncPipelineAdapter, SubmitFinalityProofCallBuilder, SubstrateFinalitySyncPipeline, + }, + finality_base::{best_synced_header_id, engine::Engine, SubstrateFinalityProof}, + TransactionParams, +}; + +use async_trait::async_trait; +use finality_relay::TargetClient; +use relay_substrate_client::{ + AccountKeyPairOf, Client, Error, HeaderIdOf, HeaderOf, SyncHeader, TransactionEra, + TransactionTracker, UnsignedTransaction, +}; +use relay_utils::relay_loop::Client as RelayClient; +use sp_runtime::traits::Header; + +/// Substrate client as Substrate finality target. +pub struct SubstrateFinalityTarget { + client: Client, + transaction_params: TransactionParams>, +} + +impl SubstrateFinalityTarget

{ + /// Create new Substrate headers target. + pub fn new( + client: Client, + transaction_params: TransactionParams>, + ) -> Self { + SubstrateFinalityTarget { client, transaction_params } + } + + /// Ensure that the bridge pallet at target chain is active. + pub async fn ensure_pallet_active(&self) -> Result<(), Error> { + let is_halted = P::FinalityEngine::is_halted(&self.client).await?; + if is_halted { + return Err(Error::BridgePalletIsHalted) + } + + let is_initialized = P::FinalityEngine::is_initialized(&self.client).await?; + if !is_initialized { + return Err(Error::BridgePalletIsNotInitialized) + } + + Ok(()) + } +} + +impl Clone for SubstrateFinalityTarget

{ + fn clone(&self) -> Self { + SubstrateFinalityTarget { + client: self.client.clone(), + transaction_params: self.transaction_params.clone(), + } + } +} + +#[async_trait] +impl RelayClient for SubstrateFinalityTarget

{ + type Error = Error; + + async fn reconnect(&mut self) -> Result<(), Error> { + self.client.reconnect().await + } +} + +#[async_trait] +impl TargetClient> + for SubstrateFinalityTarget

+{ + type TransactionTracker = TransactionTracker>; + + async fn best_finalized_source_block_id(&self) -> Result, Error> { + // we can't continue to relay finality if target node is out of sync, because + // it may have already received (some of) headers that we're going to relay + self.client.ensure_synced().await?; + // we can't relay finality if bridge pallet at target chain is halted + self.ensure_pallet_active().await?; + + Ok(best_synced_header_id::( + &self.client, + self.client.best_header().await?.hash(), + ) + .await? + .ok_or(Error::BridgePalletIsNotInitialized)?) + } + + async fn submit_finality_proof( + &self, + header: SyncHeader>, + mut proof: SubstrateFinalityProof

, + ) -> Result { + // verify and runtime module at target chain may require optimized finality proof + let context = + P::FinalityEngine::verify_and_optimize_proof(&self.client, &header, &mut proof).await?; + + // now we may submit optimized finality proof + let mortality = self.transaction_params.mortality; + let call = P::SubmitFinalityProofCallBuilder::build_submit_finality_proof_call( + header, proof, context, + ); + self.client + .submit_and_watch_signed_extrinsic( + &self.transaction_params.signer, + move |best_block_id, transaction_nonce| { + Ok(UnsignedTransaction::new(call.into(), transaction_nonce) + .era(TransactionEra::new(best_block_id, mortality))) + }, + ) + .await + } +} diff --git a/bridges/relays/lib-substrate-relay/src/finality_base/engine.rs b/bridges/relays/lib-substrate-relay/src/finality_base/engine.rs new file mode 100644 index 0000000000000..e517b0fd9b9ab --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/finality_base/engine.rs @@ -0,0 +1,464 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Support of different finality engines, available in Substrate. + +use crate::error::Error; +use async_trait::async_trait; +use bp_header_chain::{ + justification::{ + verify_and_optimize_justification, GrandpaEquivocationsFinder, GrandpaJustification, + JustificationVerificationContext, + }, + max_expected_submit_finality_proof_arguments_size, AuthoritySet, ConsensusLogReader, + FinalityProof, FindEquivocations, GrandpaConsensusLogReader, HeaderFinalityInfo, + HeaderGrandpaInfo, StoredHeaderGrandpaInfo, +}; +use bp_runtime::{BasicOperatingMode, HeaderIdProvider, OperatingMode}; +use codec::{Decode, Encode}; +use num_traits::{One, Zero}; +use relay_substrate_client::{ + BlockNumberOf, Chain, ChainWithGrandpa, Client, Error as SubstrateError, HashOf, HeaderOf, + Subscription, SubstrateFinalityClient, SubstrateGrandpaFinalityClient, +}; +use sp_consensus_grandpa::{AuthorityList as GrandpaAuthoritiesSet, GRANDPA_ENGINE_ID}; +use sp_core::{storage::StorageKey, Bytes}; +use sp_runtime::{scale_info::TypeInfo, traits::Header, ConsensusEngineId, SaturatedConversion}; +use std::{fmt::Debug, marker::PhantomData}; + +/// Result of checking maximal expected call size. +pub enum MaxExpectedCallSizeCheck { + /// Size is ok and call will be refunded. + Ok, + /// The call size exceeds the maximal expected and relayer will only get partial refund. + Exceeds { + /// Actual call size. + call_size: u32, + /// Maximal expected call size. + max_call_size: u32, + }, +} + +/// Finality engine, used by the Substrate chain. +#[async_trait] +pub trait Engine: Send { + /// Unique consensus engine identifier. + const ID: ConsensusEngineId; + /// A reader that can extract the consensus log from the header digest and interpret it. + type ConsensusLogReader: ConsensusLogReader; + /// Type of Finality RPC client used by this engine. + type FinalityClient: SubstrateFinalityClient; + /// Type of finality proofs, used by consensus engine. + type FinalityProof: FinalityProof, BlockNumberOf> + Decode + Encode; + /// The context needed for verifying finality proofs. + type FinalityVerificationContext: Debug + Send; + /// The type of the equivocation proof used by the consensus engine. + type EquivocationProof: Clone + Debug + Send + Sync; + /// The equivocations finder. + type EquivocationsFinder: FindEquivocations< + Self::FinalityProof, + Self::FinalityVerificationContext, + Self::EquivocationProof, + >; + /// The type of the key owner proof used by the consensus engine. + type KeyOwnerProof: Send; + /// Type of bridge pallet initialization data. + type InitializationData: Debug + Send + Sync + 'static; + /// Type of bridge pallet operating mode. + type OperatingMode: OperatingMode + 'static; + + /// Returns storage at the bridged (target) chain that corresponds to some value that is + /// missing from the storage until bridge pallet is initialized. + /// + /// Note that we don't care about type of the value - just if it present or not. + fn is_initialized_key() -> StorageKey; + + /// Returns `Ok(true)` if finality pallet at the bridged chain has already been initialized. + async fn is_initialized( + target_client: &Client, + ) -> Result { + Ok(target_client + .raw_storage_value(Self::is_initialized_key(), None) + .await? + .is_some()) + } + + /// Returns storage key at the bridged (target) chain that corresponds to the variable + /// that holds the operating mode of the pallet. + fn pallet_operating_mode_key() -> StorageKey; + + /// Returns `Ok(true)` if finality pallet at the bridged chain is halted. + async fn is_halted( + target_client: &Client, + ) -> Result { + Ok(target_client + .storage_value::(Self::pallet_operating_mode_key(), None) + .await? + .map(|operating_mode| operating_mode.is_halted()) + .unwrap_or(false)) + } + + /// A method to subscribe to encoded finality proofs, given source client. + async fn source_finality_proofs( + source_client: &Client, + ) -> Result, SubstrateError> { + source_client.subscribe_finality_justifications::().await + } + + /// Verify and optimize finality proof before sending it to the target node. + /// + /// Apart from optimization, we expect this method to perform all required checks + /// that the `header` and `proof` are valid at the current state of the target chain. + async fn verify_and_optimize_proof( + target_client: &Client, + header: &C::Header, + proof: &mut Self::FinalityProof, + ) -> Result; + + /// Checks whether the given `header` and its finality `proof` fit the maximal expected + /// call size limit. If result is `MaxExpectedCallSizeCheck::Exceeds { .. }`, this + /// submission won't be fully refunded and relayer will spend its own funds on that. + fn check_max_expected_call_size( + header: &C::Header, + proof: &Self::FinalityProof, + ) -> MaxExpectedCallSizeCheck; + + /// Prepare initialization data for the finality bridge pallet. + async fn prepare_initialization_data( + client: Client, + ) -> Result, BlockNumberOf>>; + + /// Get the context needed for validating a finality proof. + async fn finality_verification_context( + target_client: &Client, + at: HashOf, + ) -> Result; + + /// Returns the finality info associated to the source headers synced with the target + /// at the provided block. + async fn synced_headers_finality_info( + target_client: &Client, + at: TargetChain::Hash, + ) -> Result< + Vec>, + SubstrateError, + >; + + /// Generate key ownership proof for the provided equivocation. + async fn generate_source_key_ownership_proof( + source_client: &Client, + at: C::Hash, + equivocation: &Self::EquivocationProof, + ) -> Result; +} + +/// GRANDPA finality engine. +pub struct Grandpa(PhantomData); + +impl Grandpa { + /// Read header by hash from the source client. + async fn source_header( + source_client: &Client, + header_hash: C::Hash, + ) -> Result, BlockNumberOf>> { + source_client + .header_by_hash(header_hash) + .await + .map_err(|err| Error::RetrieveHeader(C::NAME, header_hash, err)) + } + + /// Read GRANDPA authorities set at given header. + async fn source_authorities_set( + source_client: &Client, + header_hash: C::Hash, + ) -> Result, BlockNumberOf>> { + let raw_authorities_set = source_client + .grandpa_authorities_set(header_hash) + .await + .map_err(|err| Error::RetrieveAuthorities(C::NAME, header_hash, err))?; + GrandpaAuthoritiesSet::decode(&mut &raw_authorities_set[..]) + .map_err(|err| Error::DecodeAuthorities(C::NAME, header_hash, err)) + } +} + +#[async_trait] +impl Engine for Grandpa { + const ID: ConsensusEngineId = GRANDPA_ENGINE_ID; + type ConsensusLogReader = GrandpaConsensusLogReader<::Number>; + type FinalityClient = SubstrateGrandpaFinalityClient; + type FinalityProof = GrandpaJustification>; + type FinalityVerificationContext = JustificationVerificationContext; + type EquivocationProof = sp_consensus_grandpa::EquivocationProof, BlockNumberOf>; + type EquivocationsFinder = GrandpaEquivocationsFinder; + type KeyOwnerProof = C::KeyOwnerProof; + type InitializationData = bp_header_chain::InitializationData; + type OperatingMode = BasicOperatingMode; + + fn is_initialized_key() -> StorageKey { + bp_header_chain::storage_keys::best_finalized_key(C::WITH_CHAIN_GRANDPA_PALLET_NAME) + } + + fn pallet_operating_mode_key() -> StorageKey { + bp_header_chain::storage_keys::pallet_operating_mode_key(C::WITH_CHAIN_GRANDPA_PALLET_NAME) + } + + async fn verify_and_optimize_proof( + target_client: &Client, + header: &C::Header, + proof: &mut Self::FinalityProof, + ) -> Result { + let verification_context = Grandpa::::finality_verification_context( + target_client, + target_client.best_header().await?.hash(), + ) + .await?; + // we're risking with race here - we have decided to submit justification some time ago and + // actual authorities set (which we have read now) may have changed, so this + // `optimize_justification` may fail. But if target chain is configured properly, it'll fail + // anyway, after we submit transaction and failing earlier is better. So - it is fine + verify_and_optimize_justification( + (header.hash(), *header.number()), + &verification_context, + proof, + ) + .map(|_| verification_context) + .map_err(|e| { + SubstrateError::Custom(format!( + "Failed to optimize {} GRANDPA jutification for header {:?}: {:?}", + C::NAME, + header.id(), + e, + )) + }) + } + + fn check_max_expected_call_size( + header: &C::Header, + proof: &Self::FinalityProof, + ) -> MaxExpectedCallSizeCheck { + let is_mandatory = Self::ConsensusLogReader::schedules_authorities_change(header.digest()); + let call_size: u32 = + header.encoded_size().saturating_add(proof.encoded_size()).saturated_into(); + let max_call_size = max_expected_submit_finality_proof_arguments_size::( + is_mandatory, + proof.commit.precommits.len().saturated_into(), + ); + if call_size > max_call_size { + MaxExpectedCallSizeCheck::Exceeds { call_size, max_call_size } + } else { + MaxExpectedCallSizeCheck::Ok + } + } + + /// Prepare initialization data for the GRANDPA verifier pallet. + async fn prepare_initialization_data( + source_client: Client, + ) -> Result, BlockNumberOf>> { + // In ideal world we just need to get best finalized header and then to read GRANDPA + // authorities set (`pallet_grandpa::CurrentSetId` + `GrandpaApi::grandpa_authorities()`) at + // this header. + // + // But now there are problems with this approach - `CurrentSetId` may return invalid value. + // So here we're waiting for the next justification, read the authorities set and then try + // to figure out the set id with bruteforce. + let justifications = Self::source_finality_proofs(&source_client) + .await + .map_err(|err| Error::Subscribe(C::NAME, err))?; + // Read next justification - the header that it finalizes will be used as initial header. + let justification = justifications + .next() + .await + .map_err(|e| Error::ReadJustification(C::NAME, e)) + .and_then(|justification| { + justification.ok_or(Error::ReadJustificationStreamEnded(C::NAME)) + })?; + + // Read initial header. + let justification: GrandpaJustification = + Decode::decode(&mut &justification.0[..]) + .map_err(|err| Error::DecodeJustification(C::NAME, err))?; + + let (initial_header_hash, initial_header_number) = + (justification.commit.target_hash, justification.commit.target_number); + + let initial_header = Self::source_header(&source_client, initial_header_hash).await?; + log::trace!(target: "bridge", "Selected {} initial header: {}/{}", + C::NAME, + initial_header_number, + initial_header_hash, + ); + + // Read GRANDPA authorities set at initial header. + let initial_authorities_set = + Self::source_authorities_set(&source_client, initial_header_hash).await?; + log::trace!(target: "bridge", "Selected {} initial authorities set: {:?}", + C::NAME, + initial_authorities_set, + ); + + // If initial header changes the GRANDPA authorities set, then we need previous authorities + // to verify justification. + let mut authorities_for_verification = initial_authorities_set.clone(); + let scheduled_change = GrandpaConsensusLogReader::>::find_scheduled_change( + initial_header.digest(), + ); + assert!( + scheduled_change.as_ref().map(|c| c.delay.is_zero()).unwrap_or(true), + "GRANDPA authorities change at {} scheduled to happen in {:?} blocks. We expect\ + regular change to have zero delay", + initial_header_hash, + scheduled_change.as_ref().map(|c| c.delay), + ); + let schedules_change = scheduled_change.is_some(); + if schedules_change { + authorities_for_verification = + Self::source_authorities_set(&source_client, *initial_header.parent_hash()).await?; + log::trace!( + target: "bridge", + "Selected {} header is scheduling GRANDPA authorities set changes. Using previous set: {:?}", + C::NAME, + authorities_for_verification, + ); + } + + // Now let's try to guess authorities set id by verifying justification. + let mut initial_authorities_set_id = 0; + let mut min_possible_block_number = C::BlockNumber::zero(); + loop { + log::trace!( + target: "bridge", "Trying {} GRANDPA authorities set id: {}", + C::NAME, + initial_authorities_set_id, + ); + + let is_valid_set_id = verify_and_optimize_justification( + (initial_header_hash, initial_header_number), + &AuthoritySet { + authorities: authorities_for_verification.clone(), + set_id: initial_authorities_set_id, + } + .try_into() + .map_err(|_| { + Error::ReadInvalidAuthorities(C::NAME, authorities_for_verification.clone()) + })?, + &mut justification.clone(), + ) + .is_ok(); + + if is_valid_set_id { + break + } + + initial_authorities_set_id += 1; + min_possible_block_number += One::one(); + if min_possible_block_number > initial_header_number { + // 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(Error::GuessInitialAuthorities(C::NAME, initial_header_number)) + } + } + + Ok(bp_header_chain::InitializationData { + header: Box::new(initial_header), + authority_list: initial_authorities_set, + set_id: if schedules_change { + initial_authorities_set_id + 1 + } else { + initial_authorities_set_id + }, + operating_mode: BasicOperatingMode::Normal, + }) + } + + async fn finality_verification_context( + target_client: &Client, + at: HashOf, + ) -> Result { + let current_authority_set_key = bp_header_chain::storage_keys::current_authority_set_key( + C::WITH_CHAIN_GRANDPA_PALLET_NAME, + ); + let authority_set: AuthoritySet = target_client + .storage_value(current_authority_set_key, Some(at)) + .await? + .map(Ok) + .unwrap_or(Err(SubstrateError::Custom(format!( + "{} `CurrentAuthoritySet` is missing from the {} storage", + C::NAME, + TargetChain::NAME, + ))))?; + + authority_set.try_into().map_err(|e| { + SubstrateError::Custom(format!( + "{} `CurrentAuthoritySet` from the {} storage is invalid: {e:?}", + C::NAME, + TargetChain::NAME, + )) + }) + } + + async fn synced_headers_finality_info( + target_client: &Client, + at: TargetChain::Hash, + ) -> Result>>, SubstrateError> { + let stored_headers_grandpa_info: Vec>> = target_client + .typed_state_call(C::SYNCED_HEADERS_GRANDPA_INFO_METHOD.to_string(), (), Some(at)) + .await?; + + let mut headers_grandpa_info = vec![]; + for stored_header_grandpa_info in stored_headers_grandpa_info { + headers_grandpa_info.push(stored_header_grandpa_info.try_into().map_err(|e| { + SubstrateError::Custom(format!( + "{} `AuthoritySet` synced to {} is invalid: {e:?} ", + C::NAME, + TargetChain::NAME, + )) + })?); + } + + Ok(headers_grandpa_info) + } + + async fn generate_source_key_ownership_proof( + source_client: &Client, + at: C::Hash, + equivocation: &Self::EquivocationProof, + ) -> Result { + let set_id = equivocation.set_id(); + let offender = equivocation.offender(); + + let opaque_key_owner_proof = source_client + .generate_grandpa_key_ownership_proof(at, set_id, offender.clone()) + .await? + .ok_or(SubstrateError::Custom(format!( + "Couldn't get GRANDPA key ownership proof from {} at block: {at} \ + for offender: {:?}, set_id: {set_id} ", + C::NAME, + offender.clone(), + )))?; + + let key_owner_proof = + opaque_key_owner_proof.decode().ok_or(SubstrateError::Custom(format!( + "Couldn't decode GRANDPA `OpaqueKeyOwnnershipProof` from {} at block: {at} + to `{:?}` for offender: {:?}, set_id: {set_id}, at block: {at}", + C::NAME, + ::type_info().path, + offender.clone(), + )))?; + + Ok(key_owner_proof) + } +} diff --git a/bridges/relays/lib-substrate-relay/src/finality_base/mod.rs b/bridges/relays/lib-substrate-relay/src/finality_base/mod.rs new file mode 100644 index 0000000000000..825960b1b3ef2 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/finality_base/mod.rs @@ -0,0 +1,107 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Types and functions intended to ease adding of new Substrate -> Substrate +//! finality pipelines. + +pub mod engine; + +use crate::finality_base::engine::Engine; + +use async_trait::async_trait; +use bp_runtime::{HashOf, HeaderIdOf}; +use codec::Decode; +use futures::{stream::unfold, Stream, StreamExt}; +use relay_substrate_client::{Chain, Client, Error}; +use std::{fmt::Debug, pin::Pin}; + +/// Substrate -> Substrate finality related pipeline. +#[async_trait] +pub trait SubstrateFinalityPipeline: 'static + Clone + Debug + Send + Sync { + /// Headers of this chain are submitted to the `TargetChain`. + type SourceChain: Chain; + /// Headers of the `SourceChain` are submitted to this chain. + type TargetChain: Chain; + /// Finality engine. + type FinalityEngine: Engine; +} + +/// Substrate finality proof. Specific to the used `FinalityEngine`. +pub type SubstrateFinalityProof

= <

::FinalityEngine as Engine< +

::SourceChain, +>>::FinalityProof; + +/// Substrate finality proofs stream. +pub type SubstrateFinalityProofsStream

= + Pin> + Send>>; + +/// Subscribe to new finality proofs. +pub async fn finality_proofs( + client: &Client, +) -> Result, Error> { + Ok(unfold( + P::FinalityEngine::source_finality_proofs(client).await?, + move |subscription| async move { + loop { + let log_error = |err| { + log::error!( + target: "bridge", + "Failed to read justification target from the {} justifications stream: {:?}", + P::SourceChain::NAME, + err, + ); + }; + + let next_justification = + subscription.next().await.map_err(|err| log_error(err.to_string())).ok()??; + + let decoded_justification = + >::FinalityProof::decode( + &mut &next_justification[..], + ); + + let justification = match decoded_justification { + Ok(j) => j, + Err(err) => { + log_error(format!("decode failed with error {err:?}")); + continue + }, + }; + + return Some((justification, subscription)) + } + }, + ) + .boxed()) +} + +/// Get the id of the best `SourceChain` header known to the `TargetChain` at the provided +/// target block using the exposed runtime API method. +/// +/// The runtime API method should be `FinalityApi::best_finalized()`. +pub async fn best_synced_header_id( + target_client: &Client, + at: HashOf, +) -> Result>, Error> +where + SourceChain: Chain, + TargetChain: Chain, +{ + // now let's read id of best finalized peer header at our best finalized block + target_client + .typed_state_call(SourceChain::BEST_FINALIZED_HEADER_ID_METHOD.into(), (), Some(at)) + .await +} diff --git a/bridges/relays/lib-substrate-relay/src/lib.rs b/bridges/relays/lib-substrate-relay/src/lib.rs new file mode 100644 index 0000000000000..b90453ae0db2d --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/lib.rs @@ -0,0 +1,129 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! The library of substrate relay. contains some public codes to provide to substrate relay. + +#![warn(missing_docs)] + +use relay_substrate_client::{Chain, ChainWithUtilityPallet, UtilityPallet}; + +use std::marker::PhantomData; + +pub mod cli; +pub mod equivocation; +pub mod error; +pub mod finality; +pub mod finality_base; +pub mod messages_lane; +pub mod messages_metrics; +pub mod messages_source; +pub mod messages_target; +pub mod on_demand; +pub mod parachains; + +/// Transaction creation parameters. +#[derive(Clone, Debug)] +pub struct TransactionParams { + /// Transactions author. + pub signer: TS, + /// Transactions mortality. + pub mortality: Option, +} + +/// Tagged relay account, which balance may be exposed as metrics by the relay. +#[derive(Clone, Debug)] +pub enum TaggedAccount { + /// Account, used to sign message (also headers and parachains) relay transactions from given + /// bridged chain. + Messages { + /// Account id. + id: AccountId, + /// Name of the bridged chain, which sends us messages or delivery confirmations. + bridged_chain: String, + }, +} + +impl TaggedAccount { + /// Returns reference to the account id. + pub fn id(&self) -> &AccountId { + match *self { + TaggedAccount::Messages { ref id, .. } => id, + } + } + + /// Returns stringified account tag. + pub fn tag(&self) -> String { + match *self { + TaggedAccount::Messages { ref bridged_chain, .. } => { + format!("{bridged_chain}Messages") + }, + } + } +} + +/// Batch call builder. +pub trait BatchCallBuilder: Clone + Send + Sync { + /// Create batch call from given calls vector. + fn build_batch_call(&self, _calls: Vec) -> Call; +} + +/// Batch call builder constructor. +pub trait BatchCallBuilderConstructor: Clone { + /// Call builder, used by this constructor. + type CallBuilder: BatchCallBuilder; + /// Create a new instance of a batch call builder. + fn new_builder() -> Option; +} + +/// Batch call builder based on `pallet-utility`. +#[derive(Clone)] +pub struct UtilityPalletBatchCallBuilder(PhantomData); + +impl BatchCallBuilder for UtilityPalletBatchCallBuilder +where + C: ChainWithUtilityPallet, +{ + fn build_batch_call(&self, calls: Vec) -> C::Call { + C::UtilityPallet::build_batch_call(calls) + } +} + +impl BatchCallBuilderConstructor for UtilityPalletBatchCallBuilder +where + C: ChainWithUtilityPallet, +{ + type CallBuilder = Self; + + fn new_builder() -> Option { + Some(Self(Default::default())) + } +} + +// A `BatchCallBuilderConstructor` that always returns `None`. +impl BatchCallBuilderConstructor for () { + type CallBuilder = (); + fn new_builder() -> Option { + None + } +} + +// Dummy `BatchCallBuilder` implementation that must never be used outside +// of the `impl BatchCallBuilderConstructor for ()` code. +impl BatchCallBuilder for () { + fn build_batch_call(&self, _calls: Vec) -> Call { + unreachable!("never called, because ()::new_builder() returns None; qed") + } +} diff --git a/bridges/relays/lib-substrate-relay/src/messages_lane.rs b/bridges/relays/lib-substrate-relay/src/messages_lane.rs new file mode 100644 index 0000000000000..abeab8c1402d6 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/messages_lane.rs @@ -0,0 +1,587 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Tools for supporting message lanes between two Substrate-based chains. + +use crate::{ + messages_source::{SubstrateMessagesProof, SubstrateMessagesSource}, + messages_target::{SubstrateMessagesDeliveryProof, SubstrateMessagesTarget}, + on_demand::OnDemandRelay, + BatchCallBuilder, BatchCallBuilderConstructor, TransactionParams, +}; + +use async_std::sync::Arc; +use bp_messages::{ChainWithMessages as _, LaneId, MessageNonce}; +use bp_runtime::{ + AccountIdOf, Chain as _, EncodedOrDecodedCall, HeaderIdOf, TransactionEra, WeightExtraOps, +}; +use bridge_runtime_common::messages::{ + source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof, +}; +use codec::Encode; +use frame_support::{dispatch::GetDispatchInfo, weights::Weight}; +use messages_relay::{message_lane::MessageLane, message_lane_loop::BatchTransaction}; +use pallet_bridge_messages::{Call as BridgeMessagesCall, Config as BridgeMessagesConfig}; +use relay_substrate_client::{ + transaction_stall_timeout, AccountKeyPairOf, BalanceOf, BlockNumberOf, CallOf, Chain, + ChainWithMessages, ChainWithTransactions, Client, Error as SubstrateError, HashOf, SignParam, + UnsignedTransaction, +}; +use relay_utils::{ + metrics::{GlobalMetrics, MetricsParams, StandaloneMetric}, + STALL_TIMEOUT, +}; +use sp_core::Pair; +use sp_runtime::traits::Zero; +use std::{convert::TryFrom, fmt::Debug, marker::PhantomData}; + +/// Substrate -> Substrate messages synchronization pipeline. +pub trait SubstrateMessageLane: 'static + Clone + Debug + Send + Sync { + /// Messages of this chain are relayed to the `TargetChain`. + type SourceChain: ChainWithMessages + ChainWithTransactions; + /// Messages from the `SourceChain` are dispatched on this chain. + type TargetChain: ChainWithMessages + ChainWithTransactions; + + /// How receive messages proof call is built? + type ReceiveMessagesProofCallBuilder: ReceiveMessagesProofCallBuilder; + /// How receive messages delivery proof call is built? + type ReceiveMessagesDeliveryProofCallBuilder: ReceiveMessagesDeliveryProofCallBuilder; + + /// How batch calls are built at the source chain? + type SourceBatchCallBuilder: BatchCallBuilderConstructor>; + /// How batch calls are built at the target chain? + type TargetBatchCallBuilder: BatchCallBuilderConstructor>; +} + +/// Adapter that allows all `SubstrateMessageLane` to act as `MessageLane`. +#[derive(Clone, Debug)] +pub struct MessageLaneAdapter { + _phantom: PhantomData

, +} + +impl MessageLane for MessageLaneAdapter

{ + const SOURCE_NAME: &'static str = P::SourceChain::NAME; + const TARGET_NAME: &'static str = P::TargetChain::NAME; + + type MessagesProof = SubstrateMessagesProof; + type MessagesReceivingProof = SubstrateMessagesDeliveryProof; + + type SourceChainBalance = BalanceOf; + type SourceHeaderNumber = BlockNumberOf; + type SourceHeaderHash = HashOf; + + type TargetHeaderNumber = BlockNumberOf; + type TargetHeaderHash = HashOf; +} + +/// Substrate <-> Substrate messages relay parameters. +pub struct MessagesRelayParams { + /// Messages source client. + pub source_client: Client, + /// Source transaction params. + pub source_transaction_params: TransactionParams>, + /// Messages target client. + pub target_client: Client, + /// Target transaction params. + pub target_transaction_params: TransactionParams>, + /// Optional on-demand source to target headers relay. + pub source_to_target_headers_relay: + Option>>, + /// Optional on-demand target to source headers relay. + pub target_to_source_headers_relay: + Option>>, + /// Identifier of lane that needs to be served. + pub lane_id: LaneId, + /// Messages relay limits. If not provided, the relay tries to determine it automatically, + /// using `TransactionPayment` pallet runtime API. + pub limits: Option, + /// Metrics parameters. + pub metrics_params: MetricsParams, +} + +/// Delivery transaction limits. +pub struct MessagesRelayLimits { + /// Maximal number of messages in the delivery transaction. + pub max_messages_in_single_batch: MessageNonce, + /// Maximal cumulative weight of messages in the delivery transaction. + pub max_messages_weight_in_single_batch: Weight, +} + +/// Batch transaction that brings headers + and messages delivery/receiving confirmations to the +/// source node. +#[derive(Clone)] +pub struct BatchProofTransaction>> { + builder: B::CallBuilder, + proved_header: HeaderIdOf, + prove_calls: Vec>, + + /// Using `fn() -> B` in order to avoid implementing `Send` for `B`. + _phantom: PhantomData B>, +} + +impl>> std::fmt::Debug + for BatchProofTransaction +{ + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.debug_struct("BatchProofTransaction") + .field("proved_header", &self.proved_header) + .finish() + } +} + +impl>> + BatchProofTransaction +{ + /// Creates a new instance of `BatchProofTransaction`. + pub async fn new( + relay: Arc>, + block_num: BlockNumberOf, + ) -> Result, SubstrateError> { + if let Some(builder) = B::new_builder() { + let (proved_header, prove_calls) = relay.prove_header(block_num).await?; + return Ok(Some(Self { + builder, + proved_header, + prove_calls, + _phantom: Default::default(), + })) + } + + Ok(None) + } + + /// Return a batch call that includes the provided call. + pub fn append_call_and_build(mut self, call: CallOf) -> CallOf { + self.prove_calls.push(call); + self.builder.build_batch_call(self.prove_calls) + } +} + +impl>> + BatchTransaction> for BatchProofTransaction +{ + fn required_header_id(&self) -> HeaderIdOf { + self.proved_header + } +} + +/// Run Substrate-to-Substrate messages sync loop. +pub async fn run(params: MessagesRelayParams

) -> anyhow::Result<()> +where + AccountIdOf: From< as Pair>::Public>, + AccountIdOf: From< as Pair>::Public>, + BalanceOf: TryFrom>, +{ + // 2/3 is reserved for proofs and tx overhead + let max_messages_size_in_single_batch = P::TargetChain::max_extrinsic_size() / 3; + let limits = match params.limits { + Some(limits) => limits, + None => + select_delivery_transaction_limits_rpc::

( + ¶ms, + P::TargetChain::max_extrinsic_weight(), + P::SourceChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, + ) + .await?, + }; + let (max_messages_in_single_batch, max_messages_weight_in_single_batch) = + (limits.max_messages_in_single_batch / 2, limits.max_messages_weight_in_single_batch / 2); + + let source_client = params.source_client; + let target_client = params.target_client; + let relayer_id_at_source: AccountIdOf = + params.source_transaction_params.signer.public().into(); + + log::info!( + target: "bridge", + "Starting {} -> {} messages relay.\n\t\ + {} relayer account id: {:?}\n\t\ + Max messages in single transaction: {}\n\t\ + Max messages size in single transaction: {}\n\t\ + Max messages weight in single transaction: {}\n\t\ + Tx mortality: {:?} (~{}m)/{:?} (~{}m)", + P::SourceChain::NAME, + P::TargetChain::NAME, + P::SourceChain::NAME, + relayer_id_at_source, + max_messages_in_single_batch, + max_messages_size_in_single_batch, + max_messages_weight_in_single_batch, + params.source_transaction_params.mortality, + transaction_stall_timeout( + params.source_transaction_params.mortality, + P::SourceChain::AVERAGE_BLOCK_INTERVAL, + STALL_TIMEOUT, + ).as_secs_f64() / 60.0f64, + params.target_transaction_params.mortality, + transaction_stall_timeout( + params.target_transaction_params.mortality, + P::TargetChain::AVERAGE_BLOCK_INTERVAL, + STALL_TIMEOUT, + ).as_secs_f64() / 60.0f64, + ); + + messages_relay::message_lane_loop::run( + messages_relay::message_lane_loop::Params { + lane: params.lane_id, + source_tick: P::SourceChain::AVERAGE_BLOCK_INTERVAL, + target_tick: P::TargetChain::AVERAGE_BLOCK_INTERVAL, + reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY, + delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams { + max_unrewarded_relayer_entries_at_target: + P::SourceChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, + max_unconfirmed_nonces_at_target: + P::SourceChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, + max_messages_in_single_batch, + max_messages_weight_in_single_batch, + max_messages_size_in_single_batch, + }, + }, + SubstrateMessagesSource::

::new( + source_client.clone(), + target_client.clone(), + params.lane_id, + params.source_transaction_params, + params.target_to_source_headers_relay, + ), + SubstrateMessagesTarget::

::new( + target_client, + source_client, + params.lane_id, + relayer_id_at_source, + params.target_transaction_params, + params.source_to_target_headers_relay, + ), + { + GlobalMetrics::new()?.register_and_spawn(¶ms.metrics_params.registry)?; + params.metrics_params + }, + futures::future::pending(), + ) + .await + .map_err(Into::into) +} + +/// Different ways of building `receive_messages_proof` calls. +pub trait ReceiveMessagesProofCallBuilder { + /// Given messages proof, build call of `receive_messages_proof` function of bridge + /// messages module at the target chain. + fn build_receive_messages_proof_call( + relayer_id_at_source: AccountIdOf, + proof: SubstrateMessagesProof, + messages_count: u32, + dispatch_weight: Weight, + trace_call: bool, + ) -> CallOf; +} + +/// Building `receive_messages_proof` call when you have direct access to the target +/// chain runtime. +pub struct DirectReceiveMessagesProofCallBuilder { + _phantom: PhantomData<(P, R, I)>, +} + +impl ReceiveMessagesProofCallBuilder

for DirectReceiveMessagesProofCallBuilder +where + P: SubstrateMessageLane, + R: BridgeMessagesConfig>, + I: 'static, + R::SourceHeaderChain: bp_messages::target_chain::SourceHeaderChain< + MessagesProof = FromBridgedChainMessagesProof>, + >, + CallOf: From> + GetDispatchInfo, +{ + fn build_receive_messages_proof_call( + relayer_id_at_source: AccountIdOf, + proof: SubstrateMessagesProof, + messages_count: u32, + dispatch_weight: Weight, + trace_call: bool, + ) -> CallOf { + let call: CallOf = BridgeMessagesCall::::receive_messages_proof { + relayer_id_at_bridged_chain: relayer_id_at_source, + proof: proof.1, + messages_count, + dispatch_weight, + } + .into(); + if trace_call { + // this trace isn't super-accurate, because limits are for transactions and we + // have a call here, but it provides required information + log::trace!( + target: "bridge", + "Prepared {} -> {} messages delivery call. Weight: {}/{}, size: {}/{}", + P::SourceChain::NAME, + P::TargetChain::NAME, + call.get_dispatch_info().weight, + P::TargetChain::max_extrinsic_weight(), + call.encode().len(), + P::TargetChain::max_extrinsic_size(), + ); + } + call + } +} + +/// Macro that generates `ReceiveMessagesProofCallBuilder` implementation for the case when +/// you only have an access to the mocked version of target chain runtime. In this case you +/// should provide "name" of the call variant for the bridge messages calls and the "name" of +/// the variant for the `receive_messages_proof` call within that first option. +#[rustfmt::skip] +#[macro_export] +macro_rules! generate_receive_message_proof_call_builder { + ($pipeline:ident, $mocked_builder:ident, $bridge_messages:path, $receive_messages_proof:path) => { + pub struct $mocked_builder; + + impl $crate::messages_lane::ReceiveMessagesProofCallBuilder<$pipeline> + for $mocked_builder + { + fn build_receive_messages_proof_call( + relayer_id_at_source: relay_substrate_client::AccountIdOf< + <$pipeline as $crate::messages_lane::SubstrateMessageLane>::SourceChain + >, + proof: $crate::messages_source::SubstrateMessagesProof< + <$pipeline as $crate::messages_lane::SubstrateMessageLane>::SourceChain + >, + messages_count: u32, + dispatch_weight: bp_messages::Weight, + _trace_call: bool, + ) -> relay_substrate_client::CallOf< + <$pipeline as $crate::messages_lane::SubstrateMessageLane>::TargetChain + > { + bp_runtime::paste::item! { + $bridge_messages($receive_messages_proof { + relayer_id_at_bridged_chain: relayer_id_at_source, + proof: proof.1, + messages_count: messages_count, + dispatch_weight: dispatch_weight, + }) + } + } + } + }; +} + +/// Different ways of building `receive_messages_delivery_proof` calls. +pub trait ReceiveMessagesDeliveryProofCallBuilder { + /// Given messages delivery proof, build call of `receive_messages_delivery_proof` function of + /// bridge messages module at the source chain. + fn build_receive_messages_delivery_proof_call( + proof: SubstrateMessagesDeliveryProof, + trace_call: bool, + ) -> CallOf; +} + +/// Building `receive_messages_delivery_proof` call when you have direct access to the source +/// chain runtime. +pub struct DirectReceiveMessagesDeliveryProofCallBuilder { + _phantom: PhantomData<(P, R, I)>, +} + +impl ReceiveMessagesDeliveryProofCallBuilder

+ for DirectReceiveMessagesDeliveryProofCallBuilder +where + P: SubstrateMessageLane, + R: BridgeMessagesConfig, + I: 'static, + R::TargetHeaderChain: bp_messages::source_chain::TargetHeaderChain< + R::OutboundPayload, + R::AccountId, + MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof>, + >, + CallOf: From> + GetDispatchInfo, +{ + fn build_receive_messages_delivery_proof_call( + proof: SubstrateMessagesDeliveryProof, + trace_call: bool, + ) -> CallOf { + let call: CallOf = + BridgeMessagesCall::::receive_messages_delivery_proof { + proof: proof.1, + relayers_state: proof.0, + } + .into(); + if trace_call { + // this trace isn't super-accurate, because limits are for transactions and we + // have a call here, but it provides required information + log::trace!( + target: "bridge", + "Prepared {} -> {} delivery confirmation transaction. Weight: {}/{}, size: {}/{}", + P::TargetChain::NAME, + P::SourceChain::NAME, + call.get_dispatch_info().weight, + P::SourceChain::max_extrinsic_weight(), + call.encode().len(), + P::SourceChain::max_extrinsic_size(), + ); + } + call + } +} + +/// Macro that generates `ReceiveMessagesDeliveryProofCallBuilder` implementation for the case when +/// you only have an access to the mocked version of source chain runtime. In this case you +/// should provide "name" of the call variant for the bridge messages calls and the "name" of +/// the variant for the `receive_messages_delivery_proof` call within that first option. +#[rustfmt::skip] +#[macro_export] +macro_rules! generate_receive_message_delivery_proof_call_builder { + ($pipeline:ident, $mocked_builder:ident, $bridge_messages:path, $receive_messages_delivery_proof:path) => { + pub struct $mocked_builder; + + impl $crate::messages_lane::ReceiveMessagesDeliveryProofCallBuilder<$pipeline> + for $mocked_builder + { + fn build_receive_messages_delivery_proof_call( + proof: $crate::messages_target::SubstrateMessagesDeliveryProof< + <$pipeline as $crate::messages_lane::SubstrateMessageLane>::TargetChain + >, + _trace_call: bool, + ) -> relay_substrate_client::CallOf< + <$pipeline as $crate::messages_lane::SubstrateMessageLane>::SourceChain + > { + bp_runtime::paste::item! { + $bridge_messages($receive_messages_delivery_proof { + proof: proof.1, + relayers_state: proof.0 + }) + } + } + } + }; +} + +/// Returns maximal number of messages and their maximal cumulative dispatch weight. +async fn select_delivery_transaction_limits_rpc( + params: &MessagesRelayParams

, + max_extrinsic_weight: Weight, + max_unconfirmed_messages_at_inbound_lane: MessageNonce, +) -> anyhow::Result +where + AccountIdOf: From< as Pair>::Public>, +{ + // We may try to guess accurate value, based on maximal number of messages and per-message + // weight overhead, but the relay loop isn't using this info in a super-accurate way anyway. + // So just a rough guess: let's say 1/3 of max tx weight is for tx itself and the rest is + // for messages dispatch. + + // Another thing to keep in mind is that our runtimes (when this code was written) accept + // messages with dispatch weight <= max_extrinsic_weight/2. So we can't reserve less than + // that for dispatch. + + let weight_for_delivery_tx = max_extrinsic_weight / 3; + let weight_for_messages_dispatch = max_extrinsic_weight - weight_for_delivery_tx; + + // weight of empty message delivery with outbound lane state + let delivery_tx_with_zero_messages = dummy_messages_delivery_transaction::

(params, 0)?; + let delivery_tx_with_zero_messages_weight = params + .target_client + .extimate_extrinsic_weight(delivery_tx_with_zero_messages) + .await + .map_err(|e| { + anyhow::format_err!("Failed to estimate delivery extrinsic weight: {:?}", e) + })?; + + // weight of single message delivery with outbound lane state + let delivery_tx_with_one_message = dummy_messages_delivery_transaction::

(params, 1)?; + let delivery_tx_with_one_message_weight = params + .target_client + .extimate_extrinsic_weight(delivery_tx_with_one_message) + .await + .map_err(|e| { + anyhow::format_err!("Failed to estimate delivery extrinsic weight: {:?}", e) + })?; + + // message overhead is roughly `delivery_tx_with_one_message_weight - + // delivery_tx_with_zero_messages_weight` + let delivery_tx_weight_rest = weight_for_delivery_tx - delivery_tx_with_zero_messages_weight; + let delivery_tx_message_overhead = + delivery_tx_with_one_message_weight.saturating_sub(delivery_tx_with_zero_messages_weight); + + let max_number_of_messages = std::cmp::min( + delivery_tx_weight_rest + .min_components_checked_div(delivery_tx_message_overhead) + .unwrap_or(u64::MAX), + max_unconfirmed_messages_at_inbound_lane, + ); + + assert!( + max_number_of_messages > 0, + "Relay should fit at least one message in every delivery transaction", + ); + assert!( + weight_for_messages_dispatch.ref_time() >= max_extrinsic_weight.ref_time() / 2, + "Relay shall be able to deliver messages with dispatch weight = max_extrinsic_weight / 2", + ); + + Ok(MessagesRelayLimits { + max_messages_in_single_batch: max_number_of_messages, + max_messages_weight_in_single_batch: weight_for_messages_dispatch, + }) +} + +/// Returns dummy message delivery transaction with zero messages and `1kb` proof. +fn dummy_messages_delivery_transaction( + params: &MessagesRelayParams

, + messages: u32, +) -> anyhow::Result<::SignedTransaction> +where + AccountIdOf: From< as Pair>::Public>, +{ + // we don't care about any call values here, because all that the estimation RPC does + // is calls `GetDispatchInfo::get_dispatch_info` for the wrapped call. So we only are + // interested in values that affect call weight - e.g. number of messages and the + // storage proof size + + let dummy_messages_delivery_call = + P::ReceiveMessagesProofCallBuilder::build_receive_messages_proof_call( + params.source_transaction_params.signer.public().into(), + ( + Weight::zero(), + FromBridgedChainMessagesProof { + bridged_header_hash: Default::default(), + // we may use per-chain `EXTRA_STORAGE_PROOF_SIZE`, but since we don't need + // exact values, this global estimation is fine + storage_proof: vec![vec![ + 42u8; + pallet_bridge_messages::EXTRA_STORAGE_PROOF_SIZE + as usize + ]], + lane: Default::default(), + nonces_start: 1, + nonces_end: messages as u64, + }, + ), + messages, + Weight::zero(), + false, + ); + P::TargetChain::sign_transaction( + SignParam { + spec_version: 0, + transaction_version: 0, + genesis_hash: Default::default(), + signer: params.target_transaction_params.signer.clone(), + }, + UnsignedTransaction { + call: EncodedOrDecodedCall::Decoded(dummy_messages_delivery_call), + nonce: Zero::zero(), + tip: Zero::zero(), + era: TransactionEra::Immortal, + }, + ) + .map_err(Into::into) +} diff --git a/bridges/relays/lib-substrate-relay/src/messages_metrics.rs b/bridges/relays/lib-substrate-relay/src/messages_metrics.rs new file mode 100644 index 0000000000000..27bf6186c3ba0 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/messages_metrics.rs @@ -0,0 +1,190 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Tools for supporting message lanes between two Substrate-based chains. + +use crate::TaggedAccount; + +use bp_messages::LaneId; +use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; +use bp_runtime::StorageDoubleMapKeyProvider; +use codec::Decode; +use frame_system::AccountInfo; +use pallet_balances::AccountData; +use relay_substrate_client::{ + metrics::{FloatStorageValue, FloatStorageValueMetric}, + AccountIdOf, BalanceOf, Chain, ChainWithBalances, ChainWithMessages, Client, + Error as SubstrateError, NonceOf, +}; +use relay_utils::metrics::{MetricsParams, StandaloneMetric}; +use sp_core::storage::StorageData; +use sp_runtime::{FixedPointNumber, FixedU128}; +use std::{convert::TryFrom, fmt::Debug, marker::PhantomData}; + +/// Add relay accounts balance metrics. +pub async fn add_relay_balances_metrics( + client: Client, + metrics: &MetricsParams, + relay_accounts: &Vec>>, + lanes: &[LaneId], +) -> anyhow::Result<()> +where + BalanceOf: Into + std::fmt::Debug, +{ + if relay_accounts.is_empty() { + return Ok(()) + } + + // if `tokenDecimals` is missing from system properties, we'll be using + let token_decimals = client + .token_decimals() + .await? + .map(|token_decimals| { + log::info!(target: "bridge", "Read `tokenDecimals` for {}: {}", C::NAME, token_decimals); + token_decimals + }) + .unwrap_or_else(|| { + // turns out it is normal not to have this property - e.g. when polkadot binary is + // started using `polkadot-local` chain. Let's use minimal nominal here + log::info!(target: "bridge", "Using default (zero) `tokenDecimals` value for {}", C::NAME); + 0 + }); + let token_decimals = u32::try_from(token_decimals).map_err(|e| { + anyhow::format_err!( + "Token decimals value ({}) of {} doesn't fit into u32: {:?}", + token_decimals, + C::NAME, + e, + ) + })?; + + for account in relay_accounts { + let relay_account_balance_metric = FloatStorageValueMetric::new( + AccountBalanceFromAccountInfo:: { token_decimals, _phantom: Default::default() }, + client.clone(), + C::account_info_storage_key(account.id()), + format!("at_{}_relay_{}_balance", C::NAME, account.tag()), + format!("Balance of the {} relay account at the {}", account.tag(), C::NAME), + )?; + relay_account_balance_metric.register_and_spawn(&metrics.registry)?; + + if let Some(relayers_pallet_name) = BC::WITH_CHAIN_RELAYERS_PALLET_NAME { + for lane in lanes { + FloatStorageValueMetric::new( + AccountBalance:: { token_decimals, _phantom: Default::default() }, + client.clone(), + bp_relayers::RelayerRewardsKeyProvider::, BalanceOf>::final_key( + relayers_pallet_name, + account.id(), + &RewardsAccountParams::new(*lane, BC::ID, RewardsAccountOwner::ThisChain), + ), + format!("at_{}_relay_{}_reward_for_msgs_from_{}_on_lane_{}", C::NAME, account.tag(), BC::NAME, hex::encode(lane.as_ref())), + format!("Reward of the {} relay account at {} for delivering messages from {} on lane {:?}", account.tag(), C::NAME, BC::NAME, lane), + )?.register_and_spawn(&metrics.registry)?; + + FloatStorageValueMetric::new( + AccountBalance:: { token_decimals, _phantom: Default::default() }, + client.clone(), + bp_relayers::RelayerRewardsKeyProvider::, BalanceOf>::final_key( + relayers_pallet_name, + account.id(), + &RewardsAccountParams::new(*lane, BC::ID, RewardsAccountOwner::BridgedChain), + ), + format!("at_{}_relay_{}_reward_for_msgs_to_{}_on_lane_{}", C::NAME, account.tag(), BC::NAME, hex::encode(lane.as_ref())), + format!("Reward of the {} relay account at {} for delivering messages confirmations from {} on lane {:?}", account.tag(), C::NAME, BC::NAME, lane), + )?.register_and_spawn(&metrics.registry)?; + } + } + } + + Ok(()) +} + +/// Adapter for `FloatStorageValueMetric` to decode account free balance. +#[derive(Clone, Debug)] +struct AccountBalanceFromAccountInfo { + token_decimals: u32, + _phantom: PhantomData, +} + +impl FloatStorageValue for AccountBalanceFromAccountInfo +where + C: Chain, + BalanceOf: Into, +{ + type Value = FixedU128; + + fn decode( + &self, + maybe_raw_value: Option, + ) -> Result, SubstrateError> { + maybe_raw_value + .map(|raw_value| { + AccountInfo::, AccountData>>::decode(&mut &raw_value.0[..]) + .map_err(SubstrateError::ResponseParseFailed) + .map(|account_data| { + convert_to_token_balance(account_data.data.free.into(), self.token_decimals) + }) + }) + .transpose() + } +} + +/// Adapter for `FloatStorageValueMetric` to decode account free balance. +#[derive(Clone, Debug)] +struct AccountBalance { + token_decimals: u32, + _phantom: PhantomData, +} + +impl FloatStorageValue for AccountBalance +where + C: Chain, + BalanceOf: Into, +{ + type Value = FixedU128; + + fn decode( + &self, + maybe_raw_value: Option, + ) -> Result, SubstrateError> { + maybe_raw_value + .map(|raw_value| { + BalanceOf::::decode(&mut &raw_value.0[..]) + .map_err(SubstrateError::ResponseParseFailed) + .map(|balance| convert_to_token_balance(balance.into(), self.token_decimals)) + }) + .transpose() + } +} + +/// Convert from raw `u128` balance (nominated in smallest chain token units) to the float regular +/// tokens value. +fn convert_to_token_balance(balance: u128, token_decimals: u32) -> FixedU128 { + FixedU128::from_inner(balance.saturating_mul(FixedU128::DIV / 10u128.pow(token_decimals))) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn token_decimals_used_properly() { + let plancks = 425_000_000_000; + let token_decimals = 10; + let dots = convert_to_token_balance(plancks, token_decimals); + assert_eq!(dots, FixedU128::saturating_from_rational(425, 10)); + } +} diff --git a/bridges/relays/lib-substrate-relay/src/messages_source.rs b/bridges/relays/lib-substrate-relay/src/messages_source.rs new file mode 100644 index 0000000000000..49deff046f9ca --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/messages_source.rs @@ -0,0 +1,713 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Substrate client as Substrate messages source. The chain we connect to should have +//! runtime that implements `HeaderApi` to allow bridging with +//! `` chain. + +use crate::{ + finality_base::best_synced_header_id, + messages_lane::{ + BatchProofTransaction, MessageLaneAdapter, ReceiveMessagesDeliveryProofCallBuilder, + SubstrateMessageLane, + }, + on_demand::OnDemandRelay, + TransactionParams, +}; + +use async_std::sync::Arc; +use async_trait::async_trait; +use bp_messages::{ + storage_keys::{operating_mode_key, outbound_lane_data_key}, + ChainWithMessages as _, InboundMessageDetails, LaneId, MessageNonce, MessagePayload, + MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails, +}; +use bp_runtime::{BasicOperatingMode, HeaderIdProvider}; +use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof; +use codec::Encode; +use frame_support::weights::Weight; +use messages_relay::{ + message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, + message_lane_loop::{ + ClientState, MessageDetails, MessageDetailsMap, MessageProofParameters, SourceClient, + SourceClientState, + }, +}; +use num_traits::Zero; +use relay_substrate_client::{ + AccountIdOf, AccountKeyPairOf, BalanceOf, Chain, ChainWithMessages, Client, + Error as SubstrateError, HashOf, HeaderIdOf, TransactionEra, TransactionTracker, + UnsignedTransaction, +}; +use relay_utils::relay_loop::Client as RelayClient; +use sp_core::Pair; +use std::ops::RangeInclusive; + +/// Intermediate message proof returned by the source Substrate node. Includes everything +/// required to submit to the target node: cumulative dispatch weight of bundled messages and +/// the proof itself. +pub type SubstrateMessagesProof = (Weight, FromBridgedChainMessagesProof>); +type MessagesToRefine<'a> = Vec<(MessagePayload, &'a mut OutboundMessageDetails)>; + +/// Substrate client as Substrate messages source. +pub struct SubstrateMessagesSource { + source_client: Client, + target_client: Client, + lane_id: LaneId, + transaction_params: TransactionParams>, + target_to_source_headers_relay: Option>>, +} + +impl SubstrateMessagesSource

{ + /// Create new Substrate headers source. + pub fn new( + source_client: Client, + target_client: Client, + lane_id: LaneId, + transaction_params: TransactionParams>, + target_to_source_headers_relay: Option< + Arc>, + >, + ) -> Self { + SubstrateMessagesSource { + source_client, + target_client, + lane_id, + transaction_params, + target_to_source_headers_relay, + } + } + + /// Read outbound lane state from the on-chain storage at given block. + async fn outbound_lane_data( + &self, + id: SourceHeaderIdOf>, + ) -> Result, SubstrateError> { + self.source_client + .storage_value( + outbound_lane_data_key( + P::TargetChain::WITH_CHAIN_MESSAGES_PALLET_NAME, + &self.lane_id, + ), + Some(id.1), + ) + .await + } + + /// Ensure that the messages pallet at source chain is active. + async fn ensure_pallet_active(&self) -> Result<(), SubstrateError> { + ensure_messages_pallet_active::(&self.source_client).await + } +} + +impl Clone for SubstrateMessagesSource

{ + fn clone(&self) -> Self { + Self { + source_client: self.source_client.clone(), + target_client: self.target_client.clone(), + lane_id: self.lane_id, + transaction_params: self.transaction_params.clone(), + target_to_source_headers_relay: self.target_to_source_headers_relay.clone(), + } + } +} + +#[async_trait] +impl RelayClient for SubstrateMessagesSource

{ + type Error = SubstrateError; + + async fn reconnect(&mut self) -> Result<(), SubstrateError> { + // since the client calls RPC methods on both sides, we need to reconnect both + self.source_client.reconnect().await?; + self.target_client.reconnect().await?; + + // call reconnect on on-demand headers relay, because we may use different chains there + // and the error that has lead to reconnect may have came from those other chains + // (see `require_target_header_on_source`) + // + // this may lead to multiple reconnects to the same node during the same call and it + // needs to be addressed in the future + // TODO: https://github.com/paritytech/parity-bridges-common/issues/1928 + if let Some(ref mut target_to_source_headers_relay) = self.target_to_source_headers_relay { + target_to_source_headers_relay.reconnect().await?; + } + + Ok(()) + } +} + +#[async_trait] +impl SourceClient> for SubstrateMessagesSource

+where + AccountIdOf: From< as Pair>::Public>, +{ + type BatchTransaction = + BatchProofTransaction; + type TransactionTracker = TransactionTracker>; + + async fn state(&self) -> Result>, SubstrateError> { + // we can't continue to deliver confirmations if source node is out of sync, because + // it may have already received confirmations that we're going to deliver + // + // we can't continue to deliver messages if target node is out of sync, because + // it may have already received (some of) messages that we're going to deliver + self.source_client.ensure_synced().await?; + self.target_client.ensure_synced().await?; + // we can't relay confirmations if messages pallet at source chain is halted + self.ensure_pallet_active().await?; + + read_client_state(&self.source_client, Some(&self.target_client)).await + } + + async fn latest_generated_nonce( + &self, + id: SourceHeaderIdOf>, + ) -> Result<(SourceHeaderIdOf>, MessageNonce), SubstrateError> { + // lane data missing from the storage is fine until first message is sent + let latest_generated_nonce = self + .outbound_lane_data(id) + .await? + .map(|data| data.latest_generated_nonce) + .unwrap_or(0); + Ok((id, latest_generated_nonce)) + } + + async fn latest_confirmed_received_nonce( + &self, + id: SourceHeaderIdOf>, + ) -> Result<(SourceHeaderIdOf>, MessageNonce), SubstrateError> { + // lane data missing from the storage is fine until first message is sent + let latest_received_nonce = self + .outbound_lane_data(id) + .await? + .map(|data| data.latest_received_nonce) + .unwrap_or(0); + Ok((id, latest_received_nonce)) + } + + async fn generated_message_details( + &self, + id: SourceHeaderIdOf>, + nonces: RangeInclusive, + ) -> Result>, SubstrateError> { + let mut out_msgs_details = self + .source_client + .typed_state_call::<_, Vec<_>>( + P::TargetChain::TO_CHAIN_MESSAGE_DETAILS_METHOD.into(), + (self.lane_id, *nonces.start(), *nonces.end()), + Some(id.1), + ) + .await?; + validate_out_msgs_details::(&out_msgs_details, nonces)?; + + // prepare arguments of the inbound message details call (if we need it) + let mut msgs_to_refine = vec![]; + for out_msg_details in out_msgs_details.iter_mut() { + // in our current strategy all messages are supposed to be paid at the target chain + + // for pay-at-target messages we may want to ask target chain for + // refined dispatch weight + let msg_key = bp_messages::storage_keys::message_key( + P::TargetChain::WITH_CHAIN_MESSAGES_PALLET_NAME, + &self.lane_id, + out_msg_details.nonce, + ); + let msg_payload: MessagePayload = + self.source_client.storage_value(msg_key, Some(id.1)).await?.ok_or_else(|| { + SubstrateError::Custom(format!( + "Message to {} {:?}/{} is missing from runtime the storage of {} at {:?}", + P::TargetChain::NAME, + self.lane_id, + out_msg_details.nonce, + P::SourceChain::NAME, + id, + )) + })?; + + msgs_to_refine.push((msg_payload, out_msg_details)); + } + + for mut msgs_to_refine_batch in + split_msgs_to_refine::(self.lane_id, msgs_to_refine)? + { + let in_msgs_details = self + .target_client + .typed_state_call::<_, Vec>( + P::SourceChain::FROM_CHAIN_MESSAGE_DETAILS_METHOD.into(), + (self.lane_id, &msgs_to_refine_batch), + None, + ) + .await?; + if in_msgs_details.len() != msgs_to_refine_batch.len() { + return Err(SubstrateError::Custom(format!( + "Call of {} at {} has returned {} entries instead of expected {}", + P::SourceChain::FROM_CHAIN_MESSAGE_DETAILS_METHOD, + P::TargetChain::NAME, + in_msgs_details.len(), + msgs_to_refine_batch.len(), + ))) + } + for ((_, out_msg_details), in_msg_details) in + msgs_to_refine_batch.iter_mut().zip(in_msgs_details) + { + log::trace!( + target: "bridge", + "Refined weight of {}->{} message {:?}/{}: at-source: {}, at-target: {}", + P::SourceChain::NAME, + P::TargetChain::NAME, + self.lane_id, + out_msg_details.nonce, + out_msg_details.dispatch_weight, + in_msg_details.dispatch_weight, + ); + out_msg_details.dispatch_weight = in_msg_details.dispatch_weight; + } + } + + let mut msgs_details_map = MessageDetailsMap::new(); + for out_msg_details in out_msgs_details { + msgs_details_map.insert( + out_msg_details.nonce, + MessageDetails { + dispatch_weight: out_msg_details.dispatch_weight, + size: out_msg_details.size as _, + reward: Zero::zero(), + }, + ); + } + + Ok(msgs_details_map) + } + + async fn prove_messages( + &self, + id: SourceHeaderIdOf>, + nonces: RangeInclusive, + proof_parameters: MessageProofParameters, + ) -> Result< + ( + SourceHeaderIdOf>, + RangeInclusive, + as MessageLane>::MessagesProof, + ), + SubstrateError, + > { + let mut storage_keys = + Vec::with_capacity(nonces.end().saturating_sub(*nonces.start()) as usize + 1); + let mut message_nonce = *nonces.start(); + while message_nonce <= *nonces.end() { + let message_key = bp_messages::storage_keys::message_key( + P::TargetChain::WITH_CHAIN_MESSAGES_PALLET_NAME, + &self.lane_id, + message_nonce, + ); + storage_keys.push(message_key); + message_nonce += 1; + } + if proof_parameters.outbound_state_proof_required { + storage_keys.push(bp_messages::storage_keys::outbound_lane_data_key( + P::TargetChain::WITH_CHAIN_MESSAGES_PALLET_NAME, + &self.lane_id, + )); + } + + let proof = self + .source_client + .prove_storage(storage_keys, id.1) + .await? + .into_iter_nodes() + .collect(); + let proof = FromBridgedChainMessagesProof { + bridged_header_hash: id.1, + storage_proof: proof, + lane: self.lane_id, + nonces_start: *nonces.start(), + nonces_end: *nonces.end(), + }; + Ok((id, nonces, (proof_parameters.dispatch_weight, proof))) + } + + async fn submit_messages_receiving_proof( + &self, + maybe_batch_tx: Option, + _generated_at_block: TargetHeaderIdOf>, + proof: as MessageLane>::MessagesReceivingProof, + ) -> Result { + let messages_proof_call = + P::ReceiveMessagesDeliveryProofCallBuilder::build_receive_messages_delivery_proof_call( + proof, + maybe_batch_tx.is_none(), + ); + let final_call = match maybe_batch_tx { + Some(batch_tx) => batch_tx.append_call_and_build(messages_proof_call), + None => messages_proof_call, + }; + + let transaction_params = self.transaction_params.clone(); + self.source_client + .submit_and_watch_signed_extrinsic( + &self.transaction_params.signer, + move |best_block_id, transaction_nonce| { + Ok(UnsignedTransaction::new(final_call.into(), transaction_nonce) + .era(TransactionEra::new(best_block_id, transaction_params.mortality))) + }, + ) + .await + } + + async fn require_target_header_on_source( + &self, + id: TargetHeaderIdOf>, + ) -> Result, SubstrateError> { + if let Some(ref target_to_source_headers_relay) = self.target_to_source_headers_relay { + if let Some(batch_tx) = + BatchProofTransaction::new(target_to_source_headers_relay.clone(), id.0).await? + { + return Ok(Some(batch_tx)) + } + + target_to_source_headers_relay.require_more_headers(id.0).await; + } + + Ok(None) + } +} + +/// Ensure that the messages pallet at source chain is active. +pub(crate) async fn ensure_messages_pallet_active( + client: &Client, +) -> Result<(), SubstrateError> +where + AtChain: ChainWithMessages, + WithChain: ChainWithMessages, +{ + let operating_mode = client + .storage_value(operating_mode_key(WithChain::WITH_CHAIN_MESSAGES_PALLET_NAME), None) + .await?; + let is_halted = + operating_mode == Some(MessagesOperatingMode::Basic(BasicOperatingMode::Halted)); + if is_halted { + Err(SubstrateError::BridgePalletIsHalted) + } else { + Ok(()) + } +} + +/// Read best blocks from given client. +/// +/// This function assumes that the chain that is followed by the `self_client` has +/// bridge GRANDPA pallet deployed and it provides `best_finalized_header_id_method_name` +/// runtime API to read the best finalized Bridged chain header. +/// +/// If `peer_client` is `None`, the value of `actual_best_finalized_peer_at_best_self` will +/// always match the `best_finalized_peer_at_best_self`. +pub async fn read_client_state( + self_client: &Client, + peer_client: Option<&Client>, +) -> Result, HeaderIdOf>, SubstrateError> +where + SelfChain: Chain, + PeerChain: Chain, +{ + // let's read our state first: we need best finalized header hash on **this** chain + let self_best_finalized_id = self_client.best_finalized_header().await?.id(); + // now let's read our best header on **this** chain + let self_best_id = self_client.best_header().await?.id(); + + // now let's read id of best finalized peer header at our best finalized block + let peer_on_self_best_finalized_id = + best_synced_header_id::(self_client, self_best_id.hash()).await?; + + // read actual header, matching the `peer_on_self_best_finalized_id` from the peer chain + let actual_peer_on_self_best_finalized_id = + match (peer_client, peer_on_self_best_finalized_id.as_ref()) { + (Some(peer_client), Some(peer_on_self_best_finalized_id)) => { + let actual_peer_on_self_best_finalized = + peer_client.header_by_number(peer_on_self_best_finalized_id.number()).await?; + Some(actual_peer_on_self_best_finalized.id()) + }, + _ => peer_on_self_best_finalized_id, + }; + + Ok(ClientState { + best_self: self_best_id, + best_finalized_self: self_best_finalized_id, + best_finalized_peer_at_best_self: peer_on_self_best_finalized_id, + actual_best_finalized_peer_at_best_self: actual_peer_on_self_best_finalized_id, + }) +} + +/// Reads best `PeerChain` header known to the `SelfChain` using provided runtime API method. +/// +/// Method is supposed to be the `FinalityApi::best_finalized()` method. +pub async fn best_finalized_peer_header_at_self( + self_client: &Client, + at_self_hash: HashOf, +) -> Result>, SubstrateError> +where + SelfChain: Chain, + PeerChain: Chain, +{ + // now let's read id of best finalized peer header at our best finalized block + self_client + .typed_state_call::<_, Option<_>>( + PeerChain::BEST_FINALIZED_HEADER_ID_METHOD.into(), + (), + Some(at_self_hash), + ) + .await +} + +fn validate_out_msgs_details( + out_msgs_details: &[OutboundMessageDetails], + nonces: RangeInclusive, +) -> Result<(), SubstrateError> { + let make_missing_nonce_error = |expected_nonce| { + Err(SubstrateError::Custom(format!( + "Missing nonce {expected_nonce} in message_details call result. Expected all nonces from {nonces:?}", + ))) + }; + + if out_msgs_details.len() > nonces.clone().count() { + return Err(SubstrateError::Custom( + "More messages than requested returned by the message_details call.".into(), + )) + } + + // Check if last nonce is missing. The loop below is not checking this. + if out_msgs_details.is_empty() && !nonces.is_empty() { + return make_missing_nonce_error(*nonces.end()) + } + + let mut nonces_iter = nonces.clone().rev().peekable(); + let mut out_msgs_details_iter = out_msgs_details.iter().rev(); + while let Some((out_msg_details, &nonce)) = out_msgs_details_iter.next().zip(nonces_iter.peek()) + { + nonces_iter.next(); + if out_msg_details.nonce != nonce { + // Some nonces are missing from the middle/tail of the range. This is critical error. + return make_missing_nonce_error(nonce) + } + } + + // Check if some nonces from the beginning of the range are missing. This may happen if + // some messages were already pruned from the source node. This is not a critical error + // and will be auto-resolved by messages lane (and target node). + if nonces_iter.peek().is_some() { + log::info!( + target: "bridge", + "Some messages are missing from the {} node: {:?}. Target node may be out of sync?", + C::NAME, + nonces_iter.rev().collect::>(), + ); + } + + Ok(()) +} + +fn split_msgs_to_refine( + lane_id: LaneId, + msgs_to_refine: MessagesToRefine, +) -> Result, SubstrateError> { + let max_batch_size = Target::max_extrinsic_size() as usize; + let mut batches = vec![]; + + let mut current_msgs_batch = msgs_to_refine; + while !current_msgs_batch.is_empty() { + let mut next_msgs_batch = vec![]; + while (lane_id, ¤t_msgs_batch).encoded_size() > max_batch_size { + if current_msgs_batch.len() <= 1 { + return Err(SubstrateError::Custom(format!( + "Call of {} at {} can't be executed even if only one message is supplied. \ + max_extrinsic_size(): {}", + Source::FROM_CHAIN_MESSAGE_DETAILS_METHOD, + Target::NAME, + Target::max_extrinsic_size(), + ))) + } + + if let Some(msg) = current_msgs_batch.pop() { + next_msgs_batch.insert(0, msg); + } + } + + batches.push(current_msgs_batch); + current_msgs_batch = next_msgs_batch; + } + + Ok(batches) +} + +#[cfg(test)] +mod tests { + use super::*; + use relay_substrate_client::test_chain::TestChain; + + fn message_details_from_rpc( + nonces: RangeInclusive, + ) -> Vec { + nonces + .into_iter() + .map(|nonce| bp_messages::OutboundMessageDetails { + nonce, + dispatch_weight: Weight::zero(), + size: 0, + }) + .collect() + } + + #[test] + fn validate_out_msgs_details_succeeds_if_no_messages_are_missing() { + assert!(validate_out_msgs_details::(&message_details_from_rpc(1..=3), 1..=3,) + .is_ok()); + } + + #[test] + fn validate_out_msgs_details_succeeds_if_head_messages_are_missing() { + assert!(validate_out_msgs_details::(&message_details_from_rpc(2..=3), 1..=3,) + .is_ok()) + } + + #[test] + fn validate_out_msgs_details_fails_if_mid_messages_are_missing() { + let mut message_details_from_rpc = message_details_from_rpc(1..=3); + message_details_from_rpc.remove(1); + assert!(matches!( + validate_out_msgs_details::(&message_details_from_rpc, 1..=3,), + Err(SubstrateError::Custom(_)) + )); + } + + #[test] + fn validate_out_msgs_details_map_fails_if_tail_messages_are_missing() { + assert!(matches!( + validate_out_msgs_details::(&message_details_from_rpc(1..=2), 1..=3,), + Err(SubstrateError::Custom(_)) + )); + } + + #[test] + fn validate_out_msgs_details_fails_if_all_messages_are_missing() { + assert!(matches!( + validate_out_msgs_details::(&[], 1..=3), + Err(SubstrateError::Custom(_)) + )); + } + + #[test] + fn validate_out_msgs_details_fails_if_more_messages_than_nonces() { + assert!(matches!( + validate_out_msgs_details::(&message_details_from_rpc(1..=5), 2..=5,), + Err(SubstrateError::Custom(_)) + )); + } + + fn check_split_msgs_to_refine( + payload_sizes: Vec, + expected_batches: Result, ()>, + ) { + let mut out_msgs_details = vec![]; + for (idx, _) in payload_sizes.iter().enumerate() { + out_msgs_details.push(OutboundMessageDetails { + nonce: idx as MessageNonce, + dispatch_weight: Weight::zero(), + size: 0, + }); + } + + let mut msgs_to_refine = vec![]; + for (&payload_size, out_msg_details) in + payload_sizes.iter().zip(out_msgs_details.iter_mut()) + { + let payload = vec![1u8; payload_size]; + msgs_to_refine.push((payload, out_msg_details)); + } + + let maybe_batches = + split_msgs_to_refine::(Default::default(), msgs_to_refine); + match expected_batches { + Ok(expected_batches) => { + let batches = maybe_batches.unwrap(); + let mut idx = 0; + assert_eq!(batches.len(), expected_batches.len()); + for (batch, &expected_batch_size) in batches.iter().zip(expected_batches.iter()) { + assert_eq!(batch.len(), expected_batch_size); + for msg_to_refine in batch { + assert_eq!(msg_to_refine.0.len(), payload_sizes[idx]); + idx += 1; + } + } + }, + Err(_) => { + matches!(maybe_batches, Err(SubstrateError::Custom(_))); + }, + } + } + + #[test] + fn test_split_msgs_to_refine() { + let max_extrinsic_size = 100000; + + // Check that an error is returned when one of the messages is too big. + check_split_msgs_to_refine(vec![max_extrinsic_size], Err(())); + check_split_msgs_to_refine(vec![50, 100, max_extrinsic_size, 200], Err(())); + + // Otherwise check that the split is valid. + check_split_msgs_to_refine(vec![100, 200, 300, 400], Ok(vec![4])); + check_split_msgs_to_refine( + vec![ + 50, + 100, + max_extrinsic_size - 500, + 500, + 1000, + 1500, + max_extrinsic_size - 3500, + 5000, + 10000, + ], + Ok(vec![3, 4, 2]), + ); + check_split_msgs_to_refine( + vec![ + 50, + 100, + max_extrinsic_size - 150, + 500, + 1000, + 1500, + max_extrinsic_size - 3000, + 5000, + 10000, + ], + Ok(vec![2, 1, 3, 1, 2]), + ); + check_split_msgs_to_refine( + vec![ + 5000, + 10000, + max_extrinsic_size - 3500, + 500, + 1000, + 1500, + max_extrinsic_size - 500, + 50, + 100, + ], + Ok(vec![2, 4, 3]), + ); + } +} diff --git a/bridges/relays/lib-substrate-relay/src/messages_target.rs b/bridges/relays/lib-substrate-relay/src/messages_target.rs new file mode 100644 index 0000000000000..9396e785530d2 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/messages_target.rs @@ -0,0 +1,300 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Substrate client as Substrate messages target. The chain we connect to should have +//! runtime that implements `HeaderApi` to allow bridging with +//! `` chain. + +use crate::{ + messages_lane::{ + BatchProofTransaction, MessageLaneAdapter, ReceiveMessagesProofCallBuilder, + SubstrateMessageLane, + }, + messages_source::{ensure_messages_pallet_active, read_client_state, SubstrateMessagesProof}, + on_demand::OnDemandRelay, + TransactionParams, +}; + +use async_std::sync::Arc; +use async_trait::async_trait; +use bp_messages::{ + storage_keys::inbound_lane_data_key, ChainWithMessages as _, InboundLaneData, LaneId, + MessageNonce, UnrewardedRelayersState, +}; +use bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof; +use messages_relay::{ + message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, + message_lane_loop::{NoncesSubmitArtifacts, TargetClient, TargetClientState}, +}; +use relay_substrate_client::{ + AccountIdOf, AccountKeyPairOf, BalanceOf, CallOf, Client, Error as SubstrateError, HashOf, + TransactionEra, TransactionTracker, UnsignedTransaction, +}; +use relay_utils::relay_loop::Client as RelayClient; +use sp_core::Pair; +use std::{convert::TryFrom, ops::RangeInclusive}; + +/// Message receiving proof returned by the target Substrate node. +pub type SubstrateMessagesDeliveryProof = + (UnrewardedRelayersState, FromBridgedChainMessagesDeliveryProof>); + +/// Substrate client as Substrate messages target. +pub struct SubstrateMessagesTarget { + target_client: Client, + source_client: Client, + lane_id: LaneId, + relayer_id_at_source: AccountIdOf, + transaction_params: TransactionParams>, + source_to_target_headers_relay: Option>>, +} + +impl SubstrateMessagesTarget

{ + /// Create new Substrate headers target. + pub fn new( + target_client: Client, + source_client: Client, + lane_id: LaneId, + relayer_id_at_source: AccountIdOf, + transaction_params: TransactionParams>, + source_to_target_headers_relay: Option< + Arc>, + >, + ) -> Self { + SubstrateMessagesTarget { + target_client, + source_client, + lane_id, + relayer_id_at_source, + transaction_params, + source_to_target_headers_relay, + } + } + + /// Read inbound lane state from the on-chain storage at given block. + async fn inbound_lane_data( + &self, + id: TargetHeaderIdOf>, + ) -> Result>>, SubstrateError> { + self.target_client + .storage_value( + inbound_lane_data_key( + P::SourceChain::WITH_CHAIN_MESSAGES_PALLET_NAME, + &self.lane_id, + ), + Some(id.1), + ) + .await + } + + /// Ensure that the messages pallet at target chain is active. + async fn ensure_pallet_active(&self) -> Result<(), SubstrateError> { + ensure_messages_pallet_active::(&self.target_client).await + } +} + +impl Clone for SubstrateMessagesTarget

{ + fn clone(&self) -> Self { + Self { + target_client: self.target_client.clone(), + source_client: self.source_client.clone(), + lane_id: self.lane_id, + relayer_id_at_source: self.relayer_id_at_source.clone(), + transaction_params: self.transaction_params.clone(), + source_to_target_headers_relay: self.source_to_target_headers_relay.clone(), + } + } +} + +#[async_trait] +impl RelayClient for SubstrateMessagesTarget

{ + type Error = SubstrateError; + + async fn reconnect(&mut self) -> Result<(), SubstrateError> { + // since the client calls RPC methods on both sides, we need to reconnect both + self.target_client.reconnect().await?; + self.source_client.reconnect().await?; + + // call reconnect on on-demand headers relay, because we may use different chains there + // and the error that has lead to reconnect may have came from those other chains + // (see `require_source_header_on_target`) + // + // this may lead to multiple reconnects to the same node during the same call and it + // needs to be addressed in the future + // TODO: https://github.com/paritytech/parity-bridges-common/issues/1928 + if let Some(ref mut source_to_target_headers_relay) = self.source_to_target_headers_relay { + source_to_target_headers_relay.reconnect().await?; + } + + Ok(()) + } +} + +#[async_trait] +impl TargetClient> for SubstrateMessagesTarget

+where + AccountIdOf: From< as Pair>::Public>, + BalanceOf: TryFrom>, +{ + type BatchTransaction = + BatchProofTransaction; + type TransactionTracker = TransactionTracker>; + + async fn state(&self) -> Result>, SubstrateError> { + // we can't continue to deliver confirmations if source node is out of sync, because + // it may have already received confirmations that we're going to deliver + // + // we can't continue to deliver messages if target node is out of sync, because + // it may have already received (some of) messages that we're going to deliver + self.source_client.ensure_synced().await?; + self.target_client.ensure_synced().await?; + // we can't relay messages if messages pallet at target chain is halted + self.ensure_pallet_active().await?; + + read_client_state(&self.target_client, Some(&self.source_client)).await + } + + async fn latest_received_nonce( + &self, + id: TargetHeaderIdOf>, + ) -> Result<(TargetHeaderIdOf>, MessageNonce), SubstrateError> { + // lane data missing from the storage is fine until first message is received + let latest_received_nonce = self + .inbound_lane_data(id) + .await? + .map(|data| data.last_delivered_nonce()) + .unwrap_or(0); + Ok((id, latest_received_nonce)) + } + + async fn latest_confirmed_received_nonce( + &self, + id: TargetHeaderIdOf>, + ) -> Result<(TargetHeaderIdOf>, MessageNonce), SubstrateError> { + // lane data missing from the storage is fine until first message is received + let last_confirmed_nonce = self + .inbound_lane_data(id) + .await? + .map(|data| data.last_confirmed_nonce) + .unwrap_or(0); + Ok((id, last_confirmed_nonce)) + } + + async fn unrewarded_relayers_state( + &self, + id: TargetHeaderIdOf>, + ) -> Result<(TargetHeaderIdOf>, UnrewardedRelayersState), SubstrateError> + { + let inbound_lane_data = + self.inbound_lane_data(id).await?.unwrap_or(InboundLaneData::default()); + Ok((id, (&inbound_lane_data).into())) + } + + async fn prove_messages_receiving( + &self, + id: TargetHeaderIdOf>, + ) -> Result< + ( + TargetHeaderIdOf>, + as MessageLane>::MessagesReceivingProof, + ), + SubstrateError, + > { + let (id, relayers_state) = self.unrewarded_relayers_state(id).await?; + let inbound_data_key = bp_messages::storage_keys::inbound_lane_data_key( + P::SourceChain::WITH_CHAIN_MESSAGES_PALLET_NAME, + &self.lane_id, + ); + let proof = self + .target_client + .prove_storage(vec![inbound_data_key], id.1) + .await? + .into_iter_nodes() + .collect(); + let proof = FromBridgedChainMessagesDeliveryProof { + bridged_header_hash: id.1, + storage_proof: proof, + lane: self.lane_id, + }; + Ok((id, (relayers_state, proof))) + } + + async fn submit_messages_proof( + &self, + maybe_batch_tx: Option, + _generated_at_header: SourceHeaderIdOf>, + nonces: RangeInclusive, + proof: as MessageLane>::MessagesProof, + ) -> Result, SubstrateError> { + let messages_proof_call = make_messages_delivery_call::

( + self.relayer_id_at_source.clone(), + proof.1.nonces_start..=proof.1.nonces_end, + proof, + maybe_batch_tx.is_none(), + ); + let final_call = match maybe_batch_tx { + Some(batch_tx) => batch_tx.append_call_and_build(messages_proof_call), + None => messages_proof_call, + }; + + let transaction_params = self.transaction_params.clone(); + let tx_tracker = self + .target_client + .submit_and_watch_signed_extrinsic( + &self.transaction_params.signer, + move |best_block_id, transaction_nonce| { + Ok(UnsignedTransaction::new(final_call.into(), transaction_nonce) + .era(TransactionEra::new(best_block_id, transaction_params.mortality))) + }, + ) + .await?; + Ok(NoncesSubmitArtifacts { nonces, tx_tracker }) + } + + async fn require_source_header_on_target( + &self, + id: SourceHeaderIdOf>, + ) -> Result, SubstrateError> { + if let Some(ref source_to_target_headers_relay) = self.source_to_target_headers_relay { + if let Some(batch_tx) = + BatchProofTransaction::new(source_to_target_headers_relay.clone(), id.0).await? + { + return Ok(Some(batch_tx)) + } + + source_to_target_headers_relay.require_more_headers(id.0).await; + } + + Ok(None) + } +} + +/// Make messages delivery call from given proof. +fn make_messages_delivery_call( + relayer_id_at_source: AccountIdOf, + nonces: RangeInclusive, + proof: SubstrateMessagesProof, + trace_call: bool, +) -> CallOf { + let messages_count = nonces.end() - nonces.start() + 1; + let dispatch_weight = proof.0; + P::ReceiveMessagesProofCallBuilder::build_receive_messages_proof_call( + relayer_id_at_source, + proof, + messages_count as _, + dispatch_weight, + trace_call, + ) +} diff --git a/bridges/relays/lib-substrate-relay/src/on_demand/headers.rs b/bridges/relays/lib-substrate-relay/src/on_demand/headers.rs new file mode 100644 index 0000000000000..e8a2a3c6c58aa --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/on_demand/headers.rs @@ -0,0 +1,550 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! On-demand Substrate -> Substrate header finality relay. + +use crate::{ + finality::SubmitFinalityProofCallBuilder, finality_base::engine::MaxExpectedCallSizeCheck, +}; + +use async_std::sync::{Arc, Mutex}; +use async_trait::async_trait; +use bp_header_chain::ConsensusLogReader; +use bp_runtime::HeaderIdProvider; +use futures::{select, FutureExt}; +use num_traits::{One, Saturating, Zero}; +use sp_runtime::traits::Header; + +use finality_relay::{FinalitySyncParams, TargetClient as FinalityTargetClient}; +use relay_substrate_client::{ + AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain, Client, Error as SubstrateError, + HeaderIdOf, +}; +use relay_utils::{ + metrics::MetricsParams, relay_loop::Client as RelayClient, FailedClient, MaybeConnectionError, + STALL_TIMEOUT, +}; + +use crate::{ + finality::{ + source::{RequiredHeaderNumberRef, SubstrateFinalitySource}, + target::SubstrateFinalityTarget, + SubstrateFinalitySyncPipeline, RECENT_FINALITY_PROOFS_LIMIT, + }, + finality_base::engine::Engine, + on_demand::OnDemandRelay, + TransactionParams, +}; + +/// On-demand Substrate <-> Substrate header finality relay. +/// +/// This relay may be requested to sync more headers, whenever some other relay (e.g. messages +/// relay) needs it to continue its regular work. When enough headers are relayed, on-demand stops +/// syncing headers. +#[derive(Clone)] +pub struct OnDemandHeadersRelay { + /// Relay task name. + relay_task_name: String, + /// Shared reference to maximal required finalized header number. + required_header_number: RequiredHeaderNumberRef, + /// Client of the source chain. + source_client: Client, + /// Client of the target chain. + target_client: Client, +} + +impl OnDemandHeadersRelay

{ + /// Create new on-demand headers relay. + /// + /// If `metrics_params` is `Some(_)`, the metrics of the finality relay are registered. + /// Otherwise, all required metrics must be exposed outside of this method. + pub fn new( + source_client: Client, + target_client: Client, + target_transaction_params: TransactionParams>, + only_mandatory_headers: bool, + metrics_params: Option, + ) -> Self + where + AccountIdOf: + From< as sp_core::Pair>::Public>, + { + let required_header_number = Arc::new(Mutex::new(Zero::zero())); + let this = OnDemandHeadersRelay { + relay_task_name: on_demand_headers_relay_name::(), + required_header_number: required_header_number.clone(), + source_client: source_client.clone(), + target_client: target_client.clone(), + }; + async_std::task::spawn(async move { + background_task::

( + source_client, + target_client, + target_transaction_params, + only_mandatory_headers, + required_header_number, + metrics_params, + ) + .await; + }); + + this + } +} + +#[async_trait] +impl OnDemandRelay + for OnDemandHeadersRelay

+{ + async fn reconnect(&self) -> Result<(), SubstrateError> { + // using clone is fine here (to avoid mut requirement), because clone on Client clones + // internal references + self.source_client.clone().reconnect().await?; + self.target_client.clone().reconnect().await + } + + async fn require_more_headers(&self, required_header: BlockNumberOf) { + let mut required_header_number = self.required_header_number.lock().await; + if required_header > *required_header_number { + log::trace!( + target: "bridge", + "[{}] More {} headers required. Going to sync up to the {}", + self.relay_task_name, + P::SourceChain::NAME, + required_header, + ); + + *required_header_number = required_header; + } + } + + async fn prove_header( + &self, + required_header: BlockNumberOf, + ) -> Result<(HeaderIdOf, Vec>), SubstrateError> { + const MAX_ITERATIONS: u32 = 4; + let mut iterations = 0; + let mut current_required_header = required_header; + loop { + // first find proper header (either `current_required_header`) or its descendant + let finality_source = + SubstrateFinalitySource::

::new(self.source_client.clone(), None); + let (header, mut proof) = + finality_source.prove_block_finality(current_required_header).await?; + let header_id = header.id(); + + // verify and optimize justification before including it into the call + let context = P::FinalityEngine::verify_and_optimize_proof( + &self.target_client, + &header, + &mut proof, + ) + .await?; + + // now we have the header and its proof, but we want to minimize our losses, so let's + // check if we'll get the full refund for submitting this header + let check_result = P::FinalityEngine::check_max_expected_call_size(&header, &proof); + if let MaxExpectedCallSizeCheck::Exceeds { call_size, max_call_size } = check_result { + iterations += 1; + current_required_header = header_id.number().saturating_add(One::one()); + if iterations < MAX_ITERATIONS { + log::debug!( + target: "bridge", + "[{}] Requested to prove {} head {:?}. Selected to prove {} head {:?}. But it is too large: {} vs {}. \ + Going to select next header", + self.relay_task_name, + P::SourceChain::NAME, + required_header, + P::SourceChain::NAME, + header_id, + call_size, + max_call_size, + ); + + continue; + } + } + + log::debug!( + target: "bridge", + "[{}] Requested to prove {} head {:?}. Selected to prove {} head {:?} (after {} iterations)", + self.relay_task_name, + P::SourceChain::NAME, + required_header, + P::SourceChain::NAME, + header_id, + iterations, + ); + + // and then craft the submit-proof call + let call = P::SubmitFinalityProofCallBuilder::build_submit_finality_proof_call( + header, proof, context, + ); + + return Ok((header_id, vec![call])); + } + } +} + +/// Background task that is responsible for starting headers relay. +async fn background_task( + source_client: Client, + target_client: Client, + target_transaction_params: TransactionParams>, + only_mandatory_headers: bool, + required_header_number: RequiredHeaderNumberRef, + metrics_params: Option, +) where + AccountIdOf: From< as sp_core::Pair>::Public>, +{ + let relay_task_name = on_demand_headers_relay_name::(); + let target_transactions_mortality = target_transaction_params.mortality; + let mut finality_source = SubstrateFinalitySource::

::new( + source_client.clone(), + Some(required_header_number.clone()), + ); + let mut finality_target = + SubstrateFinalityTarget::new(target_client.clone(), target_transaction_params); + let mut latest_non_mandatory_at_source = Zero::zero(); + + let mut restart_relay = true; + let finality_relay_task = futures::future::Fuse::terminated(); + futures::pin_mut!(finality_relay_task); + + loop { + select! { + _ = async_std::task::sleep(P::TargetChain::AVERAGE_BLOCK_INTERVAL).fuse() => {}, + _ = finality_relay_task => { + // this should never happen in practice given the current code + restart_relay = true; + }, + } + + // read best finalized source header number from source + let best_finalized_source_header_at_source = + best_finalized_source_header_at_source(&finality_source, &relay_task_name).await; + if matches!(best_finalized_source_header_at_source, Err(ref e) if e.is_connection_error()) { + relay_utils::relay_loop::reconnect_failed_client( + FailedClient::Source, + relay_utils::relay_loop::RECONNECT_DELAY, + &mut finality_source, + &mut finality_target, + ) + .await; + continue + } + + // read best finalized source header number from target + let best_finalized_source_header_at_target = + best_finalized_source_header_at_target::

(&finality_target, &relay_task_name).await; + if matches!(best_finalized_source_header_at_target, Err(ref e) if e.is_connection_error()) { + relay_utils::relay_loop::reconnect_failed_client( + FailedClient::Target, + relay_utils::relay_loop::RECONNECT_DELAY, + &mut finality_source, + &mut finality_target, + ) + .await; + continue + } + + // submit mandatory header if some headers are missing + let best_finalized_source_header_at_source_fmt = + format!("{best_finalized_source_header_at_source:?}"); + let best_finalized_source_header_at_target_fmt = + format!("{best_finalized_source_header_at_target:?}"); + let required_header_number_value = *required_header_number.lock().await; + let mandatory_scan_range = mandatory_headers_scan_range::( + best_finalized_source_header_at_source.ok(), + best_finalized_source_header_at_target.ok(), + required_header_number_value, + ) + .await; + + log::trace!( + target: "bridge", + "[{}] Mandatory headers scan range: ({:?}, {:?}, {:?}) -> {:?}", + relay_task_name, + required_header_number_value, + best_finalized_source_header_at_source_fmt, + best_finalized_source_header_at_target_fmt, + mandatory_scan_range, + ); + + if let Some(mandatory_scan_range) = mandatory_scan_range { + let relay_mandatory_header_result = relay_mandatory_header_from_range( + &finality_source, + &required_header_number, + best_finalized_source_header_at_target_fmt, + ( + std::cmp::max(mandatory_scan_range.0, latest_non_mandatory_at_source), + mandatory_scan_range.1, + ), + &relay_task_name, + ) + .await; + match relay_mandatory_header_result { + Ok(true) => (), + Ok(false) => { + // there are no (or we don't need to relay them) mandatory headers in the range + // => to avoid scanning the same headers over and over again, remember that + latest_non_mandatory_at_source = mandatory_scan_range.1; + + log::trace!( + target: "bridge", + "[{}] No mandatory {} headers in the range {:?}", + relay_task_name, + P::SourceChain::NAME, + mandatory_scan_range, + ); + }, + Err(e) => { + log::warn!( + target: "bridge", + "[{}] Failed to scan mandatory {} headers range ({:?}): {:?}", + relay_task_name, + P::SourceChain::NAME, + mandatory_scan_range, + e, + ); + + if e.is_connection_error() { + relay_utils::relay_loop::reconnect_failed_client( + FailedClient::Source, + relay_utils::relay_loop::RECONNECT_DELAY, + &mut finality_source, + &mut finality_target, + ) + .await; + continue + } + }, + } + } + + // start/restart relay + if restart_relay { + let stall_timeout = relay_substrate_client::transaction_stall_timeout( + target_transactions_mortality, + P::TargetChain::AVERAGE_BLOCK_INTERVAL, + STALL_TIMEOUT, + ); + + log::info!( + target: "bridge", + "[{}] Starting on-demand headers relay task\n\t\ + Only mandatory headers: {}\n\t\ + Tx mortality: {:?} (~{}m)\n\t\ + Stall timeout: {:?}", + relay_task_name, + only_mandatory_headers, + target_transactions_mortality, + stall_timeout.as_secs_f64() / 60.0f64, + stall_timeout, + ); + + finality_relay_task.set( + finality_relay::run( + finality_source.clone(), + finality_target.clone(), + FinalitySyncParams { + tick: std::cmp::max( + P::SourceChain::AVERAGE_BLOCK_INTERVAL, + P::TargetChain::AVERAGE_BLOCK_INTERVAL, + ), + recent_finality_proofs_limit: RECENT_FINALITY_PROOFS_LIMIT, + stall_timeout, + only_mandatory_headers, + }, + metrics_params.clone().unwrap_or_else(MetricsParams::disabled), + futures::future::pending(), + ) + .fuse(), + ); + + restart_relay = false; + } + } +} + +/// Returns `Some()` with inclusive range of headers which must be scanned for mandatory headers +/// and the first of such headers must be submitted to the target node. +async fn mandatory_headers_scan_range( + best_finalized_source_header_at_source: Option, + best_finalized_source_header_at_target: Option, + required_header_number: BlockNumberOf, +) -> Option<(C::BlockNumber, C::BlockNumber)> { + // if we have been unable to read header number from the target, then let's assume + // that it is the same as required header number. Otherwise we risk submitting + // unneeded transactions + let best_finalized_source_header_at_target = + best_finalized_source_header_at_target.unwrap_or(required_header_number); + + // if we have been unable to read header number from the source, then let's assume + // that it is the same as at the target + let best_finalized_source_header_at_source = + best_finalized_source_header_at_source.unwrap_or(best_finalized_source_header_at_target); + + // if relay is already asked to sync more headers than we have at source, don't do anything yet + if required_header_number >= best_finalized_source_header_at_source { + return None + } + + Some(( + best_finalized_source_header_at_target + One::one(), + best_finalized_source_header_at_source, + )) +} + +/// Try to find mandatory header in the inclusive headers range and, if one is found, ask to relay +/// it. +/// +/// Returns `true` if header was found and (asked to be) relayed and `false` otherwise. +async fn relay_mandatory_header_from_range( + finality_source: &SubstrateFinalitySource

, + required_header_number: &RequiredHeaderNumberRef, + best_finalized_source_header_at_target: String, + range: (BlockNumberOf, BlockNumberOf), + relay_task_name: &str, +) -> Result { + // search for mandatory header first + let mandatory_source_header_number = + find_mandatory_header_in_range(finality_source, range).await?; + + // if there are no mandatory headers - we have nothing to do + let mandatory_source_header_number = match mandatory_source_header_number { + Some(mandatory_source_header_number) => mandatory_source_header_number, + None => return Ok(false), + }; + + // `find_mandatory_header` call may take a while => check if `required_header_number` is still + // less than our `mandatory_source_header_number` before logging anything + let mut required_header_number = required_header_number.lock().await; + if *required_header_number >= mandatory_source_header_number { + return Ok(false) + } + + log::trace!( + target: "bridge", + "[{}] Too many {} headers missing at target ({} vs {}). Going to sync up to the mandatory {}", + relay_task_name, + P::SourceChain::NAME, + best_finalized_source_header_at_target, + range.1, + mandatory_source_header_number, + ); + + *required_header_number = mandatory_source_header_number; + Ok(true) +} + +/// Read best finalized source block number from source client. +/// +/// Returns `None` if we have failed to read the number. +async fn best_finalized_source_header_at_source( + finality_source: &SubstrateFinalitySource

, + relay_task_name: &str, +) -> Result, relay_substrate_client::Error> { + finality_source.on_chain_best_finalized_block_number().await.map_err(|error| { + log::error!( + target: "bridge", + "[{}] Failed to read best finalized source header from source: {:?}", + relay_task_name, + error, + ); + + error + }) +} + +/// Read best finalized source block number from target client. +/// +/// Returns `None` if we have failed to read the number. +async fn best_finalized_source_header_at_target( + finality_target: &SubstrateFinalityTarget

, + relay_task_name: &str, +) -> Result, as RelayClient>::Error> +where + AccountIdOf: From< as sp_core::Pair>::Public>, +{ + finality_target + .best_finalized_source_block_id() + .await + .map_err(|error| { + log::error!( + target: "bridge", + "[{}] Failed to read best finalized source header from target: {:?}", + relay_task_name, + error, + ); + + error + }) + .map(|id| id.0) +} + +/// Read first mandatory header in given inclusive range. +/// +/// Returns `Ok(None)` if there were no mandatory headers in the range. +async fn find_mandatory_header_in_range( + finality_source: &SubstrateFinalitySource

, + range: (BlockNumberOf, BlockNumberOf), +) -> Result>, relay_substrate_client::Error> { + let mut current = range.0; + while current <= range.1 { + let header = finality_source.client().header_by_number(current).await?; + if >::ConsensusLogReader::schedules_authorities_change( + header.digest(), + ) { + return Ok(Some(current)) + } + + current += One::one(); + } + + Ok(None) +} + +/// On-demand headers relay task name. +fn on_demand_headers_relay_name() -> String { + format!("{}-to-{}-on-demand-headers", SourceChain::NAME, TargetChain::NAME) +} + +#[cfg(test)] +mod tests { + use super::*; + use relay_substrate_client::test_chain::TestChain; + + const AT_SOURCE: Option> = Some(10); + const AT_TARGET: Option> = Some(1); + + #[async_std::test] + async fn mandatory_headers_scan_range_selects_range_if_some_headers_are_missing() { + assert_eq!( + mandatory_headers_scan_range::(AT_SOURCE, AT_TARGET, 0,).await, + Some((AT_TARGET.unwrap() + 1, AT_SOURCE.unwrap())), + ); + } + + #[async_std::test] + async fn mandatory_headers_scan_range_selects_nothing_if_already_queued() { + assert_eq!( + mandatory_headers_scan_range::(AT_SOURCE, AT_TARGET, AT_SOURCE.unwrap(),) + .await, + None, + ); + } +} diff --git a/bridges/relays/lib-substrate-relay/src/on_demand/mod.rs b/bridges/relays/lib-substrate-relay/src/on_demand/mod.rs new file mode 100644 index 0000000000000..00bb33d674093 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/on_demand/mod.rs @@ -0,0 +1,48 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Types and functions intended to ease adding of new Substrate -> Substrate +//! on-demand pipelines. + +use async_trait::async_trait; +use relay_substrate_client::{BlockNumberOf, CallOf, Chain, Error as SubstrateError, HeaderIdOf}; + +pub mod headers; +pub mod parachains; + +/// On-demand headers relay that is relaying finalizing headers only when requested. +#[async_trait] +pub trait OnDemandRelay: Send + Sync { + /// Reconnect to source and target nodes. + async fn reconnect(&self) -> Result<(), SubstrateError>; + + /// Ask relay to relay source header with given number to the target chain. + /// + /// Depending on implementation, on-demand relay may also relay `required_header` ancestors + /// (e.g. if they're mandatory), or its descendants. The request is considered complete if + /// the best avbailable header at the target chain has number that is larger than or equal + /// to the `required_header`. + async fn require_more_headers(&self, required_header: BlockNumberOf); + + /// Ask relay to prove source `required_header` to the `TargetChain`. + /// + /// Returns number of header that is proved (it may be the `required_header` or one of its + /// descendants) and calls for delivering the proof. + async fn prove_header( + &self, + required_header: BlockNumberOf, + ) -> Result<(HeaderIdOf, Vec>), SubstrateError>; +} diff --git a/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs b/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs new file mode 100644 index 0000000000000..f67c002bba7f9 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs @@ -0,0 +1,1033 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! On-demand Substrate -> Substrate parachain finality relay. + +use crate::{ + messages_source::best_finalized_peer_header_at_self, + on_demand::OnDemandRelay, + parachains::{ + source::ParachainsSource, target::ParachainsTarget, ParachainsPipelineAdapter, + SubmitParachainHeadsCallBuilder, SubstrateParachainsPipeline, + }, + TransactionParams, +}; + +use async_std::{ + channel::{unbounded, Receiver, Sender}, + sync::{Arc, Mutex}, +}; +use async_trait::async_trait; +use bp_polkadot_core::parachains::{ParaHash, ParaId}; +use bp_runtime::HeaderIdProvider; +use futures::{select, FutureExt}; +use num_traits::Zero; +use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber}; +use parachains_relay::parachains_loop::{AvailableHeader, SourceClient, TargetClient}; +use relay_substrate_client::{ + is_ancient_block, AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain, Client, + Error as SubstrateError, HashOf, HeaderIdOf, ParachainBase, +}; +use relay_utils::{ + metrics::MetricsParams, relay_loop::Client as RelayClient, BlockNumberBase, FailedClient, + HeaderId, UniqueSaturatedInto, +}; +use std::fmt::Debug; + +/// On-demand Substrate <-> Substrate parachain finality relay. +/// +/// This relay may be requested to sync more parachain headers, whenever some other relay +/// (e.g. messages relay) needs it to continue its regular work. When enough parachain headers +/// are relayed, on-demand stops syncing headers. +#[derive(Clone)] +pub struct OnDemandParachainsRelay { + /// Relay task name. + relay_task_name: String, + /// Channel used to communicate with background task and ask for relay of parachain heads. + required_header_number_sender: Sender>, + /// Source relay chain client. + source_relay_client: Client, + /// Target chain client. + target_client: Client, + /// On-demand relay chain relay. + on_demand_source_relay_to_target_headers: + Arc>, +} + +impl OnDemandParachainsRelay

{ + /// Create new on-demand parachains relay. + /// + /// Note that the argument is the source relay chain client, not the parachain client. + /// That's because parachain finality is determined by the relay chain and we don't + /// need to connect to the parachain itself here. + pub fn new( + source_relay_client: Client, + target_client: Client, + target_transaction_params: TransactionParams>, + on_demand_source_relay_to_target_headers: Arc< + dyn OnDemandRelay, + >, + ) -> Self + where + P::SourceParachain: Chain, + P::SourceRelayChain: + Chain, + AccountIdOf: + From< as sp_core::Pair>::Public>, + { + let (required_header_number_sender, required_header_number_receiver) = unbounded(); + let this = OnDemandParachainsRelay { + relay_task_name: on_demand_parachains_relay_name::( + ), + required_header_number_sender, + source_relay_client: source_relay_client.clone(), + target_client: target_client.clone(), + on_demand_source_relay_to_target_headers: on_demand_source_relay_to_target_headers + .clone(), + }; + async_std::task::spawn(async move { + background_task::

( + source_relay_client, + target_client, + target_transaction_params, + on_demand_source_relay_to_target_headers, + required_header_number_receiver, + ) + .await; + }); + + this + } +} + +#[async_trait] +impl OnDemandRelay + for OnDemandParachainsRelay

+where + P::SourceParachain: Chain, +{ + async fn reconnect(&self) -> Result<(), SubstrateError> { + // using clone is fine here (to avoid mut requirement), because clone on Client clones + // internal references + self.source_relay_client.clone().reconnect().await?; + self.target_client.clone().reconnect().await?; + // we'll probably need to reconnect relay chain relayer clients also + self.on_demand_source_relay_to_target_headers.reconnect().await + } + + async fn require_more_headers(&self, required_header: BlockNumberOf) { + if let Err(e) = self.required_header_number_sender.send(required_header).await { + log::trace!( + target: "bridge", + "[{}] Failed to request {} header {:?}: {:?}", + self.relay_task_name, + P::SourceParachain::NAME, + required_header, + e, + ); + } + } + + /// Ask relay to prove source `required_header` to the `TargetChain`. + async fn prove_header( + &self, + required_parachain_header: BlockNumberOf, + ) -> Result<(HeaderIdOf, Vec>), SubstrateError> { + // select headers to prove + let parachains_source = ParachainsSource::

::new( + self.source_relay_client.clone(), + Arc::new(Mutex::new(AvailableHeader::Missing)), + ); + let env = (self, ¶chains_source); + let (need_to_prove_relay_block, selected_relay_block, selected_parachain_block) = + select_headers_to_prove(env, required_parachain_header).await?; + + log::debug!( + target: "bridge", + "[{}] Requested to prove {} head {:?}. Selected to prove {} head {:?} and {} head {:?}", + self.relay_task_name, + P::SourceParachain::NAME, + required_parachain_header, + P::SourceParachain::NAME, + selected_parachain_block, + P::SourceRelayChain::NAME, + if need_to_prove_relay_block { + Some(selected_relay_block) + } else { + None + }, + ); + + // now let's prove relay chain block (if needed) + let mut calls = Vec::new(); + let mut proved_relay_block = selected_relay_block; + if need_to_prove_relay_block { + let (relay_block, relay_prove_call) = self + .on_demand_source_relay_to_target_headers + .prove_header(selected_relay_block.number()) + .await?; + proved_relay_block = relay_block; + calls.extend(relay_prove_call); + } + + // despite what we've selected before (in `select_headers_to_prove` call), if headers relay + // have chose the different header (e.g. because there's no GRANDPA jusstification for it), + // we need to prove parachain head available at this header + let para_id = ParaId(P::SourceParachain::PARACHAIN_ID); + let mut proved_parachain_block = selected_parachain_block; + if proved_relay_block != selected_relay_block { + proved_parachain_block = parachains_source + .on_chain_para_head_id(proved_relay_block) + .await? + // this could happen e.g. if parachain has been offboarded? + .ok_or_else(|| { + SubstrateError::MissingRequiredParachainHead( + para_id, + proved_relay_block.number().unique_saturated_into(), + ) + })?; + + log::debug!( + target: "bridge", + "[{}] Selected to prove {} head {:?} and {} head {:?}. Instead proved {} head {:?} and {} head {:?}", + self.relay_task_name, + P::SourceParachain::NAME, + selected_parachain_block, + P::SourceRelayChain::NAME, + selected_relay_block, + P::SourceParachain::NAME, + proved_parachain_block, + P::SourceRelayChain::NAME, + proved_relay_block, + ); + } + + // and finally - prove parachain head + let (para_proof, para_hash) = + parachains_source.prove_parachain_head(proved_relay_block).await?; + calls.push(P::SubmitParachainHeadsCallBuilder::build_submit_parachain_heads_call( + proved_relay_block, + vec![(para_id, para_hash)], + para_proof, + )); + + Ok((proved_parachain_block, calls)) + } +} + +/// Background task that is responsible for starting parachain headers relay. +async fn background_task( + source_relay_client: Client, + target_client: Client, + target_transaction_params: TransactionParams>, + on_demand_source_relay_to_target_headers: Arc< + dyn OnDemandRelay, + >, + required_parachain_header_number_receiver: Receiver>, +) where + P::SourceParachain: Chain, + P::SourceRelayChain: + Chain, + AccountIdOf: From< as sp_core::Pair>::Public>, +{ + let relay_task_name = on_demand_parachains_relay_name::(); + let target_transactions_mortality = target_transaction_params.mortality; + + let mut relay_state = RelayState::Idle; + let mut required_parachain_header_number = Zero::zero(); + let required_para_header_ref = Arc::new(Mutex::new(AvailableHeader::Unavailable)); + + let mut restart_relay = true; + let parachains_relay_task = futures::future::Fuse::terminated(); + futures::pin_mut!(parachains_relay_task); + + let mut parachains_source = + ParachainsSource::

::new(source_relay_client.clone(), required_para_header_ref.clone()); + let mut parachains_target = + ParachainsTarget::

::new(target_client.clone(), target_transaction_params.clone()); + + loop { + select! { + new_required_parachain_header_number = required_parachain_header_number_receiver.recv().fuse() => { + let new_required_parachain_header_number = match new_required_parachain_header_number { + Ok(new_required_parachain_header_number) => new_required_parachain_header_number, + Err(e) => { + log::error!( + target: "bridge", + "[{}] Background task has exited with error: {:?}", + relay_task_name, + e, + ); + + return; + }, + }; + + // keep in mind that we are not updating `required_para_header_ref` here, because + // then we'll be submitting all previous headers as well (while required relay headers are + // delivered) and we want to avoid that (to reduce cost) + if new_required_parachain_header_number > required_parachain_header_number { + log::trace!( + target: "bridge", + "[{}] More {} headers required. Going to sync up to the {}", + relay_task_name, + P::SourceParachain::NAME, + new_required_parachain_header_number, + ); + + required_parachain_header_number = new_required_parachain_header_number; + } + }, + _ = async_std::task::sleep(P::TargetChain::AVERAGE_BLOCK_INTERVAL).fuse() => {}, + _ = parachains_relay_task => { + // this should never happen in practice given the current code + restart_relay = true; + }, + } + + // the workflow of the on-demand parachains relay is: + // + // 1) message relay (or any other dependent relay) sees new message at parachain header + // `PH`; + // + // 2) it sees that the target chain does not know `PH`; + // + // 3) it asks on-demand parachains relay to relay `PH` to the target chain; + // + // Phase#1: relaying relay chain header + // + // 4) on-demand parachains relay waits for GRANDPA-finalized block of the source relay chain + // `RH` that is storing `PH` or its descendant. Let it be `PH'`; + // 5) it asks on-demand headers relay to relay `RH` to the target chain; + // 6) it waits until `RH` (or its descendant) is relayed to the target chain; + // + // Phase#2: relaying parachain header + // + // 7) on-demand parachains relay sets `ParachainsSource::maximal_header_number` to the + // `PH'.number()`. + // 8) parachains finality relay sees that the parachain head has been updated and relays + // `PH'` to the target chain. + + // select headers to relay + let relay_data = read_relay_data( + ¶chains_source, + ¶chains_target, + required_parachain_header_number, + ) + .await; + match relay_data { + Ok(relay_data) => { + let prev_relay_state = relay_state; + relay_state = select_headers_to_relay(&relay_data, relay_state); + log::trace!( + target: "bridge", + "[{}] Selected new relay state: {:?} using old state {:?} and data {:?}", + relay_task_name, + relay_state, + prev_relay_state, + relay_data, + ); + }, + Err(failed_client) => { + relay_utils::relay_loop::reconnect_failed_client( + failed_client, + relay_utils::relay_loop::RECONNECT_DELAY, + &mut parachains_source, + &mut parachains_target, + ) + .await; + continue + }, + } + + // we have selected our new 'state' => let's notify our source clients about our new + // requirements + match relay_state { + RelayState::Idle => (), + RelayState::RelayingRelayHeader(required_relay_header) => { + on_demand_source_relay_to_target_headers + .require_more_headers(required_relay_header) + .await; + }, + RelayState::RelayingParaHeader(required_para_header) => { + *required_para_header_ref.lock().await = + AvailableHeader::Available(required_para_header); + }, + } + + // start/restart relay + if restart_relay { + let stall_timeout = relay_substrate_client::transaction_stall_timeout( + target_transactions_mortality, + P::TargetChain::AVERAGE_BLOCK_INTERVAL, + relay_utils::STALL_TIMEOUT, + ); + + log::info!( + target: "bridge", + "[{}] Starting on-demand-parachains relay task\n\t\ + Tx mortality: {:?} (~{}m)\n\t\ + Stall timeout: {:?}", + relay_task_name, + target_transactions_mortality, + stall_timeout.as_secs_f64() / 60.0f64, + stall_timeout, + ); + + parachains_relay_task.set( + parachains_relay::parachains_loop::run( + parachains_source.clone(), + parachains_target.clone(), + MetricsParams::disabled(), + futures::future::pending(), + ) + .fuse(), + ); + + restart_relay = false; + } + } +} + +/// On-demand parachains relay task name. +fn on_demand_parachains_relay_name() -> String { + format!("{}-to-{}-on-demand-parachain", SourceChain::NAME, TargetChain::NAME) +} + +/// On-demand relay state. +#[derive(Clone, Copy, Debug, PartialEq)] +enum RelayState { + /// On-demand relay is not doing anything. + Idle, + /// Relaying given relay header to relay given parachain header later. + RelayingRelayHeader(RelayNumber), + /// Relaying given parachain header. + RelayingParaHeader(HeaderId), +} + +/// Data gathered from source and target clients, used by on-demand relay. +#[derive(Debug)] +struct RelayData { + /// Parachain header number that is required at the target chain. + pub required_para_header: ParaNumber, + /// Parachain header number, known to the target chain. + pub para_header_at_target: Option, + /// Parachain header id, known to the source (relay) chain. + pub para_header_at_source: Option>, + /// Parachain header, that is available at the source relay chain at `relay_header_at_target` + /// block. + /// + /// May be `None` if there's no `relay_header_at_target` yet, or if the + /// `relay_header_at_target` is too old and we think its state has been pruned. + pub para_header_at_relay_header_at_target: Option>, + /// Relay header number at the source chain. + pub relay_header_at_source: RelayNumber, + /// Relay header number at the target chain. + pub relay_header_at_target: Option, +} + +/// Read required data from source and target clients. +async fn read_relay_data( + source: &ParachainsSource

, + target: &ParachainsTarget

, + required_header_number: BlockNumberOf, +) -> Result< + RelayData< + HashOf, + BlockNumberOf, + BlockNumberOf, + >, + FailedClient, +> +where + ParachainsTarget

: + TargetClient> + RelayClient, +{ + let map_target_err = |e| { + log::error!( + target: "bridge", + "[{}] Failed to read relay data from {} client: {:?}", + on_demand_parachains_relay_name::(), + P::TargetChain::NAME, + e, + ); + FailedClient::Target + }; + let map_source_err = |e| { + log::error!( + target: "bridge", + "[{}] Failed to read relay data from {} client: {:?}", + on_demand_parachains_relay_name::(), + P::SourceRelayChain::NAME, + e, + ); + FailedClient::Source + }; + + let best_target_block_hash = target.best_block().await.map_err(map_target_err)?.1; + let para_header_at_target = best_finalized_peer_header_at_self::< + P::TargetChain, + P::SourceParachain, + >(target.client(), best_target_block_hash) + .await; + // if there are no parachain heads at the target (`NoParachainHeadAtTarget`), we'll need to + // submit at least one. Otherwise the pallet will be treated as uninitialized and messages + // sync will stall. + let para_header_at_target = match para_header_at_target { + Ok(Some(para_header_at_target)) => Some(para_header_at_target.0), + Ok(None) => None, + Err(e) => return Err(map_target_err(e)), + }; + + let best_finalized_relay_header = + source.client().best_finalized_header().await.map_err(map_source_err)?; + let best_finalized_relay_block_id = best_finalized_relay_header.id(); + let para_header_at_source = source + .on_chain_para_head_id(best_finalized_relay_block_id) + .await + .map_err(map_source_err)?; + + let relay_header_at_source = best_finalized_relay_block_id.0; + let relay_header_at_target = best_finalized_peer_header_at_self::< + P::TargetChain, + P::SourceRelayChain, + >(target.client(), best_target_block_hash) + .await + .map_err(map_target_err)?; + + // if relay header at target is too old then its state may already be discarded at the source + // => just use `None` in this case + // + // the same is for case when there's no relay header at target at all + let available_relay_header_at_target = + relay_header_at_target.filter(|relay_header_at_target| { + !is_ancient_block(relay_header_at_target.number(), relay_header_at_source) + }); + let para_header_at_relay_header_at_target = + if let Some(available_relay_header_at_target) = available_relay_header_at_target { + source + .on_chain_para_head_id(available_relay_header_at_target) + .await + .map_err(map_source_err)? + } else { + None + }; + + Ok(RelayData { + required_para_header: required_header_number, + para_header_at_target, + para_header_at_source, + relay_header_at_source, + relay_header_at_target: relay_header_at_target + .map(|relay_header_at_target| relay_header_at_target.0), + para_header_at_relay_header_at_target, + }) +} + +/// Select relay and parachain headers that need to be relayed. +fn select_headers_to_relay( + data: &RelayData, + state: RelayState, +) -> RelayState +where + ParaHash: Clone, + ParaNumber: Copy + PartialOrd + Zero, + RelayNumber: Copy + Debug + Ord, +{ + // we can't do anything until **relay chain** bridge GRANDPA pallet is not initialized at the + // target chain + let relay_header_at_target = match data.relay_header_at_target { + Some(relay_header_at_target) => relay_header_at_target, + None => return RelayState::Idle, + }; + + // Process the `RelayingRelayHeader` state. + if let &RelayState::RelayingRelayHeader(relay_header_number) = &state { + if relay_header_at_target < relay_header_number { + // The required relay header hasn't yet been relayed. Ask / wait for it. + return state + } + + // We may switch to `RelayingParaHeader` if parachain head is available. + if let Some(para_header_at_relay_header_at_target) = + data.para_header_at_relay_header_at_target.as_ref() + { + return RelayState::RelayingParaHeader(para_header_at_relay_header_at_target.clone()) + } + + // else use the regular process - e.g. we may require to deliver new relay header first + } + + // Process the `RelayingParaHeader` state. + if let RelayState::RelayingParaHeader(para_header_id) = &state { + let para_header_at_target_or_zero = data.para_header_at_target.unwrap_or_else(Zero::zero); + if para_header_at_target_or_zero < para_header_id.0 { + // The required parachain header hasn't yet been relayed. Ask / wait for it. + return state + } + } + + // if we haven't read para head from the source, we can't yet do anything + let para_header_at_source = match data.para_header_at_source { + Some(ref para_header_at_source) => para_header_at_source.clone(), + None => return RelayState::Idle, + }; + + // if we have parachain head at the source, but no parachain heads at the target, we'll need + // to deliver at least one parachain head + let (required_para_header, para_header_at_target) = match data.para_header_at_target { + Some(para_header_at_target) => (data.required_para_header, para_header_at_target), + None => (para_header_at_source.0, Zero::zero()), + }; + + // if we have already satisfied our "customer", do nothing + if required_para_header <= para_header_at_target { + return RelayState::Idle + } + + // if required header is not available even at the source chain, let's wait + if required_para_header > para_header_at_source.0 { + return RelayState::Idle + } + + // we will always try to sync latest parachain/relay header, even if we've been asked for some + // its ancestor + + // we need relay chain header first + if relay_header_at_target < data.relay_header_at_source { + return RelayState::RelayingRelayHeader(data.relay_header_at_source) + } + + // if all relay headers synced, we may start directly with parachain header + RelayState::RelayingParaHeader(para_header_at_source) +} + +/// Environment for the `select_headers_to_prove` call. +#[async_trait] +trait SelectHeadersToProveEnvironment { + /// Returns associated parachain id. + fn parachain_id(&self) -> ParaId; + /// Returns best finalized relay block. + async fn best_finalized_relay_block_at_source( + &self, + ) -> Result, SubstrateError>; + /// Returns best finalized relay block that is known at `P::TargetChain`. + async fn best_finalized_relay_block_at_target( + &self, + ) -> Result, SubstrateError>; + /// Returns best finalized parachain block at given source relay chain block. + async fn best_finalized_para_block_at_source( + &self, + at_relay_block: HeaderId, + ) -> Result>, SubstrateError>; +} + +#[async_trait] +impl<'a, P: SubstrateParachainsPipeline> + SelectHeadersToProveEnvironment< + BlockNumberOf, + HashOf, + BlockNumberOf, + HashOf, + > for (&'a OnDemandParachainsRelay

, &'a ParachainsSource

) +{ + fn parachain_id(&self) -> ParaId { + ParaId(P::SourceParachain::PARACHAIN_ID) + } + + async fn best_finalized_relay_block_at_source( + &self, + ) -> Result, SubstrateError> { + Ok(self.0.source_relay_client.best_finalized_header().await?.id()) + } + + async fn best_finalized_relay_block_at_target( + &self, + ) -> Result, SubstrateError> { + Ok(crate::messages_source::read_client_state::( + &self.0.target_client, + None, + ) + .await? + .best_finalized_peer_at_best_self + .ok_or(SubstrateError::BridgePalletIsNotInitialized)?) + } + + async fn best_finalized_para_block_at_source( + &self, + at_relay_block: HeaderIdOf, + ) -> Result>, SubstrateError> { + self.1.on_chain_para_head_id(at_relay_block).await + } +} + +/// Given request to prove `required_parachain_header`, select actual headers that need to be +/// proved. +async fn select_headers_to_prove( + env: impl SelectHeadersToProveEnvironment, + required_parachain_header: PBN, +) -> Result<(bool, HeaderId, HeaderId), SubstrateError> +where + RBH: Copy, + RBN: BlockNumberBase, + PBH: Copy, + PBN: BlockNumberBase, +{ + // parachains proof also requires relay header proof. Let's first select relay block + // number that we'll be dealing with + let best_finalized_relay_block_at_source = env.best_finalized_relay_block_at_source().await?; + let best_finalized_relay_block_at_target = env.best_finalized_relay_block_at_target().await?; + + // if we can't prove `required_header` even using `best_finalized_relay_block_at_source`, we + // can't do anything here + // (this shall not actually happen, given current code, because we only require finalized + // headers) + let best_possible_parachain_block = env + .best_finalized_para_block_at_source(best_finalized_relay_block_at_source) + .await? + .filter(|best_possible_parachain_block| { + best_possible_parachain_block.number() >= required_parachain_header + }) + .ok_or(SubstrateError::MissingRequiredParachainHead( + env.parachain_id(), + required_parachain_header.unique_saturated_into(), + ))?; + + // we don't require source node to be archive, so we can't craft storage proofs using + // ancient headers. So if the `best_finalized_relay_block_at_target` is too ancient, we + // can't craft storage proofs using it + let may_use_state_at_best_finalized_relay_block_at_target = !is_ancient_block( + best_finalized_relay_block_at_target.number(), + best_finalized_relay_block_at_source.number(), + ); + + // now let's check if `required_header` may be proved using + // `best_finalized_relay_block_at_target` + let selection = if may_use_state_at_best_finalized_relay_block_at_target { + env.best_finalized_para_block_at_source(best_finalized_relay_block_at_target) + .await? + .filter(|best_finalized_para_block_at_target| { + best_finalized_para_block_at_target.number() >= required_parachain_header + }) + .map(|best_finalized_para_block_at_target| { + (false, best_finalized_relay_block_at_target, best_finalized_para_block_at_target) + }) + } else { + None + }; + + Ok(selection.unwrap_or(( + true, + best_finalized_relay_block_at_source, + best_possible_parachain_block, + ))) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn relay_waits_for_relay_header_to_be_delivered() { + assert_eq!( + select_headers_to_relay( + &RelayData { + required_para_header: 90, + para_header_at_target: Some(50), + para_header_at_source: Some(HeaderId(110, 110)), + relay_header_at_source: 800, + relay_header_at_target: Some(700), + para_header_at_relay_header_at_target: Some(HeaderId(100, 100)), + }, + RelayState::RelayingRelayHeader(750), + ), + RelayState::RelayingRelayHeader(750), + ); + } + + #[test] + fn relay_starts_relaying_requested_para_header_after_relay_header_is_delivered() { + assert_eq!( + select_headers_to_relay( + &RelayData { + required_para_header: 90, + para_header_at_target: Some(50), + para_header_at_source: Some(HeaderId(110, 110)), + relay_header_at_source: 800, + relay_header_at_target: Some(750), + para_header_at_relay_header_at_target: Some(HeaderId(100, 100)), + }, + RelayState::RelayingRelayHeader(750), + ), + RelayState::RelayingParaHeader(HeaderId(100, 100)), + ); + } + + #[test] + fn relay_selects_better_para_header_after_better_relay_header_is_delivered() { + assert_eq!( + select_headers_to_relay( + &RelayData { + required_para_header: 90, + para_header_at_target: Some(50), + para_header_at_source: Some(HeaderId(110, 110)), + relay_header_at_source: 800, + relay_header_at_target: Some(780), + para_header_at_relay_header_at_target: Some(HeaderId(105, 105)), + }, + RelayState::RelayingRelayHeader(750), + ), + RelayState::RelayingParaHeader(HeaderId(105, 105)), + ); + } + #[test] + fn relay_waits_for_para_header_to_be_delivered() { + assert_eq!( + select_headers_to_relay( + &RelayData { + required_para_header: 90, + para_header_at_target: Some(50), + para_header_at_source: Some(HeaderId(110, 110)), + relay_header_at_source: 800, + relay_header_at_target: Some(780), + para_header_at_relay_header_at_target: Some(HeaderId(105, 105)), + }, + RelayState::RelayingParaHeader(HeaderId(105, 105)), + ), + RelayState::RelayingParaHeader(HeaderId(105, 105)), + ); + } + + #[test] + fn relay_stays_idle_if_required_para_header_is_already_delivered() { + assert_eq!( + select_headers_to_relay( + &RelayData { + required_para_header: 90, + para_header_at_target: Some(105), + para_header_at_source: Some(HeaderId(110, 110)), + relay_header_at_source: 800, + relay_header_at_target: Some(780), + para_header_at_relay_header_at_target: Some(HeaderId(105, 105)), + }, + RelayState::Idle, + ), + RelayState::Idle, + ); + } + + #[test] + fn relay_waits_for_required_para_header_to_appear_at_source_1() { + assert_eq!( + select_headers_to_relay( + &RelayData { + required_para_header: 120, + para_header_at_target: Some(105), + para_header_at_source: None, + relay_header_at_source: 800, + relay_header_at_target: Some(780), + para_header_at_relay_header_at_target: Some(HeaderId(105, 105)), + }, + RelayState::Idle, + ), + RelayState::Idle, + ); + } + + #[test] + fn relay_waits_for_required_para_header_to_appear_at_source_2() { + assert_eq!( + select_headers_to_relay( + &RelayData { + required_para_header: 120, + para_header_at_target: Some(105), + para_header_at_source: Some(HeaderId(110, 110)), + relay_header_at_source: 800, + relay_header_at_target: Some(780), + para_header_at_relay_header_at_target: Some(HeaderId(105, 105)), + }, + RelayState::Idle, + ), + RelayState::Idle, + ); + } + + #[test] + fn relay_starts_relaying_relay_header_when_new_para_header_is_requested() { + assert_eq!( + select_headers_to_relay( + &RelayData { + required_para_header: 120, + para_header_at_target: Some(105), + para_header_at_source: Some(HeaderId(125, 125)), + relay_header_at_source: 800, + relay_header_at_target: Some(780), + para_header_at_relay_header_at_target: Some(HeaderId(105, 105)), + }, + RelayState::Idle, + ), + RelayState::RelayingRelayHeader(800), + ); + } + + #[test] + fn relay_starts_relaying_para_header_when_new_para_header_is_requested() { + assert_eq!( + select_headers_to_relay( + &RelayData { + required_para_header: 120, + para_header_at_target: Some(105), + para_header_at_source: Some(HeaderId(125, 125)), + relay_header_at_source: 800, + relay_header_at_target: Some(800), + para_header_at_relay_header_at_target: Some(HeaderId(125, 125)), + }, + RelayState::Idle, + ), + RelayState::RelayingParaHeader(HeaderId(125, 125)), + ); + } + + #[test] + fn relay_goes_idle_when_parachain_is_deregistered() { + assert_eq!( + select_headers_to_relay::( + &RelayData { + required_para_header: 120, + para_header_at_target: Some(105), + para_header_at_source: None, + relay_header_at_source: 800, + relay_header_at_target: Some(800), + para_header_at_relay_header_at_target: None, + }, + RelayState::RelayingRelayHeader(800), + ), + RelayState::Idle, + ); + } + + #[test] + fn relay_starts_relaying_first_parachain_header() { + assert_eq!( + select_headers_to_relay::( + &RelayData { + required_para_header: 0, + para_header_at_target: None, + para_header_at_source: Some(HeaderId(125, 125)), + relay_header_at_source: 800, + relay_header_at_target: Some(800), + para_header_at_relay_header_at_target: Some(HeaderId(125, 125)), + }, + RelayState::Idle, + ), + RelayState::RelayingParaHeader(HeaderId(125, 125)), + ); + } + + #[test] + fn relay_starts_relaying_relay_header_to_relay_first_parachain_header() { + assert_eq!( + select_headers_to_relay::( + &RelayData { + required_para_header: 0, + para_header_at_target: None, + para_header_at_source: Some(HeaderId(125, 125)), + relay_header_at_source: 800, + relay_header_at_target: Some(700), + para_header_at_relay_header_at_target: Some(HeaderId(125, 125)), + }, + RelayState::Idle, + ), + RelayState::RelayingRelayHeader(800), + ); + } + + // tuple is: + // + // - best_finalized_relay_block_at_source + // - best_finalized_relay_block_at_target + // - best_finalized_para_block_at_source at best_finalized_relay_block_at_source + // - best_finalized_para_block_at_source at best_finalized_relay_block_at_target + #[async_trait] + impl SelectHeadersToProveEnvironment for (u32, u32, u32, u32) { + fn parachain_id(&self) -> ParaId { + ParaId(0) + } + + async fn best_finalized_relay_block_at_source( + &self, + ) -> Result, SubstrateError> { + Ok(HeaderId(self.0, self.0)) + } + + async fn best_finalized_relay_block_at_target( + &self, + ) -> Result, SubstrateError> { + Ok(HeaderId(self.1, self.1)) + } + + async fn best_finalized_para_block_at_source( + &self, + at_relay_block: HeaderId, + ) -> Result>, SubstrateError> { + if at_relay_block.0 == self.0 { + Ok(Some(HeaderId(self.2, self.2))) + } else if at_relay_block.0 == self.1 { + Ok(Some(HeaderId(self.3, self.3))) + } else { + Ok(None) + } + } + } + + #[async_std::test] + async fn select_headers_to_prove_returns_err_if_required_para_block_is_missing_at_source() { + assert!(matches!( + select_headers_to_prove((20_u32, 10_u32, 200_u32, 100_u32), 300_u32,).await, + Err(SubstrateError::MissingRequiredParachainHead(ParaId(0), 300_u64)), + )); + } + + #[async_std::test] + async fn select_headers_to_prove_fails_to_use_existing_ancient_relay_block() { + assert_eq!( + select_headers_to_prove((220_u32, 10_u32, 200_u32, 100_u32), 100_u32,) + .await + .map_err(drop), + Ok((true, HeaderId(220, 220), HeaderId(200, 200))), + ); + } + + #[async_std::test] + async fn select_headers_to_prove_is_able_to_use_existing_recent_relay_block() { + assert_eq!( + select_headers_to_prove((40_u32, 10_u32, 200_u32, 100_u32), 100_u32,) + .await + .map_err(drop), + Ok((false, HeaderId(10, 10), HeaderId(100, 100))), + ); + } + + #[async_std::test] + async fn select_headers_to_prove_uses_new_relay_block() { + assert_eq!( + select_headers_to_prove((20_u32, 10_u32, 200_u32, 100_u32), 200_u32,) + .await + .map_err(drop), + Ok((true, HeaderId(20, 20), HeaderId(200, 200))), + ); + } +} diff --git a/bridges/relays/lib-substrate-relay/src/parachains/mod.rs b/bridges/relays/lib-substrate-relay/src/parachains/mod.rs new file mode 100644 index 0000000000000..722f9b61f9f08 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/parachains/mod.rs @@ -0,0 +1,108 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Types and functions intended to ease adding of new Substrate -> Substrate +//! parachain finality proofs synchronization pipelines. + +use async_trait::async_trait; +use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId}; +use pallet_bridge_parachains::{ + Call as BridgeParachainsCall, Config as BridgeParachainsConfig, RelayBlockHash, + RelayBlockHasher, RelayBlockNumber, +}; +use parachains_relay::ParachainsPipeline; +use relay_substrate_client::{ + CallOf, Chain, ChainWithTransactions, HeaderIdOf, Parachain, RelayChain, +}; +use std::{fmt::Debug, marker::PhantomData}; + +pub mod source; +pub mod target; + +/// Substrate -> Substrate parachain finality proofs synchronization pipeline. +/// +/// This is currently restricted to the single parachain, because it is how it +/// will be used (at least) initially. +#[async_trait] +pub trait SubstrateParachainsPipeline: 'static + Clone + Debug + Send + Sync { + /// Headers of this parachain are submitted to the `Self::TargetChain`. + type SourceParachain: Parachain; + /// Relay chain that is storing headers of `Self::SourceParachain`. + type SourceRelayChain: RelayChain; + /// Target chain where `Self::SourceParachain` headers are submitted. + type TargetChain: ChainWithTransactions; + + /// How submit parachains heads call is built? + type SubmitParachainHeadsCallBuilder: SubmitParachainHeadsCallBuilder; +} + +/// Adapter that allows all `SubstrateParachainsPipeline` to act as `ParachainsPipeline`. +#[derive(Clone, Debug)] +pub struct ParachainsPipelineAdapter { + _phantom: PhantomData

, +} + +impl ParachainsPipeline for ParachainsPipelineAdapter

{ + type SourceParachain = P::SourceParachain; + type SourceRelayChain = P::SourceRelayChain; + type TargetChain = P::TargetChain; +} + +/// Different ways of building `submit_parachain_heads` calls. +pub trait SubmitParachainHeadsCallBuilder: + 'static + Send + Sync +{ + /// Given parachains and their heads proof, build call of `submit_parachain_heads` + /// function of bridge parachains module at the target chain. + fn build_submit_parachain_heads_call( + at_relay_block: HeaderIdOf, + parachains: Vec<(ParaId, ParaHash)>, + parachain_heads_proof: ParaHeadsProof, + ) -> CallOf; +} + +/// Building `submit_parachain_heads` call when you have direct access to the target +/// chain runtime. +pub struct DirectSubmitParachainHeadsCallBuilder { + _phantom: PhantomData<(P, R, I)>, +} + +impl SubmitParachainHeadsCallBuilder

for DirectSubmitParachainHeadsCallBuilder +where + P: SubstrateParachainsPipeline, + P::SourceRelayChain: Chain, + R: BridgeParachainsConfig + Send + Sync, + I: 'static + Send + Sync, + R::BridgedChain: bp_runtime::Chain< + BlockNumber = RelayBlockNumber, + Hash = RelayBlockHash, + Hasher = RelayBlockHasher, + >, + CallOf: From>, +{ + fn build_submit_parachain_heads_call( + at_relay_block: HeaderIdOf, + parachains: Vec<(ParaId, ParaHash)>, + parachain_heads_proof: ParaHeadsProof, + ) -> CallOf { + BridgeParachainsCall::::submit_parachain_heads { + at_relay_block: (at_relay_block.0, at_relay_block.1), + parachains, + parachain_heads_proof, + } + .into() + } +} diff --git a/bridges/relays/lib-substrate-relay/src/parachains/source.rs b/bridges/relays/lib-substrate-relay/src/parachains/source.rs new file mode 100644 index 0000000000000..4cc512b9d9b45 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/parachains/source.rs @@ -0,0 +1,181 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Parachain heads source. + +use crate::parachains::{ParachainsPipelineAdapter, SubstrateParachainsPipeline}; + +use async_std::sync::{Arc, Mutex}; +use async_trait::async_trait; +use bp_parachains::parachain_head_storage_key_at_source; +use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId}; +use bp_runtime::HeaderIdProvider; +use codec::Decode; +use parachains_relay::parachains_loop::{AvailableHeader, SourceClient}; +use relay_substrate_client::{ + is_ancient_block, Chain, Client, Error as SubstrateError, HeaderIdOf, HeaderOf, ParachainBase, + RelayChain, +}; +use relay_utils::relay_loop::Client as RelayClient; + +/// Shared updatable reference to the maximal parachain header id that we want to sync from the +/// source. +pub type RequiredHeaderIdRef = Arc>>>; + +/// Substrate client as parachain heads source. +#[derive(Clone)] +pub struct ParachainsSource { + client: Client, + max_head_id: RequiredHeaderIdRef, +} + +impl ParachainsSource

{ + /// Creates new parachains source client. + pub fn new( + client: Client, + max_head_id: RequiredHeaderIdRef, + ) -> Self { + ParachainsSource { client, max_head_id } + } + + /// Returns reference to the underlying RPC client. + pub fn client(&self) -> &Client { + &self.client + } + + /// Return decoded head of given parachain. + pub async fn on_chain_para_head_id( + &self, + at_block: HeaderIdOf, + ) -> Result>, SubstrateError> { + let para_id = ParaId(P::SourceParachain::PARACHAIN_ID); + let storage_key = + parachain_head_storage_key_at_source(P::SourceRelayChain::PARAS_PALLET_NAME, para_id); + let para_head = self.client.raw_storage_value(storage_key, Some(at_block.1)).await?; + let para_head = para_head.map(|h| ParaHead::decode(&mut &h.0[..])).transpose()?; + let para_head = match para_head { + Some(para_head) => para_head, + None => return Ok(None), + }; + let para_head: HeaderOf = Decode::decode(&mut ¶_head.0[..])?; + Ok(Some(para_head.id())) + } +} + +#[async_trait] +impl RelayClient for ParachainsSource

{ + type Error = SubstrateError; + + async fn reconnect(&mut self) -> Result<(), SubstrateError> { + self.client.reconnect().await + } +} + +#[async_trait] +impl SourceClient> + for ParachainsSource

+where + P::SourceParachain: Chain, +{ + async fn ensure_synced(&self) -> Result { + match self.client.ensure_synced().await { + Ok(_) => Ok(true), + Err(SubstrateError::ClientNotSynced(_)) => Ok(false), + Err(e) => Err(e), + } + } + + async fn parachain_head( + &self, + at_block: HeaderIdOf, + ) -> Result>, Self::Error> { + // if requested relay header is ancient, then we don't even want to try to read the + // parachain head - we simply return `Unavailable` + let best_block_number = self.client.best_finalized_header_number().await?; + if is_ancient_block(at_block.number(), best_block_number) { + log::trace!( + target: "bridge", + "{} block {:?} is ancient. Cannot prove the {} header there", + P::SourceRelayChain::NAME, + at_block, + P::SourceParachain::NAME, + ); + return Ok(AvailableHeader::Unavailable) + } + + // else - try to read head from the source client + let mut para_head_id = AvailableHeader::Missing; + if let Some(on_chain_para_head_id) = self.on_chain_para_head_id(at_block).await? { + // Never return head that is larger than requested. This way we'll never sync + // headers past `max_header_id`. + para_head_id = match *self.max_head_id.lock().await { + AvailableHeader::Unavailable => AvailableHeader::Unavailable, + AvailableHeader::Missing => { + // `max_header_id` is not set. There is no limit. + AvailableHeader::Available(on_chain_para_head_id) + }, + AvailableHeader::Available(max_head_id) if on_chain_para_head_id >= max_head_id => { + // We report at most `max_header_id`. + AvailableHeader::Available(std::cmp::min(on_chain_para_head_id, max_head_id)) + }, + AvailableHeader::Available(_) => { + // the `max_head_id` is not yet available at the source chain => wait and avoid + // syncing extra headers + AvailableHeader::Unavailable + }, + } + } + + Ok(para_head_id) + } + + async fn prove_parachain_head( + &self, + at_block: HeaderIdOf, + ) -> Result<(ParaHeadsProof, ParaHash), Self::Error> { + let parachain = ParaId(P::SourceParachain::PARACHAIN_ID); + let storage_key = + parachain_head_storage_key_at_source(P::SourceRelayChain::PARAS_PALLET_NAME, parachain); + let parachain_heads_proof = self + .client + .prove_storage(vec![storage_key.clone()], at_block.1) + .await? + .into_iter_nodes() + .collect(); + + // why we're reading parachain head here once again (it has already been read at the + // `parachain_head`)? that's because `parachain_head` sometimes returns obsolete parachain + // head and loop sometimes asks to prove this obsolete head and gets other (actual) head + // instead + // + // => since we want to provide proper hashes in our `submit_parachain_heads` call, we're + // rereading actual value here + let parachain_head = self + .client + .raw_storage_value(storage_key, Some(at_block.1)) + .await? + .map(|h| ParaHead::decode(&mut &h.0[..])) + .transpose()? + .ok_or_else(|| { + SubstrateError::Custom(format!( + "Failed to read expected parachain {parachain:?} head at {at_block:?}" + )) + })?; + let parachain_head_hash = parachain_head.hash(); + + Ok((ParaHeadsProof { storage_proof: parachain_heads_proof }, parachain_head_hash)) + } +} diff --git a/bridges/relays/lib-substrate-relay/src/parachains/target.rs b/bridges/relays/lib-substrate-relay/src/parachains/target.rs new file mode 100644 index 0000000000000..6df7bc0a742a9 --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/parachains/target.rs @@ -0,0 +1,148 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Parachain heads target. + +use crate::{ + parachains::{ + ParachainsPipelineAdapter, SubmitParachainHeadsCallBuilder, SubstrateParachainsPipeline, + }, + TransactionParams, +}; + +use async_trait::async_trait; +use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId}; +use bp_runtime::HeaderIdProvider; +use codec::Decode; +use parachains_relay::parachains_loop::TargetClient; +use relay_substrate_client::{ + AccountIdOf, AccountKeyPairOf, Chain, Client, Error as SubstrateError, HeaderIdOf, + ParachainBase, TransactionEra, TransactionTracker, UnsignedTransaction, +}; +use relay_utils::relay_loop::Client as RelayClient; +use sp_core::{Bytes, Pair}; + +/// Substrate client as parachain heads source. +pub struct ParachainsTarget { + client: Client, + transaction_params: TransactionParams>, +} + +impl ParachainsTarget

{ + /// Creates new parachains target client. + pub fn new( + client: Client, + transaction_params: TransactionParams>, + ) -> Self { + ParachainsTarget { client, transaction_params } + } + + /// Returns reference to the underlying RPC client. + pub fn client(&self) -> &Client { + &self.client + } +} + +impl Clone for ParachainsTarget

{ + fn clone(&self) -> Self { + ParachainsTarget { + client: self.client.clone(), + transaction_params: self.transaction_params.clone(), + } + } +} + +#[async_trait] +impl RelayClient for ParachainsTarget

{ + type Error = SubstrateError; + + async fn reconnect(&mut self) -> Result<(), SubstrateError> { + self.client.reconnect().await + } +} + +#[async_trait] +impl

TargetClient> for ParachainsTarget

+where + P: SubstrateParachainsPipeline, + AccountIdOf: From< as Pair>::Public>, +{ + type TransactionTracker = TransactionTracker>; + + async fn best_block(&self) -> Result, Self::Error> { + let best_header = self.client.best_header().await?; + let best_id = best_header.id(); + + Ok(best_id) + } + + async fn best_finalized_source_relay_chain_block( + &self, + at_block: &HeaderIdOf, + ) -> Result, Self::Error> { + self.client + .typed_state_call::<_, Option>>( + P::SourceRelayChain::BEST_FINALIZED_HEADER_ID_METHOD.into(), + (), + Some(at_block.1), + ) + .await? + .map(Ok) + .unwrap_or(Err(SubstrateError::BridgePalletIsNotInitialized)) + } + + async fn parachain_head( + &self, + at_block: HeaderIdOf, + ) -> Result>, Self::Error> { + let encoded_best_finalized_source_para_block = self + .client + .state_call( + P::SourceParachain::BEST_FINALIZED_HEADER_ID_METHOD.into(), + Bytes(Vec::new()), + Some(at_block.1), + ) + .await?; + + Ok(Option::>::decode( + &mut &encoded_best_finalized_source_para_block.0[..], + ) + .map_err(SubstrateError::ResponseParseFailed)?) + } + + async fn submit_parachain_head_proof( + &self, + at_relay_block: HeaderIdOf, + updated_head_hash: ParaHash, + proof: ParaHeadsProof, + ) -> Result { + let transaction_params = self.transaction_params.clone(); + let call = P::SubmitParachainHeadsCallBuilder::build_submit_parachain_heads_call( + at_relay_block, + vec![(ParaId(P::SourceParachain::PARACHAIN_ID), updated_head_hash)], + proof, + ); + self.client + .submit_and_watch_signed_extrinsic( + &transaction_params.signer, + move |best_block_id, transaction_nonce| { + Ok(UnsignedTransaction::new(call.into(), transaction_nonce) + .era(TransactionEra::new(best_block_id, transaction_params.mortality))) + }, + ) + .await + } +} diff --git a/bridges/relays/messages/Cargo.toml b/bridges/relays/messages/Cargo.toml new file mode 100644 index 0000000000000..8a411e5082fa4 --- /dev/null +++ b/bridges/relays/messages/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "messages-relay" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +repository.workspace = true +publish = false + +[lints] +workspace = true + +[dependencies] +async-std = { version = "1.9.0", features = ["attributes"] } +async-trait = "0.1.79" +env_logger = "0.11" +futures = "0.3.30" +hex = "0.4" +log = { workspace = true } +num-traits = "0.2" +parking_lot = "0.12.1" + +# Bridge Dependencies + +bp-messages = { path = "../../primitives/messages" } +finality-relay = { path = "../finality" } +relay-utils = { path = "../utils" } + +sp-arithmetic = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } diff --git a/bridges/relays/messages/src/lib.rs b/bridges/relays/messages/src/lib.rs new file mode 100644 index 0000000000000..9c62cee5ee3db --- /dev/null +++ b/bridges/relays/messages/src/lib.rs @@ -0,0 +1,37 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Relaying [`pallet-bridge-messages`](../pallet_bridge_messages/index.html) application specific +//! data. Message lane allows sending arbitrary messages between bridged chains. This +//! module provides entrypoint that starts reading messages from given message lane +//! of source chain and submits proof-of-message-at-source-chain transactions to the +//! target chain. Additionally, proofs-of-messages-delivery are sent back from the +//! target chain to the source chain. + +// required for futures::select! +#![recursion_limit = "1024"] +#![warn(missing_docs)] + +mod metrics; + +pub mod message_lane; +pub mod message_lane_loop; + +mod message_race_delivery; +mod message_race_limits; +mod message_race_loop; +mod message_race_receiving; +mod message_race_strategy; diff --git a/bridges/relays/messages/src/message_lane.rs b/bridges/relays/messages/src/message_lane.rs new file mode 100644 index 0000000000000..5c9728ad93abd --- /dev/null +++ b/bridges/relays/messages/src/message_lane.rs @@ -0,0 +1,71 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! One-way message lane types. Within single one-way lane we have three 'races' where we try to: +//! +//! 1) relay new messages from source to target node; +//! 2) relay proof-of-delivery from target to source node. + +use num_traits::{SaturatingAdd, Zero}; +use relay_utils::{BlockNumberBase, HeaderId}; +use sp_arithmetic::traits::AtLeast32BitUnsigned; +use std::{fmt::Debug, ops::Sub}; + +/// One-way message lane. +pub trait MessageLane: 'static + Clone + Send + Sync { + /// Name of the messages source. + const SOURCE_NAME: &'static str; + /// Name of the messages target. + const TARGET_NAME: &'static str; + + /// Messages proof. + type MessagesProof: Clone + Debug + Send + Sync; + /// Messages receiving proof. + type MessagesReceivingProof: Clone + Debug + Send + Sync; + + /// The type of the source chain token balance, that is used to: + /// + /// 1) pay transaction fees; + /// 2) pay message delivery and dispatch fee; + /// 3) pay relayer rewards. + type SourceChainBalance: AtLeast32BitUnsigned + + Clone + + Copy + + Debug + + PartialOrd + + Sub + + SaturatingAdd + + Zero + + Send + + Sync; + /// Number of the source header. + type SourceHeaderNumber: BlockNumberBase; + /// Hash of the source header. + type SourceHeaderHash: Clone + Debug + Default + PartialEq + Send + Sync; + + /// Number of the target header. + type TargetHeaderNumber: BlockNumberBase; + /// Hash of the target header. + type TargetHeaderHash: Clone + Debug + Default + PartialEq + Send + Sync; +} + +/// Source header id within given one-way message lane. +pub type SourceHeaderIdOf

= + HeaderId<

::SourceHeaderHash,

::SourceHeaderNumber>; + +/// Target header id within given one-way message lane. +pub type TargetHeaderIdOf

= + HeaderId<

::TargetHeaderHash,

::TargetHeaderNumber>; diff --git a/bridges/relays/messages/src/message_lane_loop.rs b/bridges/relays/messages/src/message_lane_loop.rs new file mode 100644 index 0000000000000..b681d86d2ae8f --- /dev/null +++ b/bridges/relays/messages/src/message_lane_loop.rs @@ -0,0 +1,1277 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Message delivery loop. Designed to work with messages pallet. +//! +//! Single relay instance delivers messages of single lane in single direction. +//! To serve two-way lane, you would need two instances of relay. +//! To serve N two-way lanes, you would need N*2 instances of relay. +//! +//! Please keep in mind that the best header in this file is actually best +//! finalized header. I.e. when talking about headers in lane context, we +//! only care about finalized headers. + +use std::{collections::BTreeMap, fmt::Debug, future::Future, ops::RangeInclusive, time::Duration}; + +use async_trait::async_trait; +use futures::{channel::mpsc::unbounded, future::FutureExt, stream::StreamExt}; + +use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState, Weight}; +use relay_utils::{ + interval, metrics::MetricsParams, process_future_result, relay_loop::Client as RelayClient, + retry_backoff, FailedClient, TransactionTracker, +}; + +use crate::{ + message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, + message_race_delivery::run as run_message_delivery_race, + message_race_receiving::run as run_message_receiving_race, + metrics::MessageLaneLoopMetrics, +}; + +/// Message lane loop configuration params. +#[derive(Debug, Clone)] +pub struct Params { + /// Id of lane this loop is servicing. + pub lane: LaneId, + /// Interval at which we ask target node about its updates. + pub source_tick: Duration, + /// Interval at which we ask target node about its updates. + pub target_tick: Duration, + /// Delay between moments when connection error happens and our reconnect attempt. + pub reconnect_delay: Duration, + /// Message delivery race parameters. + pub delivery_params: MessageDeliveryParams, +} + +/// Message delivery race parameters. +#[derive(Debug, Clone)] +pub struct MessageDeliveryParams { + /// Maximal number of unconfirmed relayer entries at the inbound lane. If there's that number + /// of entries in the `InboundLaneData::relayers` set, all new messages will be rejected until + /// reward payment will be proved (by including outbound lane state to the message delivery + /// transaction). + pub max_unrewarded_relayer_entries_at_target: MessageNonce, + /// Message delivery race will stop delivering messages if there are + /// `max_unconfirmed_nonces_at_target` unconfirmed nonces on the target node. The race would + /// continue once they're confirmed by the receiving race. + pub max_unconfirmed_nonces_at_target: MessageNonce, + /// Maximal number of relayed messages in single delivery transaction. + pub max_messages_in_single_batch: MessageNonce, + /// Maximal cumulative dispatch weight of relayed messages in single delivery transaction. + pub max_messages_weight_in_single_batch: Weight, + /// Maximal cumulative size of relayed messages in single delivery transaction. + pub max_messages_size_in_single_batch: u32, +} + +/// Message details. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MessageDetails { + /// Message dispatch weight. + pub dispatch_weight: Weight, + /// Message size (number of bytes in encoded payload). + pub size: u32, + /// The relayer reward paid in the source chain tokens. + pub reward: SourceChainBalance, +} + +/// Messages details map. +pub type MessageDetailsMap = + BTreeMap>; + +/// Message delivery race proof parameters. +#[derive(Debug, PartialEq, Eq)] +pub struct MessageProofParameters { + /// Include outbound lane state proof? + pub outbound_state_proof_required: bool, + /// Cumulative dispatch weight of messages that we're building proof for. + pub dispatch_weight: Weight, +} + +/// Artifacts of submitting nonces proof. +pub struct NoncesSubmitArtifacts { + /// Submitted nonces range. + pub nonces: RangeInclusive, + /// Submitted transaction tracker. + pub tx_tracker: T, +} + +/// Batch transaction that already submit some headers and needs to be extended with +/// messages/delivery proof before sending. +pub trait BatchTransaction: Debug + Send + Sync { + /// Header that was required in the original call and which is bundled within this + /// batch transaction. + fn required_header_id(&self) -> HeaderId; +} + +/// Source client trait. +#[async_trait] +pub trait SourceClient: RelayClient { + /// Type of batch transaction that submits finality and message receiving proof. + type BatchTransaction: BatchTransaction> + Clone; + /// Transaction tracker to track submitted transactions. + type TransactionTracker: TransactionTracker>; + + /// Returns state of the client. + async fn state(&self) -> Result, Self::Error>; + + /// Get nonce of instance of latest generated message. + async fn latest_generated_nonce( + &self, + id: SourceHeaderIdOf

, + ) -> Result<(SourceHeaderIdOf

, MessageNonce), Self::Error>; + + /// Get nonce of the latest message, which receiving has been confirmed by the target chain. + async fn latest_confirmed_received_nonce( + &self, + id: SourceHeaderIdOf

, + ) -> Result<(SourceHeaderIdOf

, MessageNonce), Self::Error>; + + /// Returns mapping of message nonces, generated on this client, to their weights. + /// + /// Some messages may be missing from returned map, if corresponding messages were pruned at + /// the source chain. + async fn generated_message_details( + &self, + id: SourceHeaderIdOf

, + nonces: RangeInclusive, + ) -> Result, Self::Error>; + + /// Prove messages in inclusive range [begin; end]. + async fn prove_messages( + &self, + id: SourceHeaderIdOf

, + nonces: RangeInclusive, + proof_parameters: MessageProofParameters, + ) -> Result<(SourceHeaderIdOf

, RangeInclusive, P::MessagesProof), Self::Error>; + + /// Submit messages receiving proof. + async fn submit_messages_receiving_proof( + &self, + maybe_batch_tx: Option, + generated_at_block: TargetHeaderIdOf

, + proof: P::MessagesReceivingProof, + ) -> Result; + + /// We need given finalized target header on source to continue synchronization. + /// + /// We assume that the absence of header `id` has already been checked by caller. + /// + /// The client may return `Some(_)`, which means that nothing has happened yet and + /// the caller must generate and append message receiving proof to the batch transaction + /// to actually send it (along with required header) to the node. + /// + /// If function has returned `None`, it means that the caller now must wait for the + /// appearance of the target header `id` at the source client. + async fn require_target_header_on_source( + &self, + id: TargetHeaderIdOf

, + ) -> Result, Self::Error>; +} + +/// Target client trait. +#[async_trait] +pub trait TargetClient: RelayClient { + /// Type of batch transaction that submits finality and messages proof. + type BatchTransaction: BatchTransaction> + Clone; + /// Transaction tracker to track submitted transactions. + type TransactionTracker: TransactionTracker>; + + /// Returns state of the client. + async fn state(&self) -> Result, Self::Error>; + + /// Get nonce of latest received message. + async fn latest_received_nonce( + &self, + id: TargetHeaderIdOf

, + ) -> Result<(TargetHeaderIdOf

, MessageNonce), Self::Error>; + + /// Get nonce of the latest confirmed message. + async fn latest_confirmed_received_nonce( + &self, + id: TargetHeaderIdOf

, + ) -> Result<(TargetHeaderIdOf

, MessageNonce), Self::Error>; + + /// Get state of unrewarded relayers set at the inbound lane. + async fn unrewarded_relayers_state( + &self, + id: TargetHeaderIdOf

, + ) -> Result<(TargetHeaderIdOf

, UnrewardedRelayersState), Self::Error>; + + /// Prove messages receiving at given block. + async fn prove_messages_receiving( + &self, + id: TargetHeaderIdOf

, + ) -> Result<(TargetHeaderIdOf

, P::MessagesReceivingProof), Self::Error>; + + /// Submit messages proof. + async fn submit_messages_proof( + &self, + maybe_batch_tx: Option, + generated_at_header: SourceHeaderIdOf

, + nonces: RangeInclusive, + proof: P::MessagesProof, + ) -> Result, Self::Error>; + + /// We need given finalized source header on target to continue synchronization. + /// + /// The client may return `Some(_)`, which means that nothing has happened yet and + /// the caller must generate and append messages proof to the batch transaction + /// to actually send it (along with required header) to the node. + /// + /// If function has returned `None`, it means that the caller now must wait for the + /// appearance of the source header `id` at the target client. + async fn require_source_header_on_target( + &self, + id: SourceHeaderIdOf

, + ) -> Result, Self::Error>; +} + +/// State of the client. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct ClientState { + /// The best header id of this chain. + pub best_self: SelfHeaderId, + /// Best finalized header id of this chain. + pub best_finalized_self: SelfHeaderId, + /// Best finalized header id of the peer chain read at the best block of this chain (at + /// `best_finalized_self`). + /// + /// It may be `None` e,g. if peer is a parachain and we haven't yet relayed any parachain + /// heads. + pub best_finalized_peer_at_best_self: Option, + /// Header id of the peer chain with the number, matching the + /// `best_finalized_peer_at_best_self`. + pub actual_best_finalized_peer_at_best_self: Option, +} + +/// State of source client in one-way message lane. +pub type SourceClientState

= ClientState, TargetHeaderIdOf

>; + +/// State of target client in one-way message lane. +pub type TargetClientState

= ClientState, SourceHeaderIdOf

>; + +/// Both clients state. +#[derive(Debug, Default)] +pub struct ClientsState { + /// Source client state. + pub source: Option>, + /// Target client state. + pub target: Option>, +} + +/// Return prefix that will be used by default to expose Prometheus metrics of the finality proofs +/// sync loop. +pub fn metrics_prefix(lane: &LaneId) -> String { + format!("{}_to_{}_MessageLane_{}", P::SOURCE_NAME, P::TARGET_NAME, hex::encode(lane)) +} + +/// Run message lane service loop. +pub async fn run( + params: Params, + source_client: impl SourceClient

, + target_client: impl TargetClient

, + metrics_params: MetricsParams, + exit_signal: impl Future + Send + 'static, +) -> Result<(), relay_utils::Error> { + let exit_signal = exit_signal.shared(); + relay_utils::relay_loop(source_client, target_client) + .reconnect_delay(params.reconnect_delay) + .with_metrics(metrics_params) + .loop_metric(MessageLaneLoopMetrics::new(Some(&metrics_prefix::

(¶ms.lane)))?)? + .expose() + .await? + .run(metrics_prefix::

(¶ms.lane), move |source_client, target_client, metrics| { + run_until_connection_lost( + params.clone(), + source_client, + target_client, + metrics, + exit_signal.clone(), + ) + }) + .await +} + +/// Run one-way message delivery loop until connection with target or source node is lost, or exit +/// signal is received. +async fn run_until_connection_lost, TC: TargetClient

>( + params: Params, + source_client: SC, + target_client: TC, + metrics_msg: Option, + exit_signal: impl Future, +) -> Result<(), FailedClient> { + let mut source_retry_backoff = retry_backoff(); + let mut source_client_is_online = false; + let mut source_state_required = true; + let source_state = source_client.state().fuse(); + let source_go_offline_future = futures::future::Fuse::terminated(); + let source_tick_stream = interval(params.source_tick).fuse(); + + let mut target_retry_backoff = retry_backoff(); + let mut target_client_is_online = false; + let mut target_state_required = true; + let target_state = target_client.state().fuse(); + let target_go_offline_future = futures::future::Fuse::terminated(); + let target_tick_stream = interval(params.target_tick).fuse(); + + let ( + (delivery_source_state_sender, delivery_source_state_receiver), + (delivery_target_state_sender, delivery_target_state_receiver), + ) = (unbounded(), unbounded()); + let delivery_race_loop = run_message_delivery_race( + source_client.clone(), + delivery_source_state_receiver, + target_client.clone(), + delivery_target_state_receiver, + metrics_msg.clone(), + params.delivery_params, + ) + .fuse(); + + let ( + (receiving_source_state_sender, receiving_source_state_receiver), + (receiving_target_state_sender, receiving_target_state_receiver), + ) = (unbounded(), unbounded()); + let receiving_race_loop = run_message_receiving_race( + source_client.clone(), + receiving_source_state_receiver, + target_client.clone(), + receiving_target_state_receiver, + metrics_msg.clone(), + ) + .fuse(); + + let exit_signal = exit_signal.fuse(); + + futures::pin_mut!( + source_state, + source_go_offline_future, + source_tick_stream, + target_state, + target_go_offline_future, + target_tick_stream, + delivery_race_loop, + receiving_race_loop, + exit_signal + ); + + loop { + futures::select! { + new_source_state = source_state => { + source_state_required = false; + + source_client_is_online = process_future_result( + new_source_state, + &mut source_retry_backoff, + |new_source_state| { + log::debug!( + target: "bridge", + "Received state from {} node: {:?}", + P::SOURCE_NAME, + new_source_state, + ); + let _ = delivery_source_state_sender.unbounded_send(new_source_state.clone()); + let _ = receiving_source_state_sender.unbounded_send(new_source_state.clone()); + + if let Some(metrics_msg) = metrics_msg.as_ref() { + metrics_msg.update_source_state::

(new_source_state); + } + }, + &mut source_go_offline_future, + async_std::task::sleep, + || format!("Error retrieving state from {} node", P::SOURCE_NAME), + ).fail_if_connection_error(FailedClient::Source)?; + }, + _ = source_go_offline_future => { + source_client_is_online = true; + }, + _ = source_tick_stream.next() => { + source_state_required = true; + }, + new_target_state = target_state => { + target_state_required = false; + + target_client_is_online = process_future_result( + new_target_state, + &mut target_retry_backoff, + |new_target_state| { + log::debug!( + target: "bridge", + "Received state from {} node: {:?}", + P::TARGET_NAME, + new_target_state, + ); + let _ = delivery_target_state_sender.unbounded_send(new_target_state.clone()); + let _ = receiving_target_state_sender.unbounded_send(new_target_state.clone()); + + if let Some(metrics_msg) = metrics_msg.as_ref() { + metrics_msg.update_target_state::

(new_target_state); + } + }, + &mut target_go_offline_future, + async_std::task::sleep, + || format!("Error retrieving state from {} node", P::TARGET_NAME), + ).fail_if_connection_error(FailedClient::Target)?; + }, + _ = target_go_offline_future => { + target_client_is_online = true; + }, + _ = target_tick_stream.next() => { + target_state_required = true; + }, + + delivery_error = delivery_race_loop => { + match delivery_error { + Ok(_) => unreachable!("only ends with error; qed"), + Err(err) => return Err(err), + } + }, + receiving_error = receiving_race_loop => { + match receiving_error { + Ok(_) => unreachable!("only ends with error; qed"), + Err(err) => return Err(err), + } + }, + + () = exit_signal => { + return Ok(()); + } + } + + if source_client_is_online && source_state_required { + log::debug!(target: "bridge", "Asking {} node about its state", P::SOURCE_NAME); + source_state.set(source_client.state().fuse()); + source_client_is_online = false; + } + + if target_client_is_online && target_state_required { + log::debug!(target: "bridge", "Asking {} node about its state", P::TARGET_NAME); + target_state.set(target_client.state().fuse()); + target_client_is_online = false; + } + } +} + +#[cfg(test)] +pub(crate) mod tests { + use std::sync::Arc; + + use futures::stream::StreamExt; + use parking_lot::Mutex; + + use relay_utils::{HeaderId, MaybeConnectionError, TrackedTransactionStatus}; + + use super::*; + + pub fn header_id(number: TestSourceHeaderNumber) -> TestSourceHeaderId { + HeaderId(number, number) + } + + pub type TestSourceChainBalance = u64; + pub type TestSourceHeaderId = HeaderId; + pub type TestTargetHeaderId = HeaderId; + + pub type TestMessagesProof = (RangeInclusive, Option); + pub type TestMessagesReceivingProof = MessageNonce; + + pub type TestSourceHeaderNumber = u64; + pub type TestSourceHeaderHash = u64; + + pub type TestTargetHeaderNumber = u64; + pub type TestTargetHeaderHash = u64; + + #[derive(Debug)] + pub struct TestError; + + impl MaybeConnectionError for TestError { + fn is_connection_error(&self) -> bool { + true + } + } + + #[derive(Clone)] + pub struct TestMessageLane; + + impl MessageLane for TestMessageLane { + const SOURCE_NAME: &'static str = "TestSource"; + const TARGET_NAME: &'static str = "TestTarget"; + + type MessagesProof = TestMessagesProof; + type MessagesReceivingProof = TestMessagesReceivingProof; + + type SourceChainBalance = TestSourceChainBalance; + type SourceHeaderNumber = TestSourceHeaderNumber; + type SourceHeaderHash = TestSourceHeaderHash; + + type TargetHeaderNumber = TestTargetHeaderNumber; + type TargetHeaderHash = TestTargetHeaderHash; + } + + #[derive(Clone, Debug)] + pub struct TestMessagesBatchTransaction { + required_header_id: TestSourceHeaderId, + } + + #[async_trait] + impl BatchTransaction for TestMessagesBatchTransaction { + fn required_header_id(&self) -> TestSourceHeaderId { + self.required_header_id + } + } + + #[derive(Clone, Debug)] + pub struct TestConfirmationBatchTransaction { + required_header_id: TestTargetHeaderId, + } + + #[async_trait] + impl BatchTransaction for TestConfirmationBatchTransaction { + fn required_header_id(&self) -> TestTargetHeaderId { + self.required_header_id + } + } + + #[derive(Clone, Debug)] + pub struct TestTransactionTracker(TrackedTransactionStatus); + + impl Default for TestTransactionTracker { + fn default() -> TestTransactionTracker { + TestTransactionTracker(TrackedTransactionStatus::Finalized(Default::default())) + } + } + + #[async_trait] + impl TransactionTracker for TestTransactionTracker { + type HeaderId = TestTargetHeaderId; + + async fn wait(self) -> TrackedTransactionStatus { + self.0 + } + } + + #[derive(Debug, Clone)] + pub struct TestClientData { + is_source_fails: bool, + is_source_reconnected: bool, + source_state: SourceClientState, + source_latest_generated_nonce: MessageNonce, + source_latest_confirmed_received_nonce: MessageNonce, + source_tracked_transaction_status: TrackedTransactionStatus, + submitted_messages_receiving_proofs: Vec, + is_target_fails: bool, + is_target_reconnected: bool, + target_state: SourceClientState, + target_latest_received_nonce: MessageNonce, + target_latest_confirmed_received_nonce: MessageNonce, + target_tracked_transaction_status: TrackedTransactionStatus, + submitted_messages_proofs: Vec, + target_to_source_batch_transaction: Option, + target_to_source_header_required: Option, + target_to_source_header_requirements: Vec, + source_to_target_batch_transaction: Option, + source_to_target_header_required: Option, + source_to_target_header_requirements: Vec, + } + + impl Default for TestClientData { + fn default() -> TestClientData { + TestClientData { + is_source_fails: false, + is_source_reconnected: false, + source_state: Default::default(), + source_latest_generated_nonce: 0, + source_latest_confirmed_received_nonce: 0, + source_tracked_transaction_status: TrackedTransactionStatus::Finalized(HeaderId( + 0, + Default::default(), + )), + submitted_messages_receiving_proofs: Vec::new(), + is_target_fails: false, + is_target_reconnected: false, + target_state: Default::default(), + target_latest_received_nonce: 0, + target_latest_confirmed_received_nonce: 0, + target_tracked_transaction_status: TrackedTransactionStatus::Finalized(HeaderId( + 0, + Default::default(), + )), + submitted_messages_proofs: Vec::new(), + target_to_source_batch_transaction: None, + target_to_source_header_required: None, + target_to_source_header_requirements: Vec::new(), + source_to_target_batch_transaction: None, + source_to_target_header_required: None, + source_to_target_header_requirements: Vec::new(), + } + } + } + + impl TestClientData { + fn receive_messages( + &mut self, + maybe_batch_tx: Option, + proof: TestMessagesProof, + ) { + self.target_state.best_self = + HeaderId(self.target_state.best_self.0 + 1, self.target_state.best_self.1 + 1); + self.target_state.best_finalized_self = self.target_state.best_self; + self.target_latest_received_nonce = *proof.0.end(); + if let Some(maybe_batch_tx) = maybe_batch_tx { + self.target_state.best_finalized_peer_at_best_self = + Some(maybe_batch_tx.required_header_id()); + } + if let Some(target_latest_confirmed_received_nonce) = proof.1 { + self.target_latest_confirmed_received_nonce = + target_latest_confirmed_received_nonce; + } + self.submitted_messages_proofs.push(proof); + } + + fn receive_messages_delivery_proof( + &mut self, + maybe_batch_tx: Option, + proof: TestMessagesReceivingProof, + ) { + self.source_state.best_self = + HeaderId(self.source_state.best_self.0 + 1, self.source_state.best_self.1 + 1); + self.source_state.best_finalized_self = self.source_state.best_self; + if let Some(maybe_batch_tx) = maybe_batch_tx { + self.source_state.best_finalized_peer_at_best_self = + Some(maybe_batch_tx.required_header_id()); + } + self.submitted_messages_receiving_proofs.push(proof); + self.source_latest_confirmed_received_nonce = proof; + } + } + + #[derive(Clone)] + pub struct TestSourceClient { + data: Arc>, + tick: Arc, + post_tick: Arc, + } + + impl Default for TestSourceClient { + fn default() -> Self { + TestSourceClient { + data: Arc::new(Mutex::new(TestClientData::default())), + tick: Arc::new(|_| {}), + post_tick: Arc::new(|_| {}), + } + } + } + + #[async_trait] + impl RelayClient for TestSourceClient { + type Error = TestError; + + async fn reconnect(&mut self) -> Result<(), TestError> { + { + let mut data = self.data.lock(); + (self.tick)(&mut data); + data.is_source_reconnected = true; + (self.post_tick)(&mut data); + } + Ok(()) + } + } + + #[async_trait] + impl SourceClient for TestSourceClient { + type BatchTransaction = TestConfirmationBatchTransaction; + type TransactionTracker = TestTransactionTracker; + + async fn state(&self) -> Result, TestError> { + let mut data = self.data.lock(); + (self.tick)(&mut data); + if data.is_source_fails { + return Err(TestError) + } + (self.post_tick)(&mut data); + Ok(data.source_state.clone()) + } + + async fn latest_generated_nonce( + &self, + id: SourceHeaderIdOf, + ) -> Result<(SourceHeaderIdOf, MessageNonce), TestError> { + let mut data = self.data.lock(); + (self.tick)(&mut data); + if data.is_source_fails { + return Err(TestError) + } + (self.post_tick)(&mut data); + Ok((id, data.source_latest_generated_nonce)) + } + + async fn latest_confirmed_received_nonce( + &self, + id: SourceHeaderIdOf, + ) -> Result<(SourceHeaderIdOf, MessageNonce), TestError> { + let mut data = self.data.lock(); + (self.tick)(&mut data); + (self.post_tick)(&mut data); + Ok((id, data.source_latest_confirmed_received_nonce)) + } + + async fn generated_message_details( + &self, + _id: SourceHeaderIdOf, + nonces: RangeInclusive, + ) -> Result, TestError> { + Ok(nonces + .map(|nonce| { + ( + nonce, + MessageDetails { + dispatch_weight: Weight::from_parts(1, 0), + size: 1, + reward: 1, + }, + ) + }) + .collect()) + } + + async fn prove_messages( + &self, + id: SourceHeaderIdOf, + nonces: RangeInclusive, + proof_parameters: MessageProofParameters, + ) -> Result< + (SourceHeaderIdOf, RangeInclusive, TestMessagesProof), + TestError, + > { + let mut data = self.data.lock(); + (self.tick)(&mut data); + (self.post_tick)(&mut data); + Ok(( + id, + nonces.clone(), + ( + nonces, + if proof_parameters.outbound_state_proof_required { + Some(data.source_latest_confirmed_received_nonce) + } else { + None + }, + ), + )) + } + + async fn submit_messages_receiving_proof( + &self, + maybe_batch_tx: Option, + _generated_at_block: TargetHeaderIdOf, + proof: TestMessagesReceivingProof, + ) -> Result { + let mut data = self.data.lock(); + (self.tick)(&mut data); + data.receive_messages_delivery_proof(maybe_batch_tx, proof); + (self.post_tick)(&mut data); + Ok(TestTransactionTracker(data.source_tracked_transaction_status)) + } + + async fn require_target_header_on_source( + &self, + id: TargetHeaderIdOf, + ) -> Result, Self::Error> { + let mut data = self.data.lock(); + data.target_to_source_header_required = Some(id); + data.target_to_source_header_requirements.push(id); + (self.tick)(&mut data); + (self.post_tick)(&mut data); + + Ok(data.target_to_source_batch_transaction.take().map(|mut tx| { + tx.required_header_id = id; + tx + })) + } + } + + #[derive(Clone)] + pub struct TestTargetClient { + data: Arc>, + tick: Arc, + post_tick: Arc, + } + + impl Default for TestTargetClient { + fn default() -> Self { + TestTargetClient { + data: Arc::new(Mutex::new(TestClientData::default())), + tick: Arc::new(|_| {}), + post_tick: Arc::new(|_| {}), + } + } + } + + #[async_trait] + impl RelayClient for TestTargetClient { + type Error = TestError; + + async fn reconnect(&mut self) -> Result<(), TestError> { + { + let mut data = self.data.lock(); + (self.tick)(&mut data); + data.is_target_reconnected = true; + (self.post_tick)(&mut data); + } + Ok(()) + } + } + + #[async_trait] + impl TargetClient for TestTargetClient { + type BatchTransaction = TestMessagesBatchTransaction; + type TransactionTracker = TestTransactionTracker; + + async fn state(&self) -> Result, TestError> { + let mut data = self.data.lock(); + (self.tick)(&mut data); + if data.is_target_fails { + return Err(TestError) + } + (self.post_tick)(&mut data); + Ok(data.target_state.clone()) + } + + async fn latest_received_nonce( + &self, + id: TargetHeaderIdOf, + ) -> Result<(TargetHeaderIdOf, MessageNonce), TestError> { + let mut data = self.data.lock(); + (self.tick)(&mut data); + if data.is_target_fails { + return Err(TestError) + } + (self.post_tick)(&mut data); + Ok((id, data.target_latest_received_nonce)) + } + + async fn unrewarded_relayers_state( + &self, + id: TargetHeaderIdOf, + ) -> Result<(TargetHeaderIdOf, UnrewardedRelayersState), TestError> { + Ok(( + id, + UnrewardedRelayersState { + unrewarded_relayer_entries: 0, + messages_in_oldest_entry: 0, + total_messages: 0, + last_delivered_nonce: 0, + }, + )) + } + + async fn latest_confirmed_received_nonce( + &self, + id: TargetHeaderIdOf, + ) -> Result<(TargetHeaderIdOf, MessageNonce), TestError> { + let mut data = self.data.lock(); + (self.tick)(&mut data); + if data.is_target_fails { + return Err(TestError) + } + (self.post_tick)(&mut data); + Ok((id, data.target_latest_confirmed_received_nonce)) + } + + async fn prove_messages_receiving( + &self, + id: TargetHeaderIdOf, + ) -> Result<(TargetHeaderIdOf, TestMessagesReceivingProof), TestError> { + Ok((id, self.data.lock().target_latest_received_nonce)) + } + + async fn submit_messages_proof( + &self, + maybe_batch_tx: Option, + _generated_at_header: SourceHeaderIdOf, + nonces: RangeInclusive, + proof: TestMessagesProof, + ) -> Result, TestError> { + let mut data = self.data.lock(); + (self.tick)(&mut data); + if data.is_target_fails { + return Err(TestError) + } + data.receive_messages(maybe_batch_tx, proof); + (self.post_tick)(&mut data); + Ok(NoncesSubmitArtifacts { + nonces, + tx_tracker: TestTransactionTracker(data.target_tracked_transaction_status), + }) + } + + async fn require_source_header_on_target( + &self, + id: SourceHeaderIdOf, + ) -> Result, Self::Error> { + let mut data = self.data.lock(); + data.source_to_target_header_required = Some(id); + data.source_to_target_header_requirements.push(id); + (self.tick)(&mut data); + (self.post_tick)(&mut data); + + Ok(data.source_to_target_batch_transaction.take().map(|mut tx| { + tx.required_header_id = id; + tx + })) + } + } + + fn run_loop_test( + data: Arc>, + source_tick: Arc, + source_post_tick: Arc, + target_tick: Arc, + target_post_tick: Arc, + exit_signal: impl Future + 'static + Send, + ) -> TestClientData { + async_std::task::block_on(async { + let source_client = TestSourceClient { + data: data.clone(), + tick: source_tick, + post_tick: source_post_tick, + }; + let target_client = TestTargetClient { + data: data.clone(), + tick: target_tick, + post_tick: target_post_tick, + }; + let _ = run( + Params { + lane: LaneId([0, 0, 0, 0]), + source_tick: Duration::from_millis(100), + target_tick: Duration::from_millis(100), + reconnect_delay: Duration::from_millis(0), + delivery_params: MessageDeliveryParams { + max_unrewarded_relayer_entries_at_target: 4, + max_unconfirmed_nonces_at_target: 4, + max_messages_in_single_batch: 4, + max_messages_weight_in_single_batch: Weight::from_parts(4, 0), + max_messages_size_in_single_batch: 4, + }, + }, + source_client, + target_client, + MetricsParams::disabled(), + exit_signal, + ) + .await; + let result = data.lock().clone(); + result + }) + } + + #[test] + fn message_lane_loop_is_able_to_recover_from_connection_errors() { + // with this configuration, source client will return Err, making source client + // reconnect. Then the target client will fail with Err + reconnect. Then we finally + // able to deliver messages. + let (exit_sender, exit_receiver) = unbounded(); + let result = run_loop_test( + Arc::new(Mutex::new(TestClientData { + is_source_fails: true, + source_state: ClientState { + best_self: HeaderId(0, 0), + best_finalized_self: HeaderId(0, 0), + best_finalized_peer_at_best_self: Some(HeaderId(0, 0)), + actual_best_finalized_peer_at_best_self: Some(HeaderId(0, 0)), + }, + source_latest_generated_nonce: 1, + target_state: ClientState { + best_self: HeaderId(0, 0), + best_finalized_self: HeaderId(0, 0), + best_finalized_peer_at_best_self: Some(HeaderId(0, 0)), + actual_best_finalized_peer_at_best_self: Some(HeaderId(0, 0)), + }, + target_latest_received_nonce: 0, + ..Default::default() + })), + Arc::new(|data: &mut TestClientData| { + if data.is_source_reconnected { + data.is_source_fails = false; + data.is_target_fails = true; + } + }), + Arc::new(|_| {}), + Arc::new(move |data: &mut TestClientData| { + if data.is_target_reconnected { + data.is_target_fails = false; + } + if data.target_state.best_finalized_peer_at_best_self.unwrap().0 < 10 { + data.target_state.best_finalized_peer_at_best_self = Some(HeaderId( + data.target_state.best_finalized_peer_at_best_self.unwrap().0 + 1, + data.target_state.best_finalized_peer_at_best_self.unwrap().0 + 1, + )); + } + if !data.submitted_messages_proofs.is_empty() { + exit_sender.unbounded_send(()).unwrap(); + } + }), + Arc::new(|_| {}), + exit_receiver.into_future().map(|(_, _)| ()), + ); + + assert_eq!(result.submitted_messages_proofs, vec![(1..=1, None)],); + } + + #[test] + fn message_lane_loop_is_able_to_recover_from_unsuccessful_transaction() { + // with this configuration, both source and target clients will mine their transactions, but + // their corresponding nonce won't be udpated => reconnect will happen + let (exit_sender, exit_receiver) = unbounded(); + let result = run_loop_test( + Arc::new(Mutex::new(TestClientData { + source_state: ClientState { + best_self: HeaderId(0, 0), + best_finalized_self: HeaderId(0, 0), + best_finalized_peer_at_best_self: Some(HeaderId(0, 0)), + actual_best_finalized_peer_at_best_self: Some(HeaderId(0, 0)), + }, + source_latest_generated_nonce: 1, + target_state: ClientState { + best_self: HeaderId(0, 0), + best_finalized_self: HeaderId(0, 0), + best_finalized_peer_at_best_self: Some(HeaderId(0, 0)), + actual_best_finalized_peer_at_best_self: Some(HeaderId(0, 0)), + }, + target_latest_received_nonce: 0, + ..Default::default() + })), + Arc::new(move |data: &mut TestClientData| { + // blocks are produced on every tick + data.source_state.best_self = + HeaderId(data.source_state.best_self.0 + 1, data.source_state.best_self.1 + 1); + data.source_state.best_finalized_self = data.source_state.best_self; + // syncing target headers -> source chain + if let Some(last_requirement) = data.target_to_source_header_requirements.last() { + if *last_requirement != + data.source_state.best_finalized_peer_at_best_self.unwrap() + { + data.source_state.best_finalized_peer_at_best_self = + Some(*last_requirement); + } + } + }), + Arc::new(move |data: &mut TestClientData| { + // if it is the first time we're submitting delivery proof, let's revert changes + // to source status => then the delivery confirmation transaction is "finalized", + // but the state is not altered + if data.submitted_messages_receiving_proofs.len() == 1 { + data.source_latest_confirmed_received_nonce = 0; + } + }), + Arc::new(move |data: &mut TestClientData| { + // blocks are produced on every tick + data.target_state.best_self = + HeaderId(data.target_state.best_self.0 + 1, data.target_state.best_self.1 + 1); + data.target_state.best_finalized_self = data.target_state.best_self; + // syncing source headers -> target chain + if let Some(last_requirement) = data.source_to_target_header_requirements.last() { + if *last_requirement != + data.target_state.best_finalized_peer_at_best_self.unwrap() + { + data.target_state.best_finalized_peer_at_best_self = + Some(*last_requirement); + } + } + // if source has received all messages receiving confirmations => stop + if data.source_latest_confirmed_received_nonce == 1 { + exit_sender.unbounded_send(()).unwrap(); + } + }), + Arc::new(move |data: &mut TestClientData| { + // if it is the first time we're submitting messages proof, let's revert changes + // to target status => then the messages delivery transaction is "finalized", but + // the state is not altered + if data.submitted_messages_proofs.len() == 1 { + data.target_latest_received_nonce = 0; + data.target_latest_confirmed_received_nonce = 0; + } + }), + exit_receiver.into_future().map(|(_, _)| ()), + ); + + assert_eq!(result.submitted_messages_proofs.len(), 2); + assert_eq!(result.submitted_messages_receiving_proofs.len(), 2); + } + + #[test] + fn message_lane_loop_works() { + let (exit_sender, exit_receiver) = unbounded(); + let result = run_loop_test( + Arc::new(Mutex::new(TestClientData { + source_state: ClientState { + best_self: HeaderId(10, 10), + best_finalized_self: HeaderId(10, 10), + best_finalized_peer_at_best_self: Some(HeaderId(0, 0)), + actual_best_finalized_peer_at_best_self: Some(HeaderId(0, 0)), + }, + source_latest_generated_nonce: 10, + target_state: ClientState { + best_self: HeaderId(0, 0), + best_finalized_self: HeaderId(0, 0), + best_finalized_peer_at_best_self: Some(HeaderId(0, 0)), + actual_best_finalized_peer_at_best_self: Some(HeaderId(0, 0)), + }, + target_latest_received_nonce: 0, + ..Default::default() + })), + Arc::new(|data: &mut TestClientData| { + // blocks are produced on every tick + data.source_state.best_self = + HeaderId(data.source_state.best_self.0 + 1, data.source_state.best_self.1 + 1); + data.source_state.best_finalized_self = data.source_state.best_self; + // headers relay must only be started when we need new target headers at source node + if data.target_to_source_header_required.is_some() { + assert!( + data.source_state.best_finalized_peer_at_best_self.unwrap().0 < + data.target_state.best_self.0 + ); + data.target_to_source_header_required = None; + } + // syncing target headers -> source chain + if let Some(last_requirement) = data.target_to_source_header_requirements.last() { + if *last_requirement != + data.source_state.best_finalized_peer_at_best_self.unwrap() + { + data.source_state.best_finalized_peer_at_best_self = + Some(*last_requirement); + } + } + }), + Arc::new(|_| {}), + Arc::new(move |data: &mut TestClientData| { + // blocks are produced on every tick + data.target_state.best_self = + HeaderId(data.target_state.best_self.0 + 1, data.target_state.best_self.1 + 1); + data.target_state.best_finalized_self = data.target_state.best_self; + // headers relay must only be started when we need new source headers at target node + if data.source_to_target_header_required.is_some() { + assert!( + data.target_state.best_finalized_peer_at_best_self.unwrap().0 < + data.source_state.best_self.0 + ); + data.source_to_target_header_required = None; + } + // syncing source headers -> target chain + if let Some(last_requirement) = data.source_to_target_header_requirements.last() { + if *last_requirement != + data.target_state.best_finalized_peer_at_best_self.unwrap() + { + data.target_state.best_finalized_peer_at_best_self = + Some(*last_requirement); + } + } + // if source has received all messages receiving confirmations => stop + if data.source_latest_confirmed_received_nonce == 10 { + exit_sender.unbounded_send(()).unwrap(); + } + }), + Arc::new(|_| {}), + exit_receiver.into_future().map(|(_, _)| ()), + ); + + // there are no strict restrictions on when reward confirmation should come + // (because `max_unconfirmed_nonces_at_target` is `100` in tests and this confirmation + // depends on the state of both clients) + // => we do not check it here + assert_eq!(result.submitted_messages_proofs[0].0, 1..=4); + assert_eq!(result.submitted_messages_proofs[1].0, 5..=8); + assert_eq!(result.submitted_messages_proofs[2].0, 9..=10); + assert!(!result.submitted_messages_receiving_proofs.is_empty()); + + // check that we have at least once required new source->target or target->source headers + assert!(!result.target_to_source_header_requirements.is_empty()); + assert!(!result.source_to_target_header_requirements.is_empty()); + } + + #[test] + fn message_lane_loop_works_with_batch_transactions() { + let (exit_sender, exit_receiver) = unbounded(); + let original_data = Arc::new(Mutex::new(TestClientData { + source_state: ClientState { + best_self: HeaderId(10, 10), + best_finalized_self: HeaderId(10, 10), + best_finalized_peer_at_best_self: Some(HeaderId(0, 0)), + actual_best_finalized_peer_at_best_self: Some(HeaderId(0, 0)), + }, + source_latest_generated_nonce: 10, + target_state: ClientState { + best_self: HeaderId(0, 0), + best_finalized_self: HeaderId(0, 0), + best_finalized_peer_at_best_self: Some(HeaderId(0, 0)), + actual_best_finalized_peer_at_best_self: Some(HeaderId(0, 0)), + }, + target_latest_received_nonce: 0, + ..Default::default() + })); + let result = run_loop_test( + original_data, + Arc::new(|_| {}), + Arc::new(move |data: &mut TestClientData| { + data.source_state.best_self = + HeaderId(data.source_state.best_self.0 + 1, data.source_state.best_self.1 + 1); + data.source_state.best_finalized_self = data.source_state.best_self; + if let Some(target_to_source_header_required) = + data.target_to_source_header_required.take() + { + data.target_to_source_batch_transaction = + Some(TestConfirmationBatchTransaction { + required_header_id: target_to_source_header_required, + }) + } + }), + Arc::new(|_| {}), + Arc::new(move |data: &mut TestClientData| { + data.target_state.best_self = + HeaderId(data.target_state.best_self.0 + 1, data.target_state.best_self.1 + 1); + data.target_state.best_finalized_self = data.target_state.best_self; + + if let Some(source_to_target_header_required) = + data.source_to_target_header_required.take() + { + data.source_to_target_batch_transaction = Some(TestMessagesBatchTransaction { + required_header_id: source_to_target_header_required, + }) + } + + if data.source_latest_confirmed_received_nonce == 10 { + exit_sender.unbounded_send(()).unwrap(); + } + }), + exit_receiver.into_future().map(|(_, _)| ()), + ); + + // there are no strict restrictions on when reward confirmation should come + // (because `max_unconfirmed_nonces_at_target` is `100` in tests and this confirmation + // depends on the state of both clients) + // => we do not check it here + assert_eq!(result.submitted_messages_proofs[0].0, 1..=4); + assert_eq!(result.submitted_messages_proofs[1].0, 5..=8); + assert_eq!(result.submitted_messages_proofs[2].0, 9..=10); + assert!(!result.submitted_messages_receiving_proofs.is_empty()); + + // check that we have at least once required new source->target or target->source headers + assert!(!result.target_to_source_header_requirements.is_empty()); + assert!(!result.source_to_target_header_requirements.is_empty()); + } +} diff --git a/bridges/relays/messages/src/message_race_delivery.rs b/bridges/relays/messages/src/message_race_delivery.rs new file mode 100644 index 0000000000000..137deb5b74f75 --- /dev/null +++ b/bridges/relays/messages/src/message_race_delivery.rs @@ -0,0 +1,1405 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +//! Message delivery race delivers proof-of-messages from "lane.source" to "lane.target". + +use std::{collections::VecDeque, marker::PhantomData, ops::RangeInclusive}; + +use async_trait::async_trait; +use futures::stream::FusedStream; + +use bp_messages::{MessageNonce, UnrewardedRelayersState, Weight}; +use relay_utils::FailedClient; + +use crate::{ + message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, + message_lane_loop::{ + MessageDeliveryParams, MessageDetailsMap, MessageProofParameters, NoncesSubmitArtifacts, + SourceClient as MessageLaneSourceClient, SourceClientState, + TargetClient as MessageLaneTargetClient, TargetClientState, + }, + message_race_limits::{MessageRaceLimits, RelayMessagesBatchReference}, + message_race_loop::{ + MessageRace, NoncesRange, RaceState, RaceStrategy, SourceClient, SourceClientNonces, + TargetClient, TargetClientNonces, + }, + message_race_strategy::BasicStrategy, + metrics::MessageLaneLoopMetrics, +}; + +/// Run message delivery race. +pub async fn run( + source_client: impl MessageLaneSourceClient

, + source_state_updates: impl FusedStream>, + target_client: impl MessageLaneTargetClient

, + target_state_updates: impl FusedStream>, + metrics_msg: Option, + params: MessageDeliveryParams, +) -> Result<(), FailedClient> { + crate::message_race_loop::run( + MessageDeliveryRaceSource { + client: source_client.clone(), + metrics_msg: metrics_msg.clone(), + _phantom: Default::default(), + }, + source_state_updates, + MessageDeliveryRaceTarget { + client: target_client.clone(), + metrics_msg: metrics_msg.clone(), + _phantom: Default::default(), + }, + target_state_updates, + MessageDeliveryStrategy:: { + lane_source_client: source_client, + lane_target_client: target_client, + max_unrewarded_relayer_entries_at_target: params + .max_unrewarded_relayer_entries_at_target, + max_unconfirmed_nonces_at_target: params.max_unconfirmed_nonces_at_target, + max_messages_in_single_batch: params.max_messages_in_single_batch, + max_messages_weight_in_single_batch: params.max_messages_weight_in_single_batch, + max_messages_size_in_single_batch: params.max_messages_size_in_single_batch, + latest_confirmed_nonces_at_source: VecDeque::new(), + target_nonces: None, + strategy: BasicStrategy::new(), + metrics_msg, + }, + ) + .await +} + +/// Message delivery race. +struct MessageDeliveryRace

(std::marker::PhantomData

); + +impl MessageRace for MessageDeliveryRace

{ + type SourceHeaderId = SourceHeaderIdOf

; + type TargetHeaderId = TargetHeaderIdOf

; + + type MessageNonce = MessageNonce; + type Proof = P::MessagesProof; + + fn source_name() -> String { + format!("{}::MessagesDelivery", P::SOURCE_NAME) + } + + fn target_name() -> String { + format!("{}::MessagesDelivery", P::TARGET_NAME) + } +} + +/// Message delivery race source, which is a source of the lane. +struct MessageDeliveryRaceSource { + client: C, + metrics_msg: Option, + _phantom: PhantomData

, +} + +#[async_trait] +impl SourceClient> for MessageDeliveryRaceSource +where + P: MessageLane, + C: MessageLaneSourceClient

, +{ + type Error = C::Error; + type NoncesRange = MessageDetailsMap; + type ProofParameters = MessageProofParameters; + + async fn nonces( + &self, + at_block: SourceHeaderIdOf

, + prev_latest_nonce: MessageNonce, + ) -> Result<(SourceHeaderIdOf

, SourceClientNonces), Self::Error> { + let (at_block, latest_generated_nonce) = + self.client.latest_generated_nonce(at_block).await?; + let (at_block, latest_confirmed_nonce) = + self.client.latest_confirmed_received_nonce(at_block).await?; + + if let Some(metrics_msg) = self.metrics_msg.as_ref() { + metrics_msg.update_source_latest_generated_nonce(latest_generated_nonce); + metrics_msg.update_source_latest_confirmed_nonce(latest_confirmed_nonce); + } + + let new_nonces = if latest_generated_nonce > prev_latest_nonce { + self.client + .generated_message_details( + at_block.clone(), + prev_latest_nonce + 1..=latest_generated_nonce, + ) + .await? + } else { + MessageDetailsMap::new() + }; + + Ok(( + at_block, + SourceClientNonces { new_nonces, confirmed_nonce: Some(latest_confirmed_nonce) }, + )) + } + + async fn generate_proof( + &self, + at_block: SourceHeaderIdOf

, + nonces: RangeInclusive, + proof_parameters: Self::ProofParameters, + ) -> Result<(SourceHeaderIdOf

, RangeInclusive, P::MessagesProof), Self::Error> + { + self.client.prove_messages(at_block, nonces, proof_parameters).await + } +} + +/// Message delivery race target, which is a target of the lane. +struct MessageDeliveryRaceTarget { + client: C, + metrics_msg: Option, + _phantom: PhantomData

, +} + +#[async_trait] +impl TargetClient> for MessageDeliveryRaceTarget +where + P: MessageLane, + C: MessageLaneTargetClient

, +{ + type Error = C::Error; + type TargetNoncesData = DeliveryRaceTargetNoncesData; + type BatchTransaction = C::BatchTransaction; + type TransactionTracker = C::TransactionTracker; + + async fn require_source_header( + &self, + id: SourceHeaderIdOf

, + ) -> Result, Self::Error> { + self.client.require_source_header_on_target(id).await + } + + async fn nonces( + &self, + at_block: TargetHeaderIdOf

, + update_metrics: bool, + ) -> Result<(TargetHeaderIdOf

, TargetClientNonces), Self::Error> + { + let (at_block, latest_received_nonce) = self.client.latest_received_nonce(at_block).await?; + let (at_block, latest_confirmed_nonce) = + self.client.latest_confirmed_received_nonce(at_block).await?; + let (at_block, unrewarded_relayers) = + self.client.unrewarded_relayers_state(at_block).await?; + + if update_metrics { + if let Some(metrics_msg) = self.metrics_msg.as_ref() { + metrics_msg.update_target_latest_received_nonce(latest_received_nonce); + metrics_msg.update_target_latest_confirmed_nonce(latest_confirmed_nonce); + } + } + + Ok(( + at_block, + TargetClientNonces { + latest_nonce: latest_received_nonce, + nonces_data: DeliveryRaceTargetNoncesData { + confirmed_nonce: latest_confirmed_nonce, + unrewarded_relayers, + }, + }, + )) + } + + async fn submit_proof( + &self, + maybe_batch_tx: Option, + generated_at_block: SourceHeaderIdOf

, + nonces: RangeInclusive, + proof: P::MessagesProof, + ) -> Result, Self::Error> { + self.client + .submit_messages_proof(maybe_batch_tx, generated_at_block, nonces, proof) + .await + } +} + +/// Additional nonces data from the target client used by message delivery race. +#[derive(Debug, Clone)] +struct DeliveryRaceTargetNoncesData { + /// The latest nonce that we know: (1) has been delivered to us (2) has been confirmed + /// back to the source node (by confirmations race) and (3) relayer has received + /// reward for (and this has been confirmed by the message delivery race). + confirmed_nonce: MessageNonce, + /// State of the unrewarded relayers set at the target node. + unrewarded_relayers: UnrewardedRelayersState, +} + +/// Messages delivery strategy. +struct MessageDeliveryStrategy { + /// The client that is connected to the message lane source node. + lane_source_client: SC, + /// The client that is connected to the message lane target node. + lane_target_client: TC, + /// Maximal unrewarded relayer entries at target client. + max_unrewarded_relayer_entries_at_target: MessageNonce, + /// Maximal unconfirmed nonces at target client. + max_unconfirmed_nonces_at_target: MessageNonce, + /// Maximal number of messages in the single delivery transaction. + max_messages_in_single_batch: MessageNonce, + /// Maximal cumulative messages weight in the single delivery transaction. + max_messages_weight_in_single_batch: Weight, + /// Maximal messages size in the single delivery transaction. + max_messages_size_in_single_batch: u32, + /// Latest confirmed nonces at the source client + the header id where we have first met this + /// nonce. + latest_confirmed_nonces_at_source: VecDeque<(SourceHeaderIdOf

, MessageNonce)>, + /// Target nonces available at the **best** block of the target chain. + target_nonces: Option>, + /// Basic delivery strategy. + strategy: MessageDeliveryStrategyBase

, + /// Message lane metrics. + metrics_msg: Option, +} + +type MessageDeliveryStrategyBase

= BasicStrategy< +

::SourceHeaderNumber, +

::SourceHeaderHash, +

::TargetHeaderNumber, +

::TargetHeaderHash, + MessageDetailsMap<

::SourceChainBalance>, +

::MessagesProof, +>; + +impl std::fmt::Debug for MessageDeliveryStrategy { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.debug_struct("MessageDeliveryStrategy") + .field( + "max_unrewarded_relayer_entries_at_target", + &self.max_unrewarded_relayer_entries_at_target, + ) + .field("max_unconfirmed_nonces_at_target", &self.max_unconfirmed_nonces_at_target) + .field("max_messages_in_single_batch", &self.max_messages_in_single_batch) + .field("max_messages_weight_in_single_batch", &self.max_messages_weight_in_single_batch) + .field("max_messages_size_in_single_batch", &self.max_messages_size_in_single_batch) + .field("latest_confirmed_nonces_at_source", &self.latest_confirmed_nonces_at_source) + .field("target_nonces", &self.target_nonces) + .field("strategy", &self.strategy) + .finish() + } +} + +impl MessageDeliveryStrategy +where + P: MessageLane, + SC: MessageLaneSourceClient

, + TC: MessageLaneTargetClient

, +{ + /// Returns true if some race action can be selected (with `select_race_action`) at given + /// `best_finalized_source_header_id_at_best_target` source header at target. + async fn can_submit_transaction_with< + RS: RaceState, TargetHeaderIdOf

>, + >( + &self, + mut race_state: RS, + maybe_best_finalized_source_header_id_at_best_target: Option>, + ) -> bool { + if let Some(best_finalized_source_header_id_at_best_target) = + maybe_best_finalized_source_header_id_at_best_target + { + race_state.set_best_finalized_source_header_id_at_best_target( + best_finalized_source_header_id_at_best_target, + ); + + return self.select_race_action(race_state).await.is_some() + } + + false + } + + async fn select_race_action, TargetHeaderIdOf

>>( + &self, + race_state: RS, + ) -> Option<(RangeInclusive, MessageProofParameters)> { + // if we have already selected nonces that we want to submit, do nothing + if race_state.nonces_to_submit().is_some() { + return None + } + + // if we already submitted some nonces, do nothing + if race_state.nonces_submitted().is_some() { + return None + } + + let best_target_nonce = self.strategy.best_at_target()?; + let best_finalized_source_header_id_at_best_target = + race_state.best_finalized_source_header_id_at_best_target()?; + let target_nonces = self.target_nonces.as_ref()?; + let latest_confirmed_nonce_at_source = self + .latest_confirmed_nonce_at_source(&best_finalized_source_header_id_at_best_target) + .unwrap_or(target_nonces.nonces_data.confirmed_nonce); + + // There's additional condition in the message delivery race: target would reject messages + // if there are too much unconfirmed messages at the inbound lane. + + // Ok - we may have new nonces to deliver. But target may still reject new messages, because + // we haven't notified it that (some) messages have been confirmed. So we may want to + // include updated `source.latest_confirmed` in the proof. + // + // Important note: we're including outbound state lane proof whenever there are unconfirmed + // nonces on the target chain. Other strategy is to include it only if it's absolutely + // necessary. + let latest_received_nonce_at_target = target_nonces.latest_nonce; + let latest_confirmed_nonce_at_target = target_nonces.nonces_data.confirmed_nonce; + let outbound_state_proof_required = + latest_confirmed_nonce_at_target < latest_confirmed_nonce_at_source; + + // The target node would also reject messages if there are too many entries in the + // "unrewarded relayers" set. If we are unable to prove new rewards to the target node, then + // we should wait for confirmations race. + let unrewarded_limit_reached = + target_nonces.nonces_data.unrewarded_relayers.unrewarded_relayer_entries >= + self.max_unrewarded_relayer_entries_at_target || + target_nonces.nonces_data.unrewarded_relayers.total_messages >= + self.max_unconfirmed_nonces_at_target; + if unrewarded_limit_reached { + // so there are already too many unrewarded relayer entries in the set + // + // => check if we can prove enough rewards. If not, we should wait for more rewards to + // be paid + let number_of_rewards_being_proved = + latest_confirmed_nonce_at_source.saturating_sub(latest_confirmed_nonce_at_target); + let enough_rewards_being_proved = number_of_rewards_being_proved >= + target_nonces.nonces_data.unrewarded_relayers.messages_in_oldest_entry; + if !enough_rewards_being_proved { + return None + } + } + + // If we're here, then the confirmations race did its job && sending side now knows that + // messages have been delivered. Now let's select nonces that we want to deliver. + // + // We may deliver at most: + // + // max_unconfirmed_nonces_at_target - (latest_received_nonce_at_target - + // latest_confirmed_nonce_at_target) + // + // messages in the batch. But since we're including outbound state proof in the batch, then + // it may be increased to: + // + // max_unconfirmed_nonces_at_target - (latest_received_nonce_at_target - + // latest_confirmed_nonce_at_source) + let future_confirmed_nonce_at_target = if outbound_state_proof_required { + latest_confirmed_nonce_at_source + } else { + latest_confirmed_nonce_at_target + }; + let max_nonces = latest_received_nonce_at_target + .checked_sub(future_confirmed_nonce_at_target) + .and_then(|diff| self.max_unconfirmed_nonces_at_target.checked_sub(diff)) + .unwrap_or_default(); + let max_nonces = std::cmp::min(max_nonces, self.max_messages_in_single_batch); + let max_messages_weight_in_single_batch = self.max_messages_weight_in_single_batch; + let max_messages_size_in_single_batch = self.max_messages_size_in_single_batch; + let lane_source_client = self.lane_source_client.clone(); + let lane_target_client = self.lane_target_client.clone(); + + // select nonces from nonces, available for delivery + let selected_nonces = match self.strategy.available_source_queue_indices(race_state) { + Some(available_source_queue_indices) => { + let source_queue = self.strategy.source_queue(); + let reference = RelayMessagesBatchReference { + max_messages_in_this_batch: max_nonces, + max_messages_weight_in_single_batch, + max_messages_size_in_single_batch, + lane_source_client: lane_source_client.clone(), + lane_target_client: lane_target_client.clone(), + best_target_nonce, + nonces_queue: source_queue.clone(), + nonces_queue_range: available_source_queue_indices, + metrics: self.metrics_msg.clone(), + }; + + MessageRaceLimits::decide(reference).await + }, + None => { + // we still may need to submit delivery transaction with zero messages to + // unblock the lane. But it'll only be accepted if the lane is blocked + // (i.e. when `unrewarded_limit_reached` is `true`) + None + }, + }; + + // check if we need unblocking transaction and we may submit it + #[allow(clippy::reversed_empty_ranges)] + let selected_nonces = match selected_nonces { + Some(selected_nonces) => selected_nonces, + None if unrewarded_limit_reached && outbound_state_proof_required => 1..=0, + _ => return None, + }; + + let dispatch_weight = self.dispatch_weight_for_range(&selected_nonces); + Some(( + selected_nonces, + MessageProofParameters { outbound_state_proof_required, dispatch_weight }, + )) + } + + /// Returns lastest confirmed message at source chain, given source block. + fn latest_confirmed_nonce_at_source(&self, at: &SourceHeaderIdOf

) -> Option { + self.latest_confirmed_nonces_at_source + .iter() + .take_while(|(id, _)| id.0 <= at.0) + .last() + .map(|(_, nonce)| *nonce) + } + + /// Returns total weight of all undelivered messages. + fn dispatch_weight_for_range(&self, range: &RangeInclusive) -> Weight { + self.strategy + .source_queue() + .iter() + .flat_map(|(_, subrange)| { + subrange + .iter() + .filter(|(nonce, _)| range.contains(nonce)) + .map(|(_, details)| details.dispatch_weight) + }) + .fold(Weight::zero(), |total, weight| total.saturating_add(weight)) + } +} + +#[async_trait] +impl RaceStrategy, TargetHeaderIdOf

, P::MessagesProof> + for MessageDeliveryStrategy +where + P: MessageLane, + SC: MessageLaneSourceClient

, + TC: MessageLaneTargetClient

, +{ + type SourceNoncesRange = MessageDetailsMap; + type ProofParameters = MessageProofParameters; + type TargetNoncesData = DeliveryRaceTargetNoncesData; + + fn is_empty(&self) -> bool { + self.strategy.is_empty() + } + + async fn required_source_header_at_target< + RS: RaceState, TargetHeaderIdOf

>, + >( + &self, + race_state: RS, + ) -> Option> { + // we have already submitted something - let's wait until it is mined + if race_state.nonces_submitted().is_some() { + return None + } + + // if we can deliver something using current race state, go on + let selected_nonces = self.select_race_action(race_state.clone()).await; + if selected_nonces.is_some() { + return None + } + + // check if we may deliver some messages if we'll relay require source header + // to target first + let maybe_source_header_for_delivery = + self.strategy.source_queue().back().map(|(id, _)| id.clone()); + if self + .can_submit_transaction_with( + race_state.clone(), + maybe_source_header_for_delivery.clone(), + ) + .await + { + return maybe_source_header_for_delivery + } + + // ok, we can't delivery anything even if we relay some source blocks first. But maybe + // the lane is blocked and we need to submit unblock transaction? + let maybe_source_header_for_reward_confirmation = + self.latest_confirmed_nonces_at_source.back().map(|(id, _)| id.clone()); + if self + .can_submit_transaction_with( + race_state.clone(), + maybe_source_header_for_reward_confirmation.clone(), + ) + .await + { + return maybe_source_header_for_reward_confirmation + } + + None + } + + fn best_at_source(&self) -> Option { + self.strategy.best_at_source() + } + + fn best_at_target(&self) -> Option { + self.strategy.best_at_target() + } + + fn source_nonces_updated( + &mut self, + at_block: SourceHeaderIdOf

, + nonces: SourceClientNonces, + ) { + if let Some(confirmed_nonce) = nonces.confirmed_nonce { + let is_confirmed_nonce_updated = self + .latest_confirmed_nonces_at_source + .back() + .map(|(_, prev_nonce)| *prev_nonce != confirmed_nonce) + .unwrap_or(true); + if is_confirmed_nonce_updated { + self.latest_confirmed_nonces_at_source + .push_back((at_block.clone(), confirmed_nonce)); + } + } + self.strategy.source_nonces_updated(at_block, nonces) + } + + fn reset_best_target_nonces(&mut self) { + self.target_nonces = None; + self.strategy.reset_best_target_nonces(); + } + + fn best_target_nonces_updated, TargetHeaderIdOf

>>( + &mut self, + nonces: TargetClientNonces, + race_state: &mut RS, + ) { + // best target nonces must always be ge than finalized target nonces + let latest_nonce = nonces.latest_nonce; + self.target_nonces = Some(nonces); + + self.strategy.best_target_nonces_updated( + TargetClientNonces { latest_nonce, nonces_data: () }, + race_state, + ) + } + + fn finalized_target_nonces_updated, TargetHeaderIdOf

>>( + &mut self, + nonces: TargetClientNonces, + race_state: &mut RS, + ) { + if let Some(ref best_finalized_source_header_id_at_best_target) = + race_state.best_finalized_source_header_id_at_best_target() + { + let oldest_header_number_to_keep = best_finalized_source_header_id_at_best_target.0; + while self + .latest_confirmed_nonces_at_source + .front() + .map(|(id, _)| id.0 < oldest_header_number_to_keep) + .unwrap_or(false) + { + self.latest_confirmed_nonces_at_source.pop_front(); + } + } + + if let Some(ref mut target_nonces) = self.target_nonces { + target_nonces.latest_nonce = + std::cmp::max(target_nonces.latest_nonce, nonces.latest_nonce); + } + + self.strategy.finalized_target_nonces_updated( + TargetClientNonces { latest_nonce: nonces.latest_nonce, nonces_data: () }, + race_state, + ) + } + + async fn select_nonces_to_deliver, TargetHeaderIdOf

>>( + &self, + race_state: RS, + ) -> Option<(RangeInclusive, Self::ProofParameters)> { + self.select_race_action(race_state).await + } +} + +impl NoncesRange for MessageDetailsMap { + fn begin(&self) -> MessageNonce { + self.keys().next().cloned().unwrap_or_default() + } + + fn end(&self) -> MessageNonce { + self.keys().next_back().cloned().unwrap_or_default() + } + + fn greater_than(mut self, nonce: MessageNonce) -> Option { + let gte = self.split_off(&(nonce + 1)); + if gte.is_empty() { + None + } else { + Some(gte) + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + message_lane_loop::{ + tests::{ + header_id, TestMessageLane, TestMessagesBatchTransaction, TestMessagesProof, + TestSourceChainBalance, TestSourceClient, TestSourceHeaderId, TestTargetClient, + TestTargetHeaderId, + }, + MessageDetails, + }, + message_race_loop::RaceStateImpl, + }; + + use super::*; + + const DEFAULT_DISPATCH_WEIGHT: Weight = Weight::from_parts(1, 0); + const DEFAULT_SIZE: u32 = 1; + + type TestRaceState = RaceStateImpl< + TestSourceHeaderId, + TestTargetHeaderId, + TestMessagesProof, + TestMessagesBatchTransaction, + >; + type TestStrategy = + MessageDeliveryStrategy; + + fn source_nonces( + new_nonces: RangeInclusive, + confirmed_nonce: MessageNonce, + reward: TestSourceChainBalance, + ) -> SourceClientNonces> { + SourceClientNonces { + new_nonces: new_nonces + .into_iter() + .map(|nonce| { + ( + nonce, + MessageDetails { + dispatch_weight: DEFAULT_DISPATCH_WEIGHT, + size: DEFAULT_SIZE, + reward, + }, + ) + }) + .collect(), + confirmed_nonce: Some(confirmed_nonce), + } + } + + fn prepare_strategy() -> (TestRaceState, TestStrategy) { + let mut race_state = RaceStateImpl { + best_finalized_source_header_id_at_source: Some(header_id(1)), + best_finalized_source_header_id_at_best_target: Some(header_id(1)), + best_target_header_id: Some(header_id(1)), + best_finalized_target_header_id: Some(header_id(1)), + nonces_to_submit: None, + nonces_to_submit_batch: None, + nonces_submitted: None, + }; + + let mut race_strategy = TestStrategy { + max_unrewarded_relayer_entries_at_target: 4, + max_unconfirmed_nonces_at_target: 4, + max_messages_in_single_batch: 4, + max_messages_weight_in_single_batch: Weight::from_parts(4, 0), + max_messages_size_in_single_batch: 4, + latest_confirmed_nonces_at_source: vec![(header_id(1), 19)].into_iter().collect(), + lane_source_client: TestSourceClient::default(), + lane_target_client: TestTargetClient::default(), + metrics_msg: None, + target_nonces: Some(TargetClientNonces { + latest_nonce: 19, + nonces_data: DeliveryRaceTargetNoncesData { + confirmed_nonce: 19, + unrewarded_relayers: UnrewardedRelayersState { + unrewarded_relayer_entries: 0, + messages_in_oldest_entry: 0, + total_messages: 0, + last_delivered_nonce: 0, + }, + }, + }), + strategy: BasicStrategy::new(), + }; + + race_strategy + .strategy + .source_nonces_updated(header_id(1), source_nonces(20..=23, 19, 0)); + + let target_nonces = TargetClientNonces { latest_nonce: 19, nonces_data: () }; + race_strategy + .strategy + .best_target_nonces_updated(target_nonces.clone(), &mut race_state); + race_strategy + .strategy + .finalized_target_nonces_updated(target_nonces, &mut race_state); + + (race_state, race_strategy) + } + + fn proof_parameters(state_required: bool, weight: u32) -> MessageProofParameters { + MessageProofParameters { + outbound_state_proof_required: state_required, + dispatch_weight: Weight::from_parts(weight as u64, 0), + } + } + + #[test] + fn weights_map_works_as_nonces_range() { + fn build_map( + range: RangeInclusive, + ) -> MessageDetailsMap { + range + .map(|idx| { + ( + idx, + MessageDetails { + dispatch_weight: Weight::from_parts(idx, 0), + size: idx as _, + reward: idx as _, + }, + ) + }) + .collect() + } + + let map = build_map(20..=30); + + assert_eq!(map.begin(), 20); + assert_eq!(map.end(), 30); + assert_eq!(map.clone().greater_than(10), Some(build_map(20..=30))); + assert_eq!(map.clone().greater_than(19), Some(build_map(20..=30))); + assert_eq!(map.clone().greater_than(20), Some(build_map(21..=30))); + assert_eq!(map.clone().greater_than(25), Some(build_map(26..=30))); + assert_eq!(map.clone().greater_than(29), Some(build_map(30..=30))); + assert_eq!(map.greater_than(30), None); + } + + #[async_std::test] + async fn message_delivery_strategy_selects_messages_to_deliver() { + let (state, strategy) = prepare_strategy(); + + // both sides are ready to relay new messages + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=23), proof_parameters(false, 4))) + ); + } + + #[async_std::test] + async fn message_delivery_strategy_includes_outbound_state_proof_when_new_nonces_are_available() + { + let (state, mut strategy) = prepare_strategy(); + + // if there are new confirmed nonces on source, we want to relay this information + // to target to prune rewards queue + let prev_confirmed_nonce_at_source = + strategy.latest_confirmed_nonces_at_source.back().unwrap().1; + strategy.target_nonces.as_mut().unwrap().nonces_data.confirmed_nonce = + prev_confirmed_nonce_at_source - 1; + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=23), proof_parameters(true, 4))) + ); + } + + #[async_std::test] + async fn message_delivery_strategy_selects_nothing_if_there_are_too_many_unrewarded_relayers() { + let (state, mut strategy) = prepare_strategy(); + + // if there are already `max_unrewarded_relayer_entries_at_target` entries at target, + // we need to wait until rewards will be paid + { + let unrewarded_relayers = + &mut strategy.target_nonces.as_mut().unwrap().nonces_data.unrewarded_relayers; + unrewarded_relayers.unrewarded_relayer_entries = + strategy.max_unrewarded_relayer_entries_at_target; + unrewarded_relayers.messages_in_oldest_entry = 4; + } + assert_eq!(strategy.select_nonces_to_deliver(state).await, None); + } + + #[async_std::test] + async fn message_delivery_strategy_selects_nothing_if_proved_rewards_is_not_enough_to_remove_oldest_unrewarded_entry( + ) { + let (state, mut strategy) = prepare_strategy(); + + // if there are already `max_unrewarded_relayer_entries_at_target` entries at target, + // we need to prove at least `messages_in_oldest_entry` rewards + let prev_confirmed_nonce_at_source = + strategy.latest_confirmed_nonces_at_source.back().unwrap().1; + { + let nonces_data = &mut strategy.target_nonces.as_mut().unwrap().nonces_data; + nonces_data.confirmed_nonce = prev_confirmed_nonce_at_source - 1; + let unrewarded_relayers = &mut nonces_data.unrewarded_relayers; + unrewarded_relayers.unrewarded_relayer_entries = + strategy.max_unrewarded_relayer_entries_at_target; + unrewarded_relayers.messages_in_oldest_entry = 4; + } + assert_eq!(strategy.select_nonces_to_deliver(state).await, None); + } + + #[async_std::test] + async fn message_delivery_strategy_includes_outbound_state_proof_if_proved_rewards_is_enough() { + let (state, mut strategy) = prepare_strategy(); + + // if there are already `max_unrewarded_relayer_entries_at_target` entries at target, + // we need to prove at least `messages_in_oldest_entry` rewards + let prev_confirmed_nonce_at_source = + strategy.latest_confirmed_nonces_at_source.back().unwrap().1; + { + let nonces_data = &mut strategy.target_nonces.as_mut().unwrap().nonces_data; + nonces_data.confirmed_nonce = prev_confirmed_nonce_at_source - 3; + let unrewarded_relayers = &mut nonces_data.unrewarded_relayers; + unrewarded_relayers.unrewarded_relayer_entries = + strategy.max_unrewarded_relayer_entries_at_target; + unrewarded_relayers.messages_in_oldest_entry = 3; + } + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=23), proof_parameters(true, 4))) + ); + } + + #[async_std::test] + async fn message_delivery_strategy_limits_batch_by_messages_weight() { + let (state, mut strategy) = prepare_strategy(); + + // not all queued messages may fit in the batch, because batch has max weight + strategy.max_messages_weight_in_single_batch = Weight::from_parts(3, 0); + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=22), proof_parameters(false, 3))) + ); + } + + #[async_std::test] + async fn message_delivery_strategy_accepts_single_message_even_if_its_weight_overflows_maximal_weight( + ) { + let (state, mut strategy) = prepare_strategy(); + + // first message doesn't fit in the batch, because it has weight (10) that overflows max + // weight (4) + strategy.strategy.source_queue_mut()[0].1.get_mut(&20).unwrap().dispatch_weight = + Weight::from_parts(10, 0); + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=20), proof_parameters(false, 10))) + ); + } + + #[async_std::test] + async fn message_delivery_strategy_limits_batch_by_messages_size() { + let (state, mut strategy) = prepare_strategy(); + + // not all queued messages may fit in the batch, because batch has max weight + strategy.max_messages_size_in_single_batch = 3; + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=22), proof_parameters(false, 3))) + ); + } + + #[async_std::test] + async fn message_delivery_strategy_accepts_single_message_even_if_its_weight_overflows_maximal_size( + ) { + let (state, mut strategy) = prepare_strategy(); + + // first message doesn't fit in the batch, because it has weight (10) that overflows max + // weight (4) + strategy.strategy.source_queue_mut()[0].1.get_mut(&20).unwrap().size = 10; + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=20), proof_parameters(false, 1))) + ); + } + + #[async_std::test] + async fn message_delivery_strategy_limits_batch_by_messages_count_when_there_is_upper_limit() { + let (state, mut strategy) = prepare_strategy(); + + // not all queued messages may fit in the batch, because batch has max number of messages + // limit + strategy.max_messages_in_single_batch = 3; + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=22), proof_parameters(false, 3))) + ); + } + + #[async_std::test] + async fn message_delivery_strategy_limits_batch_by_messages_count_when_there_are_unconfirmed_nonces( + ) { + let (state, mut strategy) = prepare_strategy(); + + // 1 delivery confirmation from target to source is still missing, so we may only + // relay 3 new messages + let prev_confirmed_nonce_at_source = + strategy.latest_confirmed_nonces_at_source.back().unwrap().1; + strategy.latest_confirmed_nonces_at_source = + vec![(header_id(1), prev_confirmed_nonce_at_source - 1)].into_iter().collect(); + strategy.target_nonces.as_mut().unwrap().nonces_data.confirmed_nonce = + prev_confirmed_nonce_at_source - 1; + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=22), proof_parameters(false, 3))) + ); + } + + #[async_std::test] + async fn message_delivery_strategy_waits_for_confirmed_nonce_header_to_appear_on_target() { + // 1 delivery confirmation from target to source is still missing, so we may deliver + // reward confirmation with our message delivery transaction. But the problem is that + // the reward has been paid at header 2 && this header is still unknown to target node. + // + // => so we can't deliver more than 3 messages + let (mut state, mut strategy) = prepare_strategy(); + let prev_confirmed_nonce_at_source = + strategy.latest_confirmed_nonces_at_source.back().unwrap().1; + strategy.latest_confirmed_nonces_at_source = vec![ + (header_id(1), prev_confirmed_nonce_at_source - 1), + (header_id(2), prev_confirmed_nonce_at_source), + ] + .into_iter() + .collect(); + strategy.target_nonces.as_mut().unwrap().nonces_data.confirmed_nonce = + prev_confirmed_nonce_at_source - 1; + state.best_finalized_source_header_id_at_best_target = Some(header_id(1)); + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=22), proof_parameters(false, 3))) + ); + + // the same situation, but the header 2 is known to the target node, so we may deliver + // reward confirmation + let (mut state, mut strategy) = prepare_strategy(); + let prev_confirmed_nonce_at_source = + strategy.latest_confirmed_nonces_at_source.back().unwrap().1; + strategy.latest_confirmed_nonces_at_source = vec![ + (header_id(1), prev_confirmed_nonce_at_source - 1), + (header_id(2), prev_confirmed_nonce_at_source), + ] + .into_iter() + .collect(); + strategy.target_nonces.as_mut().unwrap().nonces_data.confirmed_nonce = + prev_confirmed_nonce_at_source - 1; + state.best_finalized_source_header_id_at_source = Some(header_id(2)); + state.best_finalized_source_header_id_at_best_target = Some(header_id(2)); + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=23), proof_parameters(true, 4))) + ); + } + + #[async_std::test] + async fn source_header_is_required_when_confirmations_are_required() { + // let's prepare situation when: + // - all messages [20; 23] have been generated at source block#1; + let (mut state, mut strategy) = prepare_strategy(); + // + // - messages [20; 23] have been delivered + assert_eq!( + strategy.select_nonces_to_deliver(state.clone()).await, + Some(((20..=23), proof_parameters(false, 4))) + ); + strategy.finalized_target_nonces_updated( + TargetClientNonces { + latest_nonce: 23, + nonces_data: DeliveryRaceTargetNoncesData { + confirmed_nonce: 19, + unrewarded_relayers: UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + messages_in_oldest_entry: 4, + total_messages: 4, + last_delivered_nonce: 23, + }, + }, + }, + &mut state, + ); + // nothing needs to be delivered now and we don't need any new headers + assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, None); + assert_eq!(strategy.required_source_header_at_target(state.clone()).await, None); + + // block#2 is generated + state.best_finalized_source_header_id_at_source = Some(header_id(2)); + state.best_finalized_source_header_id_at_best_target = Some(header_id(2)); + state.best_target_header_id = Some(header_id(2)); + state.best_finalized_target_header_id = Some(header_id(2)); + + // now let's generate two more nonces [24; 25] at the source; + strategy.source_nonces_updated(header_id(2), source_nonces(24..=25, 19, 0)); + // + // we don't need to relay more headers to target, because messages [20; 23] have + // not confirmed to source yet + assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, None); + assert_eq!(strategy.required_source_header_at_target(state.clone()).await, None); + + // let's relay source block#3 + state.best_finalized_source_header_id_at_source = Some(header_id(3)); + state.best_finalized_source_header_id_at_best_target = Some(header_id(3)); + state.best_target_header_id = Some(header_id(3)); + state.best_finalized_target_header_id = Some(header_id(3)); + + // and ask strategy again => still nothing to deliver, because parallel confirmations + // race need to be pushed further + assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, None); + assert_eq!(strategy.required_source_header_at_target(state.clone()).await, None); + + // let's relay source block#3 + state.best_finalized_source_header_id_at_source = Some(header_id(4)); + state.best_finalized_source_header_id_at_best_target = Some(header_id(4)); + state.best_target_header_id = Some(header_id(4)); + state.best_finalized_target_header_id = Some(header_id(4)); + + // let's confirm messages [20; 23] + strategy.source_nonces_updated(header_id(4), source_nonces(24..=25, 23, 0)); + + // and ask strategy again => now we have everything required to deliver remaining + // [24; 25] nonces and proof of [20; 23] confirmation + assert_eq!( + strategy.select_nonces_to_deliver(state.clone()).await, + Some(((24..=25), proof_parameters(true, 2))), + ); + assert_eq!(strategy.required_source_header_at_target(state).await, None); + } + + #[async_std::test] + async fn relayer_uses_flattened_view_of_the_source_queue_to_select_nonces() { + // Real scenario that has happened on test deployments: + // 1) relayer witnessed M1 at block 1 => it has separate entry in the `source_queue` + // 2) relayer witnessed M2 at block 2 => it has separate entry in the `source_queue` + // 3) if block 2 is known to the target node, then both M1 and M2 are selected for single + // delivery, even though weight(M1+M2) > larger than largest allowed weight + // + // This was happening because selector (`select_nonces_for_delivery_transaction`) has been + // called for every `source_queue` entry separately without preserving any context. + let (mut state, mut strategy) = prepare_strategy(); + let nonces = source_nonces(24..=25, 19, 0); + strategy.strategy.source_nonces_updated(header_id(2), nonces); + strategy.max_unrewarded_relayer_entries_at_target = 100; + strategy.max_unconfirmed_nonces_at_target = 100; + strategy.max_messages_in_single_batch = 5; + strategy.max_messages_weight_in_single_batch = Weight::from_parts(100, 0); + strategy.max_messages_size_in_single_batch = 100; + state.best_finalized_source_header_id_at_best_target = Some(header_id(2)); + + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=24), proof_parameters(false, 5))) + ); + } + + #[async_std::test] + #[allow(clippy::reversed_empty_ranges)] + async fn no_source_headers_required_at_target_if_lanes_are_empty() { + let (state, _) = prepare_strategy(); + let mut strategy = TestStrategy { + max_unrewarded_relayer_entries_at_target: 4, + max_unconfirmed_nonces_at_target: 4, + max_messages_in_single_batch: 4, + max_messages_weight_in_single_batch: Weight::from_parts(4, 0), + max_messages_size_in_single_batch: 4, + latest_confirmed_nonces_at_source: VecDeque::new(), + lane_source_client: TestSourceClient::default(), + lane_target_client: TestTargetClient::default(), + metrics_msg: None, + target_nonces: None, + strategy: BasicStrategy::new(), + }; + + let source_header_id = header_id(10); + strategy.source_nonces_updated( + source_header_id, + // MessageDeliveryRaceSource::nonces returns Some(0), because that's how it is + // represented in memory (there's no Options in OutboundLaneState) + source_nonces(1u64..=0u64, 0, 0), + ); + + // even though `latest_confirmed_nonces_at_source` is not empty, new headers are not + // requested + assert_eq!( + strategy.latest_confirmed_nonces_at_source, + VecDeque::from([(source_header_id, 0)]) + ); + assert_eq!(strategy.required_source_header_at_target(state).await, None); + } + + #[async_std::test] + async fn previous_nonces_are_selected_if_reorg_happens_at_target_chain() { + // this is the copy of the similar test in the `mesage_race_strategy.rs`, but it also tests + // that the `MessageDeliveryStrategy` acts properly in the similar scenario + + // tune parameters to allow 5 nonces per delivery transaction + let (mut state, mut strategy) = prepare_strategy(); + strategy.max_unrewarded_relayer_entries_at_target = 5; + strategy.max_unconfirmed_nonces_at_target = 5; + strategy.max_messages_in_single_batch = 5; + strategy.max_messages_weight_in_single_batch = Weight::from_parts(5, 0); + strategy.max_messages_size_in_single_batch = 5; + + // in this state we have 4 available nonces for delivery + assert_eq!( + strategy.select_nonces_to_deliver(state.clone()).await, + Some(( + 20..=23, + MessageProofParameters { + outbound_state_proof_required: false, + dispatch_weight: Weight::from_parts(4, 0), + } + )), + ); + + // let's say we have submitted 20..=23 + state.nonces_submitted = Some(20..=23); + + // then new nonce 24 appear at the source block 2 + let new_nonce_24 = vec![( + 24, + MessageDetails { dispatch_weight: Weight::from_parts(1, 0), size: 0, reward: 0 }, + )] + .into_iter() + .collect(); + let source_header_2 = header_id(2); + state.best_finalized_source_header_id_at_source = Some(source_header_2); + strategy.source_nonces_updated( + source_header_2, + SourceClientNonces { new_nonces: new_nonce_24, confirmed_nonce: None }, + ); + // and nonce 23 appear at the best block of the target node (best finalized still has 0 + // nonces) + let target_nonces_data = DeliveryRaceTargetNoncesData { + confirmed_nonce: 19, + unrewarded_relayers: UnrewardedRelayersState::default(), + }; + let target_header_2 = header_id(2); + state.best_target_header_id = Some(target_header_2); + strategy.best_target_nonces_updated( + TargetClientNonces { latest_nonce: 23, nonces_data: target_nonces_data.clone() }, + &mut state, + ); + + // then best target header is retracted + strategy.best_target_nonces_updated( + TargetClientNonces { latest_nonce: 19, nonces_data: target_nonces_data.clone() }, + &mut state, + ); + + // ... and some fork with 19 delivered nonces is finalized + let target_header_2_fork = header_id(2_1); + state.best_finalized_source_header_id_at_source = Some(source_header_2); + state.best_finalized_source_header_id_at_best_target = Some(source_header_2); + state.best_target_header_id = Some(target_header_2_fork); + state.best_finalized_target_header_id = Some(target_header_2_fork); + strategy.finalized_target_nonces_updated( + TargetClientNonces { latest_nonce: 19, nonces_data: target_nonces_data.clone() }, + &mut state, + ); + + // now we have to select nonces 20..=23 for delivery again + assert_eq!( + strategy.select_nonces_to_deliver(state.clone()).await, + Some(( + 20..=24, + MessageProofParameters { + outbound_state_proof_required: false, + dispatch_weight: Weight::from_parts(5, 0), + } + )), + ); + } + + #[async_std::test] + #[allow(clippy::reversed_empty_ranges)] + async fn delivery_race_is_able_to_unblock_lane() { + // step 1: messages 20..=23 are delivered from source to target at target block 2 + fn at_target_block_2_deliver_messages( + strategy: &mut TestStrategy, + state: &mut TestRaceState, + occupied_relayer_slots: MessageNonce, + occupied_message_slots: MessageNonce, + ) { + let nonces_at_target = TargetClientNonces { + latest_nonce: 23, + nonces_data: DeliveryRaceTargetNoncesData { + confirmed_nonce: 19, + unrewarded_relayers: UnrewardedRelayersState { + unrewarded_relayer_entries: occupied_relayer_slots, + total_messages: occupied_message_slots, + ..Default::default() + }, + }, + }; + + state.best_target_header_id = Some(header_id(2)); + state.best_finalized_target_header_id = Some(header_id(2)); + + strategy.best_target_nonces_updated(nonces_at_target.clone(), state); + strategy.finalized_target_nonces_updated(nonces_at_target, state); + } + + // step 2: delivery of messages 20..=23 is confirmed to the source node at source block 2 + fn at_source_block_2_deliver_confirmations( + strategy: &mut TestStrategy, + state: &mut TestRaceState, + ) { + state.best_finalized_source_header_id_at_source = Some(header_id(2)); + + strategy.source_nonces_updated( + header_id(2), + SourceClientNonces { new_nonces: Default::default(), confirmed_nonce: Some(23) }, + ); + } + + // step 3: finalize source block 2 at target block 3 and select nonces to deliver + async fn at_target_block_3_select_nonces_to_deliver( + strategy: &TestStrategy, + mut state: TestRaceState, + ) -> Option<(RangeInclusive, MessageProofParameters)> { + state.best_finalized_source_header_id_at_best_target = Some(header_id(2)); + state.best_target_header_id = Some(header_id(3)); + state.best_finalized_target_header_id = Some(header_id(3)); + + strategy.select_nonces_to_deliver(state).await + } + + let max_unrewarded_relayer_entries_at_target = 4; + let max_unconfirmed_nonces_at_target = 4; + let expected_rewards_proof = Some(( + 1..=0, + MessageProofParameters { + outbound_state_proof_required: true, + dispatch_weight: Weight::zero(), + }, + )); + + // when lane is NOT blocked + let (mut state, mut strategy) = prepare_strategy(); + at_target_block_2_deliver_messages( + &mut strategy, + &mut state, + max_unrewarded_relayer_entries_at_target - 1, + max_unconfirmed_nonces_at_target - 1, + ); + at_source_block_2_deliver_confirmations(&mut strategy, &mut state); + assert_eq!(strategy.required_source_header_at_target(state.clone()).await, None); + assert_eq!(at_target_block_3_select_nonces_to_deliver(&strategy, state).await, None); + + // when lane is blocked by no-relayer-slots in unrewarded relayers vector + let (mut state, mut strategy) = prepare_strategy(); + at_target_block_2_deliver_messages( + &mut strategy, + &mut state, + max_unrewarded_relayer_entries_at_target, + max_unconfirmed_nonces_at_target - 1, + ); + at_source_block_2_deliver_confirmations(&mut strategy, &mut state); + assert_eq!( + strategy.required_source_header_at_target(state.clone()).await, + Some(header_id(2)) + ); + assert_eq!( + at_target_block_3_select_nonces_to_deliver(&strategy, state).await, + expected_rewards_proof + ); + + // when lane is blocked by no-message-slots in unrewarded relayers vector + let (mut state, mut strategy) = prepare_strategy(); + at_target_block_2_deliver_messages( + &mut strategy, + &mut state, + max_unrewarded_relayer_entries_at_target - 1, + max_unconfirmed_nonces_at_target, + ); + at_source_block_2_deliver_confirmations(&mut strategy, &mut state); + assert_eq!( + strategy.required_source_header_at_target(state.clone()).await, + Some(header_id(2)) + ); + assert_eq!( + at_target_block_3_select_nonces_to_deliver(&strategy, state).await, + expected_rewards_proof + ); + + // when lane is blocked by no-message-slots and no-message-slots in unrewarded relayers + // vector + let (mut state, mut strategy) = prepare_strategy(); + at_target_block_2_deliver_messages( + &mut strategy, + &mut state, + max_unrewarded_relayer_entries_at_target - 1, + max_unconfirmed_nonces_at_target, + ); + at_source_block_2_deliver_confirmations(&mut strategy, &mut state); + assert_eq!( + strategy.required_source_header_at_target(state.clone()).await, + Some(header_id(2)) + ); + assert_eq!( + at_target_block_3_select_nonces_to_deliver(&strategy, state).await, + expected_rewards_proof + ); + + // when we have already selected some nonces to deliver, we don't need to select anything + let (mut state, mut strategy) = prepare_strategy(); + at_target_block_2_deliver_messages( + &mut strategy, + &mut state, + max_unrewarded_relayer_entries_at_target - 1, + max_unconfirmed_nonces_at_target, + ); + at_source_block_2_deliver_confirmations(&mut strategy, &mut state); + state.nonces_to_submit = Some((header_id(2), 1..=0, (1..=0, None))); + assert_eq!(strategy.required_source_header_at_target(state.clone()).await, None); + assert_eq!(at_target_block_3_select_nonces_to_deliver(&strategy, state).await, None); + + // when we have already submitted some nonces, we don't need to select anything + let (mut state, mut strategy) = prepare_strategy(); + at_target_block_2_deliver_messages( + &mut strategy, + &mut state, + max_unrewarded_relayer_entries_at_target - 1, + max_unconfirmed_nonces_at_target, + ); + at_source_block_2_deliver_confirmations(&mut strategy, &mut state); + state.nonces_submitted = Some(1..=0); + assert_eq!(strategy.required_source_header_at_target(state.clone()).await, None); + assert_eq!(at_target_block_3_select_nonces_to_deliver(&strategy, state).await, None); + } + + #[async_std::test] + async fn outbound_state_proof_is_not_required_when_we_have_no_new_confirmations() { + let (mut state, mut strategy) = prepare_strategy(); + + // pretend that we haven't seen any confirmations yet (or they're at the future target chain + // blocks) + strategy.latest_confirmed_nonces_at_source.clear(); + + // emulate delivery of some nonces (20..=23 are generated, but we only deliver 20..=21) + let nonces_at_target = TargetClientNonces { + latest_nonce: 21, + nonces_data: DeliveryRaceTargetNoncesData { + confirmed_nonce: 19, + unrewarded_relayers: UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + total_messages: 2, + ..Default::default() + }, + }, + }; + state.best_target_header_id = Some(header_id(2)); + state.best_finalized_target_header_id = Some(header_id(2)); + strategy.best_target_nonces_updated(nonces_at_target.clone(), &mut state); + strategy.finalized_target_nonces_updated(nonces_at_target, &mut state); + + // we won't include outbound lane state proof into 22..=23 delivery transaction + // because it brings no new reward confirmations + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((22..=23), proof_parameters(false, 2))) + ); + } +} diff --git a/bridges/relays/messages/src/message_race_limits.rs b/bridges/relays/messages/src/message_race_limits.rs new file mode 100644 index 0000000000000..873bb6aad0425 --- /dev/null +++ b/bridges/relays/messages/src/message_race_limits.rs @@ -0,0 +1,206 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! enforcement strategy + +use num_traits::Zero; +use std::ops::RangeInclusive; + +use bp_messages::{MessageNonce, Weight}; + +use crate::{ + message_lane::MessageLane, + message_lane_loop::{ + MessageDetails, MessageDetailsMap, SourceClient as MessageLaneSourceClient, + TargetClient as MessageLaneTargetClient, + }, + message_race_loop::NoncesRange, + message_race_strategy::SourceRangesQueue, + metrics::MessageLaneLoopMetrics, +}; + +/// Reference data for participating in relay +pub struct RelayReference< + P: MessageLane, + SourceClient: MessageLaneSourceClient

, + TargetClient: MessageLaneTargetClient

, +> { + /// The client that is connected to the message lane source node. + pub lane_source_client: SourceClient, + /// The client that is connected to the message lane target node. + pub lane_target_client: TargetClient, + /// Metrics reference. + pub metrics: Option, + /// Messages size summary + pub selected_size: u32, + + /// Hard check begin nonce + pub hard_selected_begin_nonce: MessageNonce, + + /// Index by all ready nonces + pub index: usize, + /// Current nonce + pub nonce: MessageNonce, + /// Current nonce details + pub details: MessageDetails, +} + +/// Relay reference data +pub struct RelayMessagesBatchReference< + P: MessageLane, + SourceClient: MessageLaneSourceClient

, + TargetClient: MessageLaneTargetClient

, +> { + /// Maximal number of relayed messages in single delivery transaction. + pub max_messages_in_this_batch: MessageNonce, + /// Maximal cumulative dispatch weight of relayed messages in single delivery transaction. + pub max_messages_weight_in_single_batch: Weight, + /// Maximal cumulative size of relayed messages in single delivery transaction. + pub max_messages_size_in_single_batch: u32, + /// The client that is connected to the message lane source node. + pub lane_source_client: SourceClient, + /// The client that is connected to the message lane target node. + pub lane_target_client: TargetClient, + /// Metrics reference. + pub metrics: Option, + /// Best available nonce at the **best** target block. We do not want to deliver nonces + /// less than this nonce, even though the block may be retracted. + pub best_target_nonce: MessageNonce, + /// Source queue. + pub nonces_queue: SourceRangesQueue< + P::SourceHeaderHash, + P::SourceHeaderNumber, + MessageDetailsMap, + >, + /// Range of indices within the `nonces_queue` that are available for selection. + pub nonces_queue_range: RangeInclusive, +} + +/// Limits of the message race transactions. +#[derive(Clone)] +pub struct MessageRaceLimits; + +impl MessageRaceLimits { + pub async fn decide< + P: MessageLane, + SourceClient: MessageLaneSourceClient

, + TargetClient: MessageLaneTargetClient

, + >( + reference: RelayMessagesBatchReference, + ) -> Option> { + let mut hard_selected_count = 0; + + let mut selected_weight = Weight::zero(); + let mut selected_count: MessageNonce = 0; + + let hard_selected_begin_nonce = std::cmp::max( + reference.best_target_nonce + 1, + reference.nonces_queue[*reference.nonces_queue_range.start()].1.begin(), + ); + + // relay reference + let mut relay_reference = RelayReference { + lane_source_client: reference.lane_source_client.clone(), + lane_target_client: reference.lane_target_client.clone(), + metrics: reference.metrics.clone(), + + selected_size: 0, + + hard_selected_begin_nonce, + + index: 0, + nonce: 0, + details: MessageDetails { + dispatch_weight: Weight::zero(), + size: 0, + reward: P::SourceChainBalance::zero(), + }, + }; + + let all_ready_nonces = reference + .nonces_queue + .range(reference.nonces_queue_range.clone()) + .flat_map(|(_, ready_nonces)| ready_nonces.iter()) + .filter(|(nonce, _)| **nonce >= hard_selected_begin_nonce) + .enumerate(); + for (index, (nonce, details)) in all_ready_nonces { + relay_reference.index = index; + relay_reference.nonce = *nonce; + relay_reference.details = *details; + + // Since we (hopefully) have some reserves in `max_messages_weight_in_single_batch` + // and `max_messages_size_in_single_batch`, we may still try to submit transaction + // with single message if message overflows these limits. The worst case would be if + // transaction will be rejected by the target runtime, but at least we have tried. + + // limit messages in the batch by weight + let new_selected_weight = match selected_weight.checked_add(&details.dispatch_weight) { + Some(new_selected_weight) + if new_selected_weight + .all_lte(reference.max_messages_weight_in_single_batch) => + new_selected_weight, + new_selected_weight if selected_count == 0 => { + log::warn!( + target: "bridge", + "Going to submit message delivery transaction with declared dispatch \ + weight {:?} that overflows maximal configured weight {}", + new_selected_weight, + reference.max_messages_weight_in_single_batch, + ); + new_selected_weight.unwrap_or(Weight::MAX) + }, + _ => break, + }; + + // limit messages in the batch by size + let new_selected_size = match relay_reference.selected_size.checked_add(details.size) { + Some(new_selected_size) + if new_selected_size <= reference.max_messages_size_in_single_batch => + new_selected_size, + new_selected_size if selected_count == 0 => { + log::warn!( + target: "bridge", + "Going to submit message delivery transaction with message \ + size {:?} that overflows maximal configured size {}", + new_selected_size, + reference.max_messages_size_in_single_batch, + ); + new_selected_size.unwrap_or(u32::MAX) + }, + _ => break, + }; + + // limit number of messages in the batch + let new_selected_count = selected_count + 1; + if new_selected_count > reference.max_messages_in_this_batch { + break + } + relay_reference.selected_size = new_selected_size; + + hard_selected_count = index + 1; + selected_weight = new_selected_weight; + selected_count = new_selected_count; + } + + if hard_selected_count != 0 { + let selected_max_nonce = + hard_selected_begin_nonce + hard_selected_count as MessageNonce - 1; + Some(hard_selected_begin_nonce..=selected_max_nonce) + } else { + None + } + } +} diff --git a/bridges/relays/messages/src/message_race_loop.rs b/bridges/relays/messages/src/message_race_loop.rs new file mode 100644 index 0000000000000..31341a9a0c0cd --- /dev/null +++ b/bridges/relays/messages/src/message_race_loop.rs @@ -0,0 +1,835 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +//! Loop that is serving single race within message lane. This could be +//! message delivery race, receiving confirmations race or processing +//! confirmations race. +//! +//! The idea of the race is simple - we have `nonce`-s on source and target +//! nodes. We're trying to prove that the source node has this nonce (and +//! associated data - like messages, lane state, etc) to the target node by +//! generating and submitting proof. + +use crate::message_lane_loop::{BatchTransaction, ClientState, NoncesSubmitArtifacts}; + +use async_trait::async_trait; +use bp_messages::MessageNonce; +use futures::{ + future::{FutureExt, TryFutureExt}, + stream::{FusedStream, StreamExt}, +}; +use relay_utils::{ + process_future_result, retry_backoff, FailedClient, MaybeConnectionError, + TrackedTransactionStatus, TransactionTracker, +}; +use std::{ + fmt::Debug, + ops::RangeInclusive, + time::{Duration, Instant}, +}; + +/// One of races within lane. +pub trait MessageRace { + /// Header id of the race source. + type SourceHeaderId: Debug + Clone + PartialEq + Send + Sync; + /// Header id of the race source. + type TargetHeaderId: Debug + Clone + PartialEq + Send + Sync; + + /// Message nonce used in the race. + type MessageNonce: Debug + Clone; + /// Proof that is generated and delivered in this race. + type Proof: Debug + Clone + Send + Sync; + + /// Name of the race source. + fn source_name() -> String; + /// Name of the race target. + fn target_name() -> String; +} + +/// State of race source client. +type SourceClientState

= + ClientState<

::SourceHeaderId,

::TargetHeaderId>; + +/// State of race target client. +type TargetClientState

= + ClientState<

::TargetHeaderId,

::SourceHeaderId>; + +/// Inclusive nonces range. +pub trait NoncesRange: Debug + Sized { + /// Get begin of the range. + fn begin(&self) -> MessageNonce; + /// Get end of the range. + fn end(&self) -> MessageNonce; + /// Returns new range with current range nonces that are greater than the passed `nonce`. + /// If there are no such nonces, `None` is returned. + fn greater_than(self, nonce: MessageNonce) -> Option; +} + +/// Nonces on the race source client. +#[derive(Debug, Clone)] +pub struct SourceClientNonces { + /// New nonces range known to the client. `New` here means all nonces generated after + /// `prev_latest_nonce` passed to the `SourceClient::nonces` method. + pub new_nonces: NoncesRange, + /// The latest nonce that is confirmed to the bridged client. This nonce only makes + /// sense in some races. In other races it is `None`. + pub confirmed_nonce: Option, +} + +/// Nonces on the race target client. +#[derive(Debug, Clone)] +pub struct TargetClientNonces { + /// The latest nonce that is known to the target client. + pub latest_nonce: MessageNonce, + /// Additional data from target node that may be used by the race. + pub nonces_data: TargetNoncesData, +} + +/// One of message lane clients, which is source client for the race. +#[async_trait] +pub trait SourceClient { + /// Type of error these clients returns. + type Error: std::fmt::Debug + MaybeConnectionError; + /// Type of nonces range returned by the source client. + type NoncesRange: NoncesRange; + /// Additional proof parameters required to generate proof. + type ProofParameters; + + /// Return nonces that are known to the source client. + async fn nonces( + &self, + at_block: P::SourceHeaderId, + prev_latest_nonce: MessageNonce, + ) -> Result<(P::SourceHeaderId, SourceClientNonces), Self::Error>; + /// Generate proof for delivering to the target client. + async fn generate_proof( + &self, + at_block: P::SourceHeaderId, + nonces: RangeInclusive, + proof_parameters: Self::ProofParameters, + ) -> Result<(P::SourceHeaderId, RangeInclusive, P::Proof), Self::Error>; +} + +/// One of message lane clients, which is target client for the race. +#[async_trait] +pub trait TargetClient { + /// Type of error these clients returns. + type Error: std::fmt::Debug + MaybeConnectionError; + /// Type of the additional data from the target client, used by the race. + type TargetNoncesData: std::fmt::Debug; + /// Type of batch transaction that submits finality and proof to the target node. + type BatchTransaction: BatchTransaction + Clone; + /// Transaction tracker to track submitted transactions. + type TransactionTracker: TransactionTracker; + + /// Ask headers relay to relay finalized headers up to (and including) given header + /// from race source to race target. + /// + /// The client may return `Some(_)`, which means that nothing has happened yet and + /// the caller must generate and append proof to the batch transaction + /// to actually send it (along with required header) to the node. + /// + /// If function has returned `None`, it means that the caller now must wait for the + /// appearance of the required header `id` at the target client. + async fn require_source_header( + &self, + id: P::SourceHeaderId, + ) -> Result, Self::Error>; + + /// Return nonces that are known to the target client. + async fn nonces( + &self, + at_block: P::TargetHeaderId, + update_metrics: bool, + ) -> Result<(P::TargetHeaderId, TargetClientNonces), Self::Error>; + /// Submit proof to the target client. + async fn submit_proof( + &self, + maybe_batch_tx: Option, + generated_at_block: P::SourceHeaderId, + nonces: RangeInclusive, + proof: P::Proof, + ) -> Result, Self::Error>; +} + +/// Race strategy. +#[async_trait] +pub trait RaceStrategy: Debug { + /// Type of nonces range expected from the source client. + type SourceNoncesRange: NoncesRange; + /// Additional proof parameters required to generate proof. + type ProofParameters; + /// Additional data expected from the target client. + type TargetNoncesData; + + /// Should return true if nothing has to be synced. + fn is_empty(&self) -> bool; + /// Return id of source header that is required to be on target to continue synchronization. + async fn required_source_header_at_target>( + &self, + race_state: RS, + ) -> Option; + /// Return the best nonce at source node. + /// + /// `Some` is returned only if we are sure that the value is greater or equal + /// than the result of `best_at_target`. + fn best_at_source(&self) -> Option; + /// Return the best nonce at target node. + /// + /// May return `None` if value is yet unknown. + fn best_at_target(&self) -> Option; + + /// Called when nonces are updated at source node of the race. + fn source_nonces_updated( + &mut self, + at_block: SourceHeaderId, + nonces: SourceClientNonces, + ); + /// Called when we want to wait until next `best_target_nonces_updated` before selecting + /// any nonces for delivery. + fn reset_best_target_nonces(&mut self); + /// Called when best nonces are updated at target node of the race. + fn best_target_nonces_updated>( + &mut self, + nonces: TargetClientNonces, + race_state: &mut RS, + ); + /// Called when finalized nonces are updated at target node of the race. + fn finalized_target_nonces_updated>( + &mut self, + nonces: TargetClientNonces, + race_state: &mut RS, + ); + /// Should return `Some(nonces)` if we need to deliver proof of `nonces` (and associated + /// data) from source to target node. + /// Additionally, parameters required to generate proof are returned. + async fn select_nonces_to_deliver>( + &self, + race_state: RS, + ) -> Option<(RangeInclusive, Self::ProofParameters)>; +} + +/// State of the race. +pub trait RaceState: Clone + Send + Sync { + /// Set best finalized source header id at the best block on the target + /// client (at the `best_finalized_source_header_id_at_best_target`). + fn set_best_finalized_source_header_id_at_best_target(&mut self, id: SourceHeaderId); + + /// Best finalized source header id at the source client. + fn best_finalized_source_header_id_at_source(&self) -> Option; + /// Best finalized source header id at the best block on the target + /// client (at the `best_finalized_source_header_id_at_best_target`). + fn best_finalized_source_header_id_at_best_target(&self) -> Option; + /// The best header id at the target client. + fn best_target_header_id(&self) -> Option; + /// Best finalized header id at the target client. + fn best_finalized_target_header_id(&self) -> Option; + + /// Returns `true` if we have selected nonces to submit to the target node. + fn nonces_to_submit(&self) -> Option>; + /// Reset our nonces selection. + fn reset_nonces_to_submit(&mut self); + + /// Returns `true` if we have submitted some nonces to the target node and are + /// waiting for them to appear there. + fn nonces_submitted(&self) -> Option>; + /// Reset our nonces submission. + fn reset_nonces_submitted(&mut self); +} + +/// State of the race and prepared batch transaction (if available). +#[derive(Debug, Clone)] +pub(crate) struct RaceStateImpl { + /// Best finalized source header id at the source client. + pub best_finalized_source_header_id_at_source: Option, + /// Best finalized source header id at the best block on the target + /// client (at the `best_finalized_source_header_id_at_best_target`). + pub best_finalized_source_header_id_at_best_target: Option, + /// The best header id at the target client. + pub best_target_header_id: Option, + /// Best finalized header id at the target client. + pub best_finalized_target_header_id: Option, + /// Range of nonces that we have selected to submit. + pub nonces_to_submit: Option<(SourceHeaderId, RangeInclusive, Proof)>, + /// Batch transaction ready to include and deliver selected `nonces_to_submit` from the + /// `state`. + pub nonces_to_submit_batch: Option, + /// Range of nonces that is currently submitted. + pub nonces_submitted: Option>, +} + +impl Default + for RaceStateImpl +{ + fn default() -> Self { + RaceStateImpl { + best_finalized_source_header_id_at_source: None, + best_finalized_source_header_id_at_best_target: None, + best_target_header_id: None, + best_finalized_target_header_id: None, + nonces_to_submit: None, + nonces_to_submit_batch: None, + nonces_submitted: None, + } + } +} + +impl RaceState + for RaceStateImpl +where + SourceHeaderId: Clone + Send + Sync, + TargetHeaderId: Clone + Send + Sync, + Proof: Clone + Send + Sync, + BatchTx: Clone + Send + Sync, +{ + fn set_best_finalized_source_header_id_at_best_target(&mut self, id: SourceHeaderId) { + self.best_finalized_source_header_id_at_best_target = Some(id); + } + + fn best_finalized_source_header_id_at_source(&self) -> Option { + self.best_finalized_source_header_id_at_source.clone() + } + + fn best_finalized_source_header_id_at_best_target(&self) -> Option { + self.best_finalized_source_header_id_at_best_target.clone() + } + + fn best_target_header_id(&self) -> Option { + self.best_target_header_id.clone() + } + + fn best_finalized_target_header_id(&self) -> Option { + self.best_finalized_target_header_id.clone() + } + + fn nonces_to_submit(&self) -> Option> { + self.nonces_to_submit.clone().map(|(_, nonces, _)| nonces) + } + + fn reset_nonces_to_submit(&mut self) { + self.nonces_to_submit = None; + self.nonces_to_submit_batch = None; + } + + fn nonces_submitted(&self) -> Option> { + self.nonces_submitted.clone() + } + + fn reset_nonces_submitted(&mut self) { + self.nonces_submitted = None; + } +} + +/// Run race loop until connection with target or source node is lost. +pub async fn run, TC: TargetClient

>( + race_source: SC, + race_source_updated: impl FusedStream>, + race_target: TC, + race_target_updated: impl FusedStream>, + mut strategy: impl RaceStrategy< + P::SourceHeaderId, + P::TargetHeaderId, + P::Proof, + SourceNoncesRange = SC::NoncesRange, + ProofParameters = SC::ProofParameters, + TargetNoncesData = TC::TargetNoncesData, + >, +) -> Result<(), FailedClient> { + let mut progress_context = Instant::now(); + let mut race_state = RaceStateImpl::default(); + + let mut source_retry_backoff = retry_backoff(); + let mut source_client_is_online = true; + let mut source_nonces_required = false; + let mut source_required_header = None; + let source_nonces = futures::future::Fuse::terminated(); + let source_generate_proof = futures::future::Fuse::terminated(); + let source_go_offline_future = futures::future::Fuse::terminated(); + + let mut target_retry_backoff = retry_backoff(); + let mut target_client_is_online = true; + let mut target_best_nonces_required = false; + let mut target_finalized_nonces_required = false; + let mut target_batch_transaction = None; + let target_require_source_header = futures::future::Fuse::terminated(); + let target_best_nonces = futures::future::Fuse::terminated(); + let target_finalized_nonces = futures::future::Fuse::terminated(); + let target_submit_proof = futures::future::Fuse::terminated(); + let target_tx_tracker = futures::future::Fuse::terminated(); + let target_go_offline_future = futures::future::Fuse::terminated(); + + futures::pin_mut!( + race_source_updated, + source_nonces, + source_generate_proof, + source_go_offline_future, + race_target_updated, + target_require_source_header, + target_best_nonces, + target_finalized_nonces, + target_submit_proof, + target_tx_tracker, + target_go_offline_future, + ); + + loop { + futures::select! { + // when headers ids are updated + source_state = race_source_updated.next() => { + if let Some(source_state) = source_state { + let is_source_state_updated = race_state.best_finalized_source_header_id_at_source.as_ref() + != Some(&source_state.best_finalized_self); + if is_source_state_updated { + source_nonces_required = true; + race_state.best_finalized_source_header_id_at_source + = Some(source_state.best_finalized_self); + } + } + }, + target_state = race_target_updated.next() => { + if let Some(target_state) = target_state { + let is_target_best_state_updated = race_state.best_target_header_id.as_ref() + != Some(&target_state.best_self); + + if is_target_best_state_updated { + target_best_nonces_required = true; + race_state.best_target_header_id = Some(target_state.best_self); + race_state.best_finalized_source_header_id_at_best_target + = target_state.best_finalized_peer_at_best_self; + } + + let is_target_finalized_state_updated = race_state.best_finalized_target_header_id.as_ref() + != Some(&target_state.best_finalized_self); + if is_target_finalized_state_updated { + target_finalized_nonces_required = true; + race_state.best_finalized_target_header_id = Some(target_state.best_finalized_self); + } + } + }, + + // when nonces are updated + nonces = source_nonces => { + source_nonces_required = false; + + source_client_is_online = process_future_result( + nonces, + &mut source_retry_backoff, + |(at_block, nonces)| { + log::debug!( + target: "bridge", + "Received nonces from {}: {:?}", + P::source_name(), + nonces, + ); + + strategy.source_nonces_updated(at_block, nonces); + }, + &mut source_go_offline_future, + async_std::task::sleep, + || format!("Error retrieving nonces from {}", P::source_name()), + ).fail_if_connection_error(FailedClient::Source)?; + + // ask for more headers if we have nonces to deliver and required headers are missing + source_required_header = strategy + .required_source_header_at_target(race_state.clone()) + .await; + }, + nonces = target_best_nonces => { + target_best_nonces_required = false; + + target_client_is_online = process_future_result( + nonces, + &mut target_retry_backoff, + |(_, nonces)| { + log::debug!( + target: "bridge", + "Received best nonces from {}: {:?}", + P::target_name(), + nonces, + ); + + strategy.best_target_nonces_updated(nonces, &mut race_state); + }, + &mut target_go_offline_future, + async_std::task::sleep, + || format!("Error retrieving best nonces from {}", P::target_name()), + ).fail_if_connection_error(FailedClient::Target)?; + }, + nonces = target_finalized_nonces => { + target_finalized_nonces_required = false; + + target_client_is_online = process_future_result( + nonces, + &mut target_retry_backoff, + |(_, nonces)| { + log::debug!( + target: "bridge", + "Received finalized nonces from {}: {:?}", + P::target_name(), + nonces, + ); + + strategy.finalized_target_nonces_updated(nonces, &mut race_state); + }, + &mut target_go_offline_future, + async_std::task::sleep, + || format!("Error retrieving finalized nonces from {}", P::target_name()), + ).fail_if_connection_error(FailedClient::Target)?; + }, + + // proof generation and submission + maybe_batch_transaction = target_require_source_header => { + source_required_header = None; + + target_client_is_online = process_future_result( + maybe_batch_transaction, + &mut target_retry_backoff, + |maybe_batch_transaction: Option| { + log::debug!( + target: "bridge", + "Target {} client has been asked for more {} headers. Batch tx: {}", + P::target_name(), + P::source_name(), + maybe_batch_transaction + .as_ref() + .map(|bt| format!("yes ({:?})", bt.required_header_id())) + .unwrap_or_else(|| "no".into()), + ); + + target_batch_transaction = maybe_batch_transaction; + }, + &mut target_go_offline_future, + async_std::task::sleep, + || format!("Error asking for source headers at {}", P::target_name()), + ).fail_if_connection_error(FailedClient::Target)?; + }, + proof = source_generate_proof => { + source_client_is_online = process_future_result( + proof, + &mut source_retry_backoff, + |(at_block, nonces_range, proof, batch_transaction)| { + log::debug!( + target: "bridge", + "Received proof for nonces in range {:?} from {}", + nonces_range, + P::source_name(), + ); + + race_state.nonces_to_submit = Some((at_block, nonces_range, proof)); + race_state.nonces_to_submit_batch = batch_transaction; + }, + &mut source_go_offline_future, + async_std::task::sleep, + || format!("Error generating proof at {}", P::source_name()), + ).fail_if_error(FailedClient::Source).map(|_| true)?; + }, + proof_submit_result = target_submit_proof => { + target_client_is_online = process_future_result( + proof_submit_result, + &mut target_retry_backoff, + |artifacts: NoncesSubmitArtifacts| { + log::debug!( + target: "bridge", + "Successfully submitted proof of nonces {:?} to {}", + artifacts.nonces, + P::target_name(), + ); + + race_state.nonces_submitted = Some(artifacts.nonces); + target_tx_tracker.set(artifacts.tx_tracker.wait().fuse()); + }, + &mut target_go_offline_future, + async_std::task::sleep, + || format!("Error submitting proof {}", P::target_name()), + ).fail_if_connection_error(FailedClient::Target)?; + + // in any case - we don't need to retry submitting the same nonces again until + // we read nonces from the target client + race_state.reset_nonces_to_submit(); + // if we have failed to submit transaction AND that is not the connection issue, + // then we need to read best target nonces before selecting nonces again + if !target_client_is_online { + strategy.reset_best_target_nonces(); + } + }, + target_transaction_status = target_tx_tracker => { + match (target_transaction_status, race_state.nonces_submitted.as_ref()) { + (TrackedTransactionStatus::Finalized(at_block), Some(nonces_submitted)) => { + // our transaction has been mined, but was it successful or not? let's check the best + // nonce at the target node. + let _ = race_target.nonces(at_block, false) + .await + .map_err(|e| format!("failed to read nonces from target node: {e:?}")) + .and_then(|(_, nonces_at_target)| { + if nonces_at_target.latest_nonce < *nonces_submitted.end() { + Err(format!( + "best nonce at target after tx is {:?} and we've submitted {:?}", + nonces_at_target.latest_nonce, + nonces_submitted.end(), + )) + } else { + Ok(()) + } + }) + .map_err(|e| { + log::error!( + target: "bridge", + "{} -> {} race transaction failed: {}", + P::source_name(), + P::target_name(), + e, + ); + + race_state.reset_nonces_submitted(); + }); + }, + (TrackedTransactionStatus::Lost, _) => { + log::warn!( + target: "bridge", + "{} -> {} race transaction has been lost. State: {:?}. Strategy: {:?}", + P::source_name(), + P::target_name(), + race_state, + strategy, + ); + + race_state.reset_nonces_submitted(); + }, + _ => (), + } + }, + + // when we're ready to retry request + _ = source_go_offline_future => { + source_client_is_online = true; + }, + _ = target_go_offline_future => { + target_client_is_online = true; + }, + } + + progress_context = print_race_progress::(progress_context, &strategy); + + if source_client_is_online { + source_client_is_online = false; + + // if we've started to submit batch transaction, let's prioritize it + // + // we're using `take` here, because we don't need batch transaction (i.e. some + // underlying finality proof) anymore for our future calls - we were unable to + // use it for our current state, so why would we need to keep an obsolete proof + // for the future? + let target_batch_transaction = target_batch_transaction.take(); + let expected_race_state = + if let Some(ref target_batch_transaction) = target_batch_transaction { + // when selecting nonces for the batch transaction, we assume that the required + // source header is already at the target chain + let required_source_header_at_target = + target_batch_transaction.required_header_id(); + let mut expected_race_state = race_state.clone(); + expected_race_state.best_finalized_source_header_id_at_best_target = + Some(required_source_header_at_target); + expected_race_state + } else { + race_state.clone() + }; + + let nonces_to_deliver = select_nonces_to_deliver(expected_race_state, &strategy).await; + let best_at_source = strategy.best_at_source(); + + if let Some((at_block, nonces_range, proof_parameters)) = nonces_to_deliver { + log::debug!( + target: "bridge", + "Asking {} to prove nonces in range {:?} at block {:?}", + P::source_name(), + nonces_range, + at_block, + ); + + source_generate_proof.set( + race_source + .generate_proof(at_block, nonces_range, proof_parameters) + .and_then(|(at_source_block, nonces, proof)| async { + Ok((at_source_block, nonces, proof, target_batch_transaction)) + }) + .fuse(), + ); + } else if let (true, Some(best_at_source)) = (source_nonces_required, best_at_source) { + log::debug!(target: "bridge", "Asking {} about message nonces", P::source_name()); + let at_block = race_state + .best_finalized_source_header_id_at_source + .as_ref() + .expect( + "source_nonces_required is only true when\ + best_finalized_source_header_id_at_source is Some; qed", + ) + .clone(); + source_nonces.set(race_source.nonces(at_block, best_at_source).fuse()); + } else { + source_client_is_online = true; + } + } + + if target_client_is_online { + target_client_is_online = false; + + if let Some((at_block, nonces_range, proof)) = race_state.nonces_to_submit.as_ref() { + log::debug!( + target: "bridge", + "Going to submit proof of messages in range {:?} to {} node{}", + nonces_range, + P::target_name(), + race_state.nonces_to_submit_batch.as_ref().map(|tx| format!( + ". This transaction is batched with sending the proof for header {:?}.", + tx.required_header_id()) + ).unwrap_or_default(), + ); + + target_submit_proof.set( + race_target + .submit_proof( + race_state.nonces_to_submit_batch.clone(), + at_block.clone(), + nonces_range.clone(), + proof.clone(), + ) + .fuse(), + ); + } else if let Some(source_required_header) = source_required_header.clone() { + log::debug!( + target: "bridge", + "Going to require {} header {:?} at {}", + P::source_name(), + source_required_header, + P::target_name(), + ); + target_require_source_header + .set(race_target.require_source_header(source_required_header).fuse()); + } else if target_best_nonces_required { + log::debug!(target: "bridge", "Asking {} about best message nonces", P::target_name()); + let at_block = race_state + .best_target_header_id + .as_ref() + .expect("target_best_nonces_required is only true when best_target_header_id is Some; qed") + .clone(); + target_best_nonces.set(race_target.nonces(at_block, false).fuse()); + } else if target_finalized_nonces_required { + log::debug!(target: "bridge", "Asking {} about finalized message nonces", P::target_name()); + let at_block = race_state + .best_finalized_target_header_id + .as_ref() + .expect( + "target_finalized_nonces_required is only true when\ + best_finalized_target_header_id is Some; qed", + ) + .clone(); + target_finalized_nonces.set(race_target.nonces(at_block, true).fuse()); + } else { + target_client_is_online = true; + } + } + } +} + +/// Print race progress. +fn print_race_progress(prev_time: Instant, strategy: &S) -> Instant +where + P: MessageRace, + S: RaceStrategy, +{ + let now_time = Instant::now(); + + let need_update = now_time.saturating_duration_since(prev_time) > Duration::from_secs(10); + if !need_update { + return prev_time + } + + let now_best_nonce_at_source = strategy.best_at_source(); + let now_best_nonce_at_target = strategy.best_at_target(); + log::info!( + target: "bridge", + "Synced {:?} of {:?} nonces in {} -> {} race", + now_best_nonce_at_target, + now_best_nonce_at_source, + P::source_name(), + P::target_name(), + ); + now_time +} + +async fn select_nonces_to_deliver( + race_state: impl RaceState, + strategy: &Strategy, +) -> Option<(SourceHeaderId, RangeInclusive, Strategy::ProofParameters)> +where + SourceHeaderId: Clone, + Strategy: RaceStrategy, +{ + let best_finalized_source_header_id_at_best_target = + race_state.best_finalized_source_header_id_at_best_target()?; + strategy + .select_nonces_to_deliver(race_state) + .await + .map(|(nonces_range, proof_parameters)| { + (best_finalized_source_header_id_at_best_target, nonces_range, proof_parameters) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::message_race_strategy::BasicStrategy; + use relay_utils::HeaderId; + + #[async_std::test] + async fn proof_is_generated_at_best_block_known_to_target_node() { + const GENERATED_AT: u64 = 6; + const BEST_AT_SOURCE: u64 = 10; + const BEST_AT_TARGET: u64 = 8; + + // target node only knows about source' BEST_AT_TARGET block + // source node has BEST_AT_SOURCE > BEST_AT_TARGET block + let mut race_state = RaceStateImpl::<_, _, (), ()> { + best_finalized_source_header_id_at_source: Some(HeaderId( + BEST_AT_SOURCE, + BEST_AT_SOURCE, + )), + best_finalized_source_header_id_at_best_target: Some(HeaderId( + BEST_AT_TARGET, + BEST_AT_TARGET, + )), + best_target_header_id: Some(HeaderId(0, 0)), + best_finalized_target_header_id: Some(HeaderId(0, 0)), + nonces_to_submit: None, + nonces_to_submit_batch: None, + nonces_submitted: None, + }; + + // we have some nonces to deliver and they're generated at GENERATED_AT < BEST_AT_SOURCE + let mut strategy = BasicStrategy::<_, _, _, _, _, ()>::new(); + strategy.source_nonces_updated( + HeaderId(GENERATED_AT, GENERATED_AT), + SourceClientNonces { new_nonces: 0..=10, confirmed_nonce: None }, + ); + strategy.best_target_nonces_updated( + TargetClientNonces { latest_nonce: 5u64, nonces_data: () }, + &mut race_state, + ); + + // the proof will be generated on source, but using BEST_AT_TARGET block + assert_eq!( + select_nonces_to_deliver(race_state, &strategy).await, + Some((HeaderId(BEST_AT_TARGET, BEST_AT_TARGET), 6..=10, (),)) + ); + } +} diff --git a/bridges/relays/messages/src/message_race_receiving.rs b/bridges/relays/messages/src/message_race_receiving.rs new file mode 100644 index 0000000000000..e6497a1b79eb7 --- /dev/null +++ b/bridges/relays/messages/src/message_race_receiving.rs @@ -0,0 +1,235 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +//! Message receiving race delivers proof-of-messages-delivery from "lane.target" to "lane.source". + +use crate::{ + message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, + message_lane_loop::{ + NoncesSubmitArtifacts, SourceClient as MessageLaneSourceClient, SourceClientState, + TargetClient as MessageLaneTargetClient, TargetClientState, + }, + message_race_loop::{ + MessageRace, NoncesRange, SourceClient, SourceClientNonces, TargetClient, + TargetClientNonces, + }, + message_race_strategy::BasicStrategy, + metrics::MessageLaneLoopMetrics, +}; + +use async_trait::async_trait; +use bp_messages::MessageNonce; +use futures::stream::FusedStream; +use relay_utils::FailedClient; +use std::{marker::PhantomData, ops::RangeInclusive}; + +/// Message receiving confirmations delivery strategy. +type ReceivingConfirmationsBasicStrategy

= BasicStrategy< +

::TargetHeaderNumber, +

::TargetHeaderHash, +

::SourceHeaderNumber, +

::SourceHeaderHash, + RangeInclusive, +

::MessagesReceivingProof, +>; + +/// Run receiving confirmations race. +pub async fn run( + source_client: impl MessageLaneSourceClient

, + source_state_updates: impl FusedStream>, + target_client: impl MessageLaneTargetClient

, + target_state_updates: impl FusedStream>, + metrics_msg: Option, +) -> Result<(), FailedClient> { + crate::message_race_loop::run( + ReceivingConfirmationsRaceSource { + client: target_client, + metrics_msg: metrics_msg.clone(), + _phantom: Default::default(), + }, + target_state_updates, + ReceivingConfirmationsRaceTarget { + client: source_client, + metrics_msg, + _phantom: Default::default(), + }, + source_state_updates, + ReceivingConfirmationsBasicStrategy::

::new(), + ) + .await +} + +/// Messages receiving confirmations race. +struct ReceivingConfirmationsRace

(std::marker::PhantomData

); + +impl MessageRace for ReceivingConfirmationsRace

{ + type SourceHeaderId = TargetHeaderIdOf

; + type TargetHeaderId = SourceHeaderIdOf

; + + type MessageNonce = MessageNonce; + type Proof = P::MessagesReceivingProof; + + fn source_name() -> String { + format!("{}::ReceivingConfirmationsDelivery", P::TARGET_NAME) + } + + fn target_name() -> String { + format!("{}::ReceivingConfirmationsDelivery", P::SOURCE_NAME) + } +} + +/// Message receiving confirmations race source, which is a target of the lane. +struct ReceivingConfirmationsRaceSource { + client: C, + metrics_msg: Option, + _phantom: PhantomData

, +} + +#[async_trait] +impl SourceClient> for ReceivingConfirmationsRaceSource +where + P: MessageLane, + C: MessageLaneTargetClient

, +{ + type Error = C::Error; + type NoncesRange = RangeInclusive; + type ProofParameters = (); + + async fn nonces( + &self, + at_block: TargetHeaderIdOf

, + prev_latest_nonce: MessageNonce, + ) -> Result<(TargetHeaderIdOf

, SourceClientNonces), Self::Error> { + let (at_block, latest_received_nonce) = self.client.latest_received_nonce(at_block).await?; + if let Some(metrics_msg) = self.metrics_msg.as_ref() { + metrics_msg.update_target_latest_received_nonce(latest_received_nonce); + } + Ok(( + at_block, + SourceClientNonces { + new_nonces: prev_latest_nonce + 1..=latest_received_nonce, + confirmed_nonce: None, + }, + )) + } + + #[allow(clippy::unit_arg)] + async fn generate_proof( + &self, + at_block: TargetHeaderIdOf

, + nonces: RangeInclusive, + _proof_parameters: Self::ProofParameters, + ) -> Result< + (TargetHeaderIdOf

, RangeInclusive, P::MessagesReceivingProof), + Self::Error, + > { + self.client + .prove_messages_receiving(at_block) + .await + .map(|(at_block, proof)| (at_block, nonces, proof)) + } +} + +/// Message receiving confirmations race target, which is a source of the lane. +struct ReceivingConfirmationsRaceTarget { + client: C, + metrics_msg: Option, + _phantom: PhantomData

, +} + +#[async_trait] +impl TargetClient> for ReceivingConfirmationsRaceTarget +where + P: MessageLane, + C: MessageLaneSourceClient

, +{ + type Error = C::Error; + type TargetNoncesData = (); + type BatchTransaction = C::BatchTransaction; + type TransactionTracker = C::TransactionTracker; + + async fn require_source_header( + &self, + id: TargetHeaderIdOf

, + ) -> Result, Self::Error> { + self.client.require_target_header_on_source(id).await + } + + async fn nonces( + &self, + at_block: SourceHeaderIdOf

, + update_metrics: bool, + ) -> Result<(SourceHeaderIdOf

, TargetClientNonces<()>), Self::Error> { + let (at_block, latest_confirmed_nonce) = + self.client.latest_confirmed_received_nonce(at_block).await?; + if update_metrics { + if let Some(metrics_msg) = self.metrics_msg.as_ref() { + metrics_msg.update_source_latest_confirmed_nonce(latest_confirmed_nonce); + } + } + Ok((at_block, TargetClientNonces { latest_nonce: latest_confirmed_nonce, nonces_data: () })) + } + + async fn submit_proof( + &self, + maybe_batch_tx: Option, + generated_at_block: TargetHeaderIdOf

, + nonces: RangeInclusive, + proof: P::MessagesReceivingProof, + ) -> Result, Self::Error> { + let tx_tracker = self + .client + .submit_messages_receiving_proof(maybe_batch_tx, generated_at_block, proof) + .await?; + Ok(NoncesSubmitArtifacts { nonces, tx_tracker }) + } +} + +impl NoncesRange for RangeInclusive { + fn begin(&self) -> MessageNonce { + *RangeInclusive::::start(self) + } + + fn end(&self) -> MessageNonce { + *RangeInclusive::::end(self) + } + + fn greater_than(self, nonce: MessageNonce) -> Option { + let next_nonce = nonce + 1; + let end = *self.end(); + if next_nonce > end { + None + } else { + Some(std::cmp::max(self.begin(), next_nonce)..=end) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn range_inclusive_works_as_nonces_range() { + let range = 20..=30; + + assert_eq!(NoncesRange::begin(&range), 20); + assert_eq!(NoncesRange::end(&range), 30); + assert_eq!(range.clone().greater_than(10), Some(20..=30)); + assert_eq!(range.clone().greater_than(19), Some(20..=30)); + assert_eq!(range.clone().greater_than(20), Some(21..=30)); + assert_eq!(range.clone().greater_than(25), Some(26..=30)); + assert_eq!(range.clone().greater_than(29), Some(30..=30)); + assert_eq!(range.greater_than(30), None); + } +} diff --git a/bridges/relays/messages/src/message_race_strategy.rs b/bridges/relays/messages/src/message_race_strategy.rs new file mode 100644 index 0000000000000..93d178e55b04f --- /dev/null +++ b/bridges/relays/messages/src/message_race_strategy.rs @@ -0,0 +1,628 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +//! Basic delivery strategy. The strategy selects nonces if: +//! +//! 1) there are more nonces on the source side than on the target side; +//! 2) new nonces may be proved to target node (i.e. they have appeared at the block, which is known +//! to the target node). + +use crate::message_race_loop::{ + NoncesRange, RaceState, RaceStrategy, SourceClientNonces, TargetClientNonces, +}; + +use async_trait::async_trait; +use bp_messages::MessageNonce; +use relay_utils::HeaderId; +use std::{collections::VecDeque, fmt::Debug, marker::PhantomData, ops::RangeInclusive}; + +/// Queue of nonces known to the source node. +pub type SourceRangesQueue = + VecDeque<(HeaderId, SourceNoncesRange)>; + +/// Nonces delivery strategy. +#[derive(Debug)] +pub struct BasicStrategy< + SourceHeaderNumber, + SourceHeaderHash, + TargetHeaderNumber, + TargetHeaderHash, + SourceNoncesRange, + Proof, +> { + /// All queued nonces. + /// + /// The queue may contain already delivered nonces. We only remove entries from this + /// queue after corresponding nonces are finalized by the target chain. + source_queue: SourceRangesQueue, + /// The best nonce known to target node at its best block. `None` if it has not been received + /// yet. + best_target_nonce: Option, + /// Unused generic types dump. + _phantom: PhantomData<(TargetHeaderNumber, TargetHeaderHash, Proof)>, +} + +impl< + SourceHeaderNumber, + SourceHeaderHash, + TargetHeaderNumber, + TargetHeaderHash, + SourceNoncesRange, + Proof, + > + BasicStrategy< + SourceHeaderNumber, + SourceHeaderHash, + TargetHeaderNumber, + TargetHeaderHash, + SourceNoncesRange, + Proof, + > where + SourceHeaderHash: Clone, + SourceHeaderNumber: Clone + Ord, + SourceNoncesRange: NoncesRange, +{ + /// Create new delivery strategy. + pub fn new() -> Self { + BasicStrategy { + source_queue: VecDeque::new(), + best_target_nonce: None, + _phantom: Default::default(), + } + } + + /// Reference to source queue. + pub(crate) fn source_queue( + &self, + ) -> &VecDeque<(HeaderId, SourceNoncesRange)> { + &self.source_queue + } + + /// Mutable reference to source queue to use in tests. + #[cfg(test)] + pub(crate) fn source_queue_mut( + &mut self, + ) -> &mut VecDeque<(HeaderId, SourceNoncesRange)> { + &mut self.source_queue + } + + /// Returns indices of source queue entries, which may be delivered to the target node. + /// + /// The function may skip some nonces from the queue front if nonces from this entry are + /// already available at the **best** target block. After this block is finalized, the entry + /// will be removed from the queue. + /// + /// All entries before and including the range end index, are guaranteed to be witnessed + /// at source blocks that are known to be finalized at the target node. + /// + /// Returns `None` if no entries may be delivered. + pub fn available_source_queue_indices< + RS: RaceState< + HeaderId, + HeaderId, + >, + >( + &self, + race_state: RS, + ) -> Option> { + // if we do not know best nonce at target node, we can't select anything + let best_target_nonce = self.best_target_nonce?; + + // if we have already selected nonces that we want to submit, do nothing + if race_state.nonces_to_submit().is_some() { + return None + } + + // if we already submitted some nonces, do nothing + if race_state.nonces_submitted().is_some() { + return None + } + + // find first entry that may be delivered to the target node + let begin_index = self + .source_queue + .iter() + .enumerate() + .skip_while(|(_, (_, nonces))| nonces.end() <= best_target_nonce) + .map(|(index, _)| index) + .next()?; + + // 1) we want to deliver all nonces, starting from `target_nonce + 1` + // 2) we can't deliver new nonce until header, that has emitted this nonce, is finalized + // by target client + // 3) selector is used for more complicated logic + // + // => let's first select range of entries inside deque that are already finalized at + // the target client and pass this range to the selector + let best_header_at_target = race_state.best_finalized_source_header_id_at_best_target()?; + let end_index = self + .source_queue + .iter() + .enumerate() + .skip(begin_index) + .take_while(|(_, (queued_at, _))| queued_at.0 <= best_header_at_target.0) + .map(|(index, _)| index) + .last()?; + + Some(begin_index..=end_index) + } + + /// Remove all nonces that are less than or equal to given nonce from the source queue. + fn remove_le_nonces_from_source_queue(&mut self, nonce: MessageNonce) { + while let Some((queued_at, queued_range)) = self.source_queue.pop_front() { + if let Some(range_to_requeue) = queued_range.greater_than(nonce) { + self.source_queue.push_front((queued_at, range_to_requeue)); + break + } + } + } +} + +#[async_trait] +impl< + SourceHeaderNumber, + SourceHeaderHash, + TargetHeaderNumber, + TargetHeaderHash, + SourceNoncesRange, + Proof, + > + RaceStrategy< + HeaderId, + HeaderId, + Proof, + > + for BasicStrategy< + SourceHeaderNumber, + SourceHeaderHash, + TargetHeaderNumber, + TargetHeaderHash, + SourceNoncesRange, + Proof, + > where + SourceHeaderHash: Clone + Debug + Send + Sync, + SourceHeaderNumber: Clone + Ord + Debug + Send + Sync, + SourceNoncesRange: NoncesRange + Debug + Send + Sync, + TargetHeaderHash: Debug + Send + Sync, + TargetHeaderNumber: Debug + Send + Sync, + Proof: Debug + Send + Sync, +{ + type SourceNoncesRange = SourceNoncesRange; + type ProofParameters = (); + type TargetNoncesData = (); + + fn is_empty(&self) -> bool { + self.source_queue.is_empty() + } + + async fn required_source_header_at_target< + RS: RaceState< + HeaderId, + HeaderId, + >, + >( + &self, + race_state: RS, + ) -> Option> { + let current_best = race_state.best_finalized_source_header_id_at_best_target()?; + self.source_queue + .back() + .and_then(|(h, _)| if h.0 > current_best.0 { Some(h.clone()) } else { None }) + } + + fn best_at_source(&self) -> Option { + let best_in_queue = self.source_queue.back().map(|(_, range)| range.end()); + match (best_in_queue, self.best_target_nonce) { + (Some(best_in_queue), Some(best_target_nonce)) if best_in_queue > best_target_nonce => + Some(best_in_queue), + (_, Some(best_target_nonce)) => Some(best_target_nonce), + (_, None) => None, + } + } + + fn best_at_target(&self) -> Option { + self.best_target_nonce + } + + fn source_nonces_updated( + &mut self, + at_block: HeaderId, + nonces: SourceClientNonces, + ) { + let best_in_queue = self + .source_queue + .back() + .map(|(_, range)| range.end()) + .or(self.best_target_nonce) + .unwrap_or_default(); + self.source_queue.extend( + nonces + .new_nonces + .greater_than(best_in_queue) + .into_iter() + .map(move |range| (at_block.clone(), range)), + ) + } + + fn reset_best_target_nonces(&mut self) { + self.best_target_nonce = None; + } + + fn best_target_nonces_updated< + RS: RaceState< + HeaderId, + HeaderId, + >, + >( + &mut self, + nonces: TargetClientNonces<()>, + race_state: &mut RS, + ) { + let nonce = nonces.latest_nonce; + + // if **some** of nonces that we have selected to submit already present at the + // target chain => select new nonces + let need_to_select_new_nonces = race_state + .nonces_to_submit() + .map(|nonces| nonce >= *nonces.start()) + .unwrap_or(false); + if need_to_select_new_nonces { + log::trace!( + target: "bridge", + "Latest nonce at target is {}. Clearing nonces to submit: {:?}", + nonce, + race_state.nonces_to_submit(), + ); + + race_state.reset_nonces_to_submit(); + } + + // if **some** of nonces that we have submitted already present at the + // target chain => select new nonces + let need_new_nonces_to_submit = race_state + .nonces_submitted() + .map(|nonces| nonce >= *nonces.start()) + .unwrap_or(false); + if need_new_nonces_to_submit { + log::trace!( + target: "bridge", + "Latest nonce at target is {}. Clearing submitted nonces: {:?}", + nonce, + race_state.nonces_submitted(), + ); + + race_state.reset_nonces_submitted(); + } + + self.best_target_nonce = Some(nonce); + } + + fn finalized_target_nonces_updated< + RS: RaceState< + HeaderId, + HeaderId, + >, + >( + &mut self, + nonces: TargetClientNonces<()>, + _race_state: &mut RS, + ) { + self.remove_le_nonces_from_source_queue(nonces.latest_nonce); + self.best_target_nonce = Some(std::cmp::max( + self.best_target_nonce.unwrap_or(nonces.latest_nonce), + nonces.latest_nonce, + )); + } + + async fn select_nonces_to_deliver< + RS: RaceState< + HeaderId, + HeaderId, + >, + >( + &self, + race_state: RS, + ) -> Option<(RangeInclusive, Self::ProofParameters)> { + let available_indices = self.available_source_queue_indices(race_state)?; + let range_begin = std::cmp::max( + self.best_target_nonce? + 1, + self.source_queue[*available_indices.start()].1.begin(), + ); + let range_end = self.source_queue[*available_indices.end()].1.end(); + Some((range_begin..=range_end, ())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, + message_lane_loop::tests::{ + header_id, TestMessageLane, TestMessagesProof, TestSourceHeaderHash, + TestSourceHeaderNumber, + }, + message_race_loop::RaceStateImpl, + }; + + type SourceNoncesRange = RangeInclusive; + + type TestRaceStateImpl = RaceStateImpl< + SourceHeaderIdOf, + TargetHeaderIdOf, + TestMessagesProof, + (), + >; + + type BasicStrategy

= super::BasicStrategy< +

::SourceHeaderNumber, +

::SourceHeaderHash, +

::TargetHeaderNumber, +

::TargetHeaderHash, + SourceNoncesRange, +

::MessagesProof, + >; + + fn source_nonces(new_nonces: SourceNoncesRange) -> SourceClientNonces { + SourceClientNonces { new_nonces, confirmed_nonce: None } + } + + fn target_nonces(latest_nonce: MessageNonce) -> TargetClientNonces<()> { + TargetClientNonces { latest_nonce, nonces_data: () } + } + + #[test] + fn strategy_is_empty_works() { + let mut strategy = BasicStrategy::::new(); + assert!(strategy.is_empty()); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=1)); + assert!(!strategy.is_empty()); + } + + #[test] + fn best_at_source_is_never_lower_than_target_nonce() { + let mut strategy = BasicStrategy::::new(); + assert_eq!(strategy.best_at_source(), None); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=5)); + assert_eq!(strategy.best_at_source(), None); + strategy.best_target_nonces_updated(target_nonces(10), &mut TestRaceStateImpl::default()); + assert_eq!(strategy.source_queue, vec![(header_id(1), 1..=5)]); + assert_eq!(strategy.best_at_source(), Some(10)); + } + + #[test] + fn source_nonce_is_never_lower_than_known_target_nonce() { + let mut strategy = BasicStrategy::::new(); + strategy.best_target_nonces_updated(target_nonces(10), &mut TestRaceStateImpl::default()); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=5)); + assert_eq!(strategy.source_queue, vec![]); + } + + #[test] + fn source_nonce_is_never_lower_than_latest_known_source_nonce() { + let mut strategy = BasicStrategy::::new(); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=5)); + strategy.source_nonces_updated(header_id(2), source_nonces(1..=3)); + strategy.source_nonces_updated(header_id(2), source_nonces(1..=5)); + assert_eq!(strategy.source_queue, vec![(header_id(1), 1..=5)]); + } + + #[test] + fn updated_target_nonce_removes_queued_entries() { + let mut strategy = BasicStrategy::::new(); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=5)); + strategy.source_nonces_updated(header_id(2), source_nonces(6..=10)); + strategy.source_nonces_updated(header_id(3), source_nonces(11..=15)); + strategy.source_nonces_updated(header_id(4), source_nonces(16..=20)); + strategy + .finalized_target_nonces_updated(target_nonces(15), &mut TestRaceStateImpl::default()); + assert_eq!(strategy.source_queue, vec![(header_id(4), 16..=20)]); + strategy + .finalized_target_nonces_updated(target_nonces(17), &mut TestRaceStateImpl::default()); + assert_eq!(strategy.source_queue, vec![(header_id(4), 18..=20)]); + } + + #[test] + fn selected_nonces_are_dropped_on_target_nonce_update() { + let mut state = TestRaceStateImpl::default(); + let mut strategy = BasicStrategy::::new(); + state.nonces_to_submit = Some((header_id(1), 5..=10, (5..=10, None))); + // we are going to submit 5..=10, so having latest nonce 4 at target is fine + strategy.best_target_nonces_updated(target_nonces(4), &mut state); + assert!(state.nonces_to_submit.is_some()); + // any nonce larger than 4 invalidates the `nonces_to_submit` + for nonce in 5..=11 { + state.nonces_to_submit = Some((header_id(1), 5..=10, (5..=10, None))); + strategy.best_target_nonces_updated(target_nonces(nonce), &mut state); + assert!(state.nonces_to_submit.is_none()); + } + } + + #[test] + fn submitted_nonces_are_dropped_on_target_nonce_update() { + let mut state = TestRaceStateImpl::default(); + let mut strategy = BasicStrategy::::new(); + state.nonces_submitted = Some(5..=10); + // we have submitted 5..=10, so having latest nonce 4 at target is fine + strategy.best_target_nonces_updated(target_nonces(4), &mut state); + assert!(state.nonces_submitted.is_some()); + // any nonce larger than 4 invalidates the `nonces_submitted` + for nonce in 5..=11 { + state.nonces_submitted = Some(5..=10); + strategy.best_target_nonces_updated(target_nonces(nonce), &mut state); + assert!(state.nonces_submitted.is_none()); + } + } + + #[async_std::test] + async fn nothing_is_selected_if_something_is_already_selected() { + let mut state = TestRaceStateImpl::default(); + let mut strategy = BasicStrategy::::new(); + state.nonces_to_submit = Some((header_id(1), 1..=10, (1..=10, None))); + strategy.best_target_nonces_updated(target_nonces(0), &mut state); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=10)); + assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, None); + } + + #[async_std::test] + async fn nothing_is_selected_if_something_is_already_submitted() { + let mut state = TestRaceStateImpl::default(); + let mut strategy = BasicStrategy::::new(); + state.nonces_submitted = Some(1..=10); + strategy.best_target_nonces_updated(target_nonces(0), &mut state); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=10)); + assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, None); + } + + #[async_std::test] + async fn select_nonces_to_deliver_works() { + let mut state = TestRaceStateImpl::default(); + let mut strategy = BasicStrategy::::new(); + strategy.best_target_nonces_updated(target_nonces(0), &mut state); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=1)); + strategy.source_nonces_updated(header_id(2), source_nonces(2..=2)); + strategy.source_nonces_updated(header_id(3), source_nonces(3..=6)); + strategy.source_nonces_updated(header_id(5), source_nonces(7..=8)); + + state.best_finalized_source_header_id_at_best_target = Some(header_id(4)); + assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, Some((1..=6, ()))); + strategy.best_target_nonces_updated(target_nonces(6), &mut state); + assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, None); + + state.best_finalized_source_header_id_at_best_target = Some(header_id(5)); + assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, Some((7..=8, ()))); + strategy.best_target_nonces_updated(target_nonces(8), &mut state); + assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, None); + } + + #[test] + fn available_source_queue_indices_works() { + let mut state = TestRaceStateImpl::default(); + let mut strategy = BasicStrategy::::new(); + strategy.best_target_nonces_updated(target_nonces(0), &mut state); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=3)); + strategy.source_nonces_updated(header_id(2), source_nonces(4..=6)); + strategy.source_nonces_updated(header_id(3), source_nonces(7..=9)); + + state.best_finalized_source_header_id_at_best_target = Some(header_id(0)); + assert_eq!(strategy.available_source_queue_indices(state.clone()), None); + + state.best_finalized_source_header_id_at_best_target = Some(header_id(1)); + assert_eq!(strategy.available_source_queue_indices(state.clone()), Some(0..=0)); + + state.best_finalized_source_header_id_at_best_target = Some(header_id(2)); + assert_eq!(strategy.available_source_queue_indices(state.clone()), Some(0..=1)); + + state.best_finalized_source_header_id_at_best_target = Some(header_id(3)); + assert_eq!(strategy.available_source_queue_indices(state.clone()), Some(0..=2)); + + state.best_finalized_source_header_id_at_best_target = Some(header_id(4)); + assert_eq!(strategy.available_source_queue_indices(state), Some(0..=2)); + } + + #[test] + fn remove_le_nonces_from_source_queue_works() { + let mut state = TestRaceStateImpl::default(); + let mut strategy = BasicStrategy::::new(); + strategy.best_target_nonces_updated(target_nonces(0), &mut state); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=3)); + strategy.source_nonces_updated(header_id(2), source_nonces(4..=6)); + strategy.source_nonces_updated(header_id(3), source_nonces(7..=9)); + + fn source_queue_nonces( + source_queue: &SourceRangesQueue< + TestSourceHeaderHash, + TestSourceHeaderNumber, + SourceNoncesRange, + >, + ) -> Vec { + source_queue.iter().flat_map(|(_, range)| range.clone()).collect() + } + + strategy.remove_le_nonces_from_source_queue(1); + assert_eq!(source_queue_nonces(&strategy.source_queue), vec![2, 3, 4, 5, 6, 7, 8, 9],); + + strategy.remove_le_nonces_from_source_queue(5); + assert_eq!(source_queue_nonces(&strategy.source_queue), vec![6, 7, 8, 9],); + + strategy.remove_le_nonces_from_source_queue(9); + assert_eq!(source_queue_nonces(&strategy.source_queue), Vec::::new(),); + + strategy.remove_le_nonces_from_source_queue(100); + assert_eq!(source_queue_nonces(&strategy.source_queue), Vec::::new(),); + } + + #[async_std::test] + async fn previous_nonces_are_selected_if_reorg_happens_at_target_chain() { + let source_header_1 = header_id(1); + let target_header_1 = header_id(1); + + // we start in perfec sync state - all headers are synced and finalized on both ends + let mut state = TestRaceStateImpl { + best_finalized_source_header_id_at_source: Some(source_header_1), + best_finalized_source_header_id_at_best_target: Some(source_header_1), + best_target_header_id: Some(target_header_1), + best_finalized_target_header_id: Some(target_header_1), + nonces_to_submit: None, + nonces_to_submit_batch: None, + nonces_submitted: None, + }; + + // in this state we have 1 available nonce for delivery + let mut strategy = BasicStrategy:: { + source_queue: vec![(header_id(1), 1..=1)].into_iter().collect(), + best_target_nonce: Some(0), + _phantom: PhantomData, + }; + assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, Some((1..=1, ())),); + + // let's say we have submitted 1..=1 + state.nonces_submitted = Some(1..=1); + + // then new nonce 2 appear at the source block 2 + let source_header_2 = header_id(2); + state.best_finalized_source_header_id_at_source = Some(source_header_2); + strategy.source_nonces_updated( + source_header_2, + SourceClientNonces { new_nonces: 2..=2, confirmed_nonce: None }, + ); + // and nonce 1 appear at the best block of the target node (best finalized still has 0 + // nonces) + let target_header_2 = header_id(2); + state.best_target_header_id = Some(target_header_2); + strategy.best_target_nonces_updated( + TargetClientNonces { latest_nonce: 1, nonces_data: () }, + &mut state, + ); + + // then best target header is retracted + strategy.best_target_nonces_updated( + TargetClientNonces { latest_nonce: 0, nonces_data: () }, + &mut state, + ); + + // ... and some fork with zero delivered nonces is finalized + let target_header_2_fork = header_id(2_1); + state.best_finalized_source_header_id_at_source = Some(source_header_2); + state.best_finalized_source_header_id_at_best_target = Some(source_header_2); + state.best_target_header_id = Some(target_header_2_fork); + state.best_finalized_target_header_id = Some(target_header_2_fork); + strategy.finalized_target_nonces_updated( + TargetClientNonces { latest_nonce: 0, nonces_data: () }, + &mut state, + ); + + // now we have to select nonce 1 for delivery again + assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, Some((1..=2, ())),); + } +} diff --git a/bridges/relays/messages/src/metrics.rs b/bridges/relays/messages/src/metrics.rs new file mode 100644 index 0000000000000..69d80d178de80 --- /dev/null +++ b/bridges/relays/messages/src/metrics.rs @@ -0,0 +1,148 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Metrics for message lane relay loop. + +use crate::{ + message_lane::MessageLane, + message_lane_loop::{SourceClientState, TargetClientState}, +}; + +use bp_messages::MessageNonce; +use finality_relay::SyncLoopMetrics; +use relay_utils::metrics::{ + metric_name, register, GaugeVec, Metric, Opts, PrometheusError, Registry, U64, +}; + +/// Message lane relay metrics. +/// +/// Cloning only clones references. +#[derive(Clone)] +pub struct MessageLaneLoopMetrics { + /// Best finalized block numbers - "source", "source_at_target", "target_at_source". + source_to_target_finality_metrics: SyncLoopMetrics, + /// Best finalized block numbers - "source", "target", "source_at_target", "target_at_source". + target_to_source_finality_metrics: SyncLoopMetrics, + /// Lane state nonces: "source_latest_generated", "source_latest_confirmed", + /// "target_latest_received", "target_latest_confirmed". + lane_state_nonces: GaugeVec, +} + +impl MessageLaneLoopMetrics { + /// Create and register messages loop metrics. + pub fn new(prefix: Option<&str>) -> Result { + Ok(MessageLaneLoopMetrics { + source_to_target_finality_metrics: SyncLoopMetrics::new( + prefix, + "source", + "source_at_target", + )?, + target_to_source_finality_metrics: SyncLoopMetrics::new( + prefix, + "target", + "target_at_source", + )?, + lane_state_nonces: GaugeVec::new( + Opts::new(metric_name(prefix, "lane_state_nonces"), "Nonces of the lane state"), + &["type"], + )?, + }) + } + + /// Update source client state metrics. + pub fn update_source_state(&self, source_client_state: SourceClientState

) { + self.source_to_target_finality_metrics + .update_best_block_at_source(source_client_state.best_self.0); + if let Some(best_finalized_peer_at_best_self) = + source_client_state.best_finalized_peer_at_best_self + { + self.target_to_source_finality_metrics + .update_best_block_at_target(best_finalized_peer_at_best_self.0); + if let Some(actual_best_finalized_peer_at_best_self) = + source_client_state.actual_best_finalized_peer_at_best_self + { + self.target_to_source_finality_metrics.update_using_same_fork( + best_finalized_peer_at_best_self.1 == actual_best_finalized_peer_at_best_self.1, + ); + } + } + } + + /// Update target client state metrics. + pub fn update_target_state(&self, target_client_state: TargetClientState

) { + self.target_to_source_finality_metrics + .update_best_block_at_source(target_client_state.best_self.0); + if let Some(best_finalized_peer_at_best_self) = + target_client_state.best_finalized_peer_at_best_self + { + self.source_to_target_finality_metrics + .update_best_block_at_target(best_finalized_peer_at_best_self.0); + if let Some(actual_best_finalized_peer_at_best_self) = + target_client_state.actual_best_finalized_peer_at_best_self + { + self.source_to_target_finality_metrics.update_using_same_fork( + best_finalized_peer_at_best_self.1 == actual_best_finalized_peer_at_best_self.1, + ); + } + } + } + + /// Update latest generated nonce at source. + pub fn update_source_latest_generated_nonce( + &self, + source_latest_generated_nonce: MessageNonce, + ) { + self.lane_state_nonces + .with_label_values(&["source_latest_generated"]) + .set(source_latest_generated_nonce); + } + + /// Update the latest confirmed nonce at source. + pub fn update_source_latest_confirmed_nonce( + &self, + source_latest_confirmed_nonce: MessageNonce, + ) { + self.lane_state_nonces + .with_label_values(&["source_latest_confirmed"]) + .set(source_latest_confirmed_nonce); + } + + /// Update the latest received nonce at target. + pub fn update_target_latest_received_nonce(&self, target_latest_generated_nonce: MessageNonce) { + self.lane_state_nonces + .with_label_values(&["target_latest_received"]) + .set(target_latest_generated_nonce); + } + + /// Update the latest confirmed nonce at target. + pub fn update_target_latest_confirmed_nonce( + &self, + target_latest_confirmed_nonce: MessageNonce, + ) { + self.lane_state_nonces + .with_label_values(&["target_latest_confirmed"]) + .set(target_latest_confirmed_nonce); + } +} + +impl Metric for MessageLaneLoopMetrics { + fn register(&self, registry: &Registry) -> Result<(), PrometheusError> { + self.source_to_target_finality_metrics.register(registry)?; + self.target_to_source_finality_metrics.register(registry)?; + register(self.lane_state_nonces.clone(), registry)?; + Ok(()) + } +} diff --git a/bridges/relays/parachains/Cargo.toml b/bridges/relays/parachains/Cargo.toml new file mode 100644 index 0000000000000..e691168e72d08 --- /dev/null +++ b/bridges/relays/parachains/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "parachains-relay" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +repository.workspace = true +publish = false + +[lints] +workspace = true + +[dependencies] +async-std = "1.9.0" +async-trait = "0.1.79" +futures = "0.3.30" +log = { workspace = true } +relay-utils = { path = "../utils" } + +# Bridge dependencies + +bp-polkadot-core = { path = "../../primitives/polkadot-core" } +relay-substrate-client = { path = "../client-substrate" } + +[dev-dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1" } +relay-substrate-client = { path = "../client-substrate", features = ["test-helpers"] } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } diff --git a/bridges/relays/parachains/README.md b/bridges/relays/parachains/README.md new file mode 100644 index 0000000000000..9043b0b0a9cdd --- /dev/null +++ b/bridges/relays/parachains/README.md @@ -0,0 +1,50 @@ +# Parachains Finality Relay + +The parachains finality relay works with two chains - source relay chain and target chain (which may be standalone +chain, relay chain or a parachain). The source chain must have the +[`paras` pallet](https://github.com/paritytech/polkadot/tree/master/runtime/parachains/src/paras) deployed at its +runtime. The target chain must have the [bridge parachains pallet](../../modules/parachains/) deployed at its runtime. + +The relay is configured to submit heads of one or several parachains. It pokes source chain periodically and compares +parachain heads that are known to the source relay chain to heads at the target chain. If there are new heads, +the relay submits them to the target chain. + +More: [Parachains Finality Relay Sequence Diagram](../../docs/parachains-finality-relay.html). + +## How to Use the Parachains Finality Relay + +There are only two traits that need to be implemented. The [`SourceChain`](./src/parachains_loop.rs) implementation +is supposed to connect to the source chain node. It must be able to read parachain heads from the `Heads` map of +the [`paras` pallet](https://github.com/paritytech/polkadot/tree/master/runtime/parachains/src/paras). +It also must create storage proofs of `Heads` map entries, when required. + +The [`TargetChain`](./src/parachains_loop.rs) implementation connects to the target chain node. It must be able +to return the best known head of given parachain. When required, it must be able to craft and submit parachains +finality delivery transaction to the target node. + +The main entrypoint for the crate is the [`run` function](./src/parachains_loop.rs), which takes source and target +clients and [`ParachainSyncParams`](./src/parachains_loop.rs) parameters. The most imporant parameter is the +`parachains` - it is the set of parachains, which relay tracks and updates. The other important parameter that +may affect the relay operational costs is the `strategy`. If it is set to `Any`, then the finality delivery +transaction is submitted if at least one of tracked parachain heads is updated. The other option is `All`. Then +the relay waits until all tracked parachain heads are updated and submits them all in a single finality delivery +transaction. + +## Parachain Finality Relay Metrics + +Every parachain in Polkadot is identified by the 32-bit number. All metrics, exposed by the parachains finality +relay have the `parachain` label, which is set to the parachain id. And the metrics are prefixed with the prefix, +that depends on the name of the source relay and target chains. The list below shows metrics names for +Rococo (source relay chain) to BridgeHubWestend (target chain) parachains finality relay. For other chains, simply +change chain names. So the metrics are: + +- `Rococo_to_BridgeHubWestend_Parachains_best_parachain_block_number_at_source` - returns best known parachain block + number, registered in the `paras` pallet at the source relay chain (Rococo in our example); + +- `Rococo_to_BridgeHubWestend_Parachains_best_parachain_block_number_at_target` - returns best known parachain block + number, registered in the bridge parachains pallet at the target chain (BridgeHubWestend in our example). + +If relay operates properly, you should see that +the `Rococo_to_BridgeHubWestend_Parachains_best_parachain_block_number_at_target` tries to reach +the `Rococo_to_BridgeHubWestend_Parachains_best_parachain_block_number_at_source`. +And the latter one always increases. diff --git a/bridges/relays/parachains/src/lib.rs b/bridges/relays/parachains/src/lib.rs new file mode 100644 index 0000000000000..81ea983a6f76a --- /dev/null +++ b/bridges/relays/parachains/src/lib.rs @@ -0,0 +1,32 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +use std::fmt::Debug; + +use relay_substrate_client::{Chain, Parachain}; + +pub mod parachains_loop; +pub mod parachains_loop_metrics; + +/// Finality proofs synchronization pipeline. +pub trait ParachainsPipeline: 'static + Clone + Debug + Send + Sync { + /// Relay chain which is storing parachain heads in its `paras` module. + type SourceRelayChain: Chain; + /// Parachain which headers we are syncing here. + type SourceParachain: Parachain; + /// Target chain (either relay or para) which wants to know about new parachain heads. + type TargetChain: Chain; +} diff --git a/bridges/relays/parachains/src/parachains_loop.rs b/bridges/relays/parachains/src/parachains_loop.rs new file mode 100644 index 0000000000000..41ebbf5aadede --- /dev/null +++ b/bridges/relays/parachains/src/parachains_loop.rs @@ -0,0 +1,985 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +use crate::{parachains_loop_metrics::ParachainsLoopMetrics, ParachainsPipeline}; + +use async_trait::async_trait; +use bp_polkadot_core::{ + parachains::{ParaHash, ParaHeadsProof, ParaId}, + BlockNumber as RelayBlockNumber, +}; +use futures::{ + future::{FutureExt, Shared}, + poll, select_biased, +}; +use relay_substrate_client::{Chain, HeaderIdOf, ParachainBase}; +use relay_utils::{ + metrics::MetricsParams, relay_loop::Client as RelayClient, FailedClient, + TrackedTransactionStatus, TransactionTracker, +}; +use std::{future::Future, pin::Pin, task::Poll}; + +/// Parachain header availability at a certain chain. +#[derive(Clone, Copy, Debug)] +pub enum AvailableHeader { + /// The client can not report actual parachain head at this moment. + /// + /// It is a "mild" error, which may appear when e.g. on-demand parachains relay is used. + /// This variant must be treated as "we don't want to update parachain head value at the + /// target chain at this moment". + Unavailable, + /// There's no parachain header at the relay chain. + /// + /// Normally it means that the parachain is not registered there. + Missing, + /// Parachain head with given hash is available at the source chain. + Available(T), +} + +impl AvailableHeader { + /// Return available header. + pub fn as_available(&self) -> Option<&T> { + match *self { + AvailableHeader::Available(ref header) => Some(header), + _ => None, + } + } +} + +impl From> for AvailableHeader { + fn from(maybe_header: Option) -> AvailableHeader { + match maybe_header { + Some(header) => AvailableHeader::Available(header), + None => AvailableHeader::Missing, + } + } +} + +/// Source client used in parachain heads synchronization loop. +#[async_trait] +pub trait SourceClient: RelayClient { + /// Returns `Ok(true)` if client is in synced state. + async fn ensure_synced(&self) -> Result; + + /// Get parachain head id at given block. + async fn parachain_head( + &self, + at_block: HeaderIdOf, + ) -> Result>, Self::Error>; + + /// Get parachain head proof at given block. + async fn prove_parachain_head( + &self, + at_block: HeaderIdOf, + ) -> Result<(ParaHeadsProof, ParaHash), Self::Error>; +} + +/// Target client used in parachain heads synchronization loop. +#[async_trait] +pub trait TargetClient: RelayClient { + /// Transaction tracker to track submitted transactions. + type TransactionTracker: TransactionTracker>; + + /// Get best block id. + async fn best_block(&self) -> Result, Self::Error>; + + /// Get best finalized source relay chain block id. + async fn best_finalized_source_relay_chain_block( + &self, + at_block: &HeaderIdOf, + ) -> Result, Self::Error>; + + /// Get parachain head id at given block. + async fn parachain_head( + &self, + at_block: HeaderIdOf, + ) -> Result>, Self::Error>; + + /// Submit parachain heads proof. + async fn submit_parachain_head_proof( + &self, + at_source_block: HeaderIdOf, + para_head_hash: ParaHash, + proof: ParaHeadsProof, + ) -> Result; +} + +/// Return prefix that will be used by default to expose Prometheus metrics of the parachains +/// sync loop. +pub fn metrics_prefix() -> String { + format!( + "{}_to_{}_Parachains_{}", + P::SourceRelayChain::NAME, + P::TargetChain::NAME, + P::SourceParachain::PARACHAIN_ID + ) +} + +/// Run parachain heads synchronization. +pub async fn run( + source_client: impl SourceClient

, + target_client: impl TargetClient

, + metrics_params: MetricsParams, + exit_signal: impl Future + 'static + Send, +) -> Result<(), relay_utils::Error> +where + P::SourceRelayChain: Chain, +{ + let exit_signal = exit_signal.shared(); + relay_utils::relay_loop(source_client, target_client) + .with_metrics(metrics_params) + .loop_metric(ParachainsLoopMetrics::new(Some(&metrics_prefix::

()))?)? + .expose() + .await? + .run(metrics_prefix::

(), move |source_client, target_client, metrics| { + run_until_connection_lost(source_client, target_client, metrics, exit_signal.clone()) + }) + .await +} + +/// Run parachain heads synchronization. +async fn run_until_connection_lost( + source_client: impl SourceClient

, + target_client: impl TargetClient

, + metrics: Option, + exit_signal: impl Future + Send, +) -> Result<(), FailedClient> +where + P::SourceRelayChain: Chain, +{ + let exit_signal = exit_signal.fuse(); + let min_block_interval = std::cmp::min( + P::SourceRelayChain::AVERAGE_BLOCK_INTERVAL, + P::TargetChain::AVERAGE_BLOCK_INTERVAL, + ); + + let mut submitted_heads_tracker: Option> = None; + + futures::pin_mut!(exit_signal); + + // Note that the internal loop breaks with `FailedClient` error even if error is non-connection. + // It is Ok for now, but it may need to be fixed in the future to use exponential backoff for + // regular errors. + + loop { + // Either wait for new block, or exit signal. + // Please note that we are prioritizing the exit signal since if both events happen at once + // it doesn't make sense to perform one more loop iteration. + select_biased! { + _ = exit_signal => return Ok(()), + _ = async_std::task::sleep(min_block_interval).fuse() => {}, + } + + // if source client is not yet synced, we'll need to sleep. Otherwise we risk submitting too + // much redundant transactions + match source_client.ensure_synced().await { + Ok(true) => (), + Ok(false) => { + log::warn!( + target: "bridge", + "{} client is syncing. Won't do anything until it is synced", + P::SourceRelayChain::NAME, + ); + continue + }, + Err(e) => { + log::warn!( + target: "bridge", + "{} client has failed to return its sync status: {:?}", + P::SourceRelayChain::NAME, + e, + ); + return Err(FailedClient::Source) + }, + } + + // if we have active transaction, we'll need to wait until it is mined or dropped + let best_target_block = target_client.best_block().await.map_err(|e| { + log::warn!(target: "bridge", "Failed to read best {} block: {:?}", P::SourceRelayChain::NAME, e); + FailedClient::Target + })?; + let head_at_target = + read_head_at_target(&target_client, metrics.as_ref(), &best_target_block).await?; + + // check if our transaction has been mined + if let Some(tracker) = submitted_heads_tracker.take() { + match tracker.update(&best_target_block, &head_at_target).await { + SubmittedHeadStatus::Waiting(tracker) => { + // no news about our transaction and we shall keep waiting + submitted_heads_tracker = Some(tracker); + continue + }, + SubmittedHeadStatus::Final(TrackedTransactionStatus::Finalized(_)) => { + // all heads have been updated, we don't need this tracker anymore + }, + SubmittedHeadStatus::Final(TrackedTransactionStatus::Lost) => { + log::warn!( + target: "bridge", + "Parachains synchronization from {} to {} has stalled. Going to restart", + P::SourceRelayChain::NAME, + P::TargetChain::NAME, + ); + + return Err(FailedClient::Both) + }, + } + } + + // we have no active transaction and may need to update heads, but do we have something for + // update? + let best_finalized_relay_block = target_client + .best_finalized_source_relay_chain_block(&best_target_block) + .await + .map_err(|e| { + log::warn!( + target: "bridge", + "Failed to read best finalized {} block from {}: {:?}", + P::SourceRelayChain::NAME, + P::TargetChain::NAME, + e, + ); + FailedClient::Target + })?; + let head_at_source = + read_head_at_source(&source_client, metrics.as_ref(), &best_finalized_relay_block) + .await?; + let is_update_required = is_update_required::

( + head_at_source, + head_at_target, + best_finalized_relay_block, + best_target_block, + ); + + if is_update_required { + let (head_proof, head_hash) = source_client + .prove_parachain_head(best_finalized_relay_block) + .await + .map_err(|e| { + log::warn!( + target: "bridge", + "Failed to prove {} parachain ParaId({}) heads: {:?}", + P::SourceRelayChain::NAME, + P::SourceParachain::PARACHAIN_ID, + e, + ); + FailedClient::Source + })?; + log::info!( + target: "bridge", + "Submitting {} parachain ParaId({}) head update transaction to {}. Para hash at source relay {:?}: {:?}", + P::SourceRelayChain::NAME, + P::SourceParachain::PARACHAIN_ID, + P::TargetChain::NAME, + best_finalized_relay_block, + head_hash, + ); + + let transaction_tracker = target_client + .submit_parachain_head_proof(best_finalized_relay_block, head_hash, head_proof) + .await + .map_err(|e| { + log::warn!( + target: "bridge", + "Failed to submit {} parachain ParaId({}) heads proof to {}: {:?}", + P::SourceRelayChain::NAME, + P::SourceParachain::PARACHAIN_ID, + P::TargetChain::NAME, + e, + ); + FailedClient::Target + })?; + submitted_heads_tracker = + Some(SubmittedHeadsTracker::

::new(head_at_source, transaction_tracker)); + } + } +} + +/// Returns `true` if we need to submit parachain-head-update transaction. +fn is_update_required( + head_at_source: AvailableHeader>, + head_at_target: Option>, + best_finalized_relay_block_at_source: HeaderIdOf, + best_target_block: HeaderIdOf, +) -> bool +where + P::SourceRelayChain: Chain, +{ + log::trace!( + target: "bridge", + "Checking if {} parachain ParaId({}) needs update at {}:\n\t\ + At {} ({:?}): {:?}\n\t\ + At {} ({:?}): {:?}", + P::SourceRelayChain::NAME, + P::SourceParachain::PARACHAIN_ID, + P::TargetChain::NAME, + P::SourceRelayChain::NAME, + best_finalized_relay_block_at_source, + head_at_source, + P::TargetChain::NAME, + best_target_block, + head_at_target, + ); + + let needs_update = match (head_at_source, head_at_target) { + (AvailableHeader::Unavailable, _) => { + // source client has politely asked us not to update current parachain head + // at the target chain + false + }, + (AvailableHeader::Available(head_at_source), Some(head_at_target)) + if head_at_source.number() > head_at_target.number() => + { + // source client knows head that is better than the head known to the target + // client + true + }, + (AvailableHeader::Available(_), Some(_)) => { + // this is normal case when relay has recently updated heads, when parachain is + // not progressing, or when our source client is still syncing + false + }, + (AvailableHeader::Available(_), None) => { + // parachain is not yet known to the target client. This is true when parachain + // or bridge has been just onboarded/started + true + }, + (AvailableHeader::Missing, Some(_)) => { + // parachain/parathread has been offboarded removed from the system. It needs to + // be propageted to the target client + true + }, + (AvailableHeader::Missing, None) => { + // all's good - parachain is unknown to both clients + false + }, + }; + + if needs_update { + log::trace!( + target: "bridge", + "{} parachain ParaId({}) needs update at {}: {:?} vs {:?}", + P::SourceRelayChain::NAME, + P::SourceParachain::PARACHAIN_ID, + P::TargetChain::NAME, + head_at_source, + head_at_target, + ); + } + + needs_update +} + +/// Reads parachain head from the source client. +async fn read_head_at_source( + source_client: &impl SourceClient

, + metrics: Option<&ParachainsLoopMetrics>, + at_relay_block: &HeaderIdOf, +) -> Result>, FailedClient> { + let para_head = source_client.parachain_head(*at_relay_block).await; + match para_head { + Ok(AvailableHeader::Available(para_head)) => { + if let Some(metrics) = metrics { + metrics.update_best_parachain_block_at_source( + ParaId(P::SourceParachain::PARACHAIN_ID), + para_head.number(), + ); + } + Ok(AvailableHeader::Available(para_head)) + }, + Ok(r) => Ok(r), + Err(e) => { + log::warn!( + target: "bridge", + "Failed to read head of {} parachain ParaId({:?}): {:?}", + P::SourceRelayChain::NAME, + P::SourceParachain::PARACHAIN_ID, + e, + ); + Err(FailedClient::Source) + }, + } +} + +/// Reads parachain head from the target client. +async fn read_head_at_target( + target_client: &impl TargetClient

, + metrics: Option<&ParachainsLoopMetrics>, + at_block: &HeaderIdOf, +) -> Result>, FailedClient> { + let para_head_id = target_client.parachain_head(*at_block).await; + match para_head_id { + Ok(Some(para_head_id)) => { + if let Some(metrics) = metrics { + metrics.update_best_parachain_block_at_target( + ParaId(P::SourceParachain::PARACHAIN_ID), + para_head_id.number(), + ); + } + Ok(Some(para_head_id)) + }, + Ok(None) => Ok(None), + Err(e) => { + log::warn!( + target: "bridge", + "Failed to read head of {} parachain ParaId({}) at {}: {:?}", + P::SourceRelayChain::NAME, + P::SourceParachain::PARACHAIN_ID, + P::TargetChain::NAME, + e, + ); + Err(FailedClient::Target) + }, + } +} + +/// Submitted heads status. +enum SubmittedHeadStatus { + /// Heads are not yet updated. + Waiting(SubmittedHeadsTracker

), + /// Heads transaction has either been finalized or lost (i.e. received its "final" status). + Final(TrackedTransactionStatus>), +} + +/// Type of the transaction tracker that the `SubmittedHeadsTracker` is using. +/// +/// It needs to be shared because of `poll` macro and our consuming `update` method. +type SharedTransactionTracker

= Shared< + Pin< + Box< + dyn Future< + Output = TrackedTransactionStatus< + HeaderIdOf<

::TargetChain>, + >, + > + Send, + >, + >, +>; + +/// Submitted parachain heads transaction. +struct SubmittedHeadsTracker { + /// Parachain header id that we have submitted. + submitted_head: AvailableHeader>, + /// Future that waits for submitted transaction finality or loss. + /// + /// It needs to be shared because of `poll` macro and our consuming `update` method. + transaction_tracker: SharedTransactionTracker

, +} + +impl SubmittedHeadsTracker

{ + /// Creates new parachain heads transaction tracker. + pub fn new( + submitted_head: AvailableHeader>, + transaction_tracker: impl TransactionTracker> + 'static, + ) -> Self { + SubmittedHeadsTracker { + submitted_head, + transaction_tracker: transaction_tracker.wait().fuse().boxed().shared(), + } + } + + /// Returns `None` if all submitted parachain heads have been updated. + pub async fn update( + self, + at_target_block: &HeaderIdOf, + head_at_target: &Option>, + ) -> SubmittedHeadStatus

{ + // check if our head has been updated + let is_head_updated = match (self.submitted_head, head_at_target) { + (AvailableHeader::Available(submitted_head), Some(head_at_target)) + if head_at_target.number() >= submitted_head.number() => + true, + (AvailableHeader::Missing, None) => true, + _ => false, + }; + if is_head_updated { + log::trace!( + target: "bridge", + "Head of parachain ParaId({}) has been updated at {}: {:?}", + P::SourceParachain::PARACHAIN_ID, + P::TargetChain::NAME, + head_at_target, + ); + + return SubmittedHeadStatus::Final(TrackedTransactionStatus::Finalized(*at_target_block)) + } + + // if underlying transaction tracker has reported that the transaction is lost, we may + // then restart our sync + let transaction_tracker = self.transaction_tracker.clone(); + match poll!(transaction_tracker) { + Poll::Ready(TrackedTransactionStatus::Lost) => + return SubmittedHeadStatus::Final(TrackedTransactionStatus::Lost), + Poll::Ready(TrackedTransactionStatus::Finalized(_)) => { + // so we are here and our transaction is mined+finalized, but some of heads were not + // updated => we're considering our loop as stalled + return SubmittedHeadStatus::Final(TrackedTransactionStatus::Lost) + }, + _ => (), + } + + SubmittedHeadStatus::Waiting(self) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use async_std::sync::{Arc, Mutex}; + use codec::Encode; + use futures::{SinkExt, StreamExt}; + use relay_substrate_client::test_chain::{TestChain, TestParachain}; + use relay_utils::{HeaderId, MaybeConnectionError}; + use sp_core::H256; + + const PARA_10_HASH: ParaHash = H256([10u8; 32]); + const PARA_20_HASH: ParaHash = H256([20u8; 32]); + + #[derive(Clone, Debug)] + enum TestError { + Error, + } + + impl MaybeConnectionError for TestError { + fn is_connection_error(&self) -> bool { + false + } + } + + #[derive(Clone, Debug, PartialEq, Eq)] + struct TestParachainsPipeline; + + impl ParachainsPipeline for TestParachainsPipeline { + type SourceRelayChain = TestChain; + type SourceParachain = TestParachain; + type TargetChain = TestChain; + } + + #[derive(Clone, Debug)] + struct TestClient { + data: Arc>, + } + + #[derive(Clone, Debug)] + struct TestTransactionTracker(Option>>); + + #[async_trait] + impl TransactionTracker for TestTransactionTracker { + type HeaderId = HeaderIdOf; + + async fn wait(self) -> TrackedTransactionStatus> { + match self.0 { + Some(status) => status, + None => futures::future::pending().await, + } + } + } + + #[derive(Clone, Debug)] + struct TestClientData { + source_sync_status: Result, + source_head: Result>, TestError>, + source_proof: Result<(), TestError>, + + target_best_block: Result, TestError>, + target_best_finalized_source_block: Result, TestError>, + target_head: Result>, TestError>, + target_submit_result: Result<(), TestError>, + + exit_signal_sender: Option>>, + } + + impl TestClientData { + pub fn minimal() -> Self { + TestClientData { + source_sync_status: Ok(true), + source_head: Ok(AvailableHeader::Available(HeaderId(0, PARA_20_HASH))), + source_proof: Ok(()), + + target_best_block: Ok(HeaderId(0, Default::default())), + target_best_finalized_source_block: Ok(HeaderId(0, Default::default())), + target_head: Ok(None), + target_submit_result: Ok(()), + + exit_signal_sender: None, + } + } + + pub fn with_exit_signal_sender( + sender: futures::channel::mpsc::UnboundedSender<()>, + ) -> Self { + let mut client = Self::minimal(); + client.exit_signal_sender = Some(Box::new(sender)); + client + } + } + + impl From for TestClient { + fn from(data: TestClientData) -> TestClient { + TestClient { data: Arc::new(Mutex::new(data)) } + } + } + + #[async_trait] + impl RelayClient for TestClient { + type Error = TestError; + + async fn reconnect(&mut self) -> Result<(), TestError> { + unimplemented!() + } + } + + #[async_trait] + impl SourceClient for TestClient { + async fn ensure_synced(&self) -> Result { + self.data.lock().await.source_sync_status.clone() + } + + async fn parachain_head( + &self, + _at_block: HeaderIdOf, + ) -> Result>, TestError> { + self.data.lock().await.source_head.clone() + } + + async fn prove_parachain_head( + &self, + _at_block: HeaderIdOf, + ) -> Result<(ParaHeadsProof, ParaHash), TestError> { + let head = *self.data.lock().await.source_head.clone()?.as_available().unwrap(); + let storage_proof = vec![head.hash().encode()]; + let proof = (ParaHeadsProof { storage_proof }, head.hash()); + self.data.lock().await.source_proof.clone().map(|_| proof) + } + } + + #[async_trait] + impl TargetClient for TestClient { + type TransactionTracker = TestTransactionTracker; + + async fn best_block(&self) -> Result, TestError> { + self.data.lock().await.target_best_block.clone() + } + + async fn best_finalized_source_relay_chain_block( + &self, + _at_block: &HeaderIdOf, + ) -> Result, TestError> { + self.data.lock().await.target_best_finalized_source_block.clone() + } + + async fn parachain_head( + &self, + _at_block: HeaderIdOf, + ) -> Result>, TestError> { + self.data.lock().await.target_head.clone() + } + + async fn submit_parachain_head_proof( + &self, + _at_source_block: HeaderIdOf, + _updated_parachain_head: ParaHash, + _proof: ParaHeadsProof, + ) -> Result { + let mut data = self.data.lock().await; + data.target_submit_result.clone()?; + + if let Some(mut exit_signal_sender) = data.exit_signal_sender.take() { + exit_signal_sender.send(()).await.unwrap(); + } + Ok(TestTransactionTracker(Some( + TrackedTransactionStatus::Finalized(Default::default()), + ))) + } + } + + #[test] + fn when_source_client_fails_to_return_sync_state() { + let mut test_source_client = TestClientData::minimal(); + test_source_client.source_sync_status = Err(TestError::Error); + + assert_eq!( + async_std::task::block_on(run_until_connection_lost( + TestClient::from(test_source_client), + TestClient::from(TestClientData::minimal()), + None, + futures::future::pending(), + )), + Err(FailedClient::Source), + ); + } + + #[test] + fn when_target_client_fails_to_return_best_block() { + let mut test_target_client = TestClientData::minimal(); + test_target_client.target_best_block = Err(TestError::Error); + + assert_eq!( + async_std::task::block_on(run_until_connection_lost( + TestClient::from(TestClientData::minimal()), + TestClient::from(test_target_client), + None, + futures::future::pending(), + )), + Err(FailedClient::Target), + ); + } + + #[test] + fn when_target_client_fails_to_read_heads() { + let mut test_target_client = TestClientData::minimal(); + test_target_client.target_head = Err(TestError::Error); + + assert_eq!( + async_std::task::block_on(run_until_connection_lost( + TestClient::from(TestClientData::minimal()), + TestClient::from(test_target_client), + None, + futures::future::pending(), + )), + Err(FailedClient::Target), + ); + } + + #[test] + fn when_target_client_fails_to_read_best_finalized_source_block() { + let mut test_target_client = TestClientData::minimal(); + test_target_client.target_best_finalized_source_block = Err(TestError::Error); + + assert_eq!( + async_std::task::block_on(run_until_connection_lost( + TestClient::from(TestClientData::minimal()), + TestClient::from(test_target_client), + None, + futures::future::pending(), + )), + Err(FailedClient::Target), + ); + } + + #[test] + fn when_source_client_fails_to_read_heads() { + let mut test_source_client = TestClientData::minimal(); + test_source_client.source_head = Err(TestError::Error); + + assert_eq!( + async_std::task::block_on(run_until_connection_lost( + TestClient::from(test_source_client), + TestClient::from(TestClientData::minimal()), + None, + futures::future::pending(), + )), + Err(FailedClient::Source), + ); + } + + #[test] + fn when_source_client_fails_to_prove_heads() { + let mut test_source_client = TestClientData::minimal(); + test_source_client.source_proof = Err(TestError::Error); + + assert_eq!( + async_std::task::block_on(run_until_connection_lost( + TestClient::from(test_source_client), + TestClient::from(TestClientData::minimal()), + None, + futures::future::pending(), + )), + Err(FailedClient::Source), + ); + } + + #[test] + fn when_target_client_rejects_update_transaction() { + let mut test_target_client = TestClientData::minimal(); + test_target_client.target_submit_result = Err(TestError::Error); + + assert_eq!( + async_std::task::block_on(run_until_connection_lost( + TestClient::from(TestClientData::minimal()), + TestClient::from(test_target_client), + None, + futures::future::pending(), + )), + Err(FailedClient::Target), + ); + } + + #[test] + fn minimal_working_case() { + let (exit_signal_sender, exit_signal) = futures::channel::mpsc::unbounded(); + assert_eq!( + async_std::task::block_on(run_until_connection_lost( + TestClient::from(TestClientData::minimal()), + TestClient::from(TestClientData::with_exit_signal_sender(exit_signal_sender)), + None, + exit_signal.into_future().map(|(_, _)| ()), + )), + Ok(()), + ); + } + + fn test_tx_tracker() -> SubmittedHeadsTracker { + SubmittedHeadsTracker::new( + AvailableHeader::Available(HeaderId(20, PARA_20_HASH)), + TestTransactionTracker(None), + ) + } + + impl From> for Option<()> { + fn from(status: SubmittedHeadStatus) -> Option<()> { + match status { + SubmittedHeadStatus::Waiting(_) => Some(()), + _ => None, + } + } + } + + #[async_std::test] + async fn tx_tracker_update_when_head_at_target_has_none_value() { + assert_eq!( + Some(()), + test_tx_tracker() + .update(&HeaderId(0, Default::default()), &Some(HeaderId(10, PARA_10_HASH))) + .await + .into(), + ); + } + + #[async_std::test] + async fn tx_tracker_update_when_head_at_target_has_old_value() { + assert_eq!( + Some(()), + test_tx_tracker() + .update(&HeaderId(0, Default::default()), &Some(HeaderId(10, PARA_10_HASH))) + .await + .into(), + ); + } + + #[async_std::test] + async fn tx_tracker_update_when_head_at_target_has_same_value() { + assert!(matches!( + test_tx_tracker() + .update(&HeaderId(0, Default::default()), &Some(HeaderId(20, PARA_20_HASH))) + .await, + SubmittedHeadStatus::Final(TrackedTransactionStatus::Finalized(_)), + )); + } + + #[async_std::test] + async fn tx_tracker_update_when_head_at_target_has_better_value() { + assert!(matches!( + test_tx_tracker() + .update(&HeaderId(0, Default::default()), &Some(HeaderId(30, PARA_20_HASH))) + .await, + SubmittedHeadStatus::Final(TrackedTransactionStatus::Finalized(_)), + )); + } + + #[async_std::test] + async fn tx_tracker_update_when_tx_is_lost() { + let mut tx_tracker = test_tx_tracker(); + tx_tracker.transaction_tracker = + futures::future::ready(TrackedTransactionStatus::Lost).boxed().shared(); + assert!(matches!( + tx_tracker + .update(&HeaderId(0, Default::default()), &Some(HeaderId(10, PARA_10_HASH))) + .await, + SubmittedHeadStatus::Final(TrackedTransactionStatus::Lost), + )); + } + + #[async_std::test] + async fn tx_tracker_update_when_tx_is_finalized_but_heads_are_not_updated() { + let mut tx_tracker = test_tx_tracker(); + tx_tracker.transaction_tracker = + futures::future::ready(TrackedTransactionStatus::Finalized(Default::default())) + .boxed() + .shared(); + assert!(matches!( + tx_tracker + .update(&HeaderId(0, Default::default()), &Some(HeaderId(10, PARA_10_HASH))) + .await, + SubmittedHeadStatus::Final(TrackedTransactionStatus::Lost), + )); + } + + #[test] + fn parachain_is_not_updated_if_it_is_unavailable() { + assert!(!is_update_required::( + AvailableHeader::Unavailable, + None, + Default::default(), + Default::default(), + )); + assert!(!is_update_required::( + AvailableHeader::Unavailable, + Some(HeaderId(10, PARA_10_HASH)), + Default::default(), + Default::default(), + )); + } + + #[test] + fn parachain_is_not_updated_if_it_is_unknown_to_both_clients() { + assert!(!is_update_required::( + AvailableHeader::Missing, + None, + Default::default(), + Default::default(), + ),); + } + + #[test] + fn parachain_is_not_updated_if_target_has_better_head() { + assert!(!is_update_required::( + AvailableHeader::Available(HeaderId(10, Default::default())), + Some(HeaderId(20, Default::default())), + Default::default(), + Default::default(), + ),); + } + + #[test] + fn parachain_is_updated_after_offboarding() { + assert!(is_update_required::( + AvailableHeader::Missing, + Some(HeaderId(20, Default::default())), + Default::default(), + Default::default(), + ),); + } + + #[test] + fn parachain_is_updated_after_onboarding() { + assert!(is_update_required::( + AvailableHeader::Available(HeaderId(30, Default::default())), + None, + Default::default(), + Default::default(), + ),); + } + + #[test] + fn parachain_is_updated_if_newer_head_is_known() { + assert!(is_update_required::( + AvailableHeader::Available(HeaderId(40, Default::default())), + Some(HeaderId(30, Default::default())), + Default::default(), + Default::default(), + ),); + } +} diff --git a/bridges/relays/parachains/src/parachains_loop_metrics.rs b/bridges/relays/parachains/src/parachains_loop_metrics.rs new file mode 100644 index 0000000000000..8138a43b3b3dc --- /dev/null +++ b/bridges/relays/parachains/src/parachains_loop_metrics.rs @@ -0,0 +1,86 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +use bp_polkadot_core::parachains::ParaId; +use relay_utils::{ + metrics::{metric_name, register, Gauge, Metric, PrometheusError, Registry, U64}, + UniqueSaturatedInto, +}; + +/// Parachains sync metrics. +#[derive(Clone)] +pub struct ParachainsLoopMetrics { + /// Best parachains header numbers at the source. + best_source_block_numbers: Gauge, + /// Best parachains header numbers at the target. + best_target_block_numbers: Gauge, +} + +impl ParachainsLoopMetrics { + /// Create and register parachains loop metrics. + pub fn new(prefix: Option<&str>) -> Result { + Ok(ParachainsLoopMetrics { + best_source_block_numbers: Gauge::new( + metric_name(prefix, "best_parachain_block_number_at_source"), + "Best parachain block numbers at the source relay chain".to_string(), + )?, + best_target_block_numbers: Gauge::new( + metric_name(prefix, "best_parachain_block_number_at_target"), + "Best parachain block numbers at the target chain".to_string(), + )?, + }) + } + + /// Update best block number at source. + pub fn update_best_parachain_block_at_source>( + &self, + parachain: ParaId, + block_number: Number, + ) { + let block_number = block_number.unique_saturated_into(); + log::trace!( + target: "bridge-metrics", + "Updated value of metric 'best_parachain_block_number_at_source[{:?}]': {:?}", + parachain, + block_number, + ); + self.best_source_block_numbers.set(block_number); + } + + /// Update best block number at target. + pub fn update_best_parachain_block_at_target>( + &self, + parachain: ParaId, + block_number: Number, + ) { + let block_number = block_number.unique_saturated_into(); + log::trace!( + target: "bridge-metrics", + "Updated value of metric 'best_parachain_block_number_at_target[{:?}]': {:?}", + parachain, + block_number, + ); + self.best_target_block_numbers.set(block_number); + } +} + +impl Metric for ParachainsLoopMetrics { + fn register(&self, registry: &Registry) -> Result<(), PrometheusError> { + register(self.best_source_block_numbers.clone(), registry)?; + register(self.best_target_block_numbers.clone(), registry)?; + Ok(()) + } +} diff --git a/bridges/relays/utils/Cargo.toml b/bridges/relays/utils/Cargo.toml new file mode 100644 index 0000000000000..8d9addb9beef0 --- /dev/null +++ b/bridges/relays/utils/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "relay-utils" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +repository.workspace = true +publish = false + +[lints] +workspace = true + +[dependencies] +ansi_term = "0.12" +anyhow = "1.0" +async-std = "1.9.0" +async-trait = "0.1.79" +backoff = "0.4" +isahc = "1.2" +env_logger = "0.11.3" +futures = "0.3.30" +jsonpath_lib = "0.3" +log = { workspace = true } +num-traits = "0.2" +serde_json = { workspace = true, default-features = true } +sysinfo = "0.30" +time = { version = "0.3", features = ["formatting", "local-offset", "std"] } +tokio = { version = "1.37", features = ["rt"] } +thiserror = { workspace = true } + +# Bridge dependencies + +bp-runtime = { path = "../../primitives/runtime" } + +# Substrate dependencies + +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } +substrate-prometheus-endpoint = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" } diff --git a/bridges/relays/utils/src/error.rs b/bridges/relays/utils/src/error.rs new file mode 100644 index 0000000000000..26f1d0cacefd8 --- /dev/null +++ b/bridges/relays/utils/src/error.rs @@ -0,0 +1,46 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +use std::net::AddrParseError; +use thiserror::Error; + +/// Result type used by relay utilities. +pub type Result = std::result::Result; + +/// Relay utilities errors. +#[derive(Error, Debug)] +pub enum Error { + /// Failed to request a float value from HTTP service. + #[error("Failed to fetch token price from remote server: {0}")] + FetchTokenPrice(#[source] anyhow::Error), + /// Failed to parse the response from HTTP service. + #[error("Failed to parse HTTP service response: {0:?}. Response: {1:?}")] + ParseHttp(serde_json::Error, String), + /// Failed to select response value from the Json response. + #[error("Failed to select value from response: {0:?}. Response: {1:?}")] + SelectResponseValue(jsonpath_lib::JsonPathError, String), + /// Failed to parse float value from the selected value. + #[error( + "Failed to parse float value {0:?} from response. It is assumed to be positive and normal" + )] + ParseFloat(f64), + /// Couldn't found value in the JSON response. + #[error("Missing required value from response: {0:?}")] + MissingResponseValue(String), + /// Invalid host address was used for exposing Prometheus metrics. + #[error("Invalid host {0} is used to expose Prometheus metrics: {1}")] + ExposingMetricsInvalidHost(String, AddrParseError), + /// Prometheus error. + #[error("{0}")] + Prometheus(#[from] substrate_prometheus_endpoint::prometheus::Error), +} diff --git a/bridges/relays/utils/src/initialize.rs b/bridges/relays/utils/src/initialize.rs new file mode 100644 index 0000000000000..8224c1803ad2f --- /dev/null +++ b/bridges/relays/utils/src/initialize.rs @@ -0,0 +1,136 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Relayer initialization functions. + +use std::{cell::RefCell, fmt::Display, io::Write}; + +async_std::task_local! { + pub(crate) static LOOP_NAME: RefCell = RefCell::new(String::default()); +} + +/// Initialize relay environment. +pub fn initialize_relay() { + initialize_logger(true); +} + +/// Initialize Relay logger instance. +pub fn initialize_logger(with_timestamp: bool) { + let format = time::format_description::parse( + "[year]-[month]-[day] \ + [hour repr:24]:[minute]:[second] [offset_hour sign:mandatory]", + ) + .expect("static format string is valid"); + + let mut builder = env_logger::Builder::new(); + builder.filter_level(log::LevelFilter::Warn); + builder.filter_module("bridge", log::LevelFilter::Info); + builder.parse_default_env(); + if with_timestamp { + builder.format(move |buf, record| { + let timestamp = time::OffsetDateTime::now_local() + .unwrap_or_else(|_| time::OffsetDateTime::now_utc()); + let timestamp = timestamp.format(&format).unwrap_or_else(|_| timestamp.to_string()); + + let log_level = color_level(record.level()); + let log_target = color_target(record.target()); + let timestamp = if cfg!(windows) { + Either::Left(timestamp) + } else { + Either::Right(ansi_term::Colour::Fixed(8).bold().paint(timestamp)) + }; + + writeln!( + buf, + "{}{} {} {} {}", + loop_name_prefix(), + timestamp, + log_level, + log_target, + record.args(), + ) + }); + } else { + builder.format(move |buf, record| { + let log_level = color_level(record.level()); + let log_target = color_target(record.target()); + + writeln!(buf, "{}{log_level} {log_target} {}", loop_name_prefix(), record.args(),) + }); + } + + builder.init(); +} + +/// Initialize relay loop. Must only be called once per every loop task. +pub(crate) fn initialize_loop(loop_name: String) { + LOOP_NAME.with(|g_loop_name| *g_loop_name.borrow_mut() = loop_name); +} + +/// Returns loop name prefix to use in logs. The prefix is initialized with the `initialize_loop` +/// call. +fn loop_name_prefix() -> String { + // try_with to avoid panic outside of async-std task context + LOOP_NAME + .try_with(|loop_name| { + // using borrow is ok here, because loop is only initialized once (=> borrow_mut will + // only be called once) + let loop_name = loop_name.borrow(); + if loop_name.is_empty() { + String::new() + } else { + format!("[{loop_name}] ") + } + }) + .unwrap_or_else(|_| String::new()) +} + +enum Either { + Left(A), + Right(B), +} +impl Display for Either { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Left(a) => write!(fmt, "{a}"), + Self::Right(b) => write!(fmt, "{b}"), + } + } +} + +fn color_target(target: &str) -> impl Display + '_ { + if cfg!(windows) { + Either::Left(target) + } else { + Either::Right(ansi_term::Colour::Fixed(8).paint(target)) + } +} + +fn color_level(level: log::Level) -> impl Display { + if cfg!(windows) { + Either::Left(level) + } else { + let s = level.to_string(); + use ansi_term::Colour as Color; + Either::Right(match level { + log::Level::Error => Color::Fixed(9).bold().paint(s), + log::Level::Warn => Color::Fixed(11).bold().paint(s), + log::Level::Info => Color::Fixed(10).paint(s), + log::Level::Debug => Color::Fixed(14).paint(s), + log::Level::Trace => Color::Fixed(12).paint(s), + }) + } +} diff --git a/bridges/relays/utils/src/lib.rs b/bridges/relays/utils/src/lib.rs new file mode 100644 index 0000000000000..2776620be3594 --- /dev/null +++ b/bridges/relays/utils/src/lib.rs @@ -0,0 +1,318 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Utilities used by different relays. + +pub use bp_runtime::HeaderId; +pub use error::Error; +pub use relay_loop::{relay_loop, relay_metrics}; +pub use sp_runtime::traits::{UniqueSaturatedFrom, UniqueSaturatedInto}; +use std::fmt::Debug; + +use async_trait::async_trait; +use backoff::{backoff::Backoff, ExponentialBackoff}; +use futures::future::{BoxFuture, FutureExt}; +use std::time::Duration; +use thiserror::Error; + +/// Default relay loop stall timeout. If transactions generated by relay are immortal, then +/// this timeout is used. +/// +/// There are no any strict requirements on block time in Substrate. But we assume here that all +/// Substrate-based chains will be designed to produce relatively fast (compared to the slowest +/// blockchains) blocks. So 1 hour seems to be a good guess for (even congested) chains to mine +/// transaction, or remove it from the pool. +pub const STALL_TIMEOUT: Duration = Duration::from_secs(60 * 60); + +/// Max delay after connection-unrelated error happened before we'll try the +/// same request again. +pub const MAX_BACKOFF_INTERVAL: Duration = Duration::from_secs(60); +/// Delay after connection-related error happened before we'll try +/// reconnection again. +pub const CONNECTION_ERROR_DELAY: Duration = Duration::from_secs(10); + +pub mod error; +pub mod initialize; +pub mod metrics; +pub mod relay_loop; + +/// Block number traits shared by all chains that relay is able to serve. +pub trait BlockNumberBase: + 'static + + From + + UniqueSaturatedInto + + Ord + + Clone + + Copy + + Default + + Send + + Sync + + std::fmt::Debug + + std::fmt::Display + + std::hash::Hash + + std::ops::Add + + std::ops::Sub + + num_traits::CheckedSub + + num_traits::Saturating + + num_traits::Zero + + num_traits::One +{ +} + +impl BlockNumberBase for T where + T: 'static + + From + + UniqueSaturatedInto + + Ord + + Clone + + Copy + + Default + + Send + + Sync + + std::fmt::Debug + + std::fmt::Display + + std::hash::Hash + + std::ops::Add + + std::ops::Sub + + num_traits::CheckedSub + + num_traits::Saturating + + num_traits::Zero + + num_traits::One +{ +} + +/// Macro that returns (client, Err(error)) tuple from function if result is Err(error). +#[macro_export] +macro_rules! bail_on_error { + ($result: expr) => { + match $result { + (client, Ok(result)) => (client, result), + (client, Err(error)) => return (client, Err(error)), + } + }; +} + +/// Macro that returns (client, Err(error)) tuple from function if result is Err(error). +#[macro_export] +macro_rules! bail_on_arg_error { + ($result: expr, $client: ident) => { + match $result { + Ok(result) => result, + Err(error) => return ($client, Err(error)), + } + }; +} + +/// Error type that can signal connection errors. +pub trait MaybeConnectionError { + /// Returns true if error (maybe) represents connection error. + fn is_connection_error(&self) -> bool; +} + +/// Final status of the tracked transaction. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum TrackedTransactionStatus { + /// Transaction has been lost. + Lost, + /// Transaction has been mined and finalized at given block. + Finalized(BlockId), +} + +/// Transaction tracker. +#[async_trait] +pub trait TransactionTracker: Send { + /// Header id, used by the chain. + type HeaderId: Clone + Debug + Send; + + /// Wait until transaction is either finalized or invalidated/lost. + async fn wait(self) -> TrackedTransactionStatus; +} + +/// Future associated with `TransactionTracker`, monitoring the transaction status. +pub type TrackedTransactionFuture<'a, T> = + BoxFuture<'a, TrackedTransactionStatus<::HeaderId>>; + +/// Stringified error that may be either connection-related or not. +#[derive(Error, Debug)] +pub enum StringifiedMaybeConnectionError { + /// The error is connection-related error. + #[error("{0}")] + Connection(String), + /// The error is connection-unrelated error. + #[error("{0}")] + NonConnection(String), +} + +impl StringifiedMaybeConnectionError { + /// Create new stringified connection error. + pub fn new(is_connection_error: bool, error: String) -> Self { + if is_connection_error { + StringifiedMaybeConnectionError::Connection(error) + } else { + StringifiedMaybeConnectionError::NonConnection(error) + } + } +} + +impl MaybeConnectionError for StringifiedMaybeConnectionError { + fn is_connection_error(&self) -> bool { + match *self { + StringifiedMaybeConnectionError::Connection(_) => true, + StringifiedMaybeConnectionError::NonConnection(_) => false, + } + } +} + +/// Exponential backoff for connection-unrelated errors retries. +pub fn retry_backoff() -> ExponentialBackoff { + ExponentialBackoff { + // we do not want relayer to stop + max_elapsed_time: None, + max_interval: MAX_BACKOFF_INTERVAL, + ..Default::default() + } +} + +/// Compact format of IDs vector. +pub fn format_ids(mut ids: impl ExactSizeIterator) -> String { + const NTH_PROOF: &str = "we have checked len; qed"; + match ids.len() { + 0 => "".into(), + 1 => format!("{:?}", ids.next().expect(NTH_PROOF)), + 2 => { + let id0 = ids.next().expect(NTH_PROOF); + let id1 = ids.next().expect(NTH_PROOF); + format!("[{id0:?}, {id1:?}]") + }, + len => { + let id0 = ids.next().expect(NTH_PROOF); + let id_last = ids.last().expect(NTH_PROOF); + format!("{len}:[{id0:?} ... {id_last:?}]") + }, + } +} + +/// Stream that emits item every `timeout_ms` milliseconds. +pub fn interval(timeout: Duration) -> impl futures::Stream { + futures::stream::unfold((), move |_| async move { + async_std::task::sleep(timeout).await; + Some(((), ())) + }) +} + +/// Which client has caused error. +#[derive(Debug, Eq, Clone, Copy, PartialEq)] +pub enum FailedClient { + /// It is the source client who has caused error. + Source, + /// It is the target client who has caused error. + Target, + /// Both clients are failing, or we just encountered some other error that + /// should be treated like that. + Both, +} + +/// Future process result. +#[derive(Debug, Clone, Copy)] +pub enum ProcessFutureResult { + /// Future has been processed successfully. + Success, + /// Future has failed with non-connection error. + Failed, + /// Future has failed with connection error. + ConnectionFailed, +} + +impl ProcessFutureResult { + /// Returns true if result is Success. + pub fn is_ok(self) -> bool { + match self { + ProcessFutureResult::Success => true, + ProcessFutureResult::Failed | ProcessFutureResult::ConnectionFailed => false, + } + } + + /// Returns `Ok(())` if future has succeeded. + /// Returns `Err(failed_client)` otherwise. + pub fn fail_if_error(self, failed_client: FailedClient) -> Result<(), FailedClient> { + if self.is_ok() { + Ok(()) + } else { + Err(failed_client) + } + } + + /// Returns Ok(true) if future has succeeded. + /// Returns Ok(false) if future has failed with non-connection error. + /// Returns Err if future is `ConnectionFailed`. + pub fn fail_if_connection_error( + self, + failed_client: FailedClient, + ) -> Result { + match self { + ProcessFutureResult::Success => Ok(true), + ProcessFutureResult::Failed => Ok(false), + ProcessFutureResult::ConnectionFailed => Err(failed_client), + } + } +} + +/// Process result of the future from a client. +pub fn process_future_result( + result: Result, + retry_backoff: &mut ExponentialBackoff, + on_success: impl FnOnce(TResult), + go_offline_future: &mut std::pin::Pin<&mut futures::future::Fuse>, + go_offline: impl FnOnce(Duration) -> TGoOfflineFuture, + error_pattern: impl FnOnce() -> String, +) -> ProcessFutureResult +where + TError: std::fmt::Debug + MaybeConnectionError, + TGoOfflineFuture: FutureExt, +{ + match result { + Ok(result) => { + on_success(result); + retry_backoff.reset(); + ProcessFutureResult::Success + }, + Err(error) if error.is_connection_error() => { + log::error!( + target: "bridge", + "{}: {:?}. Going to restart", + error_pattern(), + error, + ); + + retry_backoff.reset(); + go_offline_future.set(go_offline(CONNECTION_ERROR_DELAY).fuse()); + ProcessFutureResult::ConnectionFailed + }, + Err(error) => { + let retry_delay = retry_backoff.next_backoff().unwrap_or(CONNECTION_ERROR_DELAY); + log::error!( + target: "bridge", + "{}: {:?}. Retrying in {}", + error_pattern(), + error, + retry_delay.as_secs_f64(), + ); + + go_offline_future.set(go_offline(retry_delay).fuse()); + ProcessFutureResult::Failed + }, + } +} diff --git a/bridges/relays/utils/src/metrics.rs b/bridges/relays/utils/src/metrics.rs new file mode 100644 index 0000000000000..2e6c8236da454 --- /dev/null +++ b/bridges/relays/utils/src/metrics.rs @@ -0,0 +1,192 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +pub use float_json_value::FloatJsonValueMetric; +pub use global::GlobalMetrics; +pub use substrate_prometheus_endpoint::{ + prometheus::core::{Atomic, Collector}, + register, Counter, CounterVec, Gauge, GaugeVec, Opts, PrometheusError, Registry, F64, I64, U64, +}; + +use async_std::sync::{Arc, RwLock}; +use async_trait::async_trait; +use std::{fmt::Debug, time::Duration}; + +mod float_json_value; +mod global; + +/// Shared reference to `f64` value that is updated by the metric. +pub type F64SharedRef = Arc>>; +/// Int gauge metric type. +pub type IntGauge = Gauge; + +/// Unparsed address that needs to be used to expose Prometheus metrics. +#[derive(Debug, Clone)] +pub struct MetricsAddress { + /// Serve HTTP requests at given host. + pub host: String, + /// Serve HTTP requests at given port. + pub port: u16, +} + +/// Prometheus endpoint MetricsParams. +#[derive(Debug, Clone)] +pub struct MetricsParams { + /// Interface and TCP port to be used when exposing Prometheus metrics. + pub address: Option, + /// Metrics registry. May be `Some(_)` if several components share the same endpoint. + pub registry: Registry, +} + +/// Metric API. +pub trait Metric: Clone + Send + Sync + 'static { + fn register(&self, registry: &Registry) -> Result<(), PrometheusError>; +} + +/// Standalone metric API. +/// +/// Metrics of this kind know how to update themselves, so we may just spawn and forget the +/// asynchronous self-update task. +#[async_trait] +pub trait StandaloneMetric: Metric { + /// Update metric values. + async fn update(&self); + + /// Metrics update interval. + fn update_interval(&self) -> Duration; + + /// Register and spawn metric. Metric is only spawned if it is registered for the first time. + fn register_and_spawn(self, registry: &Registry) -> Result<(), PrometheusError> { + match self.register(registry) { + Ok(()) => { + self.spawn(); + Ok(()) + }, + Err(PrometheusError::AlreadyReg) => Ok(()), + Err(e) => Err(e), + } + } + + /// Spawn the self update task that will keep update metric value at given intervals. + fn spawn(self) { + async_std::task::spawn(async move { + let update_interval = self.update_interval(); + loop { + self.update().await; + async_std::task::sleep(update_interval).await; + } + }); + } +} + +impl Default for MetricsAddress { + fn default() -> Self { + MetricsAddress { host: "127.0.0.1".into(), port: 9616 } + } +} + +impl MetricsParams { + /// Creates metrics params from metrics address. + pub fn new( + address: Option, + relay_version: String, + relay_commit: String, + ) -> Result { + const BUILD_INFO_METRIC: &str = "substrate_relay_build_info"; + + let registry = Registry::new(); + register( + Gauge::::with_opts( + Opts::new( + BUILD_INFO_METRIC, + "A metric with a constant '1' value labeled by version", + ) + .const_label("version", &relay_version) + .const_label("commit", &relay_commit), + )?, + ®istry, + )? + .set(1); + + log::info!( + target: "bridge", + "Exposed {} metric: version={} commit={}", + BUILD_INFO_METRIC, + relay_version, + relay_commit, + ); + + Ok(MetricsParams { address, registry }) + } + + /// Creates metrics params so that metrics are not exposed. + pub fn disabled() -> Self { + MetricsParams { address: None, registry: Registry::new() } + } + + /// Do not expose metrics. + #[must_use] + pub fn disable(mut self) -> Self { + self.address = None; + self + } +} + +/// Returns metric name optionally prefixed with given prefix. +pub fn metric_name(prefix: Option<&str>, name: &str) -> String { + if let Some(prefix) = prefix { + format!("{prefix}_{name}") + } else { + name.into() + } +} + +/// Set value of gauge metric. +/// +/// If value is `Ok(None)` or `Err(_)`, metric would have default value. +pub fn set_gauge_value, E: Debug>( + gauge: &Gauge, + value: Result, E>, +) { + gauge.set(match value { + Ok(Some(value)) => { + log::trace!( + target: "bridge-metrics", + "Updated value of metric '{:?}': {:?}", + gauge.desc().first().map(|d| &d.fq_name), + value, + ); + value + }, + Ok(None) => { + log::warn!( + target: "bridge-metrics", + "Failed to update metric '{:?}': value is empty", + gauge.desc().first().map(|d| &d.fq_name), + ); + Default::default() + }, + Err(error) => { + log::warn!( + target: "bridge-metrics", + "Failed to update metric '{:?}': {:?}", + gauge.desc().first().map(|d| &d.fq_name), + error, + ); + Default::default() + }, + }) +} diff --git a/bridges/relays/utils/src/metrics/float_json_value.rs b/bridges/relays/utils/src/metrics/float_json_value.rs new file mode 100644 index 0000000000000..17b09e050973a --- /dev/null +++ b/bridges/relays/utils/src/metrics/float_json_value.rs @@ -0,0 +1,147 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +use crate::{ + error::{self, Error}, + metrics::{ + metric_name, register, F64SharedRef, Gauge, Metric, PrometheusError, Registry, + StandaloneMetric, F64, + }, +}; + +use async_std::sync::{Arc, RwLock}; +use async_trait::async_trait; +use std::time::Duration; + +/// Value update interval. +const UPDATE_INTERVAL: Duration = Duration::from_secs(300); + +/// Metric that represents float value received from HTTP service as float gauge. +/// +/// The float value returned by the service is assumed to be normal (`f64::is_normal` +/// should return `true`) and strictly positive. +#[derive(Debug, Clone)] +pub struct FloatJsonValueMetric { + url: String, + json_path: String, + metric: Gauge, + shared_value_ref: F64SharedRef, +} + +impl FloatJsonValueMetric { + /// Create new metric instance with given name and help. + pub fn new( + url: String, + json_path: String, + name: String, + help: String, + ) -> Result { + let shared_value_ref = Arc::new(RwLock::new(None)); + Ok(FloatJsonValueMetric { + url, + json_path, + metric: Gauge::new(metric_name(None, &name), help)?, + shared_value_ref, + }) + } + + /// Get shared reference to metric value. + pub fn shared_value_ref(&self) -> F64SharedRef { + self.shared_value_ref.clone() + } + + /// Request value from HTTP service. + async fn request_value(&self) -> anyhow::Result { + use isahc::{AsyncReadResponseExt, HttpClient, Request}; + + let request = Request::get(&self.url).header("Accept", "application/json").body(())?; + let raw_response = HttpClient::new()?.send_async(request).await?.text().await?; + Ok(raw_response) + } + + /// Read value from HTTP service. + async fn read_value(&self) -> error::Result { + let raw_response = self.request_value().await.map_err(Error::FetchTokenPrice)?; + parse_service_response(&self.json_path, &raw_response) + } +} + +impl Metric for FloatJsonValueMetric { + fn register(&self, registry: &Registry) -> Result<(), PrometheusError> { + register(self.metric.clone(), registry).map(drop) + } +} + +#[async_trait] +impl StandaloneMetric for FloatJsonValueMetric { + fn update_interval(&self) -> Duration { + UPDATE_INTERVAL + } + + async fn update(&self) { + let value = self.read_value().await; + let maybe_ok = value.as_ref().ok().copied(); + crate::metrics::set_gauge_value(&self.metric, value.map(Some)); + *self.shared_value_ref.write().await = maybe_ok; + } +} + +/// Parse HTTP service response. +fn parse_service_response(json_path: &str, response: &str) -> error::Result { + let json = + serde_json::from_str(response).map_err(|err| Error::ParseHttp(err, response.to_owned()))?; + + let mut selector = jsonpath_lib::selector(&json); + let maybe_selected_value = + selector(json_path).map_err(|err| Error::SelectResponseValue(err, response.to_owned()))?; + let selected_value = maybe_selected_value + .first() + .and_then(|v| v.as_f64()) + .ok_or_else(|| Error::MissingResponseValue(response.to_owned()))?; + if !selected_value.is_normal() || selected_value < 0.0 { + return Err(Error::ParseFloat(selected_value)) + } + + Ok(selected_value) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_service_response_works() { + assert_eq!( + parse_service_response("$.kusama.usd", r#"{"kusama":{"usd":433.05}}"#).map_err(drop), + Ok(433.05), + ); + } + + #[test] + fn parse_service_response_rejects_negative_numbers() { + assert!(parse_service_response("$.kusama.usd", r#"{"kusama":{"usd":-433.05}}"#).is_err()); + } + + #[test] + fn parse_service_response_rejects_zero_numbers() { + assert!(parse_service_response("$.kusama.usd", r#"{"kusama":{"usd":0.0}}"#).is_err()); + } + + #[test] + fn parse_service_response_rejects_nan() { + assert!(parse_service_response("$.kusama.usd", r#"{"kusama":{"usd":NaN}}"#).is_err()); + } +} diff --git a/bridges/relays/utils/src/metrics/global.rs b/bridges/relays/utils/src/metrics/global.rs new file mode 100644 index 0000000000000..9b22fb86ef0cb --- /dev/null +++ b/bridges/relays/utils/src/metrics/global.rs @@ -0,0 +1,118 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Global system-wide Prometheus metrics exposed by relays. + +use crate::metrics::{ + metric_name, register, Gauge, GaugeVec, Metric, Opts, PrometheusError, Registry, + StandaloneMetric, F64, U64, +}; + +use async_std::sync::{Arc, Mutex}; +use async_trait::async_trait; +use std::time::Duration; +use sysinfo::{RefreshKind, System}; + +/// Global metrics update interval. +const UPDATE_INTERVAL: Duration = Duration::from_secs(10); + +/// Global Prometheus metrics. +#[derive(Debug, Clone)] +pub struct GlobalMetrics { + system: Arc>, + system_average_load: GaugeVec, + process_cpu_usage_percentage: Gauge, + process_memory_usage_bytes: Gauge, +} + +impl GlobalMetrics { + /// Create and register global metrics. + pub fn new() -> Result { + Ok(GlobalMetrics { + system: Arc::new(Mutex::new(System::new_with_specifics(RefreshKind::everything()))), + system_average_load: GaugeVec::new( + Opts::new(metric_name(None, "system_average_load"), "System load average"), + &["over"], + )?, + process_cpu_usage_percentage: Gauge::new( + metric_name(None, "process_cpu_usage_percentage"), + "Process CPU usage", + )?, + process_memory_usage_bytes: Gauge::new( + metric_name(None, "process_memory_usage_bytes"), + "Process memory (resident set size) usage", + )?, + }) + } +} + +impl Metric for GlobalMetrics { + fn register(&self, registry: &Registry) -> Result<(), PrometheusError> { + register(self.system_average_load.clone(), registry)?; + register(self.process_cpu_usage_percentage.clone(), registry)?; + register(self.process_memory_usage_bytes.clone(), registry)?; + Ok(()) + } +} + +#[async_trait] +impl StandaloneMetric for GlobalMetrics { + async fn update(&self) { + // update system-wide metrics + let mut system = self.system.lock().await; + let load = sysinfo::System::load_average(); + self.system_average_load.with_label_values(&["1min"]).set(load.one); + self.system_average_load.with_label_values(&["5min"]).set(load.five); + self.system_average_load.with_label_values(&["15min"]).set(load.fifteen); + + // update process-related metrics + let pid = sysinfo::get_current_pid().expect( + "only fails where pid is unavailable (os=unknown || arch=wasm32);\ + relay is not supposed to run in such MetricsParamss;\ + qed", + ); + let is_process_refreshed = system.refresh_process(pid); + match (is_process_refreshed, system.process(pid)) { + (true, Some(process_info)) => { + let cpu_usage = process_info.cpu_usage() as f64; + let memory_usage = process_info.memory() * 1024; + log::trace!( + target: "bridge-metrics", + "Refreshed process metrics: CPU={}, memory={}", + cpu_usage, + memory_usage, + ); + + self.process_cpu_usage_percentage.set(if cpu_usage.is_finite() { + cpu_usage + } else { + 0f64 + }); + self.process_memory_usage_bytes.set(memory_usage); + }, + _ => { + log::warn!( + target: "bridge-metrics", + "Failed to refresh process information. Metrics may show obsolete values", + ); + }, + } + } + + fn update_interval(&self) -> Duration { + UPDATE_INTERVAL + } +} diff --git a/bridges/relays/utils/src/relay_loop.rs b/bridges/relays/utils/src/relay_loop.rs new file mode 100644 index 0000000000000..7105190a45831 --- /dev/null +++ b/bridges/relays/utils/src/relay_loop.rs @@ -0,0 +1,262 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +use crate::{ + error::Error, + metrics::{Metric, MetricsAddress, MetricsParams}, + FailedClient, MaybeConnectionError, +}; + +use async_trait::async_trait; +use std::{fmt::Debug, future::Future, net::SocketAddr, time::Duration}; +use substrate_prometheus_endpoint::{init_prometheus, Registry}; + +/// Default pause between reconnect attempts. +pub const RECONNECT_DELAY: Duration = Duration::from_secs(10); + +/// Basic blockchain client from relay perspective. +#[async_trait] +pub trait Client: 'static + Clone + Send + Sync { + /// Type of error these clients returns. + type Error: 'static + Debug + MaybeConnectionError + Send + Sync; + + /// Try to reconnect to source node. + async fn reconnect(&mut self) -> Result<(), Self::Error>; + + /// Try to reconnect to the source node in an infinite loop until it succeeds. + async fn reconnect_until_success(&mut self, delay: Duration) { + loop { + match self.reconnect().await { + Ok(()) => break, + Err(error) => { + log::warn!( + target: "bridge", + "Failed to reconnect to client. Going to retry in {}s: {:?}", + delay.as_secs(), + error, + ); + + async_std::task::sleep(delay).await; + }, + } + } + } +} + +#[async_trait] +impl Client for () { + type Error = crate::StringifiedMaybeConnectionError; + + async fn reconnect(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +/// Returns generic loop that may be customized and started. +pub fn relay_loop(source_client: SC, target_client: TC) -> Loop { + Loop { reconnect_delay: RECONNECT_DELAY, source_client, target_client, loop_metric: None } +} + +/// Returns generic relay loop metrics that may be customized and used in one or several relay +/// loops. +pub fn relay_metrics(params: MetricsParams) -> LoopMetrics<(), (), ()> { + LoopMetrics { + relay_loop: Loop { + reconnect_delay: RECONNECT_DELAY, + source_client: (), + target_client: (), + loop_metric: None, + }, + address: params.address, + registry: params.registry, + loop_metric: None, + } +} + +/// Generic relay loop. +pub struct Loop { + reconnect_delay: Duration, + source_client: SC, + target_client: TC, + loop_metric: Option, +} + +/// Relay loop metrics builder. +pub struct LoopMetrics { + relay_loop: Loop, + address: Option, + registry: Registry, + loop_metric: Option, +} + +impl Loop { + /// Customize delay between reconnect attempts. + #[must_use] + pub fn reconnect_delay(mut self, reconnect_delay: Duration) -> Self { + self.reconnect_delay = reconnect_delay; + self + } + + /// Start building loop metrics using given prefix. + pub fn with_metrics(self, params: MetricsParams) -> LoopMetrics { + LoopMetrics { + relay_loop: Loop { + reconnect_delay: self.reconnect_delay, + source_client: self.source_client, + target_client: self.target_client, + loop_metric: None, + }, + address: params.address, + registry: params.registry, + loop_metric: None, + } + } + + /// Run relay loop. + /// + /// This function represents an outer loop, which in turn calls provided `run_loop` function to + /// do actual job. When `run_loop` returns, this outer loop reconnects to failed client (source, + /// target or both) and calls `run_loop` again. + pub async fn run(mut self, loop_name: String, run_loop: R) -> Result<(), Error> + where + R: 'static + Send + Fn(SC, TC, Option) -> F, + F: 'static + Send + Future>, + SC: 'static + Client, + TC: 'static + Client, + LM: 'static + Send + Clone, + { + let run_loop_task = async move { + crate::initialize::initialize_loop(loop_name); + + loop { + let loop_metric = self.loop_metric.clone(); + let future_result = + run_loop(self.source_client.clone(), self.target_client.clone(), loop_metric); + let result = future_result.await; + + match result { + Ok(()) => break, + Err(failed_client) => { + log::debug!(target: "bridge", "Restarting relay loop"); + + reconnect_failed_client( + failed_client, + self.reconnect_delay, + &mut self.source_client, + &mut self.target_client, + ) + .await + }, + } + } + Ok(()) + }; + + async_std::task::spawn(run_loop_task).await + } +} + +impl LoopMetrics { + /// Add relay loop metrics. + /// + /// Loop metrics will be passed to the loop callback. + pub fn loop_metric( + self, + metric: NewLM, + ) -> Result, Error> { + metric.register(&self.registry)?; + + Ok(LoopMetrics { + relay_loop: self.relay_loop, + address: self.address, + registry: self.registry, + loop_metric: Some(metric), + }) + } + + /// Convert into `MetricsParams` structure so that metrics registry may be extended later. + pub fn into_params(self) -> MetricsParams { + MetricsParams { address: self.address, registry: self.registry } + } + + /// Expose metrics using address passed at creation. + /// + /// If passed `address` is `None`, metrics are not exposed. + pub async fn expose(self) -> Result, Error> { + if let Some(address) = self.address { + let socket_addr = SocketAddr::new( + address + .host + .parse() + .map_err(|err| Error::ExposingMetricsInvalidHost(address.host.clone(), err))?, + address.port, + ); + + let registry = self.registry; + async_std::task::spawn(async move { + let runtime = + match tokio::runtime::Builder::new_current_thread().enable_all().build() { + Ok(runtime) => runtime, + Err(err) => { + log::trace!( + target: "bridge-metrics", + "Failed to create tokio runtime. Prometheus metrics are not available: {:?}", + err, + ); + return + }, + }; + + runtime.block_on(async move { + log::trace!( + target: "bridge-metrics", + "Starting prometheus endpoint at: {:?}", + socket_addr, + ); + let result = init_prometheus(socket_addr, registry).await; + log::trace!( + target: "bridge-metrics", + "Prometheus endpoint has exited with result: {:?}", + result, + ); + }); + }); + } + + Ok(Loop { + reconnect_delay: self.relay_loop.reconnect_delay, + source_client: self.relay_loop.source_client, + target_client: self.relay_loop.target_client, + loop_metric: self.loop_metric, + }) + } +} + +/// Deal with the clients that have returned connection error. +pub async fn reconnect_failed_client( + failed_client: FailedClient, + reconnect_delay: Duration, + source_client: &mut impl Client, + target_client: &mut impl Client, +) { + if failed_client == FailedClient::Source || failed_client == FailedClient::Both { + source_client.reconnect_until_success(reconnect_delay).await; + } + + if failed_client == FailedClient::Target || failed_client == FailedClient::Both { + target_client.reconnect_until_success(reconnect_delay).await; + } +}