From 5299ada7174bd2132511efd545528b576ccac906 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Tue, 22 Aug 2023 15:53:32 +0200 Subject: [PATCH] Allow integrated relay chain light client (#2270) * Add embedded light client to cli * Prepare for light-client-worker * First working version * Clean up * Remove unwanted logs * Simplify subscription code * Let jsonrpsee handle rpc management * Simplify implementation * Reorganize crate structure * Use relay chain arg chainspec for light-client * Clean up command line * Add light client worker file * Use smoldot master to avoid wasmtime conflict * Remove sleep * Improve naming of cli option * Remove conflict with `validator` * Improve docs * Update smoldot, remove unwanted change * Apply suggestions from code review Co-authored-by: Dmitry Markin * Disable collation * Reviewer comments * Update smoldot and tokio-platform * Update smoldot * Update smoldot * Adjust to new version * Patch substrate * Use constants * Add readme entry, improve zombienet tests * Apply suggestions from code review Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> * Make execution mode an enum * Update smoldot, remove substrate patch * Update client/relay-chain-rpc-interface/src/rpc_client.rs Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> * Reduce duplicate code * Update smoldot * Update smoldot * Fix build * Update smoldot * Make platform compile * Clean up dependencies * Use crates.io instead of github for smoldot * Apply suggestions from code review Co-authored-by: Davide Galassi * Docs * Improve docs * Remove `RpcFrontend` --------- Co-authored-by: Dmitry Markin Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Co-authored-by: Davide Galassi --- Cargo.lock | 564 +++++++++++++++--- README.md | 27 +- client/cli/src/lib.rs | 29 +- client/relay-chain-minimal-node/Cargo.toml | 3 - client/relay-chain-minimal-node/src/lib.rs | 56 +- client/relay-chain-rpc-interface/Cargo.toml | 8 + client/relay-chain-rpc-interface/src/lib.rs | 8 +- .../src/light_client_worker.rs | 297 +++++++++ .../src/reconnecting_ws_client.rs | 252 +++----- .../src/rpc_client.rs | 177 +++++- .../src/tokio_platform.rs | 211 +++++++ client/service/src/lib.rs | 30 +- parachain-template/node/src/command.rs | 12 +- polkadot-parachain/src/command.rs | 10 +- test/service/src/lib.rs | 57 +- zombienet/tests/0002-pov_recovery.toml | 20 +- zombienet/tests/0002-pov_recovery.zndsl | 1 + .../tests/0003-full_node_catching_up.toml | 8 + .../tests/0003-full_node_catching_up.zndsl | 1 + zombienet/tests/0007-full_node_warp_sync.toml | 17 + .../tests/0007-full_node_warp_sync.zndsl | 3 +- 21 files changed, 1406 insertions(+), 385 deletions(-) create mode 100644 client/relay-chain-rpc-interface/src/light_client_worker.rs create mode 100644 client/relay-chain-rpc-interface/src/tokio_platform.rs diff --git a/Cargo.lock b/Cargo.lock index d97b7c4e909..8b0a023b0ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -274,7 +274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df752953c49ce90719c7bf1fc587bc8227aed04732ea0c0f85e5397d7fdbd1a1" dependencies = [ "include_dir", - "itertools", + "itertools 0.10.3", "proc-macro-error", "proc-macro2", "quote", @@ -311,7 +311,7 @@ dependencies = [ "ark-std", "derivative", "hashbrown 0.13.2", - "itertools", + "itertools 0.10.3", "num-traits", "zeroize", ] @@ -340,7 +340,7 @@ dependencies = [ "ark-std", "derivative", "digest 0.10.7", - "itertools", + "itertools 0.10.3", "num-bigint", "num-traits", "paste", @@ -457,6 +457,15 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + [[package]] name = "arrayvec" version = "0.5.2" @@ -913,41 +922,98 @@ dependencies = [ [[package]] name = "async-channel" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ - "concurrent-queue 2.1.0", + "concurrent-queue", "event-listener", "futures-core", ] [[package]] -name = "async-io" +name = "async-executor" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand 1.7.0", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" dependencies = [ - "concurrent-queue 1.2.2", + "async-lock", + "autocfg", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if", + "concurrent-queue", "futures-lite", - "libc", "log", - "once_cell", "parking", "polling", + "rustix 0.37.19", "slab", "socket2 0.4.9", "waker-fn", - "winapi", ] [[package]] name = "async-lock" -version = "2.4.0" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-net" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4051e67316bc7eff608fe723df5d32ed639946adcd69e07df41fd42a7b411f1f" +dependencies = [ + "async-io", + "autocfg", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" +checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" dependencies = [ + "async-io", + "async-lock", + "autocfg", + "blocking", + "cfg-if", "event-listener", + "futures-lite", + "rustix 0.37.19", + "signal-hook", + "windows-sys 0.48.0", ] [[package]] @@ -961,6 +1027,12 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "async-task" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" + [[package]] name = "async-trait" version = "0.1.73" @@ -985,6 +1057,12 @@ dependencies = [ "pin-project-lite 0.2.12", ] +[[package]] +name = "atomic-take" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3" + [[package]] name = "atomic-waker" version = "1.1.1" @@ -1070,9 +1148,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "base64" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f1e31e207a6b8fb791a38ea3105e6cb541f55e4d029902d3039a4ad07cc4105" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "base64ct" @@ -1128,6 +1206,21 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "bip39" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" +dependencies = [ + "bitcoin_hashes", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" + [[package]] name = "bitflags" version = "1.3.2" @@ -1161,6 +1254,16 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "blake2-rfc" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +dependencies = [ + "arrayvec 0.4.12", + "constant_time_eq 0.1.5", +] + [[package]] name = "blake2b_simd" version = "1.0.1" @@ -1252,6 +1355,21 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "blocking" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "atomic-waker", + "fastrand 1.7.0", + "futures-lite", + "log", +] + [[package]] name = "bounded-collections" version = "0.1.8" @@ -1791,6 +1909,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" +[[package]] +name = "bs58" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +dependencies = [ + "tinyvec", +] + [[package]] name = "bstr" version = "0.2.17" @@ -1868,12 +1995,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "cache-padded" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" - [[package]] name = "camino" version = "1.1.2" @@ -1974,6 +2095,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures", +] + [[package]] name = "chacha20poly1305" version = "0.9.1" @@ -1981,9 +2113,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" dependencies = [ "aead 0.4.3", - "chacha20", + "chacha20 0.8.2", "cipher 0.3.0", - "poly1305", + "poly1305 0.7.2", "zeroize", ] @@ -2308,15 +2440,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101" -[[package]] -name = "concurrent-queue" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" -dependencies = [ - "cache-padded", -] - [[package]] name = "concurrent-queue" version = "2.1.0" @@ -2591,7 +2714,7 @@ dependencies = [ "cranelift-codegen", "cranelift-entity", "cranelift-frontend", - "itertools", + "itertools 0.10.3", "log", "smallvec", "wasmparser", @@ -2635,7 +2758,7 @@ dependencies = [ "criterion-plot", "futures", "is-terminal", - "itertools", + "itertools 0.10.3", "num-traits", "once_cell", "oorandom", @@ -2657,7 +2780,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.3", ] [[package]] @@ -2696,9 +2819,9 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ "cfg-if", "crossbeam-utils", @@ -3352,18 +3475,15 @@ dependencies = [ "polkadot-overseer", "polkadot-primitives", "sc-authority-discovery", - "sc-client-api", "sc-network", "sc-network-common", "sc-service", "sc-tracing", "sc-utils", "sp-api", - "sp-blockchain", "sp-consensus", "sp-consensus-babe", "sp-runtime", - "tokio", "tracing", ] @@ -3374,24 +3494,32 @@ dependencies = [ "async-trait", "cumulus-primitives-core", "cumulus-relay-chain-interface", + "either", "futures", "futures-timer", "jsonrpsee", "lru 0.11.0", "parity-scale-codec", + "pin-project", "polkadot-overseer", + "rand 0.8.5", "sc-client-api", "sc-rpc-api", "sc-service", "serde", "serde_json", + "smoldot", + "smoldot-light", "sp-api", "sp-authority-discovery", "sp-consensus-babe", "sp-core", + "sp-runtime", "sp-state-machine", "sp-storage", + "thiserror", "tokio", + "tokio-util", "tracing", "url", ] @@ -3610,6 +3738,19 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "curve25519-dalek-ng" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.6.4", + "subtle-ng", + "zeroize", +] + [[package]] name = "cxx" version = "1.0.80" @@ -4104,6 +4245,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ed25519-zebra" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e83e509bcd060ca4b54b72bde5bb306cb2088cb01e14797ebae90a24f70f5f7" +dependencies = [ + "curve25519-dalek 4.0.0", + "ed25519 2.2.2", + "hashbrown 0.14.0", + "hex", + "rand_core 0.6.4", + "sha2 0.10.7", + "zeroize", +] + [[package]] name = "either" version = "1.9.0" @@ -4299,9 +4455,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.5.1" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "exit-future" @@ -4596,7 +4752,7 @@ dependencies = [ "frame-system", "gethostname", "handlebars", - "itertools", + "itertools 0.10.3", "lazy_static", "linked-hash-map", "log", @@ -4756,7 +4912,7 @@ dependencies = [ "derive-syn-parse", "expander 2.0.0", "frame-support-procedural-tools", - "itertools", + "itertools 0.10.3", "macro_magic", "proc-macro-warning", "proc-macro2", @@ -4931,9 +5087,9 @@ checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand 1.7.0", "futures-core", @@ -5251,6 +5407,7 @@ checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" dependencies = [ "ahash 0.8.2", "allocator-api2", + "serde", ] [[package]] @@ -5815,6 +5972,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" @@ -6004,9 +6170,12 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.0" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] [[package]] name = "kusama-runtime" @@ -6333,7 +6502,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e2d584751cecb2aabaa56106be6be91338a60a0f4e420cf2af639204f596fc1" dependencies = [ - "bs58", + "bs58 0.4.0", "ed25519-dalek 1.0.1", "log", "multiaddr", @@ -6647,9 +6816,9 @@ dependencies = [ [[package]] name = "libsecp256k1" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0452aac8bab02242429380e9b2f94ea20cea2b37e2c1777a1358799bbe97f37" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" dependencies = [ "arrayref", "base64 0.13.0", @@ -7341,6 +7510,18 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "no-std-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + [[package]] name = "nohash-hasher" version = "0.2.0" @@ -7349,13 +7530,12 @@ checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] name = "nom" -version = "7.1.0" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", - "version_check", ] [[package]] @@ -7525,7 +7705,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2871aadd82a2c216ee68a69837a526dfe788ecbe74c4c5038a6acdbff6653066" dependencies = [ "expander 0.0.6", - "itertools", + "itertools 0.10.3", "petgraph", "proc-macro-crate", "proc-macro2", @@ -7995,7 +8175,7 @@ dependencies = [ "sp-runtime", "sp-std", "wasm-instrument 0.4.0", - "wasmi", + "wasmi 0.30.0", ] [[package]] @@ -9310,6 +9490,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -9453,18 +9642,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", @@ -9809,7 +9998,7 @@ dependencies = [ "polkadot-overseer", "polkadot-primitives", "sc-keystore", - "schnorrkel", + "schnorrkel 0.9.1", "sp-application-crypto", "sp-consensus", "sp-consensus-slots", @@ -10147,7 +10336,7 @@ name = "polkadot-node-metrics" version = "0.9.43" source = "git+https://github.com/paritytech/polkadot?branch=master#e39c00381e8d5e8aa6fe752fc5cf2fcba14c8599" dependencies = [ - "bs58", + "bs58 0.4.0", "futures", "futures-timer", "log", @@ -10195,7 +10384,7 @@ dependencies = [ "parity-scale-codec", "polkadot-parachain", "polkadot-primitives", - "schnorrkel", + "schnorrkel 0.9.1", "serde", "sp-application-crypto", "sp-consensus-babe", @@ -10269,7 +10458,7 @@ dependencies = [ "fatality", "futures", "futures-channel", - "itertools", + "itertools 0.10.3", "kvdb", "lru 0.11.0", "parity-db", @@ -10653,7 +10842,7 @@ name = "polkadot-runtime-metrics" version = "0.9.43" source = "git+https://github.com/paritytech/polkadot?branch=master#e39c00381e8d5e8aa6fe752fc5cf2fcba14c8599" dependencies = [ - "bs58", + "bs58 0.4.0", "parity-scale-codec", "polkadot-primitives", "sp-std", @@ -11022,6 +11211,17 @@ dependencies = [ "universal-hash 0.4.1", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug 0.3.0", + "universal-hash 0.5.1", +] + [[package]] name = "polyval" version = "0.5.3" @@ -11075,7 +11275,7 @@ checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" dependencies = [ "difflib", "float-cmp", - "itertools", + "itertools 0.10.3", "normalize-line-endings", "predicates-core", "regex", @@ -11089,7 +11289,7 @@ checksum = "1ba7d6ead3e3966038f68caa9fc1f860185d95a793180bbcfe0d0da47b3961ed" dependencies = [ "anstyle 0.3.4", "difflib", - "itertools", + "itertools 0.10.3", "predicates-core", ] @@ -11265,7 +11465,7 @@ checksum = "7f835c582e6bd972ba8347313300219fed5bfa52caf175298d860b61ff6069bb" dependencies = [ "bytes", "heck", - "itertools", + "itertools 0.10.3", "lazy_static", "log", "multimap", @@ -11284,7 +11484,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7345d5f0e08c0536d7ac7229952590239e77abf0a0100a1b1d890add6ea96364" dependencies = [ "anyhow", - "itertools", + "itertools 0.10.3", "proc-macro2", "quote", "syn 1.0.109", @@ -11556,7 +11756,7 @@ checksum = "3bd8f48b2066e9f69ab192797d66da804d1935bf22763204ed3675740cb0f221" dependencies = [ "derive_more", "fs-err", - "itertools", + "itertools 0.10.3", "static_init 0.5.2", "thiserror", ] @@ -12072,6 +12272,17 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +[[package]] +name = "ruzstd" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3ffab8f9715a0d455df4bbb9d21e91135aab3cd3ca187af0cd0c3c3f868fdc" +dependencies = [ + "byteorder", + "thiserror-core", + "twox-hash", +] + [[package]] name = "rw-stream-sink" version = "0.3.0" @@ -13270,6 +13481,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "schnorrkel" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "844b7645371e6ecdf61ff246ba1958c29e802881a749ae3fb1993675d210d28d" +dependencies = [ + "arrayref", + "arrayvec 0.7.4", + "curve25519-dalek-ng", + "merlin 3.0.0", + "rand_core 0.6.4", + "sha2 0.9.8", + "subtle-ng", + "zeroize", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -13568,9 +13795,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ "digest 0.10.7", "keccak", @@ -13622,6 +13849,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -13672,9 +13909,12 @@ checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" [[package]] name = "slab" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] [[package]] name = "slice-group-by" @@ -13709,6 +13949,113 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +[[package]] +name = "smol" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1" +dependencies = [ + "async-channel", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-net", + "async-process", + "blocking", + "futures-lite", +] + +[[package]] +name = "smoldot" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0bb30cf57b7b5f6109ce17c3164445e2d6f270af2cb48f6e4d31c2967c9a9f5" +dependencies = [ + "arrayvec 0.7.4", + "async-lock", + "atomic-take", + "base64 0.21.2", + "bip39", + "blake2-rfc", + "bs58 0.5.0", + "chacha20 0.9.1", + "crossbeam-queue", + "derive_more", + "ed25519-zebra 4.0.2", + "either", + "event-listener", + "fnv", + "futures-lite", + "futures-util", + "hashbrown 0.14.0", + "hex", + "hmac 0.12.1", + "itertools 0.11.0", + "libsecp256k1", + "merlin 3.0.0", + "no-std-net", + "nom", + "num-bigint", + "num-rational", + "num-traits", + "pbkdf2 0.12.2", + "pin-project", + "poly1305 0.8.0", + "rand 0.8.5", + "rand_chacha 0.3.1", + "ruzstd", + "schnorrkel 0.10.2", + "serde", + "serde_json", + "sha2 0.10.7", + "sha3", + "siphasher", + "slab", + "smallvec", + "soketto", + "twox-hash", + "wasmi 0.31.0", + "x25519-dalek 2.0.0", + "zeroize", +] + +[[package]] +name = "smoldot-light" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256b5bad1d6b49045e95fe87492ce73d5af81545d8b4d8318a872d2007024c33" +dependencies = [ + "async-channel", + "async-lock", + "base64 0.21.2", + "blake2-rfc", + "derive_more", + "either", + "event-listener", + "fnv", + "futures-channel", + "futures-lite", + "futures-util", + "hashbrown 0.14.0", + "hex", + "itertools 0.11.0", + "log", + "lru 0.11.0", + "no-std-net", + "parking_lot 0.12.1", + "pin-project", + "rand 0.8.5", + "rand_chacha 0.3.1", + "serde", + "serde_json", + "siphasher", + "slab", + "smol", + "smoldot", + "zeroize", +] + [[package]] name = "snap" version = "1.0.5" @@ -13984,9 +14331,9 @@ dependencies = [ "bitflags 1.3.2", "blake2", "bounded-collections", - "bs58", + "bs58 0.4.0", "dyn-clonable", - "ed25519-zebra", + "ed25519-zebra 3.1.0", "futures", "hash-db", "hash256-std-hasher", @@ -14002,7 +14349,7 @@ dependencies = [ "rand 0.8.5", "regex", "scale-info", - "schnorrkel", + "schnorrkel 0.9.1", "secp256k1", "secrecy", "serde", @@ -14677,7 +15024,7 @@ checksum = "49eee6965196b32f882dd2ee85a92b1dbead41b04e53907f269de3b0dc04733c" dependencies = [ "hmac 0.11.0", "pbkdf2 0.8.0", - "schnorrkel", + "schnorrkel 0.9.1", "sha2 0.9.8", "zeroize", ] @@ -14828,6 +15175,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + [[package]] name = "syn" version = "1.0.109" @@ -14946,6 +15299,26 @@ dependencies = [ "thiserror-impl", ] +[[package]] +name = "thiserror-core" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d97345f6437bb2004cd58819d8a9ef8e36cdd7661c2abc4bbde0a7c40d9f497" +dependencies = [ + "thiserror-core-impl", +] + +[[package]] +name = "thiserror-core-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ac1c5050e43014d16b2f94d0d2ce79e65ffdd8b38d8048f9c8f6a8a6da62ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "thiserror-impl" version = "1.0.47" @@ -15184,9 +15557,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.1" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", @@ -15897,7 +16270,20 @@ dependencies = [ "smallvec", "spin 0.9.4", "wasmi_arena", - "wasmi_core", + "wasmi_core 0.12.0", + "wasmparser-nostd", +] + +[[package]] +name = "wasmi" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f341edb80021141d4ae6468cbeefc50798716a347d4085c3811900049ea8945" +dependencies = [ + "smallvec", + "spin 0.9.4", + "wasmi_arena", + "wasmi_core 0.13.0", "wasmparser-nostd", ] @@ -15919,6 +16305,18 @@ dependencies = [ "paste", ] +[[package]] +name = "wasmi_core" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" +dependencies = [ + "downcast-rs", + "libm", + "num-traits", + "paste", +] + [[package]] name = "wasmparser" version = "0.102.0" @@ -15982,7 +16380,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c86437fa68626fe896e5afc69234bb2b5894949083586535f200385adfd71213" dependencies = [ "anyhow", - "base64 0.21.1", + "base64 0.21.2", "bincode", "directories-next", "file-per-thread-logger", diff --git a/README.md b/README.md index c4dfa4e0ba4..4ceb9ff0910 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,10 @@ You may run `polkadot-parachain` locally after building it or using one of the c ### Relay Chain Interaction To operate a parachain node, a connection to the corresponding relay chain is necessary. This can be -achieved in one of two ways: +achieved in one of three ways: 1. Run a full relay chain node within the parachain node (default) 2. Connect to an external relay chain node via WebSocket RPC +3. Run a light client for the relay chain #### In-process Relay Chain Node If an external relay chain node is not specified (default behavior), then a full relay chain node is @@ -55,7 +56,7 @@ This node has all of the typical components of a regular Polkadot node and will with the relay chain to work. ##### Example command -```shell= +```bash polkadot-parachain \ --chain parachain-chainspec.json \ --tmp \ @@ -76,7 +77,7 @@ they will use fewer system resources. relay chain node in-process. Even though they lack the majority of normal Polkadot subsystems, they will still need to connect directly to the relay chain network. ##### Example command -```shell= +```bash polkadot-parachain \ --chain parachain-chainspec.json \ --tmp \ @@ -87,6 +88,26 @@ polkadot-parachain \ --chain relaychain-chainspec.json ``` +#### Relay Chain Light Client +An internal relay chain light client provides a fast and lightweight approach for connecting to the relay chain network. +It provides relay chain notifications and facilitates runtime calls. + +To specify which chain the light client should connect to, users need to supply a relay chain chain-spec as part of the relay chain arguments. + +**Note:** At this time, any parachain nodes using this feature will still spawn a significantly cut-down +relay chain node in-process. Even though they lack the majority of normal Polkadot subsystems, they +will still need to connect directly to the relay chain network. + +##### Example command +```bash +polkadot-parachain \ + --chain parachain-chainspec.json \ + --tmp \ + --relay-chain-light-client \ + -- \ + --chain relaychain-chainspec.json +``` + ## Installation and Setup Before building Cumulus SDK based nodes / runtimes prepare your environment by following Substrate [installation instructions](https://docs.substrate.io/main-docs/install/). diff --git a/client/cli/src/lib.rs b/client/cli/src/lib.rs index 60d90e0299d..3ad68eb04ea 100644 --- a/client/cli/src/lib.rs +++ b/client/cli/src/lib.rs @@ -304,6 +304,11 @@ pub struct RunCmd { alias = "relay-chain-rpc-url" )] pub relay_chain_rpc_urls: Vec, + + /// EXPERIMENTAL: Embed a light client for the relay chain. Only supported for full-nodes. + /// Will use the specified relay chain chainspec. + #[arg(long, conflicts_with_all = ["relay_chain_rpc_urls", "collator"])] + pub relay_chain_light_client: bool, } impl RunCmd { @@ -319,15 +324,33 @@ impl RunCmd { /// Create [`CollatorOptions`] representing options only relevant to parachain collator nodes pub fn collator_options(&self) -> CollatorOptions { - CollatorOptions { relay_chain_rpc_urls: self.relay_chain_rpc_urls.clone() } + let relay_chain_mode = + match (self.relay_chain_light_client, !self.relay_chain_rpc_urls.is_empty()) { + (true, _) => RelayChainMode::LightClient, + (_, true) => RelayChainMode::ExternalRpc(self.relay_chain_rpc_urls.clone()), + _ => RelayChainMode::Embedded, + }; + + CollatorOptions { relay_chain_mode } } } +/// Possible modes for the relay chain to operate in. +#[derive(Clone, Debug)] +pub enum RelayChainMode { + /// Spawn embedded relay chain node + Embedded, + /// Connect to remote relay chain node via websocket RPC + ExternalRpc(Vec), + /// Spawn embedded relay chain light client + LightClient, +} + /// Options only relevant for collator nodes #[derive(Clone, Debug)] pub struct CollatorOptions { - /// Location of relay chain full node - pub relay_chain_rpc_urls: Vec, + /// How this collator retrieves relay chain information + pub relay_chain_mode: RelayChainMode, } /// A non-redundant version of the `RunCmd` that sets the `validator` field when the diff --git a/client/relay-chain-minimal-node/Cargo.toml b/client/relay-chain-minimal-node/Cargo.toml index 897a464137b..83b8832dee7 100644 --- a/client/relay-chain-minimal-node/Cargo.toml +++ b/client/relay-chain-minimal-node/Cargo.toml @@ -20,14 +20,12 @@ polkadot-node-core-runtime-api = { git = "https://github.com/paritytech/polkadot # substrate deps sc-authority-discovery = { git = "https://github.com/paritytech/substrate", branch = "master" } -sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" } sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" } sc-network-common = { git = "https://github.com/paritytech/substrate", branch = "master" } sc-service = { git = "https://github.com/paritytech/substrate", branch = "master" } sc-tracing = { git = "https://github.com/paritytech/substrate", branch = "master" } sc-utils = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-consensus-babe = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } @@ -42,7 +40,6 @@ lru = "0.11.0" tracing = "0.1.37" async-trait = "0.1.73" futures = "0.3.28" -tokio = { version = "1.32.0", features = ["macros"] } [features] network-protocol-staging = ["polkadot-node-network-protocol/network-protocol-staging"] diff --git a/client/relay-chain-minimal-node/src/lib.rs b/client/relay-chain-minimal-node/src/lib.rs index ed58dc81e11..8d022821908 100644 --- a/client/relay-chain-minimal-node/src/lib.rs +++ b/client/relay-chain-minimal-node/src/lib.rs @@ -17,7 +17,7 @@ use collator_overseer::{CollatorOverseerGenArgs, NewMinimalNode}; use cumulus_relay_chain_interface::{RelayChainError, RelayChainInterface, RelayChainResult}; -use cumulus_relay_chain_rpc_interface::{RelayChainRpcInterface, Url}; +use cumulus_relay_chain_rpc_interface::{RelayChainRpcClient, RelayChainRpcInterface, Url}; use network::build_collator_network; use polkadot_network_bridge::{peer_sets_info, IsAuthority}; use polkadot_node_network_protocol::{ @@ -38,13 +38,14 @@ use sp_runtime::{app_crypto::Pair, traits::Block as BlockT}; use futures::StreamExt; use std::sync::Arc; +mod blockchain_rpc_client; mod collator_overseer; - mod network; -mod blockchain_rpc_client; pub use blockchain_rpc_client::BlockChainRpcClient; +const LOG_TARGET: &str = "minimal-relaychain-node"; + fn build_authority_discovery_service( task_manager: &TaskManager, client: Arc, @@ -82,16 +83,11 @@ fn build_authority_discovery_service( service } -pub async fn build_minimal_relay_chain_node( +async fn build_interface( polkadot_config: Configuration, task_manager: &mut TaskManager, - relay_chain_url: Vec, + client: RelayChainRpcClient, ) -> RelayChainResult<(Arc<(dyn RelayChainInterface + 'static)>, Option)> { - let client = cumulus_relay_chain_rpc_interface::create_client_and_start_worker( - relay_chain_url, - task_manager, - ) - .await?; let collator_pair = CollatorPair::generate().0; let collator_node = new_minimal_relay_chain( polkadot_config, @@ -106,6 +102,44 @@ pub async fn build_minimal_relay_chain_node( )) } +pub async fn build_minimal_relay_chain_node_with_rpc( + polkadot_config: Configuration, + task_manager: &mut TaskManager, + relay_chain_url: Vec, +) -> RelayChainResult<(Arc<(dyn RelayChainInterface + 'static)>, Option)> { + let client = cumulus_relay_chain_rpc_interface::create_client_and_start_worker( + relay_chain_url, + task_manager, + ) + .await?; + + build_interface(polkadot_config, task_manager, client).await +} + +pub async fn build_minimal_relay_chain_node_light_client( + polkadot_config: Configuration, + task_manager: &mut TaskManager, +) -> RelayChainResult<(Arc<(dyn RelayChainInterface + 'static)>, Option)> { + tracing::info!( + target: LOG_TARGET, + chain_name = polkadot_config.chain_spec.name(), + chain_id = polkadot_config.chain_spec.id(), + "Initializing embedded light client with chain spec." + ); + + let spec = polkadot_config + .chain_spec + .as_json(false) + .map_err(RelayChainError::GenericError)?; + + let client = cumulus_relay_chain_rpc_interface::create_client_and_start_light_client_worker( + spec, + task_manager, + ) + .await?; + + build_interface(polkadot_config, task_manager, client).await +} /// Builds a minimal relay chain node. Chain data is fetched /// via [`BlockChainRpcClient`] and fed into the overseer and its subsystems. /// @@ -117,8 +151,6 @@ pub async fn build_minimal_relay_chain_node( /// - NetworkBridgeRx /// - NetworkBridgeTx /// - RuntimeApi -/// - ChainApi -/// - AvailabilityDistribution #[sc_tracing::logging::prefix_logs_with("Relaychain")] async fn new_minimal_relay_chain( config: Configuration, diff --git a/client/relay-chain-rpc-interface/Cargo.toml b/client/relay-chain-rpc-interface/Cargo.toml index 4df55cea2d8..9db12a35ac9 100644 --- a/client/relay-chain-rpc-interface/Cargo.toml +++ b/client/relay-chain-rpc-interface/Cargo.toml @@ -17,11 +17,13 @@ sp-consensus-babe = { git = "https://github.com/paritytech/substrate", branch = sp-authority-discovery = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-state-machine = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-storage = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" } sc-rpc-api = { git = "https://github.com/paritytech/substrate", branch = "master" } sc-service = { git = "https://github.com/paritytech/substrate", branch = "master" } tokio = { version = "1.32.0", features = ["sync"] } +tokio-util = { version = "0.7.8", features = ["compat"] } futures = "0.3.28" futures-timer = "3.0.2" @@ -33,3 +35,9 @@ url = "2.4.0" serde_json = "1.0.105" serde = "1.0.183" lru = "0.11.0" +smoldot = { version = "0.11.0", default_features = false, features = ["std"]} +smoldot-light = { version = "0.9.0", default_features = false, features = ["std"] } +either = "1.8.1" +thiserror = "1.0.38" +rand = "0.8.5" +pin-project = "1.1.3" diff --git a/client/relay-chain-rpc-interface/src/lib.rs b/client/relay-chain-rpc-interface/src/lib.rs index db01af3cdc0..c01f38433dc 100644 --- a/client/relay-chain-rpc-interface/src/lib.rs +++ b/client/relay-chain-rpc-interface/src/lib.rs @@ -38,9 +38,15 @@ use std::pin::Pin; use cumulus_primitives_core::relay_chain::BlockId; pub use url::Url; +mod light_client_worker; mod reconnecting_ws_client; mod rpc_client; -pub use rpc_client::{create_client_and_start_worker, RelayChainRpcClient}; +mod tokio_platform; + +pub use rpc_client::{ + create_client_and_start_light_client_worker, create_client_and_start_worker, + RelayChainRpcClient, +}; const TIMEOUT_IN_SECONDS: u64 = 6; diff --git a/client/relay-chain-rpc-interface/src/light_client_worker.rs b/client/relay-chain-rpc-interface/src/light_client_worker.rs new file mode 100644 index 00000000000..b6773870512 --- /dev/null +++ b/client/relay-chain-rpc-interface/src/light_client_worker.rs @@ -0,0 +1,297 @@ +// Copyright 2023 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +//! This module contains a backend that sends RPC requests to an +//! embedded light client. Even though no networking is involved, +//! we treat the light-client as a normal JsonRPC target. + +use futures::{channel::mpsc::Sender, prelude::*, stream::FuturesUnordered}; +use jsonrpsee::core::{ + client::{ + Client as JsonRpseeClient, ClientBuilder, ClientT, ReceivedMessage, TransportReceiverT, + TransportSenderT, + }, + Error, +}; +use smoldot_light::{ChainId, Client as SmoldotClient, JsonRpcResponses}; +use std::{num::NonZeroU32, sync::Arc}; +use tokio::sync::mpsc::{channel as tokio_channel, Receiver, Sender as TokioSender}; + +use cumulus_primitives_core::relay_chain::{ + Block as RelayBlock, BlockNumber as RelayNumber, Hash as RelayHash, Header as RelayHeader, +}; +use cumulus_relay_chain_interface::{RelayChainError, RelayChainResult}; + +use sp_runtime::generic::SignedBlock; + +use sc_rpc_api::chain::ChainApiClient; +use sc_service::SpawnTaskHandle; + +use crate::{rpc_client::RpcDispatcherMessage, tokio_platform::TokioPlatform}; + +const LOG_TARGET: &str = "rpc-light-client-worker"; +const MAX_PENDING_REQUESTS: u32 = 128; +const MAX_SUBSCRIPTIONS: u32 = 64; + +#[derive(thiserror::Error, Debug)] +enum LightClientError { + #[error("Error occured while executing smoldot request: {0}")] + SmoldotError(String), + #[error("Nothing returned from json_rpc_responses")] + EmptyResult, +} + +/// Sending adapter allowing JsonRpsee to send messages to smoldot +struct SimpleStringSender { + inner: SmoldotClient, + chain_id: ChainId, +} + +#[async_trait::async_trait] +impl TransportSenderT for SimpleStringSender { + type Error = LightClientError; + + async fn send(&mut self, msg: String) -> Result<(), Self::Error> { + self.inner + .json_rpc_request(msg, self.chain_id) + .map_err(|e| LightClientError::SmoldotError(e.to_string())) + } +} + +/// Receiving adapter allowing JsonRpsee to receive messages from smoldot +struct SimpleStringReceiver { + inner: JsonRpcResponses, +} + +#[async_trait::async_trait] +impl TransportReceiverT for SimpleStringReceiver { + type Error = LightClientError; + + async fn receive(&mut self) -> Result { + self.inner + .next() + .await + .map(|message| jsonrpsee::core::client::ReceivedMessage::Text(message)) + .ok_or(LightClientError::EmptyResult) + } +} + +/// Build a smoldot client and add the specified chain spec to it. +pub async fn build_smoldot_client( + spawner: SpawnTaskHandle, + chain_spec: &str, +) -> RelayChainResult<(SmoldotClient, ChainId, JsonRpcResponses)> { + let platform = TokioPlatform::new(spawner); + let mut client = SmoldotClient::new(platform); + + // Ask the client to connect to a chain. + let smoldot_light::AddChainSuccess { chain_id, json_rpc_responses } = client + .add_chain(smoldot_light::AddChainConfig { + specification: chain_spec, + json_rpc: smoldot_light::AddChainConfigJsonRpc::Enabled { + max_pending_requests: NonZeroU32::new(MAX_PENDING_REQUESTS) + .expect("Constant larger than 0; qed"), + max_subscriptions: MAX_SUBSCRIPTIONS, + }, + potential_relay_chains: core::iter::empty(), + database_content: "", + user_data: (), + }) + .map_err(|e| RelayChainError::GenericError(e.to_string()))?; + + Ok((client, chain_id, json_rpc_responses.expect("JSON RPC is enabled; qed"))) +} + +/// Worker to process incoming [`RpcDispatcherMessage`] requests. +/// On startup, this worker opens subscriptions for imported, best and finalized +/// heads. Incoming notifications are distributed to registered listeners. +pub struct LightClientRpcWorker { + client_receiver: Receiver, + imported_header_listeners: Vec>, + finalized_header_listeners: Vec>, + best_header_listeners: Vec>, + smoldot_client: Arc, +} + +fn handle_notification( + maybe_header: Option>, + senders: &mut Vec>, +) -> Result<(), ()> { + match maybe_header { + Some(Ok(header)) => { + crate::rpc_client::distribute_header(header, senders); + Ok(()) + }, + None => { + tracing::error!(target: LOG_TARGET, "Subscription closed."); + Err(()) + }, + Some(Err(error)) => { + tracing::error!(target: LOG_TARGET, ?error, "Error in RPC subscription."); + Err(()) + }, + } +} + +impl LightClientRpcWorker { + /// Create new light-client worker. + /// + /// Returns the worker itself and a channel to send messages. + pub fn new( + smoldot_client: smoldot_light::Client, + json_rpc_responses: JsonRpcResponses, + chain_id: ChainId, + ) -> (LightClientRpcWorker, TokioSender) { + let (tx, rx) = tokio_channel(100); + let smoldot_adapter_sender = SimpleStringSender { inner: smoldot_client, chain_id }; + let smoldot_adapter_receiver = SimpleStringReceiver { inner: json_rpc_responses }; + + // Build jsonrpsee client that will talk to the inprocess smoldot node + let smoldot_jsonrpsee_client = Arc::new( + ClientBuilder::default() + .build_with_tokio(smoldot_adapter_sender, smoldot_adapter_receiver), + ); + + let worker = LightClientRpcWorker { + client_receiver: rx, + imported_header_listeners: Default::default(), + finalized_header_listeners: Default::default(), + best_header_listeners: Default::default(), + smoldot_client: smoldot_jsonrpsee_client, + }; + (worker, tx) + } + + // Main worker loop. + // + // Does the following: + // 1. Initialize notification streams + // 2. Enter main loop + // a. On listening request, register listener for respective notification stream + // b. On incoming notification, distribute notification to listeners + // c. On incoming request, forward request to smoldot + // d. Advance execution of pending requests + pub async fn run(mut self) { + let mut pending_requests = FuturesUnordered::new(); + + let Ok(mut new_head_subscription) = , + >>::subscribe_new_heads(&self.smoldot_client) + .await + else { + tracing::error!( + target: LOG_TARGET, + "Unable to initialize new heads subscription" + ); + return + }; + + let Ok(mut finalized_head_subscription) = + , + >>::subscribe_finalized_heads(&self.smoldot_client) + .await + else { + tracing::error!( + target: LOG_TARGET, + "Unable to initialize finalized heads subscription" + ); + return + }; + + let Ok(mut all_head_subscription) = , + >>::subscribe_all_heads(&self.smoldot_client) + .await + else { + tracing::error!( + target: LOG_TARGET, + "Unable to initialize all heads subscription" + ); + return + }; + + loop { + tokio::select! { + evt = self.client_receiver.recv() => match evt { + Some(RpcDispatcherMessage::RegisterBestHeadListener(tx)) => { + self.best_header_listeners.push(tx); + }, + Some(RpcDispatcherMessage::RegisterImportListener(tx)) => { + self.imported_header_listeners.push(tx) + }, + Some(RpcDispatcherMessage::RegisterFinalizationListener(tx)) => { + self.finalized_header_listeners.push(tx) + }, + Some(RpcDispatcherMessage::Request(method, params, response_sender)) => { + let closure_client = self.smoldot_client.clone(); + tracing::debug!( + target: LOG_TARGET, + len = pending_requests.len(), + method, + "Request" + ); + pending_requests.push(async move { + let response = closure_client.request(&method, params).await; + tracing::debug!( + target: LOG_TARGET, + method, + ?response, + "Response" + ); + if let Err(err) = response_sender.send(response) { + tracing::debug!( + target: LOG_TARGET, + ?err, + "Recipient no longer interested in request result" + ); + }; + }); + }, + None => { + tracing::error!(target: LOG_TARGET, "RPC client receiver closed. Stopping RPC Worker."); + return; + } + }, + _ = pending_requests.next(), if !pending_requests.is_empty() => {}, + import_event = all_head_subscription.next() => { + if handle_notification(import_event, &mut self.imported_header_listeners).is_err() { + return + } + }, + best_header_event = new_head_subscription.next() => { + if handle_notification(best_header_event, &mut self.best_header_listeners).is_err() { + return + } + } + finalized_event = finalized_head_subscription.next() => { + if handle_notification(finalized_event, &mut self.finalized_header_listeners).is_err() { + return + } + } + } + } + } +} diff --git a/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs b/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs index 0869dace733..5add8a96ef1 100644 --- a/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs +++ b/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs @@ -15,53 +15,47 @@ // along with Cumulus. If not, see . use cumulus_primitives_core::relay_chain::{ - BlockNumber as RelayBlockNumber, Header as RelayHeader, + Block as RelayBlock, BlockNumber as RelayNumber, Hash as RelayHash, Header as RelayHeader, }; -use cumulus_relay_chain_interface::{RelayChainError, RelayChainResult}; use futures::{ - channel::{ - mpsc::{Receiver, Sender}, - oneshot::Sender as OneshotSender, - }, + channel::{mpsc::Sender, oneshot::Sender as OneshotSender}, future::BoxFuture, stream::FuturesUnordered, FutureExt, StreamExt, }; use jsonrpsee::{ core::{ - client::{Client as JsonRpcClient, ClientT, Subscription, SubscriptionClientT}, + client::{Client as JsonRpcClient, ClientT, Subscription}, params::ArrayParams, Error as JsonRpseeError, JsonValue, }, - rpc_params, ws_client::WsClientBuilder, }; use lru::LruCache; -use sc_service::TaskManager; +use sc_rpc_api::chain::ChainApiClient; +use sp_runtime::generic::SignedBlock; use std::{num::NonZeroUsize, sync::Arc}; use tokio::sync::mpsc::{ channel as tokio_channel, Receiver as TokioReceiver, Sender as TokioSender, }; use url::Url; -const NOTIFICATION_CHANNEL_SIZE_LIMIT: usize = 20; +use crate::rpc_client::{distribute_header, RpcDispatcherMessage}; + const LOG_TARGET: &str = "reconnecting-websocket-client"; -/// Messages for communication between [`ReconnectingWsClient`] and [`ReconnectingWebsocketWorker`]. -#[derive(Debug)] -enum RpcDispatcherMessage { - RegisterBestHeadListener(Sender), - RegisterImportListener(Sender), - RegisterFinalizationListener(Sender), - Request(String, ArrayParams, OneshotSender>), -} +/// Worker that should be used in combination with [`RelayChainRpcClient`]. +/// +/// Must be polled to distribute header notifications to listeners. +pub struct ReconnectingWebsocketWorker { + ws_urls: Vec, + /// Communication channel with the RPC client + client_receiver: TokioReceiver, -/// Frontend for performing websocket requests. -/// Requests and stream requests are forwarded to [`ReconnectingWebsocketWorker`]. -#[derive(Debug, Clone)] -pub struct ReconnectingWsClient { - /// Channel to communicate with the RPC worker - to_worker_channel: TokioSender, + /// Senders to distribute incoming header notifications to + imported_header_listeners: Vec>, + finalized_header_listeners: Vec>, + best_header_listeners: Vec>, } /// Format url and force addition of a port @@ -83,115 +77,6 @@ fn url_to_string_with_port(url: Url) -> Option { )) } -impl ReconnectingWsClient { - /// Create a new websocket client frontend. - pub async fn new(urls: Vec, task_manager: &mut TaskManager) -> RelayChainResult { - tracing::debug!(target: LOG_TARGET, "Instantiating reconnecting websocket client"); - - let (worker, sender) = ReconnectingWebsocketWorker::new(urls).await; - - task_manager - .spawn_essential_handle() - .spawn("relay-chain-rpc-worker", None, worker.run()); - - Ok(Self { to_worker_channel: sender }) - } -} - -impl ReconnectingWsClient { - /// Perform a request via websocket connection. - pub async fn request(&self, method: &str, params: ArrayParams) -> Result - where - R: serde::de::DeserializeOwned, - { - let (tx, rx) = futures::channel::oneshot::channel(); - - let message = RpcDispatcherMessage::Request(method.into(), params, tx); - self.to_worker_channel.send(message).await.map_err(|err| { - RelayChainError::WorkerCommunicationError(format!( - "Unable to send message to RPC worker: {}", - err - )) - })?; - - let value = rx.await.map_err(|err| { - RelayChainError::WorkerCommunicationError(format!( - "Unexpected channel close on RPC worker side: {}", - err - )) - })??; - - serde_json::from_value(value) - .map_err(|_| RelayChainError::GenericError("Unable to deserialize value".to_string())) - } - - /// Get a stream of new best relay chain headers - pub fn get_best_heads_stream(&self) -> Result, RelayChainError> { - let (tx, rx) = - futures::channel::mpsc::channel::(NOTIFICATION_CHANNEL_SIZE_LIMIT); - self.send_register_message_to_worker(RpcDispatcherMessage::RegisterBestHeadListener(tx))?; - Ok(rx) - } - - /// Get a stream of finalized relay chain headers - pub fn get_finalized_heads_stream(&self) -> Result, RelayChainError> { - let (tx, rx) = - futures::channel::mpsc::channel::(NOTIFICATION_CHANNEL_SIZE_LIMIT); - self.send_register_message_to_worker(RpcDispatcherMessage::RegisterFinalizationListener( - tx, - ))?; - Ok(rx) - } - - /// Get a stream of all imported relay chain headers - pub fn get_imported_heads_stream(&self) -> Result, RelayChainError> { - let (tx, rx) = - futures::channel::mpsc::channel::(NOTIFICATION_CHANNEL_SIZE_LIMIT); - self.send_register_message_to_worker(RpcDispatcherMessage::RegisterImportListener(tx))?; - Ok(rx) - } - - fn send_register_message_to_worker( - &self, - message: RpcDispatcherMessage, - ) -> Result<(), RelayChainError> { - self.to_worker_channel - .try_send(message) - .map_err(|e| RelayChainError::WorkerCommunicationError(e.to_string())) - } -} - -/// Worker that should be used in combination with [`crate::RelayChainRpcClient`]. -/// -/// Must be polled to distribute header notifications to listeners. -struct ReconnectingWebsocketWorker { - ws_urls: Vec, - /// Communication channel with the RPC client - client_receiver: TokioReceiver, - - /// Senders to distribute incoming header notifications to - imported_header_listeners: Vec>, - finalized_header_listeners: Vec>, - best_header_listeners: Vec>, -} - -fn distribute_header(header: RelayHeader, senders: &mut Vec>) { - senders.retain_mut(|e| { - match e.try_send(header.clone()) { - // Receiver has been dropped, remove Sender from list. - Err(error) if error.is_disconnected() => false, - // Channel is full. This should not happen. - // TODO: Improve error handling here - // https://github.com/paritytech/cumulus/issues/1482 - Err(error) => { - tracing::error!(target: LOG_TARGET, ?error, "Event distribution channel has reached its limit. This can lead to missed notifications."); - true - }, - _ => true, - } - }); -} - /// Manages the active websocket client. /// Responsible for creating request futures, subscription streams /// and reconnections. @@ -248,54 +133,53 @@ impl ClientManager { } async fn get_subscriptions(&self) -> Result { - let import_subscription = self - .active_client - .subscribe::( - "chain_subscribeAllHeads", - rpc_params![], - "chain_unsubscribeAllHeads", - ) - .await - .map_err(|e| { - tracing::error!( - target: LOG_TARGET, - ?e, - "Unable to open `chain_subscribeAllHeads` subscription." - ); - e - })?; - let best_subscription = self - .active_client - .subscribe::( - "chain_subscribeNewHeads", - rpc_params![], - "chain_unsubscribeNewHeads", - ) - .await - .map_err(|e| { - tracing::error!( - target: LOG_TARGET, - ?e, - "Unable to open `chain_subscribeNewHeads` subscription." - ); - e - })?; - let finalized_subscription = self - .active_client - .subscribe::( - "chain_subscribeFinalizedHeads", - rpc_params![], - "chain_unsubscribeFinalizedHeads", - ) - .await - .map_err(|e| { - tracing::error!( - target: LOG_TARGET, - ?e, - "Unable to open `chain_subscribeFinalizedHeads` subscription." - ); - e - })?; + let import_subscription = , + >>::subscribe_all_heads(&self.active_client) + .await + .map_err(|e| { + tracing::error!( + target: LOG_TARGET, + ?e, + "Unable to open `chain_subscribeAllHeads` subscription." + ); + e + })?; + + let best_subscription = , + >>::subscribe_new_heads(&self.active_client) + .await + .map_err(|e| { + tracing::error!( + target: LOG_TARGET, + ?e, + "Unable to open `chain_subscribeNewHeads` subscription." + ); + e + })?; + + let finalized_subscription = , + >>::subscribe_finalized_heads(&self.active_client) + .await + .map_err(|e| { + tracing::error!( + target: LOG_TARGET, + ?e, + "Unable to open `chain_subscribeFinalizedHeads` subscription." + ); + e + })?; Ok(RelayChainSubscriptions { import_subscription, @@ -344,7 +228,7 @@ enum ConnectionStatus { impl ReconnectingWebsocketWorker { /// Create new worker. Returns the worker and a channel to register new listeners. - async fn new( + pub async fn new( urls: Vec, ) -> (ReconnectingWebsocketWorker, TokioSender) { let urls = urls.into_iter().filter_map(url_to_string_with_port).collect(); @@ -410,7 +294,7 @@ impl ReconnectingWebsocketWorker { /// the sender from the list. /// - Find a new valid RPC server to connect to in case the websocket connection is terminated. /// If the worker is not able to connec to an RPC server from the list, the worker shuts down. - async fn run(mut self) { + pub async fn run(mut self) { let mut pending_requests = FuturesUnordered::new(); let urls = std::mem::take(&mut self.ws_urls); @@ -426,7 +310,7 @@ impl ReconnectingWebsocketWorker { let mut imported_blocks_cache = LruCache::new(NonZeroUsize::new(40).expect("40 is nonzero; qed.")); let mut should_reconnect = ConnectionStatus::Connected; - let mut last_seen_finalized_num: RelayBlockNumber = 0; + let mut last_seen_finalized_num: RelayNumber = 0; loop { // This branch is taken if the websocket connection to the current RPC server is closed. if let ConnectionStatus::ReconnectRequired(maybe_failed_request) = should_reconnect { diff --git a/client/relay-chain-rpc-interface/src/rpc_client.rs b/client/relay-chain-rpc-interface/src/rpc_client.rs index 863b70db862..b079294b784 100644 --- a/client/relay-chain-rpc-interface/src/rpc_client.rs +++ b/client/relay-chain-rpc-interface/src/rpc_client.rs @@ -14,19 +14,19 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use futures::channel::mpsc::Receiver; -use jsonrpsee::{core::params::ArrayParams, rpc_params}; -use parity_scale_codec::{Decode, Encode}; +use futures::channel::{ + mpsc::{Receiver, Sender}, + oneshot::Sender as OneshotSender, +}; +use jsonrpsee::{ + core::{params::ArrayParams, Error as JsonRpseeError}, + rpc_params, +}; use serde::de::DeserializeOwned; -pub use url::Url; +use serde_json::Value as JsonValue; +use tokio::sync::mpsc::Sender as TokioSender; -use sc_client_api::StorageData; -use sc_rpc_api::{state::ReadProof, system::Health}; -use sc_service::TaskManager; -use sp_api::RuntimeVersion; -use sp_consensus_babe::Epoch; -use sp_core::sp_std::collections::btree_map::BTreeMap; -use sp_storage::StorageKey; +use parity_scale_codec::{Decode, Encode}; use cumulus_primitives_core::{ relay_chain::{ @@ -42,35 +42,96 @@ use cumulus_primitives_core::{ }; use cumulus_relay_chain_interface::{RelayChainError, RelayChainResult}; -use crate::reconnecting_ws_client::ReconnectingWsClient; +use sc_client_api::StorageData; +use sc_rpc_api::{state::ReadProof, system::Health}; +use sc_service::TaskManager; +use sp_api::RuntimeVersion; +use sp_consensus_babe::Epoch; +use sp_core::sp_std::collections::btree_map::BTreeMap; +use sp_storage::StorageKey; -const LOG_TARGET: &str = "relay-chain-rpc-client"; +use crate::{ + light_client_worker::{build_smoldot_client, LightClientRpcWorker}, + reconnecting_ws_client::ReconnectingWebsocketWorker, +}; +pub use url::Url; -/// Client that maps RPC methods and deserializes results -#[derive(Clone)] -pub struct RelayChainRpcClient { - /// Websocket client to make calls - ws_client: ReconnectingWsClient, +const LOG_TARGET: &str = "relay-chain-rpc-client"; +const NOTIFICATION_CHANNEL_SIZE_LIMIT: usize = 20; + +/// Messages for communication between [`RelayChainRpcClient`] and the RPC workers. +#[derive(Debug)] +pub enum RpcDispatcherMessage { + /// Register new listener for the best headers stream. Contains a sender which will be used + /// to send incoming headers. + RegisterBestHeadListener(Sender), + + /// Register new listener for the import headers stream. Contains a sender which will be used + /// to send incoming headers. + RegisterImportListener(Sender), + + /// Register new listener for the finalized headers stream. Contains a sender which will be + /// used to send incoming headers. + RegisterFinalizationListener(Sender), + + /// Register new listener for the finalized headers stream. + /// Contains the following: + /// - [`String`] representing the RPC method to be called + /// - [`ArrayParams`] for the parameters to the RPC call + /// - [`OneshotSender`] for the return value of the request + Request(String, ArrayParams, OneshotSender>), } -/// Entry point to create [`RelayChainRpcClient`] and start a worker that distributes notifications. +/// Entry point to create [`RelayChainRpcClient`] and start a worker that communicates +/// to JsonRPC servers over the network. pub async fn create_client_and_start_worker( urls: Vec, task_manager: &mut TaskManager, ) -> RelayChainResult { - let ws_client = ReconnectingWsClient::new(urls, task_manager).await?; + let (worker, sender) = ReconnectingWebsocketWorker::new(urls).await; + + task_manager + .spawn_essential_handle() + .spawn("relay-chain-rpc-worker", None, worker.run()); + + let client = RelayChainRpcClient::new(sender); + + Ok(client) +} + +/// Entry point to create [`RelayChainRpcClient`] and start a worker that communicates +/// with an embedded smoldot instance. +pub async fn create_client_and_start_light_client_worker( + chain_spec: String, + task_manager: &mut TaskManager, +) -> RelayChainResult { + let (client, chain_id, json_rpc_responses) = + build_smoldot_client(task_manager.spawn_handle(), &chain_spec).await?; + let (worker, sender) = LightClientRpcWorker::new(client, json_rpc_responses, chain_id); + + task_manager + .spawn_essential_handle() + .spawn("relay-light-client-worker", None, worker.run()); - let client = RelayChainRpcClient::new(ws_client).await?; + let client = RelayChainRpcClient::new(sender); Ok(client) } +/// Client that maps RPC methods and deserializes results +#[derive(Clone)] +pub struct RelayChainRpcClient { + /// Sender to send messages to the worker. + worker_channel: TokioSender, +} + impl RelayChainRpcClient { /// Initialize new RPC Client. - async fn new(ws_client: ReconnectingWsClient) -> RelayChainResult { - let client = RelayChainRpcClient { ws_client }; - - Ok(client) + /// + /// This client expects a channel connected to a worker that processes + /// requests sent via this channel. + pub(crate) fn new(worker_channel: TokioSender) -> Self { + RelayChainRpcClient { worker_channel } } /// Call a call to `state_call` rpc method. @@ -129,8 +190,25 @@ impl RelayChainRpcClient { R: DeserializeOwned + std::fmt::Debug, OR: Fn(&RelayChainError), { - self.ws_client.request(method, params).await.map_err(|err| { - trace_error(&err); + let (tx, rx) = futures::channel::oneshot::channel(); + + let message = RpcDispatcherMessage::Request(method.into(), params, tx); + self.worker_channel.send(message).await.map_err(|err| { + RelayChainError::WorkerCommunicationError(format!( + "Unable to send message to RPC worker: {}", + err + )) + })?; + + let value = rx.await.map_err(|err| { + RelayChainError::WorkerCommunicationError(format!( + "Unexpected channel close on RPC worker side: {}", + err + )) + })??; + + serde_json::from_value(value).map_err(|_| { + trace_error(&RelayChainError::GenericError("Unable to deserialize value".to_string())); RelayChainError::RpcCallError(method.to_string()) }) } @@ -537,18 +615,57 @@ impl RelayChainRpcClient { .await } + fn send_register_message_to_worker( + &self, + message: RpcDispatcherMessage, + ) -> Result<(), RelayChainError> { + self.worker_channel + .try_send(message) + .map_err(|e| RelayChainError::WorkerCommunicationError(e.to_string())) + } + /// Get a stream of all imported relay chain headers pub fn get_imported_heads_stream(&self) -> Result, RelayChainError> { - self.ws_client.get_imported_heads_stream() + let (tx, rx) = + futures::channel::mpsc::channel::(NOTIFICATION_CHANNEL_SIZE_LIMIT); + self.send_register_message_to_worker(RpcDispatcherMessage::RegisterImportListener(tx))?; + Ok(rx) } /// Get a stream of new best relay chain headers pub fn get_best_heads_stream(&self) -> Result, RelayChainError> { - self.ws_client.get_best_heads_stream() + let (tx, rx) = + futures::channel::mpsc::channel::(NOTIFICATION_CHANNEL_SIZE_LIMIT); + self.send_register_message_to_worker(RpcDispatcherMessage::RegisterBestHeadListener(tx))?; + Ok(rx) } /// Get a stream of finalized relay chain headers pub fn get_finalized_heads_stream(&self) -> Result, RelayChainError> { - self.ws_client.get_finalized_heads_stream() + let (tx, rx) = + futures::channel::mpsc::channel::(NOTIFICATION_CHANNEL_SIZE_LIMIT); + self.send_register_message_to_worker(RpcDispatcherMessage::RegisterFinalizationListener( + tx, + ))?; + Ok(rx) } } + +/// Send `header` through all channels contained in `senders`. +/// If no one is listening to the sender, it is removed from the vector. +pub fn distribute_header(header: RelayHeader, senders: &mut Vec>) { + senders.retain_mut(|e| { + match e.try_send(header.clone()) { + // Receiver has been dropped, remove Sender from list. + Err(error) if error.is_disconnected() => false, + // Channel is full. This should not happen. + // TODO: Improve error handling here + // https://github.com/paritytech/cumulus/issues/1482 + Err(error) => { + tracing::error!(target: LOG_TARGET, ?error, "Event distribution channel has reached its limit. This can lead to missed notifications."); + true + }, + _ => true, + } + }); +} diff --git a/client/relay-chain-rpc-interface/src/tokio_platform.rs b/client/relay-chain-rpc-interface/src/tokio_platform.rs new file mode 100644 index 00000000000..7b8c69645b6 --- /dev/null +++ b/client/relay-chain-rpc-interface/src/tokio_platform.rs @@ -0,0 +1,211 @@ +// Copyright 2023 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +use core::time::Duration; +use futures::prelude::*; +use sc_service::SpawnTaskHandle; +use smoldot::libp2p::{websocket, with_buffers}; +use smoldot_light::platform::{ + Address, ConnectError, ConnectionType, IpAddr, MultiStreamWebRtcConnection, PlatformRef, + SubstreamDirection, +}; +use std::{net::SocketAddr, pin::Pin, time::Instant}; +use tokio::net::TcpStream; + +use tokio_util::compat::{Compat, TokioAsyncReadCompatExt}; +type CompatTcpStream = Compat; + +/// Platform implementation for tokio +/// This implementation is a port of the implementation for smol: +/// https://github.com/smol-dot/smoldot/blob/8c577b4a753fe96190f813070564ecc742b91a16/light-base/src/platform/default.rs +#[derive(Clone)] +pub struct TokioPlatform { + spawner: SpawnTaskHandle, +} + +impl TokioPlatform { + pub fn new(spawner: SpawnTaskHandle) -> Self { + TokioPlatform { spawner } + } +} + +impl PlatformRef for TokioPlatform { + type Delay = future::BoxFuture<'static, ()>; + type Instant = Instant; + type MultiStream = std::convert::Infallible; + type Stream = Stream; + type StreamConnectFuture = future::BoxFuture<'static, Result>; + type MultiStreamConnectFuture = future::BoxFuture< + 'static, + Result, ConnectError>, + >; + type ReadWriteAccess<'a> = with_buffers::ReadWriteAccess<'a>; + type StreamUpdateFuture<'a> = future::BoxFuture<'a, ()>; + type StreamErrorRef<'a> = &'a std::io::Error; + type NextSubstreamFuture<'a> = future::Pending>; + + fn now_from_unix_epoch(&self) -> Duration { + // Intentionally panic if the time is configured earlier than the UNIX EPOCH. + std::time::UNIX_EPOCH.elapsed().unwrap() + } + + fn now(&self) -> Self::Instant { + Instant::now() + } + + fn fill_random_bytes(&self, buffer: &mut [u8]) { + rand::RngCore::fill_bytes(&mut rand::thread_rng(), buffer); + } + + fn sleep(&self, duration: Duration) -> Self::Delay { + tokio::time::sleep(duration).boxed() + } + + fn sleep_until(&self, when: Self::Instant) -> Self::Delay { + let duration = when.saturating_duration_since(Instant::now()); + self.sleep(duration) + } + + fn supports_connection_type(&self, connection_type: ConnectionType) -> bool { + matches!( + connection_type, + ConnectionType::TcpIpv4 | + ConnectionType::TcpIpv6 | + ConnectionType::TcpDns | + ConnectionType::WebSocketIpv4 { .. } | + ConnectionType::WebSocketIpv6 { .. } | + ConnectionType::WebSocketDns { secure: false, .. } + ) + } + + fn connect_stream(&self, multiaddr: Address) -> Self::StreamConnectFuture { + let (tcp_socket_addr, host_if_websocket): ( + either::Either, + Option, + ) = match multiaddr { + Address::TcpDns { hostname, port } => + (either::Right((hostname.to_string(), port)), None), + Address::TcpIp { ip: IpAddr::V4(ip), port } => + (either::Left(SocketAddr::from((ip, port))), None), + Address::TcpIp { ip: IpAddr::V6(ip), port } => + (either::Left(SocketAddr::from((ip, port))), None), + Address::WebSocketDns { hostname, port, secure: false } => ( + either::Right((hostname.to_string(), port)), + Some(format!("{}:{}", hostname, port)), + ), + Address::WebSocketIp { ip: IpAddr::V4(ip), port } => { + let addr = SocketAddr::from((ip, port)); + (either::Left(addr), Some(addr.to_string())) + }, + Address::WebSocketIp { ip: IpAddr::V6(ip), port } => { + let addr = SocketAddr::from((ip, port)); + (either::Left(addr), Some(addr.to_string())) + }, + + // The API user of the `PlatformRef` trait is never supposed to open connections of + // a type that isn't supported. + _ => unreachable!(), + }; + + Box::pin(async move { + let tcp_socket = match tcp_socket_addr { + either::Left(socket_addr) => TcpStream::connect(socket_addr).await, + either::Right((dns, port)) => TcpStream::connect((&dns[..], port)).await, + }; + + if let Ok(tcp_socket) = &tcp_socket { + let _ = tcp_socket.set_nodelay(true); + } + + let socket: TcpOrWs = match (tcp_socket, host_if_websocket) { + (Ok(tcp_socket), Some(host)) => future::Either::Right( + websocket::websocket_client_handshake(websocket::Config { + tcp_socket: tcp_socket.compat(), + host: &host, + url: "/", + }) + .await + .map_err(|err| ConnectError { + message: format!("Failed to negotiate WebSocket: {err}"), + })?, + ), + (Ok(tcp_socket), None) => future::Either::Left(tcp_socket.compat()), + (Err(err), _) => + return Err(ConnectError { message: format!("Failed to reach peer: {err}") }), + }; + + Ok(Stream(with_buffers::WithBuffers::new(socket))) + }) + } + + fn open_out_substream(&self, _c: &mut Self::MultiStream) { + // This function can only be called with so-called "multi-stream" connections. We never + // open such connection. + } + + fn next_substream<'a>(&self, c: &'a mut Self::MultiStream) -> Self::NextSubstreamFuture<'a> { + // This function can only be called with so-called "multi-stream" connections. We never + // open such connection. + match *c {} + } + + fn spawn_task( + &self, + _: std::borrow::Cow, + task: impl Future + Send + 'static, + ) { + self.spawner.spawn("cumulus-internal-light-client-task", None, task) + } + + fn client_name(&self) -> std::borrow::Cow { + "cumulus-relay-chain-light-client".into() + } + + fn client_version(&self) -> std::borrow::Cow { + env!("CARGO_PKG_VERSION").into() + } + + fn connect_multistream( + &self, + _address: smoldot_light::platform::MultiStreamAddress, + ) -> Self::MultiStreamConnectFuture { + unimplemented!("Multistream not supported!") + } + + fn read_write_access<'a>( + &self, + stream: Pin<&'a mut Self::Stream>, + ) -> Result, &'a std::io::Error> { + let stream = stream.project(); + stream.0.read_write_access(Instant::now()) + } + + fn wait_read_write_again<'a>( + &self, + stream: Pin<&'a mut Self::Stream>, + ) -> Self::StreamUpdateFuture<'a> { + let stream = stream.project(); + Box::pin(stream.0.wait_read_write_again(|when| async move { + tokio::time::sleep_until(when.into()).await; + })) + } +} + +type TcpOrWs = future::Either>; + +/// Implementation detail of [`TokioPlatform`]. +#[pin_project::pin_project] +pub struct Stream(#[pin] with_buffers::WithBuffers); diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index f888df1252f..11b6240eb73 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -25,7 +25,9 @@ use cumulus_client_pov_recovery::{PoVRecovery, RecoveryDelayRange, RecoveryHandl use cumulus_primitives_core::{CollectCollationInfo, ParaId}; use cumulus_relay_chain_inprocess_interface::build_inprocess_relay_chain; use cumulus_relay_chain_interface::{RelayChainInterface, RelayChainResult}; -use cumulus_relay_chain_minimal_node::build_minimal_relay_chain_node; +use cumulus_relay_chain_minimal_node::{ + build_minimal_relay_chain_node_light_client, build_minimal_relay_chain_node_with_rpc, +}; use futures::{ channel::{mpsc, oneshot}, FutureExt, StreamExt, @@ -342,28 +344,30 @@ pub fn prepare_node_config(mut parachain_config: Configuration) -> Configuration /// Will return a minimal relay chain node with RPC /// client or an inprocess node, based on the [`CollatorOptions`] passed in. pub async fn build_relay_chain_interface( - polkadot_config: Configuration, + relay_chain_config: Configuration, parachain_config: &Configuration, telemetry_worker_handle: Option, task_manager: &mut TaskManager, collator_options: CollatorOptions, hwbench: Option, ) -> RelayChainResult<(Arc<(dyn RelayChainInterface + 'static)>, Option)> { - if !collator_options.relay_chain_rpc_urls.is_empty() { - build_minimal_relay_chain_node( - polkadot_config, - task_manager, - collator_options.relay_chain_rpc_urls, - ) - .await - } else { - build_inprocess_relay_chain( - polkadot_config, + match collator_options.relay_chain_mode { + cumulus_client_cli::RelayChainMode::Embedded => build_inprocess_relay_chain( + relay_chain_config, parachain_config, telemetry_worker_handle, task_manager, hwbench, - ) + ), + cumulus_client_cli::RelayChainMode::ExternalRpc(rpc_target_urls) => + build_minimal_relay_chain_node_with_rpc( + relay_chain_config, + task_manager, + rpc_target_urls, + ) + .await, + cumulus_client_cli::RelayChainMode::LightClient => + build_minimal_relay_chain_node_light_client(relay_chain_config, task_manager).await, } } diff --git a/parachain-template/node/src/command.rs b/parachain-template/node/src/command.rs index 46c57aa2c67..5ba88126fcd 100644 --- a/parachain-template/node/src/command.rs +++ b/parachain-template/node/src/command.rs @@ -2,7 +2,7 @@ use std::net::SocketAddr; use cumulus_primitives_core::ParaId; use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE}; -use log::{info, warn}; +use log::info; use parachain_template_runtime::Block; use sc_cli::{ ChainSpec, CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams, @@ -278,16 +278,6 @@ pub fn run() -> Result<()> { info!("Parachain Account: {parachain_account}"); info!("Is collating: {}", if config.role.is_authority() { "yes" } else { "no" }); - if !collator_options.relay_chain_rpc_urls.is_empty() && - !cli.relay_chain_args.is_empty() - { - warn!( - "Detected relay chain node arguments together with --relay-chain-rpc-url. \ - This command starts a minimal Polkadot node that only uses a \ - network-related subset of all relay chain CLI options." - ); - } - crate::service::start_parachain_node( config, polkadot_config, diff --git a/polkadot-parachain/src/command.rs b/polkadot-parachain/src/command.rs index 42ae887db6e..a502b40fe6c 100644 --- a/polkadot-parachain/src/command.rs +++ b/polkadot-parachain/src/command.rs @@ -21,7 +21,7 @@ use crate::{ }; use cumulus_primitives_core::ParaId; use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE}; -use log::{info, warn}; +use log::info; use parachains_common::{AssetHubPolkadotAuraId, AuraId}; use sc_cli::{ ChainSpec, CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams, @@ -821,14 +821,6 @@ pub fn run() -> Result<()> { info!("Parachain Account: {}", parachain_account); info!("Is collating: {}", if config.role.is_authority() { "yes" } else { "no" }); - if !collator_options.relay_chain_rpc_urls.is_empty() && !cli.relaychain_args.is_empty() { - warn!( - "Detected relay chain node arguments together with --relay-chain-rpc-url. \ - This command starts a minimal Polkadot node that only uses a \ - network-related subset of all relay chain CLI options." - ); - } - match config.chain_spec.runtime() { Runtime::AssetHubPolkadot => crate::service::start_generic_aura_node::< asset_hub_polkadot_runtime::RuntimeApi, diff --git a/test/service/src/lib.rs b/test/service/src/lib.rs index c2b981e385d..7dcab7c5076 100644 --- a/test/service/src/lib.rs +++ b/test/service/src/lib.rs @@ -34,7 +34,7 @@ use std::{ use url::Url; use crate::runtime::Weight; -use cumulus_client_cli::CollatorOptions; +use cumulus_client_cli::{CollatorOptions, RelayChainMode}; use cumulus_client_consensus_common::{ ParachainBlockImport as TParachainBlockImport, ParachainCandidate, ParachainConsensus, }; @@ -48,7 +48,9 @@ use cumulus_client_service::{ use cumulus_primitives_core::ParaId; use cumulus_relay_chain_inprocess_interface::RelayChainInProcessInterface; use cumulus_relay_chain_interface::{RelayChainError, RelayChainInterface, RelayChainResult}; -use cumulus_relay_chain_minimal_node::build_minimal_relay_chain_node; +use cumulus_relay_chain_minimal_node::{ + build_minimal_relay_chain_node_light_client, build_minimal_relay_chain_node_with_rpc, +}; use cumulus_test_runtime::{Hash, Header, NodeBlock as Block, RuntimeApi}; @@ -249,26 +251,30 @@ async fn build_relay_chain_interface( collator_options: CollatorOptions, task_manager: &mut TaskManager, ) -> RelayChainResult> { - if !collator_options.relay_chain_rpc_urls.is_empty() { - return build_minimal_relay_chain_node( + let relay_chain_full_node = match collator_options.relay_chain_mode { + cumulus_client_cli::RelayChainMode::Embedded => polkadot_test_service::new_full( relay_chain_config, - task_manager, - collator_options.relay_chain_rpc_urls, + if let Some(ref key) = collator_key { + polkadot_service::IsParachainNode::Collator(key.clone()) + } else { + polkadot_service::IsParachainNode::Collator(CollatorPair::generate().0) + }, + None, ) - .await - .map(|r| r.0) - } - - let relay_chain_full_node = polkadot_test_service::new_full( - relay_chain_config, - if let Some(ref key) = collator_key { - polkadot_service::IsParachainNode::Collator(key.clone()) - } else { - polkadot_service::IsParachainNode::Collator(CollatorPair::generate().0) - }, - None, - ) - .map_err(|e| RelayChainError::Application(Box::new(e) as Box<_>))?; + .map_err(|e| RelayChainError::Application(Box::new(e) as Box<_>))?, + cumulus_client_cli::RelayChainMode::ExternalRpc(rpc_target_urls) => + return build_minimal_relay_chain_node_with_rpc( + relay_chain_config, + task_manager, + rpc_target_urls, + ) + .await + .map(|r| r.0), + cumulus_client_cli::RelayChainMode::LightClient => + return build_minimal_relay_chain_node_light_client(relay_chain_config, task_manager) + .await + .map(|r| r.0), + }; task_manager.add_child(relay_chain_full_node.task_manager); tracing::info!("Using inprocess node."); @@ -505,7 +511,7 @@ pub struct TestNodeBuilder { storage_update_func_parachain: Option>, storage_update_func_relay_chain: Option>, consensus: Consensus, - relay_chain_full_node_url: Vec, + relay_chain_mode: RelayChainMode, endowed_accounts: Vec, } @@ -529,8 +535,8 @@ impl TestNodeBuilder { storage_update_func_parachain: None, storage_update_func_relay_chain: None, consensus: Consensus::RelayChain, - relay_chain_full_node_url: vec![], endowed_accounts: Default::default(), + relay_chain_mode: RelayChainMode::Embedded, } } @@ -624,7 +630,7 @@ impl TestNodeBuilder { /// Connect to full node via RPC. pub fn use_external_relay_chain_node_at_url(mut self, network_address: Url) -> Self { - self.relay_chain_full_node_url = vec![network_address]; + self.relay_chain_mode = RelayChainMode::ExternalRpc(vec![network_address]); self } @@ -633,7 +639,7 @@ impl TestNodeBuilder { let mut localhost_url = Url::parse("ws://localhost").expect("Should be able to parse localhost Url"); localhost_url.set_port(Some(port)).expect("Should be able to set port"); - self.relay_chain_full_node_url = vec![localhost_url]; + self.relay_chain_mode = RelayChainMode::ExternalRpc(vec![localhost_url]); self } @@ -665,8 +671,7 @@ impl TestNodeBuilder { false, ); - let collator_options = - CollatorOptions { relay_chain_rpc_urls: self.relay_chain_full_node_url }; + let collator_options = CollatorOptions { relay_chain_mode: self.relay_chain_mode }; relay_chain_config.network.node_name = format!("{} (relay chain)", relay_chain_config.network.node_name); diff --git a/zombienet/tests/0002-pov_recovery.toml b/zombienet/tests/0002-pov_recovery.toml index 0df0293e348..7c74a74a750 100644 --- a/zombienet/tests/0002-pov_recovery.toml +++ b/zombienet/tests/0002-pov_recovery.toml @@ -33,8 +33,8 @@ add_to_genesis = false command = "test-parachain" args = ["--disable-block-announcements"] - # run alice as a parachain collator who does not produce blocks - # alice is a bootnode for bob and charlie + # run 'alice' as a parachain collator who does not produce blocks + # 'alice' is a bootnode for 'bob' and 'charlie' [[parachains.collators]] name = "alice" validator = true # collator @@ -42,7 +42,7 @@ add_to_genesis = false command = "test-parachain" args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--use-null-consensus", "--disable-block-announcements", "--bootnodes {{'bob'|zombie('multiAddress')}}", "--", "--reserved-only", "--reserved-nodes {{'ferdie'|zombie('multiAddress')}}"] - # run eve as a parachain full node + # run 'charlie' as a parachain full node [[parachains.collators]] name = "charlie" validator = false # full node @@ -50,7 +50,7 @@ add_to_genesis = false command = "test-parachain" args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--disable-block-announcements", "--bootnodes {{'bob'|zombie('multiAddress')}}","--", "--reserved-only", "--reserved-nodes {{'ferdie'|zombie('multiAddress')}}"] - # we fail recovery for eve from time to time to test retries + # we fail recovery for 'eve' from time to time to test retries [[parachains.collators]] name = "eve" validator = true # collator @@ -58,7 +58,7 @@ add_to_genesis = false command = "test-parachain" args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--fail-pov-recovery", "--use-null-consensus", "--disable-block-announcements", "--bootnodes {{'bob'|zombie('multiAddress')}}", "--", "--reserved-only", "--reserved-nodes {{'ferdie'|zombie('multiAddress')}}"] - # run one as a RPC collator who does not produce blocks + # run 'one' as a RPC collator who does not produce blocks [[parachains.collators]] name = "one" validator = true # collator @@ -66,10 +66,18 @@ add_to_genesis = false command = "test-parachain" args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--use-null-consensus", "--disable-block-announcements", "--bootnodes {{'bob'|zombie('multiAddress')}}", "--relay-chain-rpc-url {{'ferdie'|zombie('wsUri')}}", "--", "--reserved-only", "--reserved-nodes {{'ferdie'|zombie('multiAddress')}}"] - # run two as a RPC parachain full node + # run 'two' as a RPC parachain full node [[parachains.collators]] name = "two" validator = false # full node image = "{{COL_IMAGE}}" command = "test-parachain" args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--disable-block-announcements", "--bootnodes {{'bob'|zombie('multiAddress')}}", "--relay-chain-rpc-url {{'ferdie'|zombie('wsUri')}}", "--", "--reserved-only", "--reserved-nodes {{'ferdie'|zombie('multiAddress')}}"] + + # run 'three' with light client + [[parachains.collators]] + name = "three" + validator = false # full node + image = "{{COL_IMAGE}}" + command = "test-parachain" + args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--disable-block-announcements", "--bootnodes {{'bob'|zombie('multiAddress')}}", "--relay-chain-light-client", "--", "--reserved-only", "--reserved-nodes {{'ferdie'|zombie('multiAddress')}}"] diff --git a/zombienet/tests/0002-pov_recovery.zndsl b/zombienet/tests/0002-pov_recovery.zndsl index 3eb28bb2aeb..12ff00210f3 100644 --- a/zombienet/tests/0002-pov_recovery.zndsl +++ b/zombienet/tests/0002-pov_recovery.zndsl @@ -13,4 +13,5 @@ alice: reports block height is at least 20 within 600 seconds charlie: reports block height is at least 20 within 600 seconds one: reports block height is at least 20 within 800 seconds two: reports block height is at least 20 within 800 seconds +three: reports block height is at least 20 within 800 seconds eve: reports block height is at least 20 within 800 seconds diff --git a/zombienet/tests/0003-full_node_catching_up.toml b/zombienet/tests/0003-full_node_catching_up.toml index 48ce352975f..350a7682bfa 100644 --- a/zombienet/tests/0003-full_node_catching_up.toml +++ b/zombienet/tests/0003-full_node_catching_up.toml @@ -40,3 +40,11 @@ cumulus_based = true image = "{{COL_IMAGE}}" command = "test-parachain" args = ["--reserved-only", "--reserved-nodes {{'charlie'|zombie('multiAddress')}}", "--relay-chain-rpc-url {{'alice'|zombie('wsUri')}}"] + + # run cumulus node ferdie (with embedded light client) and wait for it to sync some blocks + [[parachains.collators]] + name = "ferdie" + validator = false + image = "{{COL_IMAGE}}" + command = "test-parachain" + args = ["--relay-chain-light-client", "--reserved-only", "--reserved-nodes {{'charlie'|zombie('multiAddress')}}"] diff --git a/zombienet/tests/0003-full_node_catching_up.zndsl b/zombienet/tests/0003-full_node_catching_up.zndsl index 9c6eb0fb830..49b6d9e94fd 100644 --- a/zombienet/tests/0003-full_node_catching_up.zndsl +++ b/zombienet/tests/0003-full_node_catching_up.zndsl @@ -5,3 +5,4 @@ Creds: config alice: parachain 2000 is registered within 225 seconds dave: reports block height is at least 7 within 250 seconds eve: reports block height is at least 7 within 250 seconds +ferdie: reports block height is at least 7 within 250 seconds diff --git a/zombienet/tests/0007-full_node_warp_sync.toml b/zombienet/tests/0007-full_node_warp_sync.toml index 493363fd3cf..937c0b83683 100644 --- a/zombienet/tests/0007-full_node_warp_sync.toml +++ b/zombienet/tests/0007-full_node_warp_sync.toml @@ -31,6 +31,7 @@ cumulus_based = true chain_spec_path = "zombienet/tests/0007-warp-sync-parachain-spec.json" add_to_genesis = false + # Run 'dave' as parachain collator. [[parachains.collators]] name = "dave" validator = true @@ -39,6 +40,7 @@ add_to_genesis = false args = ["-lparachain=debug"] db_snapshot = "https://storage.googleapis.com/zombienet-db-snaps/cumulus/0007-full_node_warp_sync/parachain-587c1ed24ddd7de05c237cf7c158fff53b8f5b26.tgz" + # Run 'eve' as parachain collator. [[parachains.collators]] name = "eve" validator = true @@ -47,6 +49,7 @@ add_to_genesis = false args = ["-lparachain=debug"] db_snapshot = "https://storage.googleapis.com/zombienet-db-snaps/cumulus/0007-full_node_warp_sync/parachain-587c1ed24ddd7de05c237cf7c158fff53b8f5b26.tgz" + # Run 'ferdie' as parachain collator. [[parachains.collators]] name = "ferdie" validator = true @@ -55,6 +58,7 @@ add_to_genesis = false args = ["-lparachain=debug"] db_snapshot = "https://storage.googleapis.com/zombienet-db-snaps/cumulus/0007-full_node_warp_sync/parachain-587c1ed24ddd7de05c237cf7c158fff53b8f5b26.tgz" + # Run 'one' as parachain full node. Parachain and relay chain are warpsyncing. [[parachains.collators]] name = "one" validator = false @@ -62,6 +66,8 @@ add_to_genesis = false command = "test-parachain" args = ["-lsync=debug","--sync warp","--","--sync warp"] + # Run 'two' as parachain full node. Parachain is warpsyncing and the node + # uses relay chain node 'alice' as external RPC node. [[parachains.collators]] name = "two" validator = false @@ -69,9 +75,20 @@ add_to_genesis = false command = "test-parachain" args = ["-lsync=debug","--sync warp","--relay-chain-rpc-urls {{'alice'|zombie('wsUri')}}"] + # Run 'three' as parachain full node. Parachain is warpsyncing and the node + # uses relay chain node 'dave' as external RPC node. [[parachains.collators]] name = "three" validator = false image = "{{COL_IMAGE}}" command = "test-parachain" args = ["-lsync=debug","--sync warp","--relay-chain-rpc-urls {{'dave'|zombie('wsUri')}}"] + + # Run 'four' as parachain full node. Parachain is warpsyncing and the node + # uses an internal relay chain light client. + [[parachains.collators]] + name = "four" + validator = false + image = "{{COL_IMAGE}}" + command = "test-parachain" + args = ["-lsync=debug","--sync warp","--relay-chain-light-client"] diff --git a/zombienet/tests/0007-full_node_warp_sync.zndsl b/zombienet/tests/0007-full_node_warp_sync.zndsl index 1bcc35e80c4..9f503b2ad63 100644 --- a/zombienet/tests/0007-full_node_warp_sync.zndsl +++ b/zombienet/tests/0007-full_node_warp_sync.zndsl @@ -5,4 +5,5 @@ Creds: config alice: parachain 2000 is registered within 225 seconds one: reports block height is at least 770 within 225 seconds two: reports block height is at least 770 within 225 seconds -three: reports block height is at least 770 within 225 seconds \ No newline at end of file +three: reports block height is at least 770 within 225 seconds +four: reports block height is at least 770 within 225 seconds