Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remote-externalities: store block header in snapshot #4349

Merged
merged 16 commits into from
May 12, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .config/lychee.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ exclude = [
"https://github.com/paritytech/polkadot-sdk/substrate/frame/timestamp",
"https://github.com/paritytech/substrate/frame/fast-unstake",
"https://github.com/zkcrypto/bls12_381/blob/e224ad4ea1babfc582ccd751c2bf128611d10936/src/test-data/mod.rs",
"https://polkadot-try-runtime-node.parity-chains.parity.io/",
"https://polkadot.network/the-path-of-a-parachain-block/",
"https://research.web3.foundation/en/latest/polkadot/BABE/Babe/#6-practical-results",
"https://research.web3.foundation/en/latest/polkadot/NPoS/3.%20Balancing.html",
Expand Down
9 changes: 9 additions & 0 deletions prdoc/pr_4349.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
title: "Store Header in RemoteExt Snapshot"

doc:
- audience: Runtime Dev
description: Replaces the block hash in the RemoteExt snapshot with the block header.

crates:
- name: frame-remote-externalities
bump: major
90 changes: 51 additions & 39 deletions substrate/utils/frame/remote-externalities/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use sp_core::{
},
};
use sp_runtime::{
traits::{Block as BlockT, Hash, HashingFor},
traits::{Block as BlockT, HashingFor},
StateVersion,
};
use sp_state_machine::TestExternalities;
Expand All @@ -58,37 +58,39 @@ type ChildKeyValues = Vec<(ChildInfo, Vec<KeyValue>)>;
type SnapshotVersion = Compact<u16>;

const LOG_TARGET: &str = "remote-ext";
const DEFAULT_HTTP_ENDPOINT: &str = "https://rpc.polkadot.io:443";
const SNAPSHOT_VERSION: SnapshotVersion = Compact(3);
const DEFAULT_HTTP_ENDPOINT: &str = "https://polkadot-try-runtime-node.parity-chains.parity.io:443";
ggwpez marked this conversation as resolved.
Show resolved Hide resolved
const SNAPSHOT_VERSION: SnapshotVersion = Compact(4);

