diff --git a/Cargo.lock b/Cargo.lock index 750e880f9f..61227fdbea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -340,12 +340,11 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-channel" -version = "2.2.1" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", - "event-listener 5.3.0", "event-listener-strategy 0.5.2", "futures-core", "pin-project-lite", @@ -2590,9 +2589,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -2673,7 +2672,7 @@ dependencies = [ "rustls", "rustls-pki-types", "rustls-platform-verifier", - "soketto 0.8.0", + "soketto", "thiserror", "tokio", "tokio-rustls", @@ -2752,7 +2751,7 @@ dependencies = [ "route-recognizer", "serde", "serde_json", - "soketto 0.8.0", + "soketto", "thiserror", "tokio", "tokio-stream", @@ -3034,10 +3033,10 @@ dependencies = [ ] [[package]] -name = "no-std-net" -version = "0.6.0" +name = "multi-stash" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" +checksum = "685a9ac4b61f4e728e1d2c6a7844609c16527aeb5e6c865915c08e619c16410f" [[package]] name = "nodrop" @@ -3082,6 +3081,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "num-format" version = "0.4.4" @@ -3115,9 +3125,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -4033,13 +4043,12 @@ dependencies = [ [[package]] name = "ruzstd" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c4eb8a81997cf040a091d1f7e1938aeab6749d3a0dfa73af43cdc32393483d" +checksum = "5174a470eeb535a721ae9fdd6e291c2411a906b96592182d05217591d5c5cf7b" dependencies = [ "byteorder", "derive_more", - "twox-hash", ] [[package]] @@ -4503,19 +4512,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha-1" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha1" version = "0.10.6" @@ -4641,14 +4637,14 @@ dependencies = [ [[package]] name = "smoldot" -version = "0.16.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d1eaa97d77be4d026a1e7ffad1bb3b78448763b357ea6f8188d3e6f736a9b9" +checksum = "966e72d77a3b2171bb7461d0cb91f43670c63558c62d7cf42809cae6c8b6b818" dependencies = [ "arrayvec 0.7.4", "async-lock", "atomic-take", - "base64 0.21.7", + "base64 0.22.1", "bip39", "blake2-rfc", "bs58", @@ -4657,18 +4653,17 @@ dependencies = [ "derive_more", "ed25519-zebra 4.0.3", "either", - "event-listener 4.0.3", + "event-listener 5.3.0", "fnv", "futures-lite", "futures-util", "hashbrown 0.14.5", "hex", "hmac 0.12.1", - "itertools 0.12.1", + "itertools 0.13.0", "libm", "libsecp256k1", "merlin", - "no-std-net", "nom", "num-bigint", "num-rational", @@ -4687,7 +4682,7 @@ dependencies = [ "siphasher", "slab", "smallvec", - "soketto 0.7.1", + "soketto", "twox-hash", "wasmi", "x25519-dalek", @@ -4696,27 +4691,27 @@ dependencies = [ [[package]] name = "smoldot-light" -version = "0.14.0" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5496f2d116b7019a526b1039ec2247dd172b8670633b1a64a614c9ea12c9d8c7" +checksum = "2a33b06891f687909632ce6a4e3fd7677b24df930365af3d0bcb078310129f3f" dependencies = [ "async-channel", "async-lock", - "base64 0.21.7", + "base64 0.22.1", "blake2-rfc", + "bs58", "derive_more", "either", - "event-listener 4.0.3", + "event-listener 5.3.0", "fnv", "futures-channel", "futures-lite", "futures-util", "hashbrown 0.14.5", "hex", - "itertools 0.12.1", + "itertools 0.13.0", "log", "lru", - "no-std-net", "parking_lot", "pin-project", "rand", @@ -4740,21 +4735,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "soketto" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" -dependencies = [ - "base64 0.13.1", - "bytes", - "futures", - "httparse", - "log", - "rand", - "sha-1", -] - [[package]] name = "soketto" version = "0.8.0" @@ -5528,6 +5508,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "string-interner" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c6a0d765f5807e98a091107bae0a56ea3799f66a5de47b2c84c94a39c09974e" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", +] + [[package]] name = "strip-ansi-escapes" version = "0.2.0" @@ -6597,28 +6587,37 @@ dependencies = [ [[package]] name = "wasmi" -version = "0.31.2" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8281d1d660cdf54c76a3efa9ddd0c270cada1383a995db3ccb43d166456c7" +checksum = "50386c99b9c32bd2ed71a55b6dd4040af2580530fae8bdb9a6576571a80d0cca" dependencies = [ + "arrayvec 0.7.4", + "multi-stash", + "num-derive", + "num-traits", "smallvec", "spin", - "wasmi_arena", + "wasmi_collections", "wasmi_core", "wasmparser-nostd", ] [[package]] -name = "wasmi_arena" -version = "0.4.1" +name = "wasmi_collections" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" +checksum = "9c128c039340ffd50d4195c3f8ce31aac357f06804cfc494c8b9508d4b30dca4" +dependencies = [ + "ahash 0.8.11", + "hashbrown 0.14.5", + "string-interner", +] [[package]] name = "wasmi_core" -version = "0.13.0" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" +checksum = "a23b3a7f6c8c3ceeec6b83531ee61f0013c56e51cbf2b14b0f213548b23a4b41" dependencies = [ "downcast-rs", "libm", diff --git a/Cargo.toml b/Cargo.toml index 56acc38556..fe72f5c531 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,8 +120,8 @@ sc-executor = "0.40.0" sc-executor-common = "0.35.0" # Light client support: -smoldot = { version = "0.16.0", default-features = false } -smoldot-light = { version = "0.14.0", default-features = false } +smoldot = { version = "0.18.0", default-features = false } +smoldot-light = { version = "0.16.2", default-features = false } tokio-stream = "0.1.15" futures-util = "0.3.30" rand = "0.8.5" diff --git a/lightclient/src/background.rs b/lightclient/src/background.rs index e9bd9424e3..4ec6828806 100644 --- a/lightclient/src/background.rs +++ b/lightclient/src/background.rs @@ -108,7 +108,7 @@ impl BackgroundTaskHandle { /// coming to/from Smoldot. #[allow(clippy::type_complexity)] pub struct BackgroundTask { - channels: BackgroundTaskChannels, + channels: BackgroundTaskChannels, data: BackgroundTaskData, } @@ -117,7 +117,7 @@ impl BackgroundTask { pub(crate) fn new( client: SharedClient, chain_id: smoldot_light::ChainId, - from_back: smoldot_light::JsonRpcResponses, + from_back: smoldot_light::JsonRpcResponses, ) -> (BackgroundTask, BackgroundTaskHandle) { let (tx, rx) = mpsc::unbounded_channel(); @@ -176,10 +176,11 @@ impl BackgroundTask { tracing::trace!(target: LOG_TARGET, "Smoldot RPC responses channel closed"); break; }; + tracing::trace!( target: LOG_TARGET, - "Received smoldot RPC chain {:?} result {:?}", - chain_id, back_message + "Received smoldot RPC chain {chain_id:?} result {}", + trim_message(&back_message), ); data.handle_rpc_response(back_message); @@ -191,11 +192,11 @@ impl BackgroundTask { } } -struct BackgroundTaskChannels { +struct BackgroundTaskChannels { /// Messages sent into this background task from the front end. from_front: UnboundedReceiverStream, /// Messages sent into the background task from Smoldot. - from_back: smoldot_light::JsonRpcResponses, + from_back: smoldot_light::JsonRpcResponses, } struct BackgroundTaskData { @@ -242,6 +243,18 @@ struct ActiveSubscription { unsubscribe_method: String, } +fn trim_message(s: &str) -> &str { + const MAX_SIZE: usize = 512; + if s.len() < MAX_SIZE { + return s; + } + + match s.char_indices().nth(MAX_SIZE) { + None => s, + Some((idx, _)) => &s[..idx], + } +} + impl BackgroundTaskData { /// Fetch and increment the request ID. fn next_id(&mut self) -> usize { @@ -359,7 +372,7 @@ impl BackgroundTaskData { /// Parse the response received from the light client and sent it to the appropriate user. fn handle_rpc_response(&mut self, response: String) { let chain_id = self.chain_id; - tracing::trace!(target: LOG_TARGET, "Received from smoldot response='{response}' chain={chain_id:?}"); + tracing::trace!(target: LOG_TARGET, "Received from smoldot response='{}' chain={chain_id:?}", trim_message(&response)); match RpcResponse::from_str(&response) { Ok(RpcResponse::Method { id, result }) => { diff --git a/lightclient/src/lib.rs b/lightclient/src/lib.rs index 6df5c2ec47..3884bf2ab4 100644 --- a/lightclient/src/lib.rs +++ b/lightclient/src/lib.rs @@ -185,7 +185,7 @@ impl LightClientRpc { pub(crate) fn new_raw( client: impl Into>, chain_id: smoldot_light::ChainId, - rpc_responses: smoldot_light::JsonRpcResponses, + rpc_responses: smoldot_light::JsonRpcResponses, ) -> Self where TPlat: smoldot_light::platform::PlatformRef + Send + 'static, diff --git a/lightclient/src/platform/wasm_platform.rs b/lightclient/src/platform/wasm_platform.rs index ebebff5532..2bd36ef079 100644 --- a/lightclient/src/platform/wasm_platform.rs +++ b/lightclient/src/platform/wasm_platform.rs @@ -4,12 +4,16 @@ use super::wasm_socket::WasmSocket; -use core::time::Duration; +use core::{ + fmt::{self, Write as _}, + net::IpAddr, + time::Duration, +}; use futures::prelude::*; use smoldot::libp2p::with_buffers; use smoldot_light::platform::{ - Address, ConnectionType, IpAddr, MultiStreamAddress, MultiStreamWebRtcConnection, PlatformRef, - SubstreamDirection, + Address, ConnectionType, LogLevel, MultiStreamAddress, MultiStreamWebRtcConnection, + PlatformRef, SubstreamDirection, }; use std::{io, net::SocketAddr, pin::Pin}; @@ -187,4 +191,33 @@ impl PlatformRef for SubxtPlatform { super::wasm_helpers::sleep(duration).await; })) } + + fn log<'a>( + &self, + log_level: LogLevel, + log_target: &'a str, + message: &'a str, + key_values: impl Iterator, + ) { + let mut message_build = String::with_capacity(128); + message_build.push_str(message); + let mut first = true; + for (key, value) in key_values { + if first { + let _ = write!(message_build, "; "); + first = false; + } else { + let _ = write!(message_build, ", "); + } + let _ = write!(message_build, "{}={}", key, value); + } + + match log_level { + LogLevel::Error => tracing::error!("target={} {}", log_target, message_build), + LogLevel::Warn => tracing::warn!("target={} {}", log_target, message_build), + LogLevel::Info => tracing::info!("target={} {}", log_target, message_build), + LogLevel::Debug => tracing::debug!("target={} {}", log_target, message_build), + LogLevel::Trace => tracing::trace!("target={} {}", log_target, message_build), + }; + } } diff --git a/lightclient/src/shared_client.rs b/lightclient/src/shared_client.rs index d725030599..a954550f7d 100644 --- a/lightclient/src/shared_client.rs +++ b/lightclient/src/shared_client.rs @@ -38,7 +38,7 @@ impl SharedClient { pub(crate) fn add_chain( &self, config: sl::AddChainConfig<'_, TChain, impl Iterator>, - ) -> Result { + ) -> Result, sl::AddChainError> { self.client .lock() .expect("mutex should not be poisoned") diff --git a/testing/integration-tests/src/light_client/mod.rs b/testing/integration-tests/src/light_client/mod.rs index 235ba09f85..80fe1de484 100644 --- a/testing/integration-tests/src/light_client/mod.rs +++ b/testing/integration-tests/src/light_client/mod.rs @@ -29,26 +29,47 @@ use crate::utils::node_runtime; use codec::Compact; +use futures::StreamExt; +use std::sync::Arc; +use subxt::backend::rpc::RpcClient; +use subxt::backend::unstable::UnstableBackend; use subxt::{client::OnlineClient, config::PolkadotConfig, lightclient::LightClient}; use subxt_metadata::Metadata; type Client = OnlineClient; +/// The Polkadot chainspec. +const POLKADOT_SPEC: &str = include_str!("../../../../artifacts/demo_chain_specs/polkadot.json"); + // Check that we can subscribe to non-finalized blocks. async fn non_finalized_headers_subscription(api: &Client) -> Result<(), subxt::Error> { + let now = std::time::Instant::now(); + + tracing::trace!("Check non_finalized_headers_subscription"); let mut sub = api.blocks().subscribe_best().await?; let _block = sub.next().await.unwrap()?; + tracing::trace!("First block took {:?}", now.elapsed()); + let _block = sub.next().await.unwrap()?; + tracing::trace!("Second block took {:?}", now.elapsed()); + let _block = sub.next().await.unwrap()?; + tracing::trace!("Third block took {:?}", now.elapsed()); Ok(()) } // Check that we can subscribe to finalized blocks. async fn finalized_headers_subscription(api: &Client) -> Result<(), subxt::Error> { + let now = std::time::Instant::now(); + + tracing::trace!("Check finalized_headers_subscription"); + let mut sub = api.blocks().subscribe_finalized().await?; let header = sub.next().await.unwrap()?; + tracing::trace!("First block took {:?}", now.elapsed()); + let finalized_hash = api .backend() .latest_finalized_block_ref() @@ -56,20 +77,34 @@ async fn finalized_headers_subscription(api: &Client) -> Result<(), subxt::Error .unwrap() .hash(); + tracing::trace!( + "Finalized hash: {:?} took {:?}", + finalized_hash, + now.elapsed() + ); + assert_eq!(header.hash(), finalized_hash); + tracing::trace!("Check progress {:?}", now.elapsed()); let _block = sub.next().await.unwrap()?; + tracing::trace!("Second block took {:?}", now.elapsed()); let _block = sub.next().await.unwrap()?; + tracing::trace!("Third block took {:?}", now.elapsed()); let _block = sub.next().await.unwrap()?; + tracing::trace!("Fourth block took {:?}\n", now.elapsed()); Ok(()) } // Check that we can subscribe to non-finalized blocks. async fn runtime_api_call(api: &Client) -> Result<(), subxt::Error> { + let now = std::time::Instant::now(); + tracing::trace!("Check runtime_api_call"); + let mut sub = api.blocks().subscribe_best().await?; let block = sub.next().await.unwrap()?; + tracing::trace!("First block took {:?}", now.elapsed()); let rt = block.runtime_api().await?; // get metadata via state_call. if it decodes ok, it's probably all good. @@ -77,11 +112,16 @@ async fn runtime_api_call(api: &Client) -> Result<(), subxt::Error> { .call_raw::<(Compact, Metadata)>("Metadata_metadata", None) .await?; + tracing::trace!("Made runtime API call in {:?}\n", now.elapsed()); + Ok(()) } // Lookup for the `Timestamp::now` plain storage entry. async fn storage_plain_lookup(api: &Client) -> Result<(), subxt::Error> { + let now = std::time::Instant::now(); + tracing::trace!("Check storage_plain_lookup"); + let addr = node_runtime::storage().timestamp().now(); let entry = api .storage() @@ -90,6 +130,8 @@ async fn storage_plain_lookup(api: &Client) -> Result<(), subxt::Error> { .fetch_or_default(&addr) .await?; + tracing::trace!("Storage lookup took {:?}\n", now.elapsed()); + assert!(entry > 0); Ok(()) @@ -97,30 +139,75 @@ async fn storage_plain_lookup(api: &Client) -> Result<(), subxt::Error> { // Make a dynamic constant query for `System::BlockLenght`. async fn dynamic_constant_query(api: &Client) -> Result<(), subxt::Error> { + let now = std::time::Instant::now(); + tracing::trace!("Check dynamic_constant_query"); + let constant_query = subxt::dynamic::constant("System", "BlockLength"); let _value = api.constants().at(&constant_query)?; + tracing::trace!("Dynamic constant query took {:?}\n", now.elapsed()); + Ok(()) } // Fetch a few all events from the latest block and decode them dynamically. async fn dynamic_events(api: &Client) -> Result<(), subxt::Error> { + let now = std::time::Instant::now(); + tracing::trace!("Check dynamic_events"); + let events = api.events().at_latest().await?; for event in events.iter() { let _event = event?; + + tracing::trace!("Event decoding took {:?}", now.elapsed()); } + tracing::trace!("Dynamic events took {:?}\n", now.elapsed()); + Ok(()) } -#[tokio::test] -async fn light_client_testing() -> Result<(), subxt::Error> { - let chainspec = subxt::utils::fetch_chainspec_from_rpc_node("wss://rpc.polkadot.io:443") - .await - .unwrap(); - let (_lc, rpc) = LightClient::relay_chain(chainspec.get())?; - let api = Client::from_rpc_client(rpc).await?; +async fn run_test(backend: BackendType) -> Result<(), subxt::Error> { + // Note: This code fetches the chainspec from the Polkadot public RPC node. + // This is not recommended for production use, as it may be slow and unreliable. + // However, this can come in handy for testing purposes. + // + // let chainspec = subxt::utils::fetch_chainspec_from_rpc_node("wss://rpc.polkadot.io:443") + // .await + // .unwrap(); + // let chain_config = chainspec.get(); + + tracing::trace!("Init light clinet"); + let now = std::time::Instant::now(); + let (_lc, rpc) = LightClient::relay_chain(POLKADOT_SPEC)?; + + let api = match backend { + BackendType::Unstable => { + let (backend, mut driver) = UnstableBackend::builder().build(RpcClient::new(rpc)); + tokio::spawn(async move { + while let Some(val) = driver.next().await { + if let Err(e) = val { + if e.is_disconnected_will_reconnect() { + tracing::info!( + "The RPC connection was lost and we may have missed a few blocks" + ); + continue; + } + + tracing::error!("Error driving unstable backend: {e}"); + } + } + }); + let api: OnlineClient = + OnlineClient::from_backend(Arc::new(backend)).await?; + api + } + + BackendType::Legacy => Client::from_rpc_client(rpc).await?, + }; + + tracing::trace!("Light client initialization took {:?}", now.elapsed()); non_finalized_headers_subscription(&api).await?; finalized_headers_subscription(&api).await?; @@ -129,5 +216,25 @@ async fn light_client_testing() -> Result<(), subxt::Error> { dynamic_constant_query(&api).await?; dynamic_events(&api).await?; + tracing::trace!("Light complete testing took {:?}", now.elapsed()); + Ok(()) +} + +/// Backend type for light client testing. +enum BackendType { + /// Use the unstable backend (ie chainHead). + Unstable, + /// Use the legacy backend. + Legacy, +} + +#[tokio::test] +async fn light_client_testing() -> Result<(), subxt::Error> { + tracing_subscriber::fmt::init(); + + // Run light client test with both backends. + run_test(BackendType::Unstable).await?; + run_test(BackendType::Legacy).await?; + Ok(()) }