Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions prdoc/pr_10866.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json

title: Extend remote externalities `Client` and child storage query unit tests

doc:
- audience: Runtime Dev
description: |
This PR fixes a test failure in the remote externalities crate concerning optionality of child key loading.
It also adds follow-up tests to #10779 to check `Client` construction from valid/invalid URIs.

crates:
- name: frame-remote-externalities
bump: patch
11 changes: 8 additions & 3 deletions substrate/utils/frame/remote-externalities/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,18 @@ sp-state-machine = { workspace = true, default-features = true }
spinners = { workspace = true }
substrate-rpc-client = { workspace = true, default-features = true }
tokio = { features = [
"macros",
"rt-multi-thread",
"sync",
"macros",
"rt-multi-thread",
"sync",
], workspace = true, default-features = true }

[dev-dependencies]
frame-support = { workspace = true, default-features = true }
jsonrpsee = { features = [
"http-client",
"ws-client",
"server",
], workspace = true }
sp-tracing = { workspace = true, default-features = true }

[features]
Expand Down
49 changes: 49 additions & 0 deletions substrate/utils/frame/remote-externalities/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,52 @@ impl ConnectionManager {
client.recreate(failed.version).await;
}
}

#[cfg(test)]
mod tests {
use super::*;

/// Start a local WS server in a random port.
///
/// Used to test WS client building for remote externality client initialization.
async fn start_local_ws_server() -> (String, jsonrpsee::server::ServerHandle) {
let server = jsonrpsee::server::ServerBuilder::default()
.build("127.0.0.1:0")
.await
.expect("local ws server should start");

let addr = server.local_addr().expect("local ws server should have a local addr");
let handle = server.start(jsonrpsee::RpcModule::new(()));

println!("local ws server started at {addr}");
(format!("ws://{addr}"), handle)
}

#[tokio::test]
async fn can_init_client() {
let (uri, _server) = start_local_ws_server().await;

let client = Client::new(uri.clone()).await;
assert!(client.is_some(), "Client should initialize successfully with valid WebSocket URI");

// Test create_ws_client directly
let ws_client = Client::create_ws_client(&uri).await;
assert!(ws_client.is_ok(), "create_ws_client should succeed with valid URI");
}

#[tokio::test]
async fn cannot_init_client_with_invalid_uri() {
// HTTP/HTTPS are invalid, only WS/WSS are supported
assert!(Client::new("http://try-runtime.polkadot.io:443").await.is_none());
assert!(Client::create_ws_client("http://try-runtime.polkadot.io:443").await.is_err());

assert!(Client::new("https://try-runtime.polkadot.io:443").await.is_none());
assert!(Client::create_ws_client("https://try-runtime.polkadot.io:443").await.is_err());

// Invalid URIs/garbage
assert!(Client::new("wsss://try-runtime.polkadot.io:443").await.is_none());
assert!(Client::create_ws_client("wsss://try-runtime.polkadot.io:443").await.is_err());
assert!(Client::new("garbage").await.is_none());
assert!(Client::create_ws_client("garbage").await.is_err());
}
}
2 changes: 1 addition & 1 deletion substrate/utils/frame/remote-externalities/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use std::{

use crate::Result;

pub(crate) const DEFAULT_HTTP_ENDPOINT: &str = "https://try-runtime.polkadot.io:443";
pub(crate) const DEFAULT_HTTP_ENDPOINT: &str = "wss://try-runtime.polkadot.io:443";
pub(crate) type SnapshotVersion = Compact<u16>;
pub(crate) const SNAPSHOT_VERSION: SnapshotVersion = Compact(4);

Expand Down
79 changes: 70 additions & 9 deletions substrate/utils/frame/remote-externalities/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use client::{with_timeout, Client, ConnectionManager, RPC_TIMEOUT};
use codec::Encode;
use config::Snapshot;
#[cfg(all(test, feature = "remote-test"))]
use config::DEFAULT_HTTP_ENDPOINT;
use config::DEFAULT_HTTP_ENDPOINT as DEFAULT_WS_ENDPOINT;
use indicatif::{ProgressBar, ProgressStyle};
use jsonrpsee::core::params::ArrayParams;
use log::*;
Expand Down Expand Up @@ -94,7 +94,7 @@ impl<B: BlockT> DerefMut for RemoteExternalities<B> {
}
}