/// The snapshot that we store on disk.
#[derive(Decode, Encode)]
struct Snapshot<H> {
struct Snapshot<B: BlockT> {
snapshot_version: SnapshotVersion,
state_version: StateVersion,
block_hash: H,
// <Vec<Key, (Value, MemoryDbRefCount)>>
raw_storage: Vec<(Vec<u8>, (Vec<u8>, i32))>,
storage_root: H,
// The storage root of the state. This may vary from the storage root in the header, if not the
// entire state was fetched.
storage_root: B::Hash,
header: B::Header,
liamaharon marked this conversation as resolved.
Show resolved Hide resolved
}

impl<H: Decode> Snapshot<H> {
impl<B: BlockT> Snapshot<B> {
pub fn new(
state_version: StateVersion,
block_hash: H,
raw_storage: Vec<(Vec<u8>, (Vec<u8>, i32))>,
storage_root: H,
storage_root: B::Hash,
header: B::Header,
) -> Self {
Self {
snapshot_version: SNAPSHOT_VERSION,
state_version,
block_hash,
raw_storage,
storage_root,
header,
}
}

fn load(path: &PathBuf) -> Result<Snapshot<H>, &'static str> {
fn load(path: &PathBuf) -> Result<Snapshot<B>, &'static str> {
let bytes = fs::read(path).map_err(|_| "fs::read failed.")?;
// The first item in the SCALE encoded struct bytes is the snapshot version. We decode and
// check that first, before proceeding to decode the rest of the snapshot.
Expand All @@ -105,21 +107,21 @@ impl<H: Decode> Snapshot<H> {

/// An externalities that acts exactly the same as [`sp_io::TestExternalities`] but has a few extra
/// bits and pieces to it, and can be loaded remotely.
pub struct RemoteExternalities<H: Hash> {
pub struct RemoteExternalities<B: BlockT> {
/// The inner externalities.
pub inner_ext: TestExternalities<H>,
/// The block hash with which we created this externality env.
pub block_hash: H::Out,
pub inner_ext: TestExternalities<HashingFor<B>>,
/// The block header which we created this externality env.
pub header: B::Header,
}

impl<H: Hash> Deref for RemoteExternalities<H> {
type Target = TestExternalities<H>;
impl<B: BlockT> Deref for RemoteExternalities<B> {
type Target = TestExternalities<HashingFor<B>>;
fn deref(&self) -> &Self::Target {
&self.inner_ext
}
}

impl<H: Hash> DerefMut for RemoteExternalities<H> {
impl<B: BlockT> DerefMut for RemoteExternalities<B> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner_ext
}
Expand Down Expand Up @@ -859,7 +861,7 @@ where
}
}

impl<B: BlockT + DeserializeOwned> Builder<B>
impl<B: BlockT> Builder<B>
where
B::Hash: DeserializeOwned,
B::Header: DeserializeOwned,
Expand Down Expand Up @@ -1030,6 +1032,21 @@ where
Ok(())
}

async fn load_header(&self) -> Result<B::Header, &'static str> {
let retry_strategy =
FixedInterval::new(Self::KEYS_PAGE_RETRY_INTERVAL).take(Self::MAX_RETRIES);
let get_header_closure = || {
ChainApi::<(), _, B::Header, ()>::header(
self.as_online().rpc_client(),
Some(self.as_online().at_expected()),
)
};
Retry::spawn(retry_strategy, get_header_closure)
.await
.map_err(|_| "Failed to fetch header for block from network")?
.ok_or("Network returned None block header")
}

/// Load the data from a remote server. The main code path is calling into `load_top_remote` and
/// `load_child_remote`.
///
Expand Down Expand Up @@ -1058,13 +1075,11 @@ where
// If we need to save a snapshot, save the raw storage and root hash to the snapshot.
if let Some(path) = self.as_online().state_snapshot.clone().map(|c| c.path) {
let (raw_storage, storage_root) = pending_ext.into_raw_snapshot();
let snapshot = Snapshot::<B::Hash>::new(
let snapshot = Snapshot::<B>::new(
state_version,
self.as_online()
.at
.expect("set to `Some` in `init_remote_client`; must be called before; qed"),
raw_storage.clone(),
storage_root,
self.load_header().await?,
);
let encoded = snapshot.encode();
log::info!(
Expand All @@ -1086,22 +1101,21 @@ where
Ok(pending_ext)
}

async fn do_load_remote(&mut self) -> Result<RemoteExternalities<HashingFor<B>>, &'static str> {
async fn do_load_remote(&mut self) -> Result<RemoteExternalities<B>, &'static str> {
self.init_remote_client().await?;
let block_hash = self.as_online().at_expected();
let inner_ext = self.load_remote_and_maybe_save().await?;
Ok(RemoteExternalities { block_hash, inner_ext })
Ok(RemoteExternalities { header: self.load_header().await?, inner_ext })
}

fn do_load_offline(
&mut self,
config: OfflineConfig,
) -> Result<RemoteExternalities<HashingFor<B>>, &'static str> {
) -> Result<RemoteExternalities<B>, &'static str> {
let mut sp = Spinner::with_timer(Spinners::Dots, "Loading snapshot...".into());
let start = Instant::now();
info!(target: LOG_TARGET, "Loading snapshot from {:?}", &config.state_snapshot.path);
let Snapshot { snapshot_version: _, block_hash, state_version, raw_storage, storage_root } =
Snapshot::<B::Hash>::load(&config.state_snapshot.path)?;
let Snapshot { snapshot_version: _, header, state_version, raw_storage, storage_root } =
Snapshot::<B>::load(&config.state_snapshot.path)?;

let inner_ext = TestExternalities::from_raw_snapshot(
raw_storage,
Expand All @@ -1110,12 +1124,10 @@ where
);
sp.stop_with_message(format!("✅ Loaded snapshot ({:.2}s)", start.elapsed().as_secs_f32()));

Ok(RemoteExternalities { inner_ext, block_hash })
Ok(RemoteExternalities { inner_ext, header })
}

pub(crate) async fn pre_build(
mut self,
) -> Result<RemoteExternalities<HashingFor<B>>, &'static str> {
pub(crate) async fn pre_build(mut self) -> Result<RemoteExternalities<B>, &'static str> {
let mut ext = match self.mode.clone() {
Mode::Offline(config) => self.do_load_offline(config)?,
Mode::Online(_) => self.do_load_remote().await?,
Expand Down Expand Up @@ -1154,7 +1166,7 @@ where
}

// Public methods
impl<B: BlockT + DeserializeOwned> Builder<B>
impl<B: BlockT> Builder<B>
where
B::Hash: DeserializeOwned,
B::Header: DeserializeOwned,
Expand Down Expand Up @@ -1191,7 +1203,7 @@ where
self
}

pub async fn build(self) -> Result<RemoteExternalities<HashingFor<B>>, &'static str> {
pub async fn build(self) -> Result<RemoteExternalities<B>, &'static str> {
let mut ext = self.pre_build().await?;
ext.commit_all().unwrap();

Expand Down Expand Up @@ -1226,7 +1238,7 @@ mod tests {
init_logger();
Builder::<Block>::new()
.mode(Mode::Offline(OfflineConfig {
state_snapshot: SnapshotConfig::new("test_data/proxy_test"),
state_snapshot: SnapshotConfig::new("test_data/test.snap"),
}))
.build()
.await
Expand All @@ -1241,7 +1253,7 @@ mod tests {
// get the first key from the snapshot file.
let some_key = Builder::<Block>::new()
.mode(Mode::Offline(OfflineConfig {
state_snapshot: SnapshotConfig::new("test_data/proxy_test"),
state_snapshot: SnapshotConfig::new("test_data/test.snap"),
}))
.build()
.await
Expand All @@ -1255,7 +1267,7 @@ mod tests {

Builder::<Block>::new()
.mode(Mode::Offline(OfflineConfig {
state_snapshot: SnapshotConfig::new("test_data/proxy_test"),
state_snapshot: SnapshotConfig::new("test_data/test.snap"),
liamaharon marked this conversation as resolved.
Show resolved Hide resolved
}))
.blacklist_hashed_key(&some_key)
.build()
Expand Down Expand Up @@ -1341,7 +1353,7 @@ mod remote_tests {
.await
.unwrap();

assert_eq!(ext.block_hash, cached_ext.block_hash);
assert_eq!(ext.header.hash(), cached_ext.header.hash());
}

#[tokio::test]
Expand Down
Binary file not shown.
Binary file not shown.
Loading