diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index beb9e2c896..8ecef7e212 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -166,6 +166,38 @@ jobs: command: nextest args: run --workspace + nonwasm_light_client_tests: + name: "Test Light Client" + runs-on: ubuntu-latest-16-cores + timeout-minutes: 15 + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Download Substrate + run: | + curl $SUBSTRATE_URL --output substrate --location + chmod +x substrate + ./substrate --version + mkdir -p ~/.local/bin + mv substrate ~/.local/bin + + - name: Install Rust stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - name: Rust Cache + uses: Swatinem/rust-cache@988c164c3d0e93c4dbab36aaf5bbeb77425b2894 # v2.4.0 + + - name: Run tests + uses: actions-rs/cargo@v1.0.3 + with: + command: test + args: --release --package integration-tests --features unstable-light-client + clippy: name: Cargo clippy runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index 091e7f169d..fddc207497 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,41 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug 0.3.0", +] + +[[package]] +name = "aes-gcm" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.7.6" @@ -52,9 +87,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] @@ -156,6 +191,15 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +[[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" @@ -164,9 +208,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "assert_matches" @@ -174,6 +218,63 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "async-channel" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +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", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "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", + "log", + "parking", + "polling", + "rustix 0.37.20", + "slab", + "socket2", + "waker-fn", +] + [[package]] name = "async-lock" version = "2.7.0" @@ -183,6 +284,42 @@ 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 = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" +dependencies = [ + "async-io", + "async-lock", + "autocfg", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 0.37.20", + "signal-hook", + "windows-sys 0.48.0", +] + +[[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.68" @@ -194,6 +331,18 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic-waker" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" + [[package]] name = "atty" version = "0.2.14" @@ -315,6 +464,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" @@ -322,8 +481,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" dependencies = [ "arrayref", - "arrayvec 0.7.2", - "constant_time_eq", + "arrayvec 0.7.4", + "constant_time_eq 0.2.6", ] [[package]] @@ -365,11 +524,26 @@ dependencies = [ "byte-tools", ] +[[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", + "futures-lite", + "log", +] + [[package]] name = "bounded-collections" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fbd1d11282a1eb134d3c3b7cf8ce213b5161c6e5f73fb1b98618482c606b64" +checksum = "eb5b05133427c07c4776906f673ccf36c21b102c9829c641a5b56bd151d44fd6" dependencies = [ "log", "parity-scale-codec", @@ -383,6 +557,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 = "bumpalo" version = "3.13.0" @@ -431,6 +614,31 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.26" @@ -470,6 +678,15 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "clap" version = "3.2.25" @@ -576,11 +793,32 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "concurrent-queue" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "constant_time_eq" -version = "0.2.5" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" + +[[package]] +name = "convert_case" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "core-foundation" @@ -609,9 +847,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" dependencies = [ "libc", ] @@ -693,22 +931,32 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.14" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset", + "memoffset 0.9.0", "scopeguard", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] @@ -759,6 +1007,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "2.1.3" @@ -785,6 +1042,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "curve25519-dalek" +version = "4.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d4ba9852b42210c7538b75484f9daa0655e9a3ac04f693747bb0f02cf3cfe16" +dependencies = [ + "cfg-if", + "fiat-crypto", + "packed_simd_2", + "platforms", + "subtle", + "zeroize", +] + [[package]] name = "curve25519-dalek-ng" version = "4.1.1" @@ -885,8 +1156,10 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ + "convert_case", "proc-macro2", "quote", + "rustc_version", "syn 1.0.109", ] @@ -925,6 +1198,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + [[package]] name = "dyn-clonable" version = "0.9.0" @@ -1048,6 +1327,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fiat-crypto" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" + [[package]] name = "fixed-hash" version = "0.8.0" @@ -1068,9 +1362,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] @@ -1142,6 +1436,21 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-macro" version = "0.3.28" @@ -1236,11 +1545,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug 0.3.0", + "polyval", +] + [[package]] name = "gimli" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" dependencies = [ "fallible-iterator", "indexmap", @@ -1293,9 +1612,9 @@ dependencies = [ [[package]] name = "gloo-utils" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e8fc851e9c7b9852508bc6e3f690f452f474417e8545ec9857b7f7377036b5" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" dependencies = [ "js-sys", "serde", @@ -1362,6 +1681,15 @@ dependencies = [ "ahash 0.8.3", ] +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "serde", +] + [[package]] name = "heck" version = "0.4.1" @@ -1514,9 +1842,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1543,9 +1871,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1597,6 +1925,21 @@ dependencies = [ "serde", ] +[[package]] +name = "indexmap-nostd" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "integer-sqrt" version = "0.1.5" @@ -1633,6 +1976,12 @@ dependencies = [ "which", ] +[[package]] +name = "intx" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f38a50a899dc47a6d0ed5508e7f601a2e34c3a85303514b5d137f3c10a0c75" + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -1652,7 +2001,7 @@ checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", - "rustix 0.37.19", + "rustix 0.37.20", "windows-sys 0.48.0", ] @@ -1673,9 +2022,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -1791,9 +2140,21 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.144" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" + +[[package]] +name = "libm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "libsecp256k1" @@ -1857,9 +2218,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -1867,9 +2228,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.18" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "lru" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03f1160296536f10c833a82dca22267d5486734230d47bf00bf435885814ba1e" [[package]] name = "mach" @@ -1901,7 +2268,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffc89ccdc6e10d6907450f753537ebc5c5d3460d2e4e62ea74bd571db62c0f9e" dependencies = [ - "rustix 0.37.19", + "rustix 0.37.20", ] [[package]] @@ -1913,6 +2280,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + [[package]] name = "memory-db" version = "0.32.0" @@ -1946,6 +2322,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.6.2" @@ -1966,12 +2348,34 @@ dependencies = [ "windows-sys 0.48.0", ] +[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1982,16 +2386,49 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-format" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" dependencies = [ - "arrayvec 0.7.2", + "arrayvec 0.7.4", "itoa", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -2013,9 +2450,9 @@ dependencies = [ [[package]] name = "object" -version = "0.30.3" +version = "0.30.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" dependencies = [ "crc32fast", "hashbrown 0.13.2", @@ -2025,9 +2462,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.2" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "oorandom" @@ -2055,9 +2492,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "os_str_bytes" -version = "6.5.0" +version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" +checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" [[package]] name = "output_vt100" @@ -2080,13 +2517,23 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "packed_simd_2" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1914cd452d8fccd6f9db48147b29fd4ae05bea9dc5d9ad578509f72415de282" +dependencies = [ + "cfg-if", + "libm 0.1.4", +] + [[package]] name = "parity-scale-codec" -version = "3.5.0" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ddb756ca205bd108aee3c62c6d3c994e1df84a59b9d6d4a5ea42ee1fd5a9a28" +checksum = "2287753623c76f953acd29d15d8100bcab84d29db78fb6f352adb3c53e83b967" dependencies = [ - "arrayvec 0.7.2", + "arrayvec 0.7.4", "bitvec", "byte-slice-cast", "bytes", @@ -2097,9 +2544,9 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.1.4" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" +checksum = "2b6937b5e67bfba3351b87b040d48352a2fcb6ad72f81855412ce97b45c8f110" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2113,6 +2560,12 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" +[[package]] +name = "parking" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" + [[package]] name = "parking_lot" version = "0.12.1" @@ -2125,15 +2578,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.48.0", ] [[package]] @@ -2171,9 +2624,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project" @@ -2207,11 +2660,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "platforms" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" + [[package]] name = "plotters" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ "num-traits", "plotters-backend", @@ -2222,19 +2681,58 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" [[package]] name = "plotters-svg" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" dependencies = [ "plotters-backend", ] +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures", + "opaque-debug 0.3.0", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug 0.3.0", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2428,9 +2926,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags", ] @@ -2496,7 +2994,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted", "web-sys", "winapi", @@ -2520,6 +3018,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.36.14" @@ -2536,9 +3043,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.19" +version = "0.37.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" dependencies = [ "bitflags", "errno", @@ -2562,9 +3069,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -2587,6 +3094,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 = "ryu" version = "1.0.13" @@ -2760,7 +3278,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "844b7645371e6ecdf61ff246ba1958c29e802881a749ae3fb1993675d210d28d" dependencies = [ "arrayref", - "arrayvec 0.7.2", + "arrayvec 0.7.4", "curve25519-dalek-ng", "merlin 3.0.0", "rand_core 0.6.4", @@ -2836,6 +3354,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + [[package]] name = "send_wrapper" version = "0.4.0" @@ -2922,9 +3446,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", @@ -2950,12 +3474,37 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "signature" version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "slab" version = "0.4.8" @@ -2971,6 +3520,120 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[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.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cce5e2881b30bad7ef89f383a816ad0b22c45915911f28499026de4a76d20ee" +dependencies = [ + "arrayvec 0.7.4", + "async-lock", + "atomic", + "base64 0.21.2", + "bip39", + "blake2-rfc", + "bs58 0.5.0", + "crossbeam-queue", + "derive_more", + "ed25519-zebra", + "either", + "event-listener", + "fnv", + "futures-channel", + "futures-util", + "hashbrown 0.14.0", + "hex", + "hmac 0.12.1", + "itertools", + "libsecp256k1", + "merlin 3.0.0", + "no-std-net", + "nom", + "num-bigint", + "num-rational", + "num-traits", + "pbkdf2 0.12.1", + "pin-project", + "rand 0.8.5", + "rand_chacha 0.3.1", + "ruzstd", + "schnorrkel 0.10.2", + "serde", + "serde_json", + "sha2 0.10.7", + "siphasher", + "slab", + "smallvec", + "smol", + "snow", + "soketto", + "tiny-keccak", + "twox-hash", + "wasmi", +] + +[[package]] +name = "smoldot-light" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2f7b4687b83ff244ef6137735ed5716ad37dcdf3ee16c4eb1a32fb9808fa47" +dependencies = [ + "async-lock", + "blake2-rfc", + "derive_more", + "either", + "event-listener", + "fnv", + "futures-channel", + "futures-util", + "hashbrown 0.14.0", + "hex", + "itertools", + "log", + "lru", + "parking_lot", + "rand 0.8.5", + "serde", + "serde_json", + "siphasher", + "slab", + "smol", + "smoldot", +] + +[[package]] +name = "snow" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ccba027ba85743e09d15c03296797cad56395089b832b48b5a5217880f57733" +dependencies = [ + "aes-gcm", + "blake2", + "chacha20poly1305", + "curve25519-dalek 4.0.0-rc.1", + "rand_core 0.6.4", + "rustc_version", + "sha2 0.10.7", + "subtle", +] + [[package]] name = "socket2" version = "0.4.9" @@ -3035,7 +3698,7 @@ dependencies = [ "bitflags", "blake2", "bounded-collections", - "bs58", + "bs58 0.4.0", "dyn-clonable", "ed25519-zebra", "futures", @@ -3079,7 +3742,7 @@ dependencies = [ "blake2b_simd", "byteorder", "digest 0.10.7", - "sha2 0.10.6", + "sha2 0.10.7", "sha3", "sp-std", "twox-hash", @@ -3383,6 +4046,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "ss58-registry" version = "1.40.0" @@ -3479,6 +4148,7 @@ dependencies = [ "either", "frame-metadata", "futures", + "futures-util", "getrandom 0.2.10", "hex", "impl-serde", @@ -3492,6 +4162,7 @@ dependencies = [ "scale-value", "serde", "serde_json", + "smoldot-light", "sp-core", "sp-core-hashing", "sp-keyring", @@ -3502,7 +4173,9 @@ dependencies = [ "subxt-signer", "thiserror", "tokio", + "tokio-stream", "tracing", + "tracing-subscriber 0.3.17", ] [[package]] @@ -3582,7 +4255,7 @@ dependencies = [ "regex", "schnorrkel 0.10.2", "secrecy", - "sha2 0.10.6", + "sha2 0.10.7", "sp-core", "sp-core-hashing", "sp-keyring", @@ -3664,6 +4337,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.40" @@ -3697,13 +4390,22 @@ dependencies = [ "pbkdf2 0.11.0", "rand 0.8.5", "rustc-hash", - "sha2 0.10.6", + "sha2 0.10.7", "thiserror", "unicode-normalization", "wasm-bindgen", "zeroize", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -3768,6 +4470,17 @@ dependencies = [ "webpki", ] +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.8" @@ -3820,9 +4533,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "8803eee176538f94ae9a14b55b2804eb7e1441f8210b1c31290b3bccdccff73b" dependencies = [ "proc-macro2", "quote", @@ -4018,6 +4731,16 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array 0.14.7", + "subtle", +] + [[package]] name = "untrusted" version = "0.7.1" @@ -4026,9 +4749,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", @@ -4076,6 +4799,12 @@ dependencies = [ "glob 0.2.11", ] +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.3" @@ -4088,11 +4817,10 @@ dependencies = [ [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -4110,9 +4838,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -4120,9 +4848,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", @@ -4135,9 +4863,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -4147,9 +4875,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4157,9 +4885,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", @@ -4170,9 +4898,41 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wasmi" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51fb5c61993e71158abf5bb863df2674ca3ec39ed6471c64f07aeaf751d67b4" +dependencies = [ + "intx", + "smallvec", + "spin 0.9.8", + "wasmi_arena", + "wasmi_core", + "wasmparser-nostd", +] + +[[package]] +name = "wasmi_arena" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "401c1f35e413fac1846d4843745589d9ec678977ab35a384db8ae7830525d468" + +[[package]] +name = "wasmi_core" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "624e6333e861ef49095d2d678b76ebf30b06bf37effca845be7e5b87c90071b7" +dependencies = [ + "downcast-rs", + "libm 0.2.7", + "num-traits", + "paste", +] [[package]] name = "wasmparser" @@ -4184,6 +4944,15 @@ dependencies = [ "url", ] +[[package]] +name = "wasmparser-nostd" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9157cab83003221bfd385833ab587a039f5d6fa7304854042ba358a3b09e0724" +dependencies = [ + "indexmap-nostd", +] + [[package]] name = "wasmtime" version = "8.0.1" @@ -4294,7 +5063,7 @@ dependencies = [ "log", "mach", "memfd", - "memoffset", + "memoffset 0.8.0", "paste", "rand 0.8.5", "rustix 0.36.14", @@ -4318,9 +5087,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -4545,9 +5314,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index f1539e2139..fba1684d33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,11 +76,16 @@ wabt = "0.10.0" wasm-bindgen-test = "0.3.24" which = "4.4.0" +# Light client support: +smoldot-light = { version = "0.6.0", default-features = false } +tokio-stream = "0.1.14" +futures-util = "0.3.28" + # Substrate crates: sp-core = { version = "21.0.0", default-features = false } sp-core-hashing = "9.0.0" -sp-keyring = "24.0.0" sp-runtime = "24.0.0" +sp-keyring = "24.0.0" sp-version = "22.0.0" # Subxt workspace crates: diff --git a/subxt/Cargo.toml b/subxt/Cargo.toml index 287d3db8ff..d7d78127eb 100644 --- a/subxt/Cargo.toml +++ b/subxt/Cargo.toml @@ -53,10 +53,15 @@ substrate-compat = [ # latest features exposed by the metadata. unstable-metadata = [] -# Enable this to expose functionality only used for integration testing. -# The exposed functionality is subject to breaking changes at any point, -# and should not be relied upon. -integration-tests = [] +# Activate this to expose the Light Client functionality. +# Note that this feature is experimental and things may break or not work as expected. +unstable-light-client = [ + "smoldot-light/std", + "tokio-stream", + "tokio/sync", + "tokio/rt", + "futures-util", +] [dependencies] codec = { package = "parity-scale-codec", workspace = true, features = ["derive"] } @@ -95,6 +100,12 @@ sp-runtime = { workspace = true, optional = true } subxt-macro = { workspace = true } subxt-metadata = { workspace = true } +# Light client support: +smoldot-light = { workspace = true, optional = true } +tokio = { workspace = true, optional = true } +tokio-stream = { workspace = true, optional = true } +futures-util = { workspace = true, optional = true } + # Included if "web" feature is enabled, to enable its js feature. getrandom = { workspace = true, optional = true } @@ -108,4 +119,19 @@ sp-runtime = { workspace = true } sp-keyring = { workspace = true } sp-version = { workspace = true } assert_matches = { workspace = true } -subxt-signer = { workspace = true, features = ["subxt"] } \ No newline at end of file +subxt-signer = { workspace = true, features = ["subxt"] } +# Tracing subscriber is useful for light-client examples to ensure that +# the `bootNodes` and chain spec are configured correctly. If all is fine, then +# the light-client wlll emit INFO logs with +# `GrandPa warp sync finished` and `Finalized block runtime ready.` +tracing-subscriber = { workspace = true } + +[[example]] +name = "unstable_light_client_tx_basic" +path = "examples/unstable_light_client_tx_basic.rs" +required-features = ["unstable-light-client", "jsonrpsee"] + +[profile.dev.package.smoldot-light] +opt-level = 2 +[profile.test.package.smoldot-light] +opt-level = 2 diff --git a/subxt/examples/storage_iterating.rs b/subxt/examples/storage_iterating.rs index 7b7864cf7c..07704d1d01 100644 --- a/subxt/examples/storage_iterating.rs +++ b/subxt/examples/storage_iterating.rs @@ -12,7 +12,7 @@ async fn main() -> Result<(), Box> { let storage_query = polkadot::storage().system().account_root(); // Get back an iterator of results (here, we are fetching 10 items at - // a time from the node, but we always iterate over oen at a time). + // a time from the node, but we always iterate over one at a time). let mut results = api .storage() .at_latest() diff --git a/subxt/examples/unstable_light_client_tx_basic.rs b/subxt/examples/unstable_light_client_tx_basic.rs new file mode 100644 index 0000000000..e6384c5644 --- /dev/null +++ b/subxt/examples/unstable_light_client_tx_basic.rs @@ -0,0 +1,51 @@ +use sp_keyring::AccountKeyring; +use subxt::{ + client::{LightClient, LightClientBuilder, OfflineClientT}, + tx::PairSigner, + PolkadotConfig, +}; + +// Generate an interface that we can use from the node's metadata. +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")] +pub mod polkadot {} + +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber::fmt::init(); + + // Create a light client by fetching the chain spec of a local running node. + // In this case, because we start one single node, the bootnodes must be overwritten + // for the light client to connect to the local node. + // + // The `12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp` is the P2P address + // from a local polkadot node starting with + // `--node-key 0000000000000000000000000000000000000000000000000000000000000001` + let api: LightClient = LightClientBuilder::new() + .bootnodes([ + "/ip4/127.0.0.1/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp", + ]) + .build_from_url("ws://127.0.0.1:9944") + .await?; + + // Build a balance transfer extrinsic. + let dest = AccountKeyring::Bob.to_account_id().into(); + let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000); + + // Submit the balance transfer extrinsic from Alice, and wait for it to be successful + // and in a finalized block. We get back the extrinsic events if all is well. + let from = PairSigner::new(AccountKeyring::Alice.pair()); + let events = api + .tx() + .sign_and_submit_then_watch_default(&balance_transfer_tx, &from) + .await? + .wait_for_finalized_success() + .await?; + + // Find a Transfer event and print it. + let transfer_event = events.find_first::()?; + if let Some(event) = transfer_event { + println!("Balance transfer success: {event:?}"); + } + + Ok(()) +} diff --git a/subxt/src/book/usage/light_client.rs b/subxt/src/book/usage/light_client.rs new file mode 100644 index 0000000000..d5c642afbc --- /dev/null +++ b/subxt/src/book/usage/light_client.rs @@ -0,0 +1,48 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +//! # Light Client +//! +//! The Light Client aims to contribute to the decentralization of blockchains by providing connectivity +//! to the P2P network and behaving similarly to a full node. +//! +//! To enable this functionality, the unstable-light-client feature flag needs to be enabled. +//! +//! To connect to a blockchain network, the Light Client requires a trusted sync state of the network, named "chain spec". +//! This can be obtained by making a `sync_state_genSyncSpec` RPC call to a trusted node. +//! +//! The following is an example of fetching the chain spec from a local running onde on port 9933. +//! +//! ```bash +//! curl -H "Content-Type: application/json" -d '{"id":1, "jsonrpc":"2.0", "method": "sync_state_genSyncSpec", "params":[true]}' http://localhost:9933/ | jq .result > chain_spec.json +//! ``` +//! +//! ## Example +//! +//! You can construct a Light Client from a trusted chain spec stored on disk. +//! Similary, the Light Client can fetch the chain spec from a running node and +//! overwrite the bootNodes section. The `jsonrpsee` feature flag exposes the +//! `build_from_url` method. +//! +//! ```rust,ignore +//! let light_client = LightClientBuilder::new() +//! .bootnodes( +//! ["/ip4/127.0.0.1/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp"] +//! ) +//! .build_from_url("ws://127.0.0.1:9944") +//! .await?; +//! ``` +//! +//! Here's an example which connects to a local chain and submits a transaction. +//! +//! You can run the example using the following command: +//! +//! ```bash +//! cargo run --example unstable_light_client_tx_basic --features="unstable-light-client jsonrpsee" +//! ``` +//! +//! ```rust,ignore +#![doc = include_str!("../../../examples/unstable_light_client_tx_basic.rs")] +//! ``` +//! diff --git a/subxt/src/book/usage/mod.rs b/subxt/src/book/usage/mod.rs index 1742fcfc0a..1029e63444 100644 --- a/subxt/src/book/usage/mod.rs +++ b/subxt/src/book/usage/mod.rs @@ -10,12 +10,14 @@ //! - [Constants](constants) //! - [Blocks](blocks) //! - [Runtime APIs](runtime_apis) +//! - [Unstable Light Client](light_client) //! //! Alternately, [go back](super). pub mod blocks; pub mod constants; pub mod events; +pub mod light_client; pub mod runtime_apis; pub mod storage; pub mod transactions; diff --git a/subxt/src/client/lightclient/background.rs b/subxt/src/client/lightclient/background.rs new file mode 100644 index 0000000000..6de5103be8 --- /dev/null +++ b/subxt/src/client/lightclient/background.rs @@ -0,0 +1,436 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +use futures::stream::StreamExt; +use futures_util::future::{self, Either}; +use serde::Deserialize; +use serde_json::value::RawValue; +use std::{collections::HashMap, str::FromStr, sync::Arc}; +use tokio::sync::{mpsc, oneshot}; + +use super::LightClientError; +use smoldot_light::{platform::default::DefaultPlatform as Platform, ChainId}; + +const LOG_TARGET: &str = "light-client-background"; + +/// The response of an RPC method. +pub type MethodResponse = Result, LightClientError>; + +/// Message protocol between the front-end client that submits the RPC requests +/// and the backend handler that produces responses from the chain. +/// +/// The light client uses a single object [`smoldot_light::JsonRpcResponses`] to +/// handle all requests and subscriptions from a chain. A background task is spawned +/// to multiplex the rpc responses and to provide them back to their rightful submitters. +#[derive(Debug)] +pub enum FromSubxt { + /// The RPC method request. + Request { + /// The method of the request. + method: String, + /// The parameters of the request. + params: String, + /// Channel used to send back the result. + sender: oneshot::Sender, + }, + /// The RPC subscription (pub/sub) request. + Subscription { + /// The method of the request. + method: String, + /// The parameters of the request. + params: String, + /// Channel used to send back the subscription ID if successful. + sub_id: oneshot::Sender, + /// Channel used to send back the notifcations. + sender: mpsc::UnboundedSender>, + }, +} + +/// Background task data. +pub struct BackgroundTask { + /// Smoldot light client implementation that leverages the exposed platform. + client: smoldot_light::Client>, + /// The ID of the chain used to identify the chain protocol (ie. substrate). + /// + /// Note: A single chain is supported for a client. This aligns with the subxt's + /// vision of the Client. + chain_id: ChainId, + /// Unique ID for RPC calls. + request_id: usize, + /// Map the request ID of a RPC method to the frontend `Sender`. + requests: HashMap>, + /// Subscription calls first need to make a plain RPC method + /// request to obtain the subscription ID. + /// + /// The RPC method request is made in the background and the response should + /// not be sent back to the user. + /// Map the request ID of a RPC method to the frontend `Sender`. + id_to_subscription: HashMap< + usize, + ( + oneshot::Sender, + mpsc::UnboundedSender>, + ), + >, + /// Map the subscription ID to the frontend `Sender`. + subscriptions: HashMap>>, +} + +impl BackgroundTask { + /// Constructs a new [`BackgroundTask`]. + pub fn new(client: smoldot_light::Client>, chain_id: ChainId) -> BackgroundTask { + BackgroundTask { + client, + chain_id, + request_id: 1, + requests: Default::default(), + id_to_subscription: Default::default(), + subscriptions: Default::default(), + } + } + + /// Fetch and increment the request ID. + fn next_id(&mut self) -> usize { + let next = self.request_id; + self.request_id = self.request_id.wrapping_add(1); + next + } + + /// Handle the registration messages received from the user. + async fn handle_requests(&mut self, message: FromSubxt) { + match message { + FromSubxt::Request { + method, + params, + sender, + } => { + let id = self.next_id(); + let request = format!( + r#"{{"jsonrpc":"2.0","id":"{}", "method":"{}","params":{}}}"#, + id, method, params + ); + + self.requests.insert(id, sender); + + let result = self.client.json_rpc_request(request, self.chain_id); + if let Err(err) = result { + tracing::warn!( + target: LOG_TARGET, + "Cannot send RPC request to lightclient {:?}", + err.to_string() + ); + let sender = self + .requests + .remove(&id) + .expect("Channel is inserted above; qed"); + + // Send the error back to frontend. + if sender + .send(Err(LightClientError::Request(err.to_string()))) + .is_err() + { + tracing::warn!( + target: LOG_TARGET, + "Cannot send RPC request error to id={id}", + ); + } + } + } + FromSubxt::Subscription { + method, + params, + sub_id, + sender, + } => { + // For subscriptions we need to make a plain RPC request to the subscription method. + // The server will return as a result the subscription ID. + let id = self.next_id(); + let request = format!( + r#"{{"jsonrpc":"2.0","id":"{}", "method":"{}","params":{}}}"#, + id, method, params + ); + + self.id_to_subscription.insert(id, (sub_id, sender)); + + let result = self.client.json_rpc_request(request, self.chain_id); + if let Err(err) = result { + tracing::warn!( + target: LOG_TARGET, + "Cannot send RPC request to lightclient {:?}", + err.to_string() + ); + let (sub_id, _) = self + .id_to_subscription + .remove(&id) + .expect("Channels are inserted above; qed"); + + // Send the error back to frontend. + if sub_id + .send(Err(LightClientError::Request(err.to_string()))) + .is_err() + { + tracing::warn!( + target: LOG_TARGET, + "Cannot send RPC request error to id={id}", + ); + } + } + } + }; + } + + /// Parse the response received from the light client and sent it to the appropriate user. + fn handle_rpc_response(&mut self, response: String) { + match RpcResponse::from_str(&response) { + Ok(RpcResponse::Error { id, error }) => { + let Ok(id) = id.parse::() else { + tracing::warn!(target: LOG_TARGET, "Cannot send error. Id={id} is not a valid number"); + return + }; + + if let Some(sender) = self.requests.remove(&id) { + if sender + .send(Err(LightClientError::Request(error.to_string()))) + .is_err() + { + tracing::warn!( + target: LOG_TARGET, + "Cannot send method response to id={id}", + ); + } + } else if let Some((sub_id_sender, _)) = self.id_to_subscription.remove(&id) { + if sub_id_sender + .send(Err(LightClientError::Request(error.to_string()))) + .is_err() + { + tracing::warn!( + target: LOG_TARGET, + "Cannot send method response to id {:?}", + id + ); + } + } + } + Ok(RpcResponse::Method { id, result }) => { + let Ok(id) = id.parse::() else { + tracing::warn!(target: LOG_TARGET, "Cannot send response. Id={id} is not a valid number"); + return + }; + + // Send the response back. + if let Some(sender) = self.requests.remove(&id) { + if sender.send(Ok(result)).is_err() { + tracing::warn!( + target: LOG_TARGET, + "Cannot send method response to id={id}", + ); + } + } else if let Some((sub_id_sender, sender)) = self.id_to_subscription.remove(&id) { + let Ok(sub_id) = result + .get() + .trim_start_matches('"') + .trim_end_matches('"') + .parse::() else { + tracing::warn!( + target: LOG_TARGET, + "Subscription id={result} is not a valid number", + ); + return; + }; + + tracing::trace!(target: LOG_TARGET, "Received subscription id={sub_id}"); + + if sub_id_sender.send(Ok(result)).is_err() { + tracing::warn!( + target: LOG_TARGET, + "Cannot send method response to id={id}", + ); + } else { + // Track this subscription ID if send is successful. + self.subscriptions.insert(sub_id, sender); + } + } + } + Ok(RpcResponse::Subscription { method, id, result }) => { + let Ok(id) = id.parse::() else { + tracing::warn!(target: LOG_TARGET, "Cannot send subscription. Id={id} is not a valid number"); + return + }; + + if let Some(sender) = self.subscriptions.get_mut(&id) { + // Send the current notification response. + if sender.send(result).is_err() { + tracing::warn!( + target: LOG_TARGET, + "Cannot send notification to subscription id={id} method={method}", + ); + + // Remove the sender if the subscription dropped the receiver. + self.subscriptions.remove(&id); + } + } + } + Err(err) => { + tracing::warn!(target: LOG_TARGET, "cannot decode RPC response {:?}", err); + } + } + } + + /// Perform the main background task: + /// - receiving requests from subxt RPC method / subscriptions + /// - provides the results from the light client back to users. + pub async fn start_task( + &mut self, + from_subxt: mpsc::UnboundedReceiver, + from_node: smoldot_light::JsonRpcResponses, + ) { + let from_subxt_event = tokio_stream::wrappers::UnboundedReceiverStream::new(from_subxt); + let from_node_event = futures_util::stream::unfold(from_node, |mut from_node| async { + from_node.next().await.map(|result| (result, from_node)) + }); + + tokio::pin!(from_subxt_event, from_node_event); + + let mut from_subxt_event_fut = from_subxt_event.next(); + let mut from_node_event_fut = from_node_event.next(); + + loop { + match future::select(from_subxt_event_fut, from_node_event_fut).await { + // Message received from subxt. + Either::Left((subxt_message, previous_fut)) => { + let Some(message) = subxt_message else { + tracing::trace!(target: LOG_TARGET, "Subxt channel closed"); + break; + }; + tracing::trace!( + target: LOG_TARGET, + "Received register message {:?}", + message + ); + + self.handle_requests(message).await; + + from_subxt_event_fut = from_subxt_event.next(); + from_node_event_fut = previous_fut; + } + // Message received from rpc handler: lightclient response. + Either::Right((node_message, previous_fut)) => { + // Smoldot returns `None` if the chain has been removed (which subxt does not remove). + let Some(response) = node_message else { + tracing::trace!(target: LOG_TARGET, "Smoldot RPC responses channel closed"); + break; + }; + tracing::trace!( + target: LOG_TARGET, + "Received smoldot RPC result {:?}", + response + ); + + self.handle_rpc_response(response); + + // Advance backend, save frontend. + from_subxt_event_fut = previous_fut; + from_node_event_fut = from_node_event.next(); + } + } + } + + tracing::trace!(target: LOG_TARGET, "Task closed"); + } +} + +/// The RPC response from the light-client. +/// This can either be a response of a method, or a notification from a subscription. +#[derive(Debug, Clone)] +enum RpcResponse { + Method { + /// Response ID. + id: String, + /// The result of the method call. + result: Box, + }, + Subscription { + /// RPC method that generated the notification. + method: String, + /// Subscription ID. + id: String, + /// Result. + result: Box, + }, + Error { + /// Response ID. + id: String, + /// Error. + error: Box, + }, +} + +impl std::str::FromStr for RpcResponse { + type Err = serde_json::Error; + + fn from_str(response: &str) -> Result { + // Helper structures to deserialize from raw RPC strings. + #[derive(Deserialize, Debug)] + struct Response { + /// JSON-RPC version. + #[allow(unused)] + jsonrpc: String, + /// Result. + result: Box, + /// Request ID + id: String, + } + #[derive(Deserialize)] + struct NotificationParams { + /// The ID of the subscription. + subscription: String, + /// Result. + result: Box, + } + #[derive(Deserialize)] + struct ResponseNotification { + /// JSON-RPC version. + #[allow(unused)] + jsonrpc: String, + /// RPC method that generated the notification. + method: String, + /// Result. + params: NotificationParams, + } + #[derive(Deserialize)] + struct ErrorResponse { + /// JSON-RPC version. + #[allow(unused)] + jsonrpc: String, + /// Request ID. + id: String, + /// Error. + error: Box, + } + + // Check if the response can be mapped as an RPC method response. + let result: Result = serde_json::from_str(response); + if let Ok(response) = result { + return Ok(RpcResponse::Method { + id: response.id, + result: response.result, + }); + } + + let result: Result = serde_json::from_str(response); + if let Ok(notification) = result { + return Ok(RpcResponse::Subscription { + id: notification.params.subscription, + method: notification.method, + result: notification.params.result, + }); + } + + let error: ErrorResponse = serde_json::from_str(response)?; + Ok(RpcResponse::Error { + id: error.id, + error: error.error, + }) + } +} diff --git a/subxt/src/client/lightclient/builder.rs b/subxt/src/client/lightclient/builder.rs new file mode 100644 index 0000000000..f27542959e --- /dev/null +++ b/subxt/src/client/lightclient/builder.rs @@ -0,0 +1,191 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +use super::{rpc::LightClientRpc, LightClient, LightClientError}; +use crate::{config::Config, error::Error, OnlineClient}; + +#[cfg(feature = "jsonrpsee")] +use jsonrpsee::{ + async_client::ClientBuilder, + client_transport::ws::{Uri, WsTransportClientBuilder}, + core::client::ClientT, + rpc_params, +}; +use smoldot_light::ChainId; +use std::num::NonZeroU32; +use std::sync::Arc; + +/// Builder for [`LightClient`]. +#[derive(Clone, Debug)] +pub struct LightClientBuilder { + max_pending_requests: NonZeroU32, + max_subscriptions: u32, + bootnodes: Option>, + potential_relay_chains: Option>, +} + +impl Default for LightClientBuilder { + fn default() -> Self { + Self { + max_pending_requests: NonZeroU32::new(128) + .expect("Valid number is greater than zero; qed"), + max_subscriptions: 1024, + bootnodes: None, + potential_relay_chains: None, + } + } +} + +impl LightClientBuilder { + /// Create a new [`LightClientBuilder`]. + pub fn new() -> LightClientBuilder { + LightClientBuilder::default() + } + + /// Overwrite the bootnodes of the chain specification. + /// + /// Can be used to provide trusted entities to the chain spec, or for + /// testing environments. + pub fn bootnodes<'a>(mut self, bootnodes: impl IntoIterator) -> Self { + self.bootnodes = Some(bootnodes.into_iter().map(Into::into).collect()); + self + } + + /// Maximum number of JSON-RPC in the queue of requests waiting to be processed. + /// This parameter is necessary for situations where the JSON-RPC clients aren't + /// trusted. If you control all the requests that are sent out and don't want them + /// to fail, feel free to pass `u32::max_value()`. + /// + /// Default is 128. + pub fn max_pending_requests(mut self, max_pending_requests: NonZeroU32) -> Self { + self.max_pending_requests = max_pending_requests; + self + } + + /// Maximum number of active subscriptions before new ones are automatically + /// rejected. Any JSON-RPC request that causes the server to generate notifications + /// counts as a subscription. + /// + /// Default is 1024. + pub fn max_subscriptions(mut self, max_subscriptions: u32) -> Self { + self.max_subscriptions = max_subscriptions; + self + } + + /// If the chain spec defines a parachain, contains the list of relay chains to choose + /// from. Ignored if not a parachain. + /// + /// This field is necessary because multiple different chain can have the same identity. + /// + /// For example: if user A adds a chain named "Kusama", then user B adds a different chain + /// also named "Kusama", then user B adds a parachain whose relay chain is "Kusama", it would + /// be wrong to connect to the "Kusama" created by user A. + pub fn potential_relay_chains( + mut self, + potential_relay_chains: impl IntoIterator, + ) -> Self { + self.potential_relay_chains = Some(potential_relay_chains.into_iter().collect()); + self + } + + /// Build the light client with specified URL to connect to. + /// You must provide the port number in the URL. + /// + /// ## Panics + /// + /// Panics if being called outside of `tokio` runtime context. + #[cfg(feature = "jsonrpsee")] + pub async fn build_from_url>( + self, + url: Url, + ) -> Result, Error> { + let chain_spec = fetch_url(url.as_ref()).await?; + + self.build_client(chain_spec).await + } + + /// Build the light client from chain spec. + /// + /// The most important field of the configuration is the chain specification. + /// This is a JSON document containing all the information necessary for the client to + /// connect to said chain. + /// + /// The chain spec must be obtained from a trusted entity. + /// + /// It can be fetched from a trused node with the following command: + /// ```bash + /// curl -H "Content-Type: application/json" -d '{"id":1, "jsonrpc":"2.0", "method": "sync_state_genSyncSpec", "params":[true]}' http://localhost:9944/ | jq .result > res.spec + /// ``` + /// + /// # Note + /// + /// For testing environments, please populate the "bootNodes" if the not already provided. + /// See [`Self::bootnodes`] for more details. + /// + /// ## Panics + /// + /// Panics if being called outside of `tokio` runtime context. + pub async fn build(self, chain_spec: &str) -> Result, Error> { + let chain_spec = serde_json::from_str(chain_spec) + .map_err(|_| Error::LightClient(LightClientError::InvalidChainSpec))?; + + self.build_client(chain_spec).await + } + + /// Build the light client. + async fn build_client( + self, + mut chain_spec: serde_json::Value, + ) -> Result, Error> { + // Set custom bootnodes if provided. + if let Some(bootnodes) = self.bootnodes { + if let serde_json::Value::Object(map) = &mut chain_spec { + map.insert("bootNodes".to_string(), serde_json::Value::Array(bootnodes)); + } + } + + let config = smoldot_light::AddChainConfig { + specification: &chain_spec.to_string(), + json_rpc: smoldot_light::AddChainConfigJsonRpc::Enabled { + max_pending_requests: self.max_pending_requests, + max_subscriptions: self.max_subscriptions, + }, + potential_relay_chains: self.potential_relay_chains.unwrap_or_default().into_iter(), + database_content: "", + user_data: (), + }; + + let rpc = LightClientRpc::new(config)?; + let online_client = OnlineClient::::from_rpc_client(Arc::new(rpc)).await?; + Ok(LightClient(online_client)) + } +} + +/// Fetch the chain spec from the URL. +#[cfg(feature = "jsonrpsee")] +async fn fetch_url(url: impl AsRef) -> Result { + let url = url + .as_ref() + .parse::() + .map_err(|_| Error::LightClient(LightClientError::InvalidUrl))?; + + if url.scheme_str() != Some("ws") && url.scheme_str() != Some("wss") { + return Err(Error::LightClient(LightClientError::InvalidScheme)); + } + + let (sender, receiver) = WsTransportClientBuilder::default() + .build(url) + .await + .map_err(|_| LightClientError::Handshake)?; + + let client = ClientBuilder::default() + .request_timeout(core::time::Duration::from_secs(180)) + .max_notifs_per_subscription(4096) + .build_with_tokio(sender, receiver); + + client + .request("sync_state_genSyncSpec", rpc_params![true]) + .await + .map_err(|err| Error::Rpc(crate::error::RpcError::ClientError(Box::new(err)))) +} diff --git a/subxt/src/client/lightclient/mod.rs b/subxt/src/client/lightclient/mod.rs new file mode 100644 index 0000000000..5f07be3f9e --- /dev/null +++ b/subxt/src/client/lightclient/mod.rs @@ -0,0 +1,75 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +//! This module provides support for light clients. + +mod background; +mod builder; +mod rpc; + +use derivative::Derivative; + +use crate::{ + client::{OfflineClientT, OnlineClientT}, + config::Config, + OnlineClient, +}; + +pub use builder::LightClientBuilder; + +/// Light client error. +#[derive(Debug, thiserror::Error)] +pub enum LightClientError { + /// Error encountered while adding the chain to the light-client. + #[error("Failed to add the chain to the light client: {0}.")] + AddChainError(String), + /// The background task is closed. + #[error("Failed to communicate with the background task.")] + BackgroundClosed, + /// Invalid RPC parameters cannot be serialized as JSON string. + #[error("RPC parameters cannot be serialized as JSON string.")] + InvalidParams, + /// Error originated while trying to submit a RPC request. + #[error("RPC request cannot be sent: {0}.")] + Request(String), + /// The provided URL scheme is invalid. + /// + /// Supported versions: WS, WSS. + #[error("The provided URL scheme is invalid.")] + InvalidScheme, + /// The provided URL is invalid. + #[error("The provided URL scheme is invalid.")] + InvalidUrl, + /// The provided chain spec is invalid. + #[error("The provided chain spec is not a valid JSON object.")] + InvalidChainSpec, + /// Handshake error while connecting to a node. + #[error("WS handshake failed.")] + Handshake, +} + +/// The light-client RPC implementation that is used to connect with the chain. +#[derive(Derivative)] +#[derivative(Clone(bound = ""))] +pub struct LightClient(OnlineClient); + +impl OnlineClientT for LightClient { + fn rpc(&self) -> &crate::rpc::Rpc { + self.0.rpc() + } +} + +impl OfflineClientT for LightClient { + fn metadata(&self) -> crate::Metadata { + self.0.metadata() + } + + fn genesis_hash(&self) -> ::Hash { + self.0.genesis_hash() + } + + fn runtime_version(&self) -> crate::rpc::types::RuntimeVersion { + self.0.runtime_version() + } +} diff --git a/subxt/src/client/lightclient/rpc.rs b/subxt/src/client/lightclient/rpc.rs new file mode 100644 index 0000000000..bfa13c6370 --- /dev/null +++ b/subxt/src/client/lightclient/rpc.rs @@ -0,0 +1,209 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +use super::{ + background::{BackgroundTask, FromSubxt, MethodResponse}, + LightClientError, +}; +use crate::{ + error::{Error, RpcError}, + rpc::{RpcClientT, RpcFuture, RpcSubscription}, +}; +use futures::{stream::StreamExt, Stream}; +use serde_json::value::RawValue; +use smoldot_light::{platform::default::DefaultPlatform as Platform, ChainId}; +use std::pin::Pin; +use tokio::sync::{mpsc, mpsc::error::SendError, oneshot}; +use tokio_stream::wrappers::UnboundedReceiverStream; + +pub const LOG_TARGET: &str = "light-client"; + +/// The light-client RPC implementation that is used to connect with the chain. +#[derive(Clone)] +pub struct LightClientRpc { + /// Communicate with the backend task that multiplexes the responses + /// back to the frontend. + to_backend: mpsc::UnboundedSender, +} + +impl LightClientRpc { + /// Constructs a new [`LightClientRpc`], providing the chain specification. + /// + /// The chain specification can be downloaded from a trusted network via + /// the `sync_state_genSyncSpec` RPC method. This parameter expects the + /// chain spec in text format (ie not in hex-encoded scale-encoded as RPC methods + /// will provide). + /// + /// ## Panics + /// + /// Panics if being called outside of `tokio` runtime context. + pub fn new( + config: smoldot_light::AddChainConfig<'_, (), impl Iterator>, + ) -> Result { + tracing::trace!(target: LOG_TARGET, "Create light client"); + + let mut client = smoldot_light::Client::new(Platform::new( + env!("CARGO_PKG_NAME").into(), + env!("CARGO_PKG_VERSION").into(), + )); + + let smoldot_light::AddChainSuccess { + chain_id, + json_rpc_responses, + } = client + .add_chain(config) + .map_err(|err| LightClientError::AddChainError(err.to_string()))?; + + let (to_backend, backend) = mpsc::unbounded_channel(); + + // `json_rpc_responses` can only be `None` if we had passed `json_rpc: Disabled`. + let rpc_responses = json_rpc_responses.expect("Light client RPC configured; qed"); + + tokio::spawn(async move { + let mut task = BackgroundTask::new(client, chain_id); + task.start_task(backend, rpc_responses).await; + }); + + Ok(LightClientRpc { to_backend }) + } + + /// Submits an RPC method request to the light-client. + /// + /// This method sends a request to the light-client to execute an RPC method with the provided parameters. + /// The parameters are parsed into a valid JSON object in the background. + fn method_request( + &self, + method: String, + params: String, + ) -> Result, SendError> { + let (sender, receiver) = oneshot::channel(); + + self.to_backend.send(FromSubxt::Request { + method, + params, + sender, + })?; + + Ok(receiver) + } + + /// Makes an RPC subscription call to the light-client. + /// + /// This method sends a request to the light-client to establish an RPC subscription with the provided parameters. + /// The parameters are parsed into a valid JSON object in the background. + fn subscription_request( + &self, + method: String, + params: String, + ) -> Result< + ( + oneshot::Receiver, + mpsc::UnboundedReceiver>, + ), + SendError, + > { + let (sub_id, sub_id_rx) = oneshot::channel(); + let (sender, receiver) = mpsc::unbounded_channel(); + + self.to_backend.send(FromSubxt::Subscription { + method, + params, + sub_id, + sender, + })?; + + Ok((sub_id_rx, receiver)) + } +} + +impl RpcClientT for LightClientRpc { + fn request_raw<'a>( + &'a self, + method: &'a str, + params: Option>, + ) -> RpcFuture<'a, Box> { + let client = self.clone(); + + Box::pin(async move { + let params = match params { + Some(params) => serde_json::to_string(¶ms).map_err(|_| { + RpcError::ClientError(Box::new(LightClientError::InvalidParams)) + })?, + None => "[]".into(), + }; + + // Fails if the background is closed. + let rx = client + .method_request(method.to_string(), params) + .map_err(|_| RpcError::ClientError(Box::new(LightClientError::BackgroundClosed)))?; + + // Fails if the background is closed. + let response = rx + .await + .map_err(|_| RpcError::ClientError(Box::new(LightClientError::BackgroundClosed)))?; + + tracing::trace!(target: LOG_TARGET, "RPC response {:?}", response); + + response.map_err(|err| RpcError::ClientError(Box::new(err))) + }) + } + + fn subscribe_raw<'a>( + &'a self, + sub: &'a str, + params: Option>, + _unsub: &'a str, + ) -> RpcFuture<'a, RpcSubscription> { + let client = self.clone(); + + Box::pin(async move { + tracing::trace!( + target: LOG_TARGET, + "Subscribe to {:?} with params {:?}", + sub, + params + ); + + let params = match params { + Some(params) => serde_json::to_string(¶ms).map_err(|_| { + RpcError::ClientError(Box::new(LightClientError::InvalidParams)) + })?, + None => "[]".into(), + }; + + // Fails if the background is closed. + let (sub_id, notif) = client + .subscription_request(sub.to_string(), params) + .map_err(|_| RpcError::ClientError(Box::new(LightClientError::BackgroundClosed)))?; + + // Fails if the background is closed. + let result = sub_id + .await + .map_err(|_| RpcError::ClientError(Box::new(LightClientError::BackgroundClosed)))? + .map_err(|err| { + RpcError::ClientError(Box::new(LightClientError::Request(err.to_string()))) + })?; + + let sub_id = result + .get() + .trim_start_matches('"') + .trim_end_matches('"') + .to_string(); + tracing::trace!(target: LOG_TARGET, "Received subscription ID: {}", sub_id); + + let stream = UnboundedReceiverStream::new(notif); + + let rpc_substription_stream: Pin< + Box, RpcError>> + Send + 'static>, + > = Box::pin(stream.map(Ok)); + + let rpc_subscription: RpcSubscription = RpcSubscription { + stream: rpc_substription_stream, + id: Some(sub_id), + }; + + Ok(rpc_subscription) + }) + } +} diff --git a/subxt/src/client/mod.rs b/subxt/src/client/mod.rs index 0b30b00f75..b2cc087d34 100644 --- a/subxt/src/client/mod.rs +++ b/subxt/src/client/mod.rs @@ -11,6 +11,9 @@ mod offline_client; mod online_client; +#[cfg(feature = "unstable-light-client")] +mod lightclient; + pub use offline_client::{OfflineClient, OfflineClientT}; pub use online_client::{ ClientRuntimeUpdater, OnlineClient, OnlineClientT, RuntimeUpdaterStream, Update, UpgradeError, @@ -18,3 +21,6 @@ pub use online_client::{ #[cfg(feature = "jsonrpsee")] pub use online_client::default_rpc_client; + +#[cfg(feature = "unstable-light-client")] +pub use lightclient::{LightClient, LightClientBuilder, LightClientError}; diff --git a/subxt/src/error/mod.rs b/subxt/src/error/mod.rs index 70685f1a31..c47e27babc 100644 --- a/subxt/src/error/mod.rs +++ b/subxt/src/error/mod.rs @@ -8,6 +8,9 @@ mod dispatch_error; use core::fmt::Debug; +#[cfg(feature = "unstable-light-client")] +pub use crate::client::LightClientError; + // Re-export dispatch error types: pub use dispatch_error::{ ArithmeticError, DispatchError, ModuleError, RawModuleError, TokenError, TransactionalError, @@ -64,6 +67,10 @@ pub enum Error { /// The bytes representing an error that we were unable to decode. #[error("An error occurred but it could not be decoded: {0:?}")] Unknown(Vec), + /// Light client error. + #[cfg(feature = "unstable-light-client")] + #[error("An error occurred but it could not be decoded: {0:?}")] + LightClient(#[from] LightClientError), /// Other error. #[error("Other error: {0}")] Other(String), diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index 4dbd4e9274..cb0eb088d9 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -53,6 +53,11 @@ mod only_used_in_docs_or_tests { use tokio as _; } +// Suppress an unused dependency warning because tracing_subscriber is +// only used in example code snippets at the time of writing. +#[cfg(test)] +use tracing_subscriber as _; + // Used to enable the js feature for wasm. #[cfg(feature = "web")] #[allow(unused_imports)] diff --git a/subxt/src/rpc/types.rs b/subxt/src/rpc/types.rs index 2500507da0..c12c8d34df 100644 --- a/subxt/src/rpc/types.rs +++ b/subxt/src/rpc/types.rs @@ -239,6 +239,8 @@ pub type SystemProperties = serde_json::Map; /// /// This is copied from `sp-transaction-pool` to avoid a dependency on that crate. Therefore it /// must be kept compatible with that type from the target substrate version. +/// +/// Substrate produces `camelCase` events, while smoldot produces `CamelCase` events. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum SubstrateTxStatus { diff --git a/testing/integration-tests/Cargo.toml b/testing/integration-tests/Cargo.toml index 8d49576130..10fd7e6c6c 100644 --- a/testing/integration-tests/Cargo.toml +++ b/testing/integration-tests/Cargo.toml @@ -13,7 +13,10 @@ homepage.workspace = true description = "Subxt integration tests that rely on the Substrate binary" [features] -default = ["subxt/integration-tests"] +default = [] + +# Enable to run the tests with Light Client support. +unstable-light-client = ["subxt/unstable-light-client"] [dev-dependencies] assert_matches = { workspace = true } @@ -24,7 +27,6 @@ hex = { workspace = true } regex = { workspace = true } scale-info = { workspace = true, features = ["bit-vec"] } sp-core = { workspace = true } -sp-runtime = { workspace = true } syn = { workspace = true } subxt = { workspace = true, features = ["unstable-metadata", "native", "jsonrpsee", "substrate-compat"] } subxt-signer = { workspace = true, features = ["subxt"] } @@ -37,3 +39,4 @@ tracing-subscriber = { workspace = true } wabt = { workspace = true } which = { workspace = true } substrate-runner = { workspace = true } +sp-runtime = { workspace = true } diff --git a/testing/integration-tests/src/blocks/mod.rs b/testing/integration-tests/src/full_client/blocks/mod.rs similarity index 100% rename from testing/integration-tests/src/blocks/mod.rs rename to testing/integration-tests/src/full_client/blocks/mod.rs diff --git a/testing/integration-tests/src/client/mod.rs b/testing/integration-tests/src/full_client/client/mod.rs similarity index 100% rename from testing/integration-tests/src/client/mod.rs rename to testing/integration-tests/src/full_client/client/mod.rs diff --git a/testing/integration-tests/src/codegen/codegen_tests.rs b/testing/integration-tests/src/full_client/codegen/codegen_tests.rs similarity index 100% rename from testing/integration-tests/src/codegen/codegen_tests.rs rename to testing/integration-tests/src/full_client/codegen/codegen_tests.rs diff --git a/testing/integration-tests/src/codegen/documentation.rs b/testing/integration-tests/src/full_client/codegen/documentation.rs similarity index 100% rename from testing/integration-tests/src/codegen/documentation.rs rename to testing/integration-tests/src/full_client/codegen/documentation.rs diff --git a/testing/integration-tests/src/codegen/mod.rs b/testing/integration-tests/src/full_client/codegen/mod.rs similarity index 100% rename from testing/integration-tests/src/codegen/mod.rs rename to testing/integration-tests/src/full_client/codegen/mod.rs diff --git a/testing/integration-tests/src/codegen/polkadot.rs b/testing/integration-tests/src/full_client/codegen/polkadot.rs similarity index 100% rename from testing/integration-tests/src/codegen/polkadot.rs rename to testing/integration-tests/src/full_client/codegen/polkadot.rs diff --git a/testing/integration-tests/src/frame/balances.rs b/testing/integration-tests/src/full_client/frame/balances.rs similarity index 100% rename from testing/integration-tests/src/frame/balances.rs rename to testing/integration-tests/src/full_client/frame/balances.rs diff --git a/testing/integration-tests/src/frame/contracts.rs b/testing/integration-tests/src/full_client/frame/contracts.rs similarity index 100% rename from testing/integration-tests/src/frame/contracts.rs rename to testing/integration-tests/src/full_client/frame/contracts.rs diff --git a/testing/integration-tests/src/frame/mod.rs b/testing/integration-tests/src/full_client/frame/mod.rs similarity index 100% rename from testing/integration-tests/src/frame/mod.rs rename to testing/integration-tests/src/full_client/frame/mod.rs diff --git a/testing/integration-tests/src/frame/staking.rs b/testing/integration-tests/src/full_client/frame/staking.rs similarity index 100% rename from testing/integration-tests/src/frame/staking.rs rename to testing/integration-tests/src/full_client/frame/staking.rs diff --git a/testing/integration-tests/src/frame/sudo.rs b/testing/integration-tests/src/full_client/frame/sudo.rs similarity index 100% rename from testing/integration-tests/src/frame/sudo.rs rename to testing/integration-tests/src/full_client/frame/sudo.rs diff --git a/testing/integration-tests/src/frame/system.rs b/testing/integration-tests/src/full_client/frame/system.rs similarity index 100% rename from testing/integration-tests/src/frame/system.rs rename to testing/integration-tests/src/full_client/frame/system.rs diff --git a/testing/integration-tests/src/frame/timestamp.rs b/testing/integration-tests/src/full_client/frame/timestamp.rs similarity index 100% rename from testing/integration-tests/src/frame/timestamp.rs rename to testing/integration-tests/src/full_client/frame/timestamp.rs diff --git a/testing/integration-tests/src/metadata/mod.rs b/testing/integration-tests/src/full_client/metadata/mod.rs similarity index 100% rename from testing/integration-tests/src/metadata/mod.rs rename to testing/integration-tests/src/full_client/metadata/mod.rs diff --git a/testing/integration-tests/src/metadata/validation.rs b/testing/integration-tests/src/full_client/metadata/validation.rs similarity index 100% rename from testing/integration-tests/src/metadata/validation.rs rename to testing/integration-tests/src/full_client/metadata/validation.rs diff --git a/testing/integration-tests/src/full_client/mod.rs b/testing/integration-tests/src/full_client/mod.rs new file mode 100644 index 0000000000..5b496ccea3 --- /dev/null +++ b/testing/integration-tests/src/full_client/mod.rs @@ -0,0 +1,16 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +#[cfg(test)] +mod blocks; +#[cfg(test)] +mod client; +#[cfg(test)] +mod frame; +#[cfg(test)] +mod metadata; +#[cfg(test)] +mod runtime_api; +#[cfg(test)] +mod storage; diff --git a/testing/integration-tests/src/runtime_api/mod.rs b/testing/integration-tests/src/full_client/runtime_api/mod.rs similarity index 100% rename from testing/integration-tests/src/runtime_api/mod.rs rename to testing/integration-tests/src/full_client/runtime_api/mod.rs diff --git a/testing/integration-tests/src/storage/mod.rs b/testing/integration-tests/src/full_client/storage/mod.rs similarity index 100% rename from testing/integration-tests/src/storage/mod.rs rename to testing/integration-tests/src/full_client/storage/mod.rs diff --git a/testing/integration-tests/src/lib.rs b/testing/integration-tests/src/lib.rs index 9b68870e7b..90b024b4bb 100644 --- a/testing/integration-tests/src/lib.rs +++ b/testing/integration-tests/src/lib.rs @@ -5,27 +5,28 @@ #![deny(unused_crate_dependencies)] #[cfg(test)] -mod codegen; -#[cfg(test)] -mod utils; +pub mod utils; #[cfg(test)] -mod blocks; -#[cfg(test)] -mod client; -#[cfg(test)] -mod frame; -#[cfg(test)] -mod metadata; -#[cfg(test)] -mod runtime_api; -#[cfg(test)] -mod storage; +#[cfg_attr(test, allow(unused_imports))] +use utils::*; + +#[cfg(all(test, not(feature = "unstable-light-client")))] +mod full_client; + +#[cfg(all(test, feature = "unstable-light-client"))] +mod light_client; #[cfg(test)] use test_runtime::node_runtime; -#[cfg(test)] -use utils::*; + +// These dependencies are used for the full client. +#[cfg(all(test, not(feature = "unstable-light-client")))] +use regex as _; +#[cfg(all(test, not(feature = "unstable-light-client")))] +use subxt_codegen as _; +#[cfg(all(test, not(feature = "unstable-light-client")))] +use syn as _; // We don't use this dependency, but it's here so that we // can enable logging easily if need be. Add this to a test diff --git a/testing/integration-tests/src/light_client/mod.rs b/testing/integration-tests/src/light_client/mod.rs new file mode 100644 index 0000000000..42e3b7f510 --- /dev/null +++ b/testing/integration-tests/src/light_client/mod.rs @@ -0,0 +1,197 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +//! # Light Client Initialization and Testing +//! +//! The initialization process of the light client can be slow, especially when +//! it needs to synchronize with a local running node for each individual +//! #[tokio::test] in subxt. To optimize this process, a subset of tests is +//! exposed to ensure the light client remains functional over time. Currently, +//! these tests are placed under an unstable feature flag. +//! +//! Ideally, we would place the light client initialization in a shared static +//! using `OnceCell`. However, during the initialization, tokio::spawn is used +//! to multiplex between subxt requests and node responses. The #[tokio::test] +//! macro internally creates a new Runtime for each individual test. This means +//! that only the first test, which spawns the substrate binary and synchronizes +//! the light client, would have access to the background task. The cleanup process +//! would destroy the spawned background task, preventing subsequent tests from +//! accessing it. +//! +//! To address this issue, we can consider creating a slim proc-macro that +//! transforms the #[tokio::test] into a plain #[test] and runs all the tests +//! on a shared tokio runtime. This approach would allow multiple tests to share +//! the same background task, ensuring consistent access to the light client. +//! +//! For more context see: https://github.com/tokio-rs/tokio/issues/2374. +//! + +use crate::utils::node_runtime; +use codec::{Compact, Encode}; +use futures::StreamExt; +use subxt::{ + client::{LightClient, LightClientBuilder, OfflineClientT, OnlineClientT}, + config::PolkadotConfig, + rpc::types::FollowEvent, +}; +use subxt_metadata::Metadata; + +// We don't use these dependencies. +use assert_matches as _; +use frame_metadata as _; +use hex as _; +use regex as _; +use scale_info as _; +use sp_core as _; +use sp_runtime as _; +use subxt_codegen as _; +use subxt_signer as _; +use syn as _; +use tracing as _; +use wabt as _; + +type Client = LightClient; + +// Check that we can subscribe to non-finalized blocks. +async fn non_finalized_headers_subscription(api: &Client) -> Result<(), subxt::Error> { + let mut sub = api.blocks().subscribe_best().await?; + let header = sub.next().await.unwrap()?; + let block_hash = header.hash(); + let current_block_hash = api.rpc().block_hash(None).await?.unwrap(); + + assert_eq!(block_hash, current_block_hash); + + let _block = sub.next().await.unwrap()?; + let _block = sub.next().await.unwrap()?; + let _block = sub.next().await.unwrap()?; + + Ok(()) +} + +// Check that we can subscribe to finalized blocks. +async fn finalized_headers_subscription(api: &Client) -> Result<(), subxt::Error> { + let mut sub = api.blocks().subscribe_finalized().await?; + let header = sub.next().await.unwrap()?; + let finalized_hash = api.rpc().finalized_head().await?; + + assert_eq!(header.hash(), finalized_hash); + + let _block = sub.next().await.unwrap()?; + let _block = sub.next().await.unwrap()?; + let _block = sub.next().await.unwrap()?; + + Ok(()) +} + +// Check that we can subscribe to non-finalized blocks. +async fn runtime_api_call(api: &Client) -> Result<(), subxt::Error> { + let mut sub = api.blocks().subscribe_best().await?; + + let block = sub.next().await.unwrap()?; + let rt = block.runtime_api().await?; + + // get metadata via state_call. + let (_, meta1) = rt + .call_raw::<(Compact, Metadata)>("Metadata_metadata", None) + .await?; + + // get metadata via `state_getMetadata`. + let meta2 = api.rpc().metadata_legacy(None).await?; + + // They should be the same. + assert_eq!(meta1.encode(), meta2.encode()); + + Ok(()) +} + +// Lookup for the `Timestamp::now` plain storage entry. +async fn storage_plain_lookup(api: &Client) -> Result<(), subxt::Error> { + let addr = node_runtime::storage().timestamp().now(); + let entry = api + .storage() + .at_latest() + .await? + .fetch_or_default(&addr) + .await?; + assert!(entry > 0); + + Ok(()) +} + +// Subscribe to produced blocks using the `ChainHead` spec V2 and fetch the header of +// just a few reported blocks. +async fn follow_chain_head(api: &Client) -> Result<(), subxt::Error> { + let mut blocks = api.rpc().chainhead_unstable_follow(false).await?; + let sub_id = blocks + .subscription_id() + .expect("RPC provides a valid subscription id; qed") + .to_owned(); + + let event = blocks.next().await.unwrap()?; + if let FollowEvent::BestBlockChanged(best_block) = event { + let hash = best_block.best_block_hash; + let _header = api + .rpc() + .chainhead_unstable_header(sub_id.clone(), hash) + .await? + .unwrap(); + } + + let event = blocks.next().await.unwrap()?; + if let FollowEvent::BestBlockChanged(best_block) = event { + let hash = best_block.best_block_hash; + let _header = api + .rpc() + .chainhead_unstable_header(sub_id.clone(), hash) + .await? + .unwrap(); + } + Ok(()) +} + +// Make a dynamic constant query for `System::BlockLenght`. +async fn dynamic_constant_query(api: &Client) -> Result<(), subxt::Error> { + let constant_query = subxt::dynamic::constant("System", "BlockLength"); + let _value = api.constants().at(&constant_query)?; + + Ok(()) +} + +// Fetch a few all events from the latest block and decode them dynamically. +async fn dynamic_events(api: &Client) -> Result<(), subxt::Error> { + let events = api.events().at_latest().await?; + + for event in events.iter() { + let _event = event?; + } + + Ok(()) +} + +// Make a few raw RPC calls to the chain. +async fn various_rpc_calls(api: &Client) -> Result<(), subxt::Error> { + let _system_chain = api.rpc().system_chain().await?; + let _system_name = api.rpc().system_name().await?; + let _finalized_hash = api.rpc().finalized_head().await?; + + Ok(()) +} + +#[tokio::test] +async fn light_client_testing() -> Result<(), subxt::Error> { + let api: LightClient = LightClientBuilder::new() + .build_from_url("wss://rpc.polkadot.io:443") + .await?; + + non_finalized_headers_subscription(&api).await?; + finalized_headers_subscription(&api).await?; + runtime_api_call(&api).await?; + storage_plain_lookup(&api).await?; + follow_chain_head(&api).await?; + dynamic_constant_query(&api).await?; + dynamic_events(&api).await?; + various_rpc_calls(&api).await?; + + Ok(()) +} diff --git a/testing/integration-tests/src/utils/context.rs b/testing/integration-tests/src/utils/context.rs index d816ab7fae..7a1305c2ac 100644 --- a/testing/integration-tests/src/utils/context.rs +++ b/testing/integration-tests/src/utils/context.rs @@ -2,7 +2,7 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -pub(crate) use crate::{node_runtime, TestNodeProcess}; +pub(crate) use crate::{node_runtime, utils::TestNodeProcess}; use subxt::SubstrateConfig; diff --git a/testing/integration-tests/src/utils/node_proc.rs b/testing/integration-tests/src/utils/node_proc.rs index 5fdf699ead..b4c62c5760 100644 --- a/testing/integration-tests/src/utils/node_proc.rs +++ b/testing/integration-tests/src/utils/node_proc.rs @@ -6,11 +6,19 @@ use std::ffi::{OsStr, OsString}; use substrate_runner::SubstrateNode; use subxt::{Config, OnlineClient}; +#[cfg(feature = "unstable-light-client")] +use subxt::client::{LightClient, LightClientBuilder}; + /// Spawn a local substrate node for testing subxt. pub struct TestNodeProcess { // Keep a handle to the node; once it's dropped the node is killed. _proc: SubstrateNode, + + #[cfg(not(feature = "unstable-light-client"))] client: OnlineClient, + + #[cfg(feature = "unstable-light-client")] + client: LightClient, } impl TestNodeProcess @@ -26,9 +34,16 @@ where } /// Returns the subxt client connected to the running node. + #[cfg(not(feature = "unstable-light-client"))] pub fn client(&self) -> OnlineClient { self.client.clone() } + + /// Returns the subxt client connected to the running node. + #[cfg(feature = "unstable-light-client")] + pub fn client(&self) -> LightClient { + self.client.clone() + } } /// Construct a test node process. @@ -71,8 +86,13 @@ impl TestNodeProcessBuilder { let proc = node_builder.spawn().map_err(|e| e.to_string())?; let ws_url = format!("ws://127.0.0.1:{}", proc.ws_port()); + #[cfg(feature = "unstable-light-client")] + let client = build_light_client(&proc).await; + // Connect to the node with a subxt client: + #[cfg(not(feature = "unstable-light-client"))] let client = OnlineClient::from_url(ws_url.clone()).await; + match client { Ok(client) => Ok(TestNodeProcess { _proc: proc, @@ -82,3 +102,30 @@ impl TestNodeProcessBuilder { } } } + +#[cfg(feature = "unstable-light-client")] +async fn build_light_client(proc: &SubstrateNode) -> Result, String> { + // RPC endpoint. + let ws_url = format!("ws://127.0.0.1:{}", proc.ws_port()); + + // Step 1. Wait for a few blocks to be produced using the subxt client. + let client = OnlineClient::::from_url(ws_url.clone()) + .await + .map_err(|err| format!("Failed to connect to node rpc at {ws_url}: {err}"))?; + + super::wait_for_blocks(&client).await; + + // Step 2. Construct the light client. + // P2p bootnode. + let bootnode = format!( + "/ip4/127.0.0.1/tcp/{}/p2p/{}", + proc.p2p_port(), + proc.p2p_address() + ); + + LightClientBuilder::new() + .bootnodes([bootnode.as_str()]) + .build_from_url(ws_url.as_str()) + .await + .map_err(|e| format!("Failed to construct light client {}", e.to_string())) +} diff --git a/testing/integration-tests/src/utils/wait_for_blocks.rs b/testing/integration-tests/src/utils/wait_for_blocks.rs index 39ac5e65ea..2934baee16 100644 --- a/testing/integration-tests/src/utils/wait_for_blocks.rs +++ b/testing/integration-tests/src/utils/wait_for_blocks.rs @@ -7,8 +7,20 @@ use subxt::{client::OnlineClientT, Config}; /// Wait for blocks to be produced before running tests. Waiting for two blocks /// (the genesis block and another one) seems to be enough to allow tests /// like `dry_run_passes` to work properly. +/// +/// If the "unstable-light-client" feature flag is enabled, this will wait for +/// 5 blocks instead of two. The light client needs the extra blocks to avoid +/// errors caused by loading information that is not available in the first 2 blocks +/// (`Failed to load the block weight for block`). pub async fn wait_for_blocks(api: &impl OnlineClientT) { let mut sub = api.rpc().subscribe_all_block_headers().await.unwrap(); sub.next().await; sub.next().await; + + #[cfg(feature = "unstable-light-client")] + { + sub.next().await; + sub.next().await; + sub.next().await; + } } diff --git a/testing/substrate-runner/src/error.rs b/testing/substrate-runner/src/error.rs index 39ced58ff3..c7179da2d0 100644 --- a/testing/substrate-runner/src/error.rs +++ b/testing/substrate-runner/src/error.rs @@ -6,6 +6,8 @@ pub enum Error { Io(std::io::Error), CouldNotExtractPort, + CouldNotExtractP2pAddress, + CouldNotExtractP2pPort, } impl std::fmt::Display for Error { @@ -16,6 +18,14 @@ impl std::fmt::Display for Error { f, "could not extract port from running substrate node's stdout" ), + Error::CouldNotExtractP2pAddress => write!( + f, + "could not extract p2p address from running substrate node's stdout" + ), + Error::CouldNotExtractP2pPort => write!( + f, + "could not extract p2p port from running substrate node's stdout" + ), } } } diff --git a/testing/substrate-runner/src/lib.rs b/testing/substrate-runner/src/lib.rs index 2b0dd1a047..0de5f7ab98 100644 --- a/testing/substrate-runner/src/lib.rs +++ b/testing/substrate-runner/src/lib.rs @@ -56,7 +56,7 @@ impl SubstrateNodeBuilder { pub fn spawn(self) -> Result { let mut cmd = Command::new(self.binary_path); - cmd.env("RUST_LOG", "info") + cmd.env("RUST_LOG", "info,libp2p_tcp=debug") .stdout(process::Stdio::piped()) .stderr(process::Stdio::piped()) .arg("--dev") @@ -74,16 +74,26 @@ impl SubstrateNodeBuilder { // Wait for RPC port to be logged (it's logged to stderr). let stderr = proc.stderr.take().unwrap(); - let ws_port = - try_find_substrate_port_from_output(stderr).ok_or(Error::CouldNotExtractPort)?; - - Ok(SubstrateNode { proc, ws_port }) + let (ws_port, p2p_address, p2p_port) = try_find_substrate_port_from_output(stderr); + + let ws_port = ws_port.ok_or(Error::CouldNotExtractPort)?; + let p2p_address = p2p_address.ok_or(Error::CouldNotExtractP2pAddress)?; + let p2p_port = p2p_port.ok_or(Error::CouldNotExtractP2pPort)?; + + Ok(SubstrateNode { + proc, + ws_port, + p2p_address, + p2p_port, + }) } } pub struct SubstrateNode { proc: process::Child, ws_port: u16, + p2p_address: String, + p2p_port: u32, } impl SubstrateNode { @@ -102,6 +112,16 @@ impl SubstrateNode { self.ws_port } + /// Return the libp2p address of the running node. + pub fn p2p_address(&self) -> String { + self.p2p_address.clone() + } + + /// Return the libp2p port of the running node. + pub fn p2p_port(&self) -> u32 { + self.p2p_port + } + /// Kill the process. pub fn kill(&mut self) -> std::io::Result<()> { self.proc.kill() @@ -116,28 +136,63 @@ impl Drop for SubstrateNode { // Consume a stderr reader from a spawned substrate command and // locate the port number that is logged out to it. -fn try_find_substrate_port_from_output(r: impl Read + Send + 'static) -> Option { - BufReader::new(r).lines().take(50).find_map(|line| { +fn try_find_substrate_port_from_output( + r: impl Read + Send + 'static, +) -> (Option, Option, Option) { + let mut port = None; + let mut p2p_address = None; + let mut p2p_port = None; + + for line in BufReader::new(r).lines().take(50) { let line = line.expect("failed to obtain next line from stdout for port discovery"); - // does the line contain our port (we expect this specific output from substrate). - let line_end = line + // Parse the port lines + let line_port = line // oldest message: .rsplit_once("Listening for new connections on 127.0.0.1:") // slightly newer message: .or_else(|| line.rsplit_once("Running JSON-RPC WS server: addr=127.0.0.1:")) // newest message (jsonrpsee merging http and ws servers): .or_else(|| line.rsplit_once("Running JSON-RPC server: addr=127.0.0.1:")) - .map(|(_, port_str)| port_str)?; + .map(|(_, port_str)| port_str); + + if let Some(line_port) = line_port { + // trim non-numeric chars from the end of the port part of the line. + let port_str = line_port.trim_end_matches(|b: char| !b.is_ascii_digit()); + + // expect to have a number here (the chars after '127.0.0.1:') and parse them into a u16. + let port_num = port_str + .parse() + .unwrap_or_else(|_| panic!("valid port expected for log line, got '{port_str}'")); + port = Some(port_num); + } + + // Parse the p2p address line + let line_address = line + .rsplit_once("Local node identity is: ") + .map(|(_, address_str)| address_str); + + if let Some(line_address) = line_address { + let address = line_address.trim_end_matches(|b: char| b.is_ascii_whitespace()); + p2p_address = Some(address.into()); + } - // trim non-numeric chars from the end of the port part of the line. - let port_str = line_end.trim_end_matches(|b: char| !b.is_ascii_digit()); + // Parse the p2p port line (present in debug logs) + let p2p_port_line = line + .rsplit_once("libp2p_tcp: New listen address: /ip4/127.0.0.1/tcp/") + .map(|(_, address_str)| address_str); - // expect to have a number here (the chars after '127.0.0.1:') and parse them into a u16. - let port_num = port_str - .parse() - .unwrap_or_else(|_| panic!("valid port expected for log line, got '{port_str}'")); + if let Some(line_port) = p2p_port_line { + // trim non-numeric chars from the end of the port part of the line. + let port_str = line_port.trim_end_matches(|b: char| !b.is_ascii_digit()); + + // expect to have a number here (the chars after '127.0.0.1:') and parse them into a u16. + let port_num = port_str + .parse() + .unwrap_or_else(|_| panic!("valid port expected for log line, got '{port_str}'")); + p2p_port = Some(port_num); + } + } - Some(port_num) - }) + (port, p2p_address, p2p_port) }