/// Builder for remote-externalities.
/// Builder for [`RemoteExternalities`].
#[derive(Clone)]
pub struct Builder<B: BlockT> {
/// Custom key-pairs to be injected into the final externalities. The *hashed* keys and values
Expand Down Expand Up @@ -1177,7 +1177,7 @@ mod remote_tests {
use std::{env, os::unix::fs::MetadataExt, path::Path};

fn endpoint() -> String {
env::var("TEST_WS").unwrap_or_else(|_| DEFAULT_HTTP_ENDPOINT.to_string())
env::var("TEST_WS").unwrap_or_else(|_| DEFAULT_WS_ENDPOINT.to_string())
}

#[tokio::test]
Expand Down Expand Up @@ -1255,7 +1255,11 @@ mod remote_tests {
const CACHE: &'static str = "snapshot_retains_storage";
init_logger();

// create an ext with children keys
// This test does not rely on the remote endpoint having child tries. A synthetic child
// storage entry is inserted locally and then asserted on.
use sp_state_machine::Backend;

// Create an externality with child trie scraping enabled.
let mut child_ext = Builder::<Block>::new()
.mode(Mode::Online(OnlineConfig {
transport_uris: vec![endpoint().clone()],
Expand All @@ -1268,7 +1272,7 @@ mod remote_tests {
.await
.unwrap();

// create an ext without children keys
// Create an externality without looking for children keys
let mut ext = Builder::<Block>::new()
.mode(Mode::Online(OnlineConfig {
transport_uris: vec![endpoint().clone()],
Expand All @@ -1281,11 +1285,29 @@ mod remote_tests {
.await
.unwrap();

// there should be more keys in the child ext.
assert!(
child_ext.as_backend().backend_storage().keys().len() >
ext.as_backend().backend_storage().keys().len()
// Generate artificial child storage entry, to ensure the test's assertion is valid.
let child_info = sp_core::storage::ChildInfo::new_default(b"test_child");
let child_key: Vec<u8> = b"k1".to_vec();
let child_value: Vec<u8> = b"v1".to_vec();

// Record the size of the underlying trie DB before inserting the child entry.
let child_db_keys_before = child_ext.as_backend().backend_storage().keys().len();

// Insert child storage only into `child_ext`.
child_ext.insert_child(child_info.clone(), child_key.clone(), child_value.clone());

// Assert: the child key exists only in the externalities where it is inserted.
let child_backend = child_ext.as_backend();
let backend = ext.as_backend();
assert_eq!(
child_backend.child_storage(&child_info, &child_key).unwrap(),
Some(child_value)
);
assert_eq!(backend.child_storage(&child_info, &child_key).unwrap(), None);

// Structural assertion: insertion increased the underlying DB entry count.
let child_db_keys_after = child_backend.backend_storage().keys().len();
assert!(child_db_keys_after > child_db_keys_before);
}

#[tokio::test]
Expand Down Expand Up @@ -1549,4 +1571,43 @@ mod remote_tests {
"✅ Storage root verification successful! All keys were fetched correctly."
);
}

#[tokio::test]
async fn builder_fails_with_invalid_transport_uris() {
init_logger();

// Using HTTP/HTTPS URIs should fail because Client::new() returns None for non-WS URIs
let result = Builder::<Block>::new()
.mode(Mode::Online(OnlineConfig {
transport_uris: vec!["http://try-runtime.polkadot.io:443".to_string()],
pallets: vec!["Proxy".to_owned()],
..Default::default()
}))
.build()
.await;

match result {
Err(e) => assert_eq!(e, "At least one client must be provided"),
Ok(_) => panic!("Expected error but got success"),
}

// Multiple invalid URIs should also fail
let result = Builder::<Block>::new()
.mode(Mode::Online(OnlineConfig {
transport_uris: vec![
"http://try-runtime.polkadot.io:443".to_string(),
"https://try-runtime.polkadot.io:443".to_string(),
"garbage".to_string(),
],
pallets: vec!["Proxy".to_owned()],
..Default::default()
}))
.build()
.await;

match result {
Err(e) => assert_eq!(e, "At least one client must be provided"),
Ok(_) => panic!("Expected error but got success"),
}
}
}
Loading