diff --git a/full-node/src/run.rs b/full-node/src/run.rs index 1f45f985ad..abb73700e4 100644 --- a/full-node/src/run.rs +++ b/full-node/src/run.rs @@ -21,7 +21,7 @@ use futures::{channel::oneshot, prelude::*}; use smoldot::{ chain, chain_spec, database::full_sqlite, - header, + executor, header, identity::keystore, informant::HashDisplay, libp2p::{ @@ -742,6 +742,27 @@ async fn open_database( // The database doesn't exist or is empty. full_sqlite::DatabaseOpen::Empty(empty) => { + let genesis_storage = chain_spec.genesis_storage().into_genesis_items().unwrap(); // TODO: return error instead + + // In order to determine the state_version of the genesis block, we need to compile + // the runtime. + // TODO: return errors instead of panicking + let state_version = executor::host::HostVmPrototype::new(executor::host::Config { + module: genesis_storage.value(b":code").unwrap(), + heap_pages: executor::storage_heap_pages_to_value( + genesis_storage.value(b":heappages"), + ) + .unwrap(), + exec_hint: executor::vm::ExecHint::Oneshot, + allow_unresolved_imports: true, + }) + .unwrap() + .runtime_version() + .decode() + .state_version + .map(|v| u8::from(v)) + .unwrap_or(0); + // The finalized block is the genesis block. As such, it has an empty body and // no justification. let database = empty @@ -749,11 +770,8 @@ async fn open_database( genesis_chain_information, iter::empty(), None, - chain_spec - .genesis_storage() - .into_genesis_items() - .unwrap() // TODO: return error instead - .iter(), + genesis_storage.iter(), + state_version, ) .unwrap(); (database, false) diff --git a/full-node/src/run/consensus_service.rs b/full-node/src/run/consensus_service.rs index 445af01719..a2b0c4f81c 100644 --- a/full-node/src/run/consensus_service.rs +++ b/full-node/src/run/consensus_service.rs @@ -38,7 +38,7 @@ use smoldot::{ informant::HashDisplay, libp2p, network::{self, protocol::BlockData}, - sync::all, + sync::all::{self, TrieEntryVersion}, }; use std::{ collections::BTreeMap, @@ -128,7 +128,14 @@ impl ConsensusService { best_block_number, finalized_block_storage, finalized_chain_information, - ): (_, _, _, _, BTreeMap, Vec>, _) = config + ): ( + _, + _, + _, + _, + BTreeMap, (Vec, TrieEntryVersion)>, + _, + ) = config .database .with_database({ let block_number_bytes = config.block_number_bytes; @@ -153,9 +160,17 @@ impl ConsensusService { ) .unwrap() .number; - let finalized_block_storage = database + let finalized_block_storage: Vec<(Vec, Vec, u8)> = database .finalized_block_storage_top_trie(&finalized_block_hash) .unwrap(); + // TODO: we copy all entries; it could be more optimal to have a custom implementation of FromIterator that directly does the conversion? + let finalized_block_storage = finalized_block_storage + .into_iter() + .map(|(k, val, vers)| { + let vers = TrieEntryVersion::try_from(vers).unwrap(); // TODO: don't unwrap + (k, (val, vers)) + }) + .collect(); let finalized_chain_information = database .to_chain_information(&finalized_block_hash) .unwrap(); @@ -223,11 +238,11 @@ impl ConsensusService { // Builds the runtime of the finalized block. // Assumed to always be valid, otherwise the block wouldn't have been saved in the // database, hence the large number of unwraps here. - let module = finalized_block_storage.get(&b":code"[..]).unwrap(); + let (module, _) = finalized_block_storage.get(&b":code"[..]).unwrap(); let heap_pages = executor::storage_heap_pages_to_value( finalized_block_storage .get(&b":heappages"[..]) - .map(|v| &v[..]), + .map(|(v, _)| &v[..]), ) .unwrap(); executor::host::HostVmPrototype::new(executor::host::Config { @@ -329,7 +344,7 @@ struct SyncBackground { // While reading the storage from the database is an option, doing so considerably slows down /// the verification, and also makes it impossible to insert blocks in the database in /// parallel of this verification. - finalized_block_storage: BTreeMap, Vec>, + finalized_block_storage: BTreeMap, (Vec, TrieEntryVersion)>, sync_state: Arc>, @@ -754,10 +769,11 @@ impl SyncBackground { best_block_storage_access.get(key.as_ref(), || { self.finalized_block_storage .get(key.as_ref()) - .map(|v| &v[..]) + .map(|(val, vers)| (&val[..], *vers)) }) }; - block_authoring = get.inject_value(value.map(iter::once)); + block_authoring = + get.inject_value(value.map(|(val, vers)| (iter::once(val), vers))); continue; } author::build::BuilderAuthoring::NextKey(_) => { @@ -1137,7 +1153,7 @@ impl SyncBackground { let value = self .finalized_block_storage .get(req.key().as_ref()) - .map(|v| &v[..]); + .map(|(val, vers)| (&val[..], *vers)); verify = req.inject_value(value); } all::BlockVerification::FinalizedStorageNextKey(req) => { @@ -1212,7 +1228,7 @@ impl SyncBackground { // TODO: maybe write in a separate task? but then we can't access the finalized storage immediately after? for block in &finalized_blocks { - for (key, value) in block + for (key, value, ()) in block .full .as_ref() .unwrap() @@ -1220,8 +1236,13 @@ impl SyncBackground { .diff_iter_unordered() { if let Some(value) = value { - self.finalized_block_storage - .insert(key.to_owned(), value.to_owned()); + self.finalized_block_storage.insert( + key.to_owned(), + ( + value.to_owned(), + block.full.as_ref().unwrap().state_trie_version, + ), + ); } else { let _was_there = self.finalized_block_storage.remove(key); // TODO: if a block inserts a new value, then removes it in the next block, the key will remain in `finalized_block_storage`; either solve this or document this @@ -1327,7 +1348,9 @@ async fn database_blocks( .as_ref() .unwrap() .storage_top_trie_changes - .diff_iter_unordered(), + .diff_iter_unordered() + .map(|(k, v, ())| (k, v)), + u8::from(block.full.as_ref().unwrap().state_trie_version), ); match result { diff --git a/lib/src/author/build.rs b/lib/src/author/build.rs index 24bfc2e269..8d9fb27a0c 100644 --- a/lib/src/author/build.rs +++ b/lib/src/author/build.rs @@ -28,6 +28,8 @@ use crate::{ use alloc::vec::Vec; use core::{num::NonZeroU64, time::Duration}; +pub use runtime::TrieEntryVersion; + /// Configuration for a block generation. pub struct Config<'a, TLocAuth> { /// Consensus-specific configuration. @@ -310,7 +312,7 @@ impl StorageGet { /// Injects the corresponding storage value. pub fn inject_value( self, - value: Option>>, + value: Option<(impl Iterator>, TrieEntryVersion)>, ) -> BuilderAuthoring { self.1.with_runtime_inner(self.0.inject_value(value)) } diff --git a/lib/src/author/runtime.rs b/lib/src/author/runtime.rs index 35621e64d5..0dcac6c24d 100644 --- a/lib/src/author/runtime.rs +++ b/lib/src/author/runtime.rs @@ -58,6 +58,8 @@ use crate::{ use alloc::{borrow::ToOwned as _, string::String, vec::Vec}; use core::{iter, mem}; +pub use runtime_host::TrieEntryVersion; + /// Configuration for a block generation. pub struct Config<'a> { /// Number of bytes used to encode block numbers in the header. @@ -110,6 +112,9 @@ pub struct Success { pub parent_runtime: host::HostVmPrototype, /// List of changes to the storage top trie that the block performs. pub storage_top_trie_changes: storage_diff::StorageDiff, + /// State trie version indicated by the runtime. All the storage changes indicated by + /// [`Success::storage_top_trie_changes`] should store this version alongside with them. + pub state_trie_version: TrieEntryVersion, /// List of changes to the off-chain storage that this block performs. pub offchain_storage_changes: storage_diff::StorageDiff, /// Cache used for calculating the top trie root of the new block. @@ -428,6 +433,7 @@ impl BlockBuild { body: shared.block_body, parent_runtime: success.virtual_machine.into_prototype(), storage_top_trie_changes: success.storage_top_trie_changes, + state_trie_version: success.state_trie_version, offchain_storage_changes: success.offchain_storage_changes, top_trie_root_calculation_cache: success.top_trie_root_calculation_cache, logs: shared.logs, @@ -595,7 +601,10 @@ impl StorageGet { } /// Injects the corresponding storage value. - pub fn inject_value(self, value: Option>>) -> BlockBuild { + pub fn inject_value( + self, + value: Option<(impl Iterator>, TrieEntryVersion)>, + ) -> BlockBuild { BlockBuild::from_inner(self.0.inject_value(value), self.1) } } diff --git a/lib/src/author/runtime/tests.rs b/lib/src/author/runtime/tests.rs index 88429e5a51..47bb0401a0 100644 --- a/lib/src/author/runtime/tests.rs +++ b/lib/src/author/runtime/tests.rs @@ -62,7 +62,7 @@ fn block_building_works() { .iter() .find(|(k, _)| *k == get.key().as_ref()) .map(|(_, v)| iter::once(v)); - builder = get.inject_value(value); + builder = get.inject_value(value.map(|v| (v, super::TrieEntryVersion::V0))); } super::BlockBuild::NextKey(_) => unimplemented!(), // Not needed for this test. super::BlockBuild::PrefixKeys(prefix) => { diff --git a/lib/src/chain/blocks_tree/verify.rs b/lib/src/chain/blocks_tree/verify.rs index 66aa0e4399..b571a3b2d1 100644 --- a/lib/src/chain/blocks_tree/verify.rs +++ b/lib/src/chain/blocks_tree/verify.rs @@ -36,6 +36,8 @@ use super::{ use alloc::boxed::Box; use core::cmp::Ordering; +pub use verify::header_body::TrieEntryVersion; + impl NonFinalizedTree { /// Verifies the given block. /// @@ -600,6 +602,7 @@ impl VerifyContext { parent_runtime: success.parent_runtime, new_runtime: success.new_runtime, storage_top_trie_changes: success.storage_top_trie_changes, + state_trie_version: success.state_trie_version, offchain_storage_changes: success.offchain_storage_changes, top_trie_root_calculation_cache: success.top_trie_root_calculation_cache, insert: BodyInsert { @@ -845,6 +848,10 @@ pub enum BodyVerifyStep2 { new_runtime: Option, /// List of changes to the storage top trie that the block performs. storage_top_trie_changes: storage_diff::StorageDiff, + /// State trie version indicated by the runtime. All the storage changes indicated by + /// [`BodyVerifyStep2::Finished::storage_top_trie_changes`] should store this version + /// alongside with them. + state_trie_version: TrieEntryVersion, /// List of changes to the off-chain storage that this block performs. offchain_storage_changes: storage_diff::StorageDiff, /// Cache of calculation for the storage trie of the best block. @@ -940,7 +947,7 @@ impl StorageGet { /// Injects the corresponding storage value. pub fn inject_value( self, - value: Option>>, + value: Option<(impl Iterator>, TrieEntryVersion)>, ) -> BodyVerifyStep2 { let inner = self.inner.inject_value(value); self.context.with_body_verify(inner) diff --git a/lib/src/chain_spec.rs b/lib/src/chain_spec.rs index 0567773c34..03c3e964ea 100644 --- a/lib/src/chain_spec.rs +++ b/lib/src/chain_spec.rs @@ -116,12 +116,11 @@ impl ChainSpec { let mut chain_information_build = build::ChainInformationBuild::new(build::Config { finalized_block_header: build::ConfigFinalizedBlockHeader::Genesis { state_trie_root_hash: { - let state_version = match vm_prototype.runtime_version().decode().state_version - { - Some(0) | None => trie::TrieEntryVersion::V0, - Some(1) => trie::TrieEntryVersion::V1, - Some(_) => return Err(FromGenesisStorageError::UnknownStateVersion), - }; + let state_version = vm_prototype + .runtime_version() + .decode() + .state_version + .unwrap_or(trie::TrieEntryVersion::V0); match self.genesis_storage() { GenesisStorage::TrieRootHash(hash) => *hash, @@ -144,7 +143,7 @@ impl ChainSpec { ) => { let key: alloc::vec::Vec = val.key().collect(); let value = genesis_storage.value(&key[..]); - calculation = val.inject(state_version, value); + calculation = val.inject(value.map(move |v| (v, state_version))); } } } @@ -496,8 +495,6 @@ pub enum FromGenesisStorageError { /// Error when initializing the virtual machine. #[display(fmt = "Error when initializing the virtual machine: {}", _0)] VmInitialization(executor::host::NewErr), - /// State version in runtime specification is not supported. - UnknownStateVersion, /// Chain specification doesn't contain the list of storage items. UnknownStorageItems, } diff --git a/lib/src/database/full_sqlite.rs b/lib/src/database/full_sqlite.rs index d037c735bc..977c6b2e61 100644 --- a/lib/src/database/full_sqlite.rs +++ b/lib/src/database/full_sqlite.rs @@ -317,6 +317,7 @@ impl SqliteFullDatabase { body: impl ExactSizeIterator>, storage_top_trie_changes: impl Iterator, Option>)> + Clone, + trie_entries_version: u8, ) -> Result<(), InsertError> { // Calculate the hash of the new best block. let block_hash = header::hash_from_scale_encoded_header(scale_encoded_header); @@ -375,7 +376,7 @@ impl SqliteFullDatabase { // Insert the storage changes. let mut statement = connection - .prepare("INSERT INTO non_finalized_changes(hash, key, value) VALUES (?, ?, ?)") + .prepare("INSERT INTO non_finalized_changes(hash, key, value, trie_entry_version) VALUES (?, ?, ?, ?)") .unwrap(); for (key, value) in storage_top_trie_changes { statement = statement @@ -384,10 +385,14 @@ impl SqliteFullDatabase { .bind(2, key.as_ref()) .unwrap(); if let Some(value) = value { - statement = statement.bind(3, value.as_ref()).unwrap(); + statement = statement + .bind(3, value.as_ref()) + .unwrap() + .bind(4, i64::from(trie_entries_version)) + .unwrap(); } else { // Binds NULL. - statement = statement.bind(3, ()).unwrap(); + statement = statement.bind(3, ()).unwrap().bind(4, ()).unwrap(); } statement.next().unwrap(); statement = statement.reset().unwrap(); @@ -547,8 +552,8 @@ impl SqliteFullDatabase { let mut statement = connection .prepare( - "INSERT OR REPLACE INTO finalized_storage_top_trie(key, value) - SELECT key, value + "INSERT OR REPLACE INTO finalized_storage_top_trie(key, value, trie_entry_version) + SELECT key, value, trie_entry_version FROM non_finalized_changes WHERE non_finalized_changes.hash = ? AND non_finalized_changes.value IS NOT NULL", ) @@ -672,8 +677,8 @@ impl SqliteFullDatabase { /// [`FinalizedAccessError::Obsolete`] error is returned. /// /// The return value must implement the `FromIterator` trait, being passed an iterator that - /// produces tuples of keys and values. - pub fn finalized_block_storage_top_trie, Vec)>>( + /// produces tuples of keys, values, and trie entry version. + pub fn finalized_block_storage_top_trie, Vec, u8)>>( &self, finalized_block_hash: &[u8; 32], ) -> Result { @@ -684,7 +689,7 @@ impl SqliteFullDatabase { } let mut statement = connection - .prepare(r#"SELECT key, value FROM finalized_storage_top_trie"#) + .prepare(r#"SELECT key, value, trie_entry_version FROM finalized_storage_top_trie"#) .map_err(InternalError) .map_err(CorruptedError::Internal) .map_err(AccessError::Corrupted) @@ -697,14 +702,24 @@ impl SqliteFullDatabase { let key = statement.read::>(0).unwrap(); let value = statement.read::>(1).unwrap(); - Some((key, value)) + let trie_entry_version = match u8::try_from(statement.read::(2).unwrap()) + .map_err(|_| CorruptedError::InvalidTrieEntryVersion) + .map_err(AccessError::Corrupted) + .map_err(FinalizedAccessError::Access) + { + Ok(n) => n, + Err(err) => return Some(Err(err)), + }; + + Some(Ok((key, value, trie_entry_version))) }) - .collect(); + .collect::>()?; Ok(out) } - /// Returns the value associated to a key in the storage of the finalized block. + /// Returns the value associated to a key in the storage of the finalized block, and the trie + /// entry version. /// /// In order to avoid race conditions, the known finalized block hash must be passed as /// parameter. If the finalized block in the database doesn't match the hash passed as @@ -714,7 +729,7 @@ impl SqliteFullDatabase { &self, finalized_block_hash: &[u8; 32], key: &[u8], - ) -> Result>, FinalizedAccessError> { + ) -> Result, u8)>, FinalizedAccessError> { let connection = self.database.lock(); if finalized_hash(&connection)? != *finalized_block_hash { @@ -722,7 +737,9 @@ impl SqliteFullDatabase { } let mut statement = connection - .prepare(r#"SELECT value FROM finalized_storage_top_trie WHERE key = ?"#) + .prepare( + r#"SELECT value, trie_entry_version FROM finalized_storage_top_trie WHERE key = ?"#, + ) .map_err(InternalError) .map_err(CorruptedError::Internal) .map_err(AccessError::Corrupted) @@ -740,7 +757,13 @@ impl SqliteFullDatabase { .map_err(CorruptedError::Internal) .map_err(AccessError::Corrupted) .map_err(FinalizedAccessError::Access)?; - Ok(Some(value)) + + let trie_entry_version = u8::try_from(statement.read::(1).unwrap()) + .map_err(|_| CorruptedError::InvalidTrieEntryVersion) + .map_err(AccessError::Corrupted) + .map_err(FinalizedAccessError::Access)?; + + Ok(Some((value, trie_entry_version))) } /// Returns the key in the storage of the finalized block that immediately follows the key @@ -926,6 +949,8 @@ pub enum CorruptedError { ConsensusAlgorithmMix, /// The information about a Babe epoch found in the database has failed to decode. InvalidBabeEpochInformation, + /// The version information about a storage entry has failed to decode. + InvalidTrieEntryVersion, #[display(fmt = "Internal error: {}", _0)] Internal(InternalError), } diff --git a/lib/src/database/full_sqlite/open.rs b/lib/src/database/full_sqlite/open.rs index 99a5876db2..b3186a4a8c 100644 --- a/lib/src/database/full_sqlite/open.rs +++ b/lib/src/database/full_sqlite/open.rs @@ -141,7 +141,8 @@ Storage at the highest block that is considered finalized. */ CREATE TABLE IF NOT EXISTS finalized_storage_top_trie( key BLOB NOT NULL PRIMARY KEY, - value BLOB NOT NULL + value BLOB NOT NULL, + trie_entry_version INTEGER NOT NULL ); /* @@ -155,8 +156,11 @@ CREATE TABLE IF NOT EXISTS non_finalized_changes( -- `value` is NULL if the block removes the key from the storage, and NON-NULL if it inserts -- or replaces the value at the key. value BLOB, + -- Same NULL-ness remark as for `value` + trie_entry_version INTEGER, UNIQUE(hash, key), CHECK(length(hash) == 32), + CHECK((trie_entry_version IS NULL AND value IS NULL) OR (trie_entry_version IS NOT NULL AND value IS NOT NULL)), FOREIGN KEY (hash) REFERENCES blocks(hash) ON UPDATE CASCADE ON DELETE CASCADE ); @@ -273,6 +277,7 @@ impl DatabaseEmpty { finalized_block_body: impl ExactSizeIterator, finalized_block_justification: Option>, finalized_block_storage_top_trie_entries: impl Iterator + Clone, + finalized_block_state_version: u8, ) -> Result { let chain_information = chain_information.into(); @@ -291,10 +296,16 @@ impl DatabaseEmpty { { let mut statement = self .database - .prepare("INSERT INTO finalized_storage_top_trie(key, value) VALUES(?, ?)") + .prepare("INSERT INTO finalized_storage_top_trie(key, value, trie_entry_version) VALUES(?, ?, ?)") .unwrap(); for (key, value) in finalized_block_storage_top_trie_entries { - statement = statement.bind(1, key).unwrap().bind(2, value).unwrap(); + statement = statement + .bind(1, key) + .unwrap() + .bind(2, value) + .unwrap() + .bind(3, i64::from(finalized_block_state_version)) + .unwrap(); statement.next().unwrap(); statement = statement.reset().unwrap(); } diff --git a/lib/src/executor/host.rs b/lib/src/executor/host.rs index 3cd0496d32..f2d3c66e9d 100644 --- a/lib/src/executor/host.rs +++ b/lib/src/executor/host.rs @@ -207,6 +207,7 @@ use tiny_keccak::Hasher as _; pub mod runtime_version; pub use runtime_version::{CoreVersion, CoreVersionError, CoreVersionRef}; +pub use trie::TrieEntryVersion; pub use vm::HeapPages; pub use zstd::Error as ModuleFormatError; @@ -931,8 +932,8 @@ impl ReadyToRun { macro_rules! expect_state_version { ($num:expr) => {{ match ¶ms[$num] { - vm::WasmValue::I32(0) => trie::TrieEntryVersion::V0, - vm::WasmValue::I32(1) => trie::TrieEntryVersion::V1, + vm::WasmValue::I32(0) => TrieEntryVersion::V0, + vm::WasmValue::I32(1) => TrieEntryVersion::V1, vm::WasmValue::I32(_) => { return HostVm::Error { error: Error::ParamDecodeError, @@ -1231,17 +1232,36 @@ impl ReadyToRun { }) } HostFunction::ext_storage_root_version_2 => { - let state_version = expect_state_version!(0); - match state_version { - trie::TrieEntryVersion::V0 => { - HostVm::ExternalStorageRoot(ExternalStorageRoot { - inner: self.inner, - calling: id, - child_trie_ptr_size: None, - }) - } - trie::TrieEntryVersion::V1 => host_fn_not_implemented!(), // TODO: https://github.com/paritytech/smoldot/issues/1967 + // The `ext_storage_root_version_2` host function gets passed as parameter the + // state version of the runtime. This is in fact completely unnecessary as the + // same information is found in the runtime specification, and this parameter + // should be considered as a historical accident. We verify that the version + // provided as parameter is the same as the one in the specification. + let version_param = expect_state_version!(0); + let version_spec = self + .inner + .runtime_version + .as_ref() + .unwrap() + .decode() + .state_version + .unwrap_or(TrieEntryVersion::V0); + + if version_param != version_spec { + return HostVm::Error { + error: Error::StateVersionMismatch { + parameter: version_param, + specification: version_spec, + }, + prototype: self.inner.into_prototype(), + }; } + + HostVm::ExternalStorageRoot(ExternalStorageRoot { + inner: self.inner, + calling: id, + child_trie_ptr_size: None, + }) } HostFunction::ext_storage_changes_root_version_1 => { // The changes trie is an obsolete attempt at having a second trie containing, for @@ -1538,17 +1558,37 @@ impl ReadyToRun { } HostFunction::ext_default_child_storage_root_version_2 => { let (child_trie_ptr, child_trie_size) = expect_pointer_size_raw!(0); - let state_version = expect_state_version!(1); - match state_version { - trie::TrieEntryVersion::V0 => { - HostVm::ExternalStorageRoot(ExternalStorageRoot { - inner: self.inner, - calling: id, - child_trie_ptr_size: Some((child_trie_ptr, child_trie_size)), - }) - } - trie::TrieEntryVersion::V1 => host_fn_not_implemented!(), // TODO: https://github.com/paritytech/smoldot/issues/1967 + + // The `ext_default_child_storage_root_version_2` host function gets passed as + // parameter the state version of the runtime. This is in fact completely + // unnecessary as the same information is found in the runtime specification, and + // this parameter should be considered as a historical accident. We verify that the + // version provided as parameter is the same as the one in the specification. + let version_param = expect_state_version!(1); + let version_spec = self + .inner + .runtime_version + .as_ref() + .unwrap() + .decode() + .state_version + .unwrap_or(TrieEntryVersion::V0); + + if version_param != version_spec { + return HostVm::Error { + error: Error::StateVersionMismatch { + parameter: version_param, + specification: version_spec, + }, + prototype: self.inner.into_prototype(), + }; } + + HostVm::ExternalStorageRoot(ExternalStorageRoot { + inner: self.inner, + calling: id, + child_trie_ptr_size: Some((child_trie_ptr, child_trie_size)), + }) } HostFunction::ext_crypto_ed25519_public_keys_version_1 => host_fn_not_implemented!(), HostFunction::ext_crypto_ed25519_generate_version_1 => host_fn_not_implemented!(), @@ -1928,7 +1968,7 @@ impl ReadyToRun { if matches!(host_fn, HostFunction::ext_trie_blake2_256_root_version_2) { expect_state_version!(1) } else { - trie::TrieEntryVersion::V0 + TrieEntryVersion::V0 }; let result = { @@ -1979,7 +2019,7 @@ impl ReadyToRun { ) { expect_state_version!(1) } else { - trie::TrieEntryVersion::V0 + TrieEntryVersion::V0 }; let result = { @@ -2484,6 +2524,20 @@ impl ExternalStorageSet { } } + /// Returns the state trie version indicated by the runtime. + /// + /// This information should be stored alongside with the storage value and is necessary in + /// order to properly build the trie and thus the trie root node hash. + pub fn state_trie_version(&self) -> TrieEntryVersion { + self.inner + .runtime_version + .as_ref() + .unwrap() + .decode() + .state_version + .unwrap_or(TrieEntryVersion::V0) + } + /// Resumes execution after having set the value. pub fn resume(self) -> HostVm { HostVm::ReadyToRun(ReadyToRun { @@ -3823,6 +3877,20 @@ pub enum Error { /// Pointer that was expected to be freed. pointer: u32, }, + /// Mismatch between the state trie version provided as parameter and the state trie version + /// found in the runtime specification. + #[display( + fmt = "Mismatch between the state trie version provided as parameter ({:?}) and the state \ + trie version found in the runtime specification ({:?}).", + parameter, + specification + )] + StateVersionMismatch { + /// The version passed as parameter. + parameter: TrieEntryVersion, + /// The version in the specification. + specification: TrieEntryVersion, + }, /// The host function isn't implemented. // TODO: this variant should eventually disappear as all functions are implemented #[display(fmt = "Host function not implemented: {}", function)] diff --git a/lib/src/executor/host/runtime_version.rs b/lib/src/executor/host/runtime_version.rs index 04754a1a16..d9d67a31a2 100644 --- a/lib/src/executor/host/runtime_version.rs +++ b/lib/src/executor/host/runtime_version.rs @@ -25,6 +25,8 @@ use crate::executor::host; use alloc::vec::Vec; use core::{fmt, ops, str}; +pub use super::TrieEntryVersion; + /// Tries to find the custom section containing the runtime version and checks its validity. pub fn find_embedded_runtime_version( binary_wasm_module: &[u8], @@ -203,7 +205,7 @@ pub struct CoreVersionRef<'a> { /// didn't provide this field, in which case it will contain `None`. /// /// `None` should be interpreted the same way as `Some(0)`. - pub state_version: Option, + pub state_version: Option, } impl<'a> CoreVersionRef<'a> { @@ -242,7 +244,7 @@ impl<'a> CoreVersionRef<'a> { // TODO: it's not supposed to be allowed to have a CoreVersionRef with a state_version but no transaction_version; the CoreVersionRef struct lets you do that because it was initially designed only for decoding if let Some(state_version) = self.state_version { - out.extend(state_version.to_le_bytes()); + out.extend(u8::from(state_version).to_le_bytes()); } out @@ -411,7 +413,12 @@ fn decode(scale_encoded: &[u8]) -> Result { nom::combinator::map(nom::combinator::eof, |_| None), )), nom::branch::alt(( - nom::combinator::map(nom::number::complete::u8, Some), + nom::combinator::map(nom::bytes::complete::tag(&[0]), |_| { + Some(TrieEntryVersion::V0) + }), + nom::combinator::map(nom::bytes::complete::tag(&[1]), |_| { + Some(TrieEntryVersion::V1) + }), nom::combinator::map(nom::combinator::eof, |_| None), )), )), diff --git a/lib/src/executor/runtime_host.rs b/lib/src/executor/runtime_host.rs index e26d3c11bd..4bf117d494 100644 --- a/lib/src/executor/runtime_host.rs +++ b/lib/src/executor/runtime_host.rs @@ -47,6 +47,8 @@ use alloc::{borrow::ToOwned as _, string::String, vec::Vec}; use core::fmt; use hashbrown::{hash_map::Entry, HashMap, HashSet}; +pub use trie::TrieEntryVersion; + /// Configuration for [`run`]. pub struct Config<'a, TParams> { /// Virtual machine to be run. @@ -76,12 +78,20 @@ pub struct Config<'a, TParams> { pub fn run( config: Config> + Clone>, ) -> Result { + let state_trie_version = config + .virtual_machine + .runtime_version() + .decode() + .state_version + .unwrap_or(TrieEntryVersion::V0); + Ok(Inner { vm: config .virtual_machine .run_vectored(config.function_to_call, config.parameter)? .into(), top_trie_changes: config.storage_top_trie_changes, + state_trie_version, top_trie_transaction_revert: Vec::new(), offchain_storage_changes: config.offchain_storage_changes, top_trie_root_calculation_cache: Some( @@ -101,6 +111,9 @@ pub struct Success { pub virtual_machine: SuccessVirtualMachine, /// List of changes to the storage top trie that the block performs. pub storage_top_trie_changes: storage_diff::StorageDiff, + /// State trie version indicated by the runtime. All the storage changes indicated by + /// [`Success::storage_top_trie_changes`] should store this version alongside with them. + pub state_trie_version: TrieEntryVersion, /// List of changes to the off-chain storage that this block performs. pub offchain_storage_changes: storage_diff::StorageDiff, /// Cache used for calculating the top trie root. @@ -239,30 +252,31 @@ impl StorageGet { /// Injects the corresponding storage value. pub fn inject_value( mut self, - value: Option>>, + value: Option<(impl Iterator>, TrieEntryVersion)>, ) -> RuntimeHostVm { // TODO: update the implementation to not require the folding here - let value = value.map(|i| { - i.fold(Vec::new(), |mut a, b| { + let value = value.map(|(value, version)| { + let value = value.fold(Vec::new(), |mut a, b| { a.extend_from_slice(b.as_ref()); a - }) + }); + (value, version) }); match self.inner.vm { host::HostVm::ExternalStorageGet(req) => { // TODO: should actually report the offset and max_size in the API - self.inner.vm = req.resume_full_value(value.as_ref().map(|v| &v[..])); + self.inner.vm = req.resume_full_value(value.as_ref().map(|(v, _)| &v[..])); } host::HostVm::ExternalStorageAppend(req) => { match req.key() { host::StorageKey::MainTrie { key } => { // TODO: could be less overhead? - let mut value = value.unwrap_or_default(); + let mut value = value.map(|(v, _)| v).unwrap_or_default(); append_to_storage_value(&mut value, req.value().as_ref()); self.inner .top_trie_changes - .diff_insert(key.as_ref().to_vec(), value); + .diff_insert(key.as_ref().to_vec(), value, ()); } _ => unreachable!(), } @@ -273,9 +287,7 @@ impl StorageGet { if let calculate_root::RootMerkleValueCalculation::StorageValue(value_request) = self.inner.root_calculation.take().unwrap() { - // TODO: we only support V0 for now, see https://github.com/paritytech/smoldot/issues/1967 - self.inner.root_calculation = - Some(value_request.inject(trie::TrieEntryVersion::V0, value)); + self.inner.root_calculation = Some(value_request.inject(value)); } else { // We only create a `StorageGet` if the state is `StorageValue`. panic!() @@ -370,7 +382,11 @@ impl PrefixKeys { .unwrap() .storage_value_update(&key, false); - let previous_value = self.inner.top_trie_changes.diff_insert_erase(key.clone()); + let previous_value = self + .inner + .top_trie_changes + .diff_insert_erase(key.clone(), ()) + .map(|(v, _)| v); if let Some(top_trie_transaction_revert) = self.inner.top_trie_transaction_revert.last_mut() @@ -394,12 +410,12 @@ impl PrefixKeys { self.inner .top_trie_changes .diff_get(v.as_ref()) - .map_or(true, |v| v.is_some()) + .map_or(true, |(v, _)| v.is_some()) }) .map(|v| v.as_ref().to_vec()) .collect::>(); // TODO: slow to iterate over everything? - for (key, value) in self.inner.top_trie_changes.diff_iter_unordered() { + for (key, value, ()) in self.inner.top_trie_changes.diff_iter_unordered() { if value.is_none() { continue; } @@ -599,6 +615,10 @@ struct Inner { top_trie_transaction_revert: Vec, Option>>, fnv::FnvBuildHasher>>, + /// State trie version indicated by the runtime. All the storage changes that are performed + /// use this version. + state_trie_version: TrieEntryVersion, + /// Pending changes to the off-chain storage that this execution performs. offchain_storage_changes: storage_diff::StorageDiff, @@ -634,6 +654,7 @@ impl Inner { return RuntimeHostVm::Finished(Ok(Success { virtual_machine: SuccessVirtualMachine(finished), storage_top_trie_changes: self.top_trie_changes, + state_trie_version: self.state_trie_version, offchain_storage_changes: self.offchain_storage_changes, top_trie_root_calculation_cache: self .top_trie_root_calculation_cache @@ -656,7 +677,7 @@ impl Inner { self.top_trie_changes.diff_get(key.as_ref()) }; - if let Some(overlay) = search { + if let Some((overlay, _)) = search { self.vm = req.resume_full_value(overlay); } else { self.vm = req.into(); @@ -674,10 +695,11 @@ impl Inner { let previous_value = if let Some(value) = req.value() { self.top_trie_changes - .diff_insert(key.as_ref(), value.as_ref()) + .diff_insert(key.as_ref(), value.as_ref(), ()) } else { - self.top_trie_changes.diff_insert_erase(key.as_ref()) - }; + self.top_trie_changes.diff_insert_erase(key.as_ref(), ()) + } + .map(|(v, ())| v); if let Some(top_trie_transaction_revert) = self.top_trie_transaction_revert.last_mut() @@ -709,13 +731,15 @@ impl Inner { .unwrap() .storage_value_update(key.as_ref(), true); - let current_value = self.top_trie_changes.diff_get(key.as_ref()); + let current_value = + self.top_trie_changes.diff_get(key.as_ref()).map(|(v, _)| v); if let Some(current_value) = current_value { let mut current_value = current_value.unwrap_or_default().to_vec(); append_to_storage_value(&mut current_value, req.value().as_ref()); let previous_value = self .top_trie_changes - .diff_insert(key.as_ref().to_vec(), current_value); + .diff_insert(key.as_ref().to_vec(), current_value, ()) + .map(|(v, _)| v); if let Some(top_trie_transaction_revert) = self.top_trie_transaction_revert.last_mut() { @@ -776,13 +800,14 @@ impl Inner { calculate_root::RootMerkleValueCalculation::StorageValue(value_request) => { self.vm = req.into(); // TODO: allocating a Vec, meh - if let Some(overlay) = self + if let Some((overlay, ())) = self .top_trie_changes .diff_get(&value_request.key().collect::>()) { - // TODO: we only support V0 for now, see https://github.com/paritytech/smoldot/issues/1967 - self.root_calculation = - Some(value_request.inject(trie::TrieEntryVersion::V0, overlay)); + self.root_calculation = Some( + value_request + .inject(overlay.map(|v| (v, self.state_trie_version))), + ); } else { self.root_calculation = Some(calculate_root::RootMerkleValueCalculation::StorageValue( @@ -815,11 +840,14 @@ impl Inner { host::HostVm::ExternalOffchainStorageSet(req) => { if let Some(value) = req.value() { - self.offchain_storage_changes - .diff_insert(req.key().as_ref().to_vec(), value.as_ref().to_vec()); + self.offchain_storage_changes.diff_insert( + req.key().as_ref().to_vec(), + value.as_ref().to_vec(), + (), + ); } else { self.offchain_storage_changes - .diff_insert_erase(req.key().as_ref().to_vec()); + .diff_insert_erase(req.key().as_ref().to_vec(), ()); } self.vm = req.resume(); @@ -872,9 +900,9 @@ impl Inner { for (key, value) in last { if let Some(value) = value { if let Some(value) = value { - let _ = self.top_trie_changes.diff_insert(key, value); + let _ = self.top_trie_changes.diff_insert(key, value, ()); } else { - let _ = self.top_trie_changes.diff_insert_erase(key); + let _ = self.top_trie_changes.diff_insert_erase(key, ()); } } else { let _ = self.top_trie_changes.diff_remove(&key); diff --git a/lib/src/executor/storage_diff.rs b/lib/src/executor/storage_diff.rs index 4d9556479a..e8b6473d0c 100644 --- a/lib/src/executor/storage_diff.rs +++ b/lib/src/executor/storage_diff.rs @@ -43,7 +43,7 @@ use core::{cmp, fmt, iter, ops}; use hashbrown::HashMap; #[derive(Clone)] -pub struct StorageDiff { +pub struct StorageDiff { /// Contains the same entries as [`StorageDiff::hashmap`], except that values are booleans /// indicating whether the value updates (`true`) or deletes (`false`) the underlying /// storage item. @@ -54,10 +54,10 @@ pub struct StorageDiff { /// /// A FNV hasher is used because the runtime is supposed to guarantee a uniform distribution /// of storage keys. - hashmap: HashMap, Option>, fnv::FnvBuildHasher>, + hashmap: HashMap, (Option>, T), fnv::FnvBuildHasher>, } -impl StorageDiff { +impl StorageDiff { /// Builds a new empty diff. pub fn empty() -> Self { Self { @@ -80,16 +80,19 @@ impl StorageDiff { &mut self, key: impl Into>, value: impl Into>, - ) -> Option>> { + user_data: T, + ) -> Option<(Option>, T)> { let key = key.into(); // Note that we clone the key here. This is considered as a tolerable overhead. - let previous = self.hashmap.insert(key.clone(), Some(value.into())); + let previous = self + .hashmap + .insert(key.clone(), (Some(value.into()), user_data)); match &previous { - Some(Some(_)) => { + Some((Some(_), _)) => { // No need to update `btree`. debug_assert_eq!(self.btree.get(&key), Some(&true)); } - None | Some(None) => { + None | Some((None, _)) => { self.btree.insert(key, true); } } @@ -100,16 +103,20 @@ impl StorageDiff { /// the base storage. /// /// Returns the value associated to this `key` that was previously in the diff, if any. - pub fn diff_insert_erase(&mut self, key: impl Into>) -> Option>> { + pub fn diff_insert_erase( + &mut self, + key: impl Into>, + user_data: T, + ) -> Option<(Option>, T)> { let key = key.into(); // Note that we clone the key here. This is considered as a tolerable overhead. - let previous = self.hashmap.insert(key.clone(), None); + let previous = self.hashmap.insert(key.clone(), (None, user_data)); match &previous { - Some(None) => { + Some((None, _)) => { // No need to update `btree`. debug_assert_eq!(self.btree.get(&key), Some(&false)); } - None | Some(Some(_)) => { + None | Some((Some(_), _)) => { self.btree.insert(key, false); } } @@ -119,21 +126,23 @@ impl StorageDiff { /// Removes from the diff the entry corresponding to the given `key`. /// /// Returns the value associated to this `key` that was previously in the diff, if any. - pub fn diff_remove(&mut self, key: impl AsRef<[u8]>) -> Option>> { + pub fn diff_remove(&mut self, key: impl AsRef<[u8]>) -> Option<(Option>, T)> { let previous = self.hashmap.remove(key.as_ref()); if let Some(_previous) = &previous { let _in_btree = self.btree.remove(key.as_ref()); - debug_assert_eq!(_in_btree, Some(_previous.is_some())); + debug_assert_eq!(_in_btree, Some(_previous.0.is_some())); } previous } /// Returns the diff entry at the given key. /// - /// Returns `None` if the diff doesn't have any entry for this key, and `Some(None)` if the - /// diff has an entry that deletes the storage item. - pub fn diff_get(&self, key: &[u8]) -> Option> { - self.hashmap.get(key).map(|v| v.as_ref().map(|v| &v[..])) + /// Returns `None` if the diff doesn't have any entry for this key, and `Some((None, _))` if + /// the diff has an entry that deletes the storage item. + pub fn diff_get(&self, key: &[u8]) -> Option<(Option<&[u8]>, &T)> { + self.hashmap + .get(key) + .map(|(v, ud)| (v.as_ref().map(|v| &v[..]), ud)) } /// Returns an iterator to all the entries in the diff. @@ -142,10 +151,10 @@ impl StorageDiff { /// underlying value. pub fn diff_iter_unordered( &self, - ) -> impl ExactSizeIterator)> + Clone { + ) -> impl ExactSizeIterator, &T)> + Clone { self.hashmap .iter() - .map(|(k, v)| (&k[..], v.as_ref().map(|v| &v[..]))) + .map(|(k, (v, ud))| (&k[..], v.as_ref().map(|v| &v[..]), ud)) } /// Returns an iterator to all the entries in the diff. @@ -154,19 +163,8 @@ impl StorageDiff { /// underlying value. pub fn diff_into_iter_unordered( self, - ) -> impl ExactSizeIterator, Option>)> { - self.hashmap.into_iter() - } - - /// Returns the storage value at the given key. `None` if this key doesn't have any value. - pub fn storage_get<'a, 'b>( - &'a self, - key: &'b [u8], - or_parent: impl FnOnce() -> Option<&'a [u8]>, - ) -> Option<&'a [u8]> { - self.hashmap - .get(key) - .map_or_else(or_parent, |opt| opt.as_ref().map(|v| &v[..])) + ) -> impl ExactSizeIterator, Option>, T)> { + self.hashmap.into_iter().map(|(k, (v, ud))| (k, v, ud)) } /// Returns the storage key that immediately follows the provided `key`. Must be passed the @@ -275,16 +273,30 @@ impl StorageDiff { } /// Applies the given diff on top of the current one. - pub fn merge(&mut self, other: &StorageDiff) { + pub fn merge(&mut self, other: &StorageDiff) + where + T: Clone, + { + self.merge_map(other, |v| v.clone()) + } + + /// Applies the given diff on top of the current one. + /// + /// Each user data in the other diff is first passed through the map. + pub fn merge_map(&mut self, other: &StorageDiff, mut map: impl FnMut(&U) -> T) { // TODO: provide an alternative method that consumes `other` as well? - for (key, value) in &other.hashmap { - self.hashmap.insert(key.clone(), value.clone()); + for (key, (value, user_data)) in &other.hashmap { + self.hashmap + .insert(key.clone(), (value.clone(), map(user_data))); self.btree.insert(key.clone(), value.is_some()); } } } -impl fmt::Debug for StorageDiff { +impl fmt::Debug for StorageDiff +where + T: fmt::Debug, +{ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Delegate to `self.inner` fmt::Debug::fmt(&self.hashmap, f) @@ -293,31 +305,47 @@ impl fmt::Debug for StorageDiff { // We implement `PartialEq` manually, because deriving it would mean that both the hash map and // the tree are compared. -impl cmp::PartialEq for StorageDiff { - fn eq(&self, other: &Self) -> bool { - self.hashmap == other.hashmap +impl cmp::PartialEq> for StorageDiff +where + T: cmp::PartialEq, +{ + fn eq(&self, other: &StorageDiff) -> bool { + // As of the writing of this code, HashMap doesn't implement + // PartialEq> where T: PartialEq, so we have to implement it manually. The + // way this is implemented matches what the underlying implementation of HashMap does. + if self.hashmap.len() != other.hashmap.len() { + return false; + } + + self.hashmap.iter().all(|(key, (v1, u1))| { + other + .hashmap + .get(key) + .map_or(false, |(v2, u2)| *v1 == *v2 && *u1 == *u2) + }) } } -impl cmp::Eq for StorageDiff {} +impl cmp::Eq for StorageDiff where T: cmp::Eq {} -impl Default for StorageDiff { +impl Default for StorageDiff { fn default() -> Self { StorageDiff::empty() } } -impl FromIterator<(Vec, Option>)> for StorageDiff { - fn from_iter(iter: T) -> Self +impl FromIterator<(Vec, Option>, T)> for StorageDiff { + fn from_iter(iter: I) -> Self where - T: IntoIterator, Option>)>, + I: IntoIterator, Option>, T)>, { let hashmap = iter .into_iter() - .collect::, Option>, fnv::FnvBuildHasher>>(); + .map(|(k, v, ud)| (k, (v, ud))) + .collect::, (Option>, T), fnv::FnvBuildHasher>>(); let btree = hashmap .iter() - .map(|(k, v)| (k.clone(), v.is_some())) + .map(|(k, (v, _))| (k.clone(), v.is_some())) .collect(); Self { btree, hashmap } diff --git a/lib/src/sync/all.rs b/lib/src/sync/all.rs index 3b995e9133..3c6c8f2857 100644 --- a/lib/src/sync/all.rs +++ b/lib/src/sync/all.rs @@ -46,6 +46,7 @@ use core::{ time::Duration, }; +pub use optimistic::TrieEntryVersion; pub use warp_sync::{FragmentError as WarpSyncFragmentError, WarpSyncFragment}; /// Configuration for the [`AllSync`]. @@ -1998,8 +1999,8 @@ impl<'a, TRq, TSrc, TBl> BlockStorage<'a, TRq, TSrc, TBl> { pub fn get<'val: 'a>( &'val self, // TODO: unclear lifetime key: &[u8], - or_finalized: impl FnOnce() -> Option<&'val [u8]>, - ) -> Option<&'val [u8]> { + or_finalized: impl FnOnce() -> Option<(&'val [u8], TrieEntryVersion)>, + ) -> Option<(&'val [u8], TrieEntryVersion)> { match &self.inner { BlockStorageInner::Optimistic(inner) => inner.get(key, or_finalized), } @@ -2121,6 +2122,10 @@ pub struct BlockFull { /// Changes to the storage made by this block compared to its parent. pub storage_top_trie_changes: storage_diff::StorageDiff, + /// State trie version indicated by the runtime. All the storage changes indicated by + /// [`BlockFull::storage_top_trie_changes`] should store this version alongside with them. + pub state_trie_version: TrieEntryVersion, + /// List of changes to the off-chain storage that this block performs. pub offchain_storage_changes: storage_diff::StorageDiff, } @@ -2333,6 +2338,7 @@ impl FinalityProofVerify { body: b.body, offchain_storage_changes: b.offchain_storage_changes, storage_top_trie_changes: b.storage_top_trie_changes, + state_trie_version: b.state_trie_version, }), }) .collect(), @@ -2603,7 +2609,10 @@ impl StorageGet { } /// Injects the corresponding storage value. - pub fn inject_value(self, value: Option<&[u8]>) -> BlockVerification { + pub fn inject_value( + self, + value: Option<(&[u8], TrieEntryVersion)>, + ) -> BlockVerification { let inner = self.inner.inject_value(value); BlockVerification::from_inner(inner, self.shared, self.user_data) } diff --git a/lib/src/sync/optimistic.rs b/lib/src/sync/optimistic.rs index fa631a3693..f6ad0adb6a 100644 --- a/lib/src/sync/optimistic.rs +++ b/lib/src/sync/optimistic.rs @@ -66,6 +66,8 @@ use core::{ }; use hashbrown::HashMap; +pub use blocks_tree::TrieEntryVersion; + mod verification_queue; /// Configuration for the [`OptimisticSync`]. @@ -157,7 +159,8 @@ struct OptimisticSyncInner { /// Changes in the storage of the best block compared to the finalized block. /// The `BTreeMap`'s keys are storage keys, and its values are new values or `None` if the /// value has been erased from the storage. - best_to_finalized_storage_diff: storage_diff::StorageDiff, + /// Each entry is associated with the state version of the runtime at the time of the write. + best_to_finalized_storage_diff: storage_diff::StorageDiff, /// Compiled runtime code of the best block. `None` if it is the same as /// [`OptimisticSyncInner::finalized_runtime`]. @@ -266,6 +269,10 @@ pub struct BlockFull { /// Changes to the storage made by this block compared to its parent. pub storage_top_trie_changes: storage_diff::StorageDiff, + /// State trie version indicated by the runtime. All the storage changes indicated by + /// [`BlockFull::storage_top_trie_changes`] should store this version alongside with them. + pub state_trie_version: TrieEntryVersion, + /// List of changes to the off-chain storage that this block performs. pub offchain_storage_changes: storage_diff::StorageDiff, } @@ -801,12 +808,18 @@ impl<'a, TRq, TSrc, TBl> BlockStorage<'a, TRq, TSrc, TBl> { pub fn get<'val: 'a>( &'val self, // TODO: unclear lifetime key: &[u8], - or_finalized: impl FnOnce() -> Option<&'val [u8]>, - ) -> Option<&'val [u8]> { - self.inner + or_finalized: impl FnOnce() -> Option<(&'val [u8], TrieEntryVersion)>, + ) -> Option<(&'val [u8], TrieEntryVersion)> { + match self + .inner .inner .best_to_finalized_storage_diff - .storage_get(key, or_finalized) + .diff_get(key) + { + Some((None, _)) => None, + Some((Some(val), vers)) => Some((val, *vers)), + None => or_finalized(), + } } pub fn prefix_keys_ordered<'k: 'a>( @@ -1054,6 +1067,7 @@ impl BlockVerification { Inner::Step2(blocks_tree::BodyVerifyStep2::Finished { storage_top_trie_changes, + state_trie_version, offchain_storage_changes, top_trie_root_calculation_cache, parent_runtime, @@ -1094,7 +1108,7 @@ impl BlockVerification { shared .inner .best_to_finalized_storage_diff - .merge(&storage_top_trie_changes); + .merge_map(&storage_top_trie_changes, |()| state_trie_version); let chain = { let header = insert.header().into(); @@ -1106,6 +1120,7 @@ impl BlockVerification { body: mem::take(&mut shared.block_body), storage_top_trie_changes, offchain_storage_changes, + state_trie_version, }), }) }; @@ -1135,9 +1150,13 @@ impl BlockVerification { .inner .best_to_finalized_storage_diff .diff_get(req.key().as_ref()); - if let Some(value) = value { + if let Some((value, storage_trie_node_version)) = value { inner = Inner::Step2( - req.inject_value(value.as_ref().map(|v| iter::once(&v[..]))), + req.inject_value( + value + .as_ref() + .map(|v| (iter::once(&v[..]), *storage_trie_node_version)), + ), ); continue 'verif_steps; } @@ -1422,8 +1441,13 @@ impl StorageGet { } /// Injects the corresponding storage value. - pub fn inject_value(self, value: Option<&[u8]>) -> BlockVerification { - let inner = self.inner.inject_value(value.map(iter::once)); + pub fn inject_value( + self, + value: Option<(&[u8], TrieEntryVersion)>, + ) -> BlockVerification { + let inner = self.inner.inject_value( + value.map(|(v, storage_trie_node_version)| (iter::once(v), storage_trie_node_version)), + ); BlockVerification::from(Inner::Step2(inner), self.shared) } } diff --git a/lib/src/sync/warp_sync.rs b/lib/src/sync/warp_sync.rs index 6287bab572..6bfad25b95 100644 --- a/lib/src/sync/warp_sync.rs +++ b/lib/src/sync/warp_sync.rs @@ -1222,7 +1222,7 @@ impl BuildRuntime { }; let finalized_storage_code = match decoded_downloaded_runtime.storage_value(b":code") { - Some(Some(code)) => code, + Some(Some((code, _))) => code, Some(None) => { self.inner.phase = Phase::DownloadFragments { previous_verifier_values: Some(( @@ -1248,7 +1248,7 @@ impl BuildRuntime { let finalized_storage_heappages = match decoded_downloaded_runtime.storage_value(b":heappages") { - Some(val) => val, + Some(val) => val.map(|(v, _)| v), None => { self.inner.phase = Phase::DownloadFragments { previous_verifier_values: Some(( @@ -1446,7 +1446,7 @@ impl BuildChainInformation { chain_information::build::InProgress::StorageGet(get) => { let proof = calls.get(&get.call_in_progress()).unwrap(); let value = match proof.storage_value(get.key().as_ref()) { - Some(v) => v, + Some(v) => v.map(|(v, _)| v), None => { self.inner.phase = Phase::DownloadFragments { previous_verifier_values: Some(( diff --git a/lib/src/transactions/validate.rs b/lib/src/transactions/validate.rs index e0894b9da1..fd99d8f709 100644 --- a/lib/src/transactions/validate.rs +++ b/lib/src/transactions/validate.rs @@ -25,6 +25,8 @@ use crate::{ use alloc::{borrow::ToOwned as _, vec::Vec}; use core::{iter, num::NonZeroU64}; +pub use runtime_host::TrieEntryVersion; + /// Configuration for a transaction validation process. pub struct Config<'a, TTx> { /// Runtime used to get the validate the transaction. Must be built using the Wasm code found @@ -570,7 +572,10 @@ impl StorageGet { } /// Injects the corresponding storage value. - pub fn inject_value(self, value: Option>>) -> Query { + pub fn inject_value( + self, + value: Option<(impl Iterator>, TrieEntryVersion)>, + ) -> Query { match self.0 { StorageGetInner::Stage1(inner, stage) => { Query::from_step1(inner.inject_value(value), stage) diff --git a/lib/src/trie.rs b/lib/src/trie.rs index 49a3b4dad6..640cf62407 100644 --- a/lib/src/trie.rs +++ b/lib/src/trie.rs @@ -55,7 +55,8 @@ //! //! In the situation where we want to know the storage value associated to a node, but we only //! know the Merkle value of the root of the trie, it is possible to ask a third-party for the -//! unhashed Merkle values of the desired node and all its ancestors. +//! unhashed Merkle values of the desired node and all its ancestors. This is called a Merkle +//! proof. //! //! After having verified that the third-party has provided correct values, and that they match //! the expected root node Merkle value known locally, we can extract the storage value from the @@ -83,6 +84,21 @@ //! its ancestors. As such, the time spent calculating the Merkle value of the root node of a trie //! mostly depends on the number of modifications that are performed on it, and only a bit on the //! size of the trie. +//! +//! ## Trie entry version +//! +//! In the Substrate/Polkadot trie, each trie node that contains a value also has a version +//! associated to it. +//! +//! This version changes the way the hash of the node is calculated and how the Merkle proof is +//! generated. Version 1 leads to more succinct Merkle proofs, which is important when these proofs +//! are sent over the Internet. +//! +//! Note that most of the time all the entries of the trie have the same version. However, it is +//! possible for the trie to be in a hybrid state where some entries have a certain version and +//! other entries a different version. For this reason, most of the trie-related APIs require you +//! to provide a trie entry version alongside with the value. +//! use crate::util; @@ -117,6 +133,27 @@ pub enum TrieEntryVersion { V1, } +impl TryFrom for TrieEntryVersion { + type Error = (); // TODO: better error? + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(TrieEntryVersion::V0), + 1 => Ok(TrieEntryVersion::V1), + _ => Err(()), + } + } +} + +impl From for u8 { + fn from(v: TrieEntryVersion) -> u8 { + match v { + TrieEntryVersion::V0 => 0, + TrieEntryVersion::V1 => 1, + } + } +} + /// Returns the Merkle value of the root of an empty trie. pub fn empty_trie_merkle_value() -> [u8; 32] { let mut calculation = calculate_root::root_merkle_value(None); @@ -128,9 +165,7 @@ pub fn empty_trie_merkle_value() -> [u8; 32] { calculation = keys.inject(iter::empty::>()); } calculate_root::RootMerkleValueCalculation::StorageValue(val) => { - // Note that the version has no influence whatsoever on the output of the - // calculation. The version passed here is a dummy value. - calculation = val.inject(TrieEntryVersion::V1, None::<&[u8]>); + calculation = val.inject(None::<(&[u8], _)>); } } } @@ -160,7 +195,7 @@ pub fn trie_root( .iter() .find(|(k, _)| k.as_ref().iter().copied().eq(value.key())) .map(|(_, v)| v); - calculation = value.inject(version, result); + calculation = value.inject(result.map(move |v| (v, version))); } } } @@ -196,7 +231,7 @@ pub fn ordered_root(version: TrieEntryVersion, entries: &[impl AsRef<[u8]>]) -> .collect::>(); let (_, key) = util::nom_scale_compact_usize::>(&key).unwrap(); - calculation = value.inject(version, entries.get(key)); + calculation = value.inject(entries.get(key).map(move |v| (v, version))); } } } diff --git a/lib/src/trie/calculate_root.rs b/lib/src/trie/calculate_root.rs index 5719de535f..402c131a84 100644 --- a/lib/src/trie/calculate_root.rs +++ b/lib/src/trie/calculate_root.rs @@ -31,8 +31,8 @@ //! use smoldot::trie::{TrieEntryVersion, calculate_root}; //! //! // In this example, the storage consists in a binary tree map. -//! let mut storage = BTreeMap::, Vec>::new(); -//! storage.insert(b"foo".to_vec(), b"bar".to_vec()); +//! let mut storage = BTreeMap::, (Vec, TrieEntryVersion)>::new(); +//! storage.insert(b"foo".to_vec(), (b"bar".to_vec(), TrieEntryVersion::V1)); //! //! let trie_root = { //! let mut calculation = calculate_root::root_merkle_value(None); @@ -44,7 +44,7 @@ //! } //! calculate_root::RootMerkleValueCalculation::StorageValue(value_request) => { //! let key = value_request.key().collect::>(); -//! calculation = value_request.inject(TrieEntryVersion::V1, storage.get(&key)); +//! calculation = value_request.inject(storage.get(&key).map(|(val, v)| (val, *v))); //! } //! } //! } @@ -303,8 +303,7 @@ impl CalcInner { let merkle_value = node_value::calculate_merkle_value(node_value::Config { ty: node_value::NodeTy::Root { key: iter::empty() }, children: (0..16).map(|_| None), - stored_value: None::>, - version: TrieEntryVersion::V1, // Version makes no difference for empty tries. + stored_value: None::<(Vec, _)>, }); return RootMerkleValueCalculation::Finished { @@ -383,8 +382,7 @@ impl CalcInner { .child_user_data(Nibble::try_from(child_idx).unwrap()) .map(|child| child.merkle_value.as_ref().unwrap()) }), - stored_value: None::>, - version: TrieEntryVersion::V1, // Version has no influence on the output if `stored_value` is `None`. + stored_value: None::<(Vec, _)>, }); current.user_data().merkle_value = Some(merkle_value); @@ -451,8 +449,7 @@ impl StorageValue { /// Indicates the storage value and advances the calculation. pub fn inject( mut self, - version: TrieEntryVersion, - stored_value: Option>, + stored_value: Option<(impl AsRef<[u8]>, TrieEntryVersion)>, ) -> RootMerkleValueCalculation { assert!(stored_value.is_some()); @@ -478,7 +475,6 @@ impl StorageValue { .map(|child| child.merkle_value.as_ref().unwrap()) }), stored_value, - version, }); current.user_data().merkle_value = Some(merkle_value); @@ -505,7 +501,7 @@ mod tests { } super::RootMerkleValueCalculation::StorageValue(value) => { let key = value.key().collect::>(); - calculation = value.inject(version, trie.get(&key)); + calculation = value.inject(trie.get(&key).map(|v| (v, version))); } } } @@ -642,7 +638,8 @@ mod tests { } super::RootMerkleValueCalculation::StorageValue(value) => { let key = value.key().collect::>(); - calculation = value.inject(TrieEntryVersion::V1, trie.get(&key)); + calculation = + value.inject(trie.get(&key).map(|v| (v, TrieEntryVersion::V1))); } } } @@ -689,7 +686,8 @@ mod tests { } super::RootMerkleValueCalculation::StorageValue(value) => { let key = value.key().collect::>(); - calculation = value.inject(TrieEntryVersion::V1, trie.get(&key)); + calculation = + value.inject(trie.get(&key).map(|v| (v, TrieEntryVersion::V1))); } } } diff --git a/lib/src/trie/node_value.rs b/lib/src/trie/node_value.rs index 76b182c4cd..ca6e58abb1 100644 --- a/lib/src/trie/node_value.rs +++ b/lib/src/trie/node_value.rs @@ -54,8 +54,7 @@ //! .cloned(), //! }, //! children: children.iter().map(|opt| opt.as_ref()), -//! stored_value: Some(b"hello world"), -//! version: TrieEntryVersion::V1, +//! stored_value: Some((b"hello world", TrieEntryVersion::V1)), //! }) //! }; //! @@ -86,13 +85,10 @@ pub struct Config { pub children: TChIter, /// Value of the node in the storage. - pub stored_value: Option, - - /// Version to use for the encoding. /// - /// Some input will lead to the same output no matter the version, but some other input will - /// produce a different output. - pub version: TrieEntryVersion, + /// If `Some`, also contains the version to use for the encoding. Some input will lead to the + /// same output no matter the version, but some other input will produce a different output. + pub stored_value: Option<(TVal, TrieEntryVersion)>, } /// Type of node whose node value is to be calculated. @@ -137,10 +133,9 @@ where let has_children = config.children.clone().any(|c| c.is_some()); - let stored_value_to_be_hashed = config - .stored_value - .as_ref() - .map(|value| matches!(config.version, TrieEntryVersion::V1) && value.as_ref().len() >= 33); + let stored_value_to_be_hashed = config.stored_value.as_ref().map(|(value, version)| { + matches!(version, TrieEntryVersion::V1) && value.as_ref().len() >= 33 + }); // This value will be used as the sink for all the components of the merkle value. let mut merkle_value_sink = if matches!(config.ty, NodeTy::Root { .. }) { @@ -215,7 +210,7 @@ where // We take a shortcut and end the calculation now. if !has_children { if let Some(hash_stored_value) = stored_value_to_be_hashed { - let stored_value = config.stored_value.unwrap(); + let stored_value = config.stored_value.unwrap().0; if hash_stored_value { merkle_value_sink.update( @@ -247,7 +242,7 @@ where // Add our own stored value. if let Some(hash_stored_value) = stored_value_to_be_hashed { - let stored_value = config.stored_value.unwrap(); + let stored_value = config.stored_value.unwrap().0; if hash_stored_value { merkle_value_sink @@ -386,8 +381,7 @@ mod tests { let obtained = super::calculate_merkle_value(super::Config { ty: super::NodeTy::Root { key: iter::empty() }, children: (0..16).map(|_| None), - stored_value: None::>, - version: TrieEntryVersion::V0, + stored_value: None::<(Vec, _)>, }); assert_eq!( @@ -406,8 +400,7 @@ mod tests { partial_key: iter::empty(), }, children: (0..16).map(|_| None), - stored_value: None::>, - version: TrieEntryVersion::V0, + stored_value: None::<(Vec, _)>, }); assert_eq!(obtained.as_ref(), &[0u8]); @@ -442,8 +435,7 @@ mod tests { .cloned(), }, children: children.iter().map(|opt| opt.as_ref()), - stored_value: Some(b"hello world"), - version: TrieEntryVersion::V0, + stored_value: Some((b"hello world", TrieEntryVersion::V0)), }); assert_eq!( @@ -463,8 +455,7 @@ mod tests { partial_key: iter::empty(), }, children: iter::empty(), - stored_value: None::>, - version: TrieEntryVersion::V0, + stored_value: None::<(Vec, _)>, }); } } diff --git a/lib/src/trie/prefix_proof.rs b/lib/src/trie/prefix_proof.rs index 7a35b56766..707f964b19 100644 --- a/lib/src/trie/prefix_proof.rs +++ b/lib/src/trie/prefix_proof.rs @@ -119,7 +119,7 @@ impl PrefixScan { if matches!( info.storage_value, - proof_decode::StorageValue::Known(_) + proof_decode::StorageValue::Known { .. } | proof_decode::StorageValue::HashKnownValueMissing(_) ) { // Trie nodes with a value are always aligned to "bytes-keys". In other words, diff --git a/lib/src/trie/proof_decode.rs b/lib/src/trie/proof_decode.rs index 9c14c5ddb6..5a083a8048 100644 --- a/lib/src/trie/proof_decode.rs +++ b/lib/src/trie/proof_decode.rs @@ -38,7 +38,7 @@ //! Once decoded, one can examine the content of the proof, in other words the list of storage //! items and values. -use super::{nibble, proof_node_codec}; +use super::{nibble, proof_node_codec, TrieEntryVersion}; use alloc::{collections::BTreeMap, vec, vec::Vec}; use core::{fmt, mem, ops}; @@ -438,9 +438,15 @@ impl> DecodedTrieProof { self.entries.iter().map( |(key, (storage_value_inner, node_value_range, children_bitmap))| { let storage_value = match storage_value_inner { - StorageValueInner::Known { offset, len, .. } => { - StorageValue::Known(&self.proof.as_ref()[*offset..][..*len]) - } + StorageValueInner::Known { + offset, + len, + is_inline, + .. + } => StorageValue::Known { + value: &self.proof.as_ref()[*offset..][..*len], + inline: *is_inline, + }, StorageValueInner::None => StorageValue::None, StorageValueInner::HashKnownValueMissing { offset } => { StorageValue::HashKnownValueMissing( @@ -518,9 +524,15 @@ impl> DecodedTrieProof { // Found exact match. Returning. return Some(TrieNodeInfo { storage_value: match storage_value { - StorageValueInner::Known { offset, len, .. } => { - StorageValue::Known(&self.proof.as_ref()[*offset..][..*len]) - } + StorageValueInner::Known { + offset, + len, + is_inline, + .. + } => StorageValue::Known { + value: &self.proof.as_ref()[*offset..][..*len], + inline: *is_inline, + }, StorageValueInner::None => StorageValue::None, StorageValueInner::HashKnownValueMissing { offset } => { StorageValue::HashKnownValueMissing( @@ -616,12 +628,19 @@ impl> DecodedTrieProof { /// > **Note**: This function is a convenient wrapper around /// > [`DecodedTrieProof::trie_node_info`]. // TODO: return a Result instead of Option? - pub fn storage_value(&'_ self, key: &[u8]) -> Option> { + pub fn storage_value(&'_ self, key: &[u8]) -> Option> { // Annoyingly we have to create a `Vec` for the key, but the API of BTreeMap gives us // no other choice. let key = nibble::bytes_to_nibbles(key.iter().copied()).collect::>(); match self.trie_node_info(&key)?.storage_value { - StorageValue::Known(v) => Some(Some(v)), + StorageValue::Known { value, inline } => Some(Some(( + value, + if inline { + TrieEntryVersion::V0 + } else { + TrieEntryVersion::V1 + }, + ))), StorageValue::HashKnownValueMissing(_) => None, StorageValue::None => Some(None), } @@ -633,8 +652,14 @@ impl> DecodedTrieProof { /// Storage value of the node. #[derive(Copy, Clone)] pub enum StorageValue<'a> { - /// The storage value was found in the proof. Contains the value. - Known(&'a [u8]), + /// The storage value was found in the proof. + Known { + /// The storage value. + value: &'a [u8], + /// `true` if the storage value was inline in the node. This indicates "version 0" of the + /// state version, while `false` indicates "version 1". + inline: bool, + }, /// The hash of the storage value was found, but the un-hashed value wasn't in the proof. This /// indicates an incomplete proof. HashKnownValueMissing(&'a [u8; 32]), @@ -645,16 +670,22 @@ pub enum StorageValue<'a> { impl<'a> fmt::Debug for StorageValue<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - StorageValue::Known(v) if v.len() <= 48 => { - write!(f, "0x{}", hex::encode(v)) + StorageValue::Known { value, inline } if value.len() <= 48 => { + write!( + f, + "0x{}{}", + hex::encode(value), + if *inline { " (inline)" } else { "" } + ) } - StorageValue::Known(v) => { + StorageValue::Known { value, inline } => { write!( f, - "0x{}…{} ({} bytes)", - hex::encode(&v[0..4]), - hex::encode(&v[v.len() - 4..]), - v.len(), + "0x{}…{} ({} bytes{})", + hex::encode(&value[0..4]), + hex::encode(&value[value.len() - 4..]), + value.len(), + if *inline { ", inline" } else { "" } ) } StorageValue::HashKnownValueMissing(hash) => { @@ -896,8 +927,8 @@ mod tests { let obtained = decoded.storage_value(&requested_key).unwrap(); assert_eq!( - obtained, - Some(&hex::decode("0d1456fdda7b8ec7f9e5c794cd83194f0593e4ea").unwrap()[..]) + obtained.unwrap().0, + &hex::decode("0d1456fdda7b8ec7f9e5c794cd83194f0593e4ea").unwrap()[..] ); } @@ -949,7 +980,7 @@ mod tests { .unwrap(); let obtained = decoded.storage_value(&requested_key).unwrap(); - assert_eq!(obtained, Some(&[80, 82, 127, 41, 119, 1, 0, 0][..])); + assert_eq!(obtained.unwrap().0, &[80, 82, 127, 41, 119, 1, 0, 0][..]); } #[test] diff --git a/lib/src/verify/header_body.rs b/lib/src/verify/header_body.rs index 2457309276..c6cec49a41 100644 --- a/lib/src/verify/header_body.rs +++ b/lib/src/verify/header_body.rs @@ -27,6 +27,8 @@ use crate::{ use alloc::{string::String, vec::Vec}; use core::{iter, num::NonZeroU64, time::Duration}; +pub use runtime_host::TrieEntryVersion; + /// Configuration for a block verification. pub struct Config<'a, TBody> { /// Runtime used to check the new block. Must be built using the `:code` of the parent @@ -115,6 +117,10 @@ pub struct Success { /// List of changes to the storage top trie that the block performs. pub storage_top_trie_changes: storage_diff::StorageDiff, + /// State trie version indicated by the runtime. All the storage changes indicated by + /// [`Success::storage_top_trie_changes`] should store this version alongside with them. + pub state_trie_version: TrieEntryVersion, + /// List of changes to the off-chain storage that this block performs. pub offchain_storage_changes: storage_diff::StorageDiff, @@ -474,7 +480,7 @@ impl VerifyInner { .diff_get(&b":heappages"[..]), ) { (None, None) => {} - (Some(None), _) => { + (Some((None, ())), _) => { return Verify::Finished(Err(( Error::CodeKeyErased, success.virtual_machine.into_prototype(), @@ -486,11 +492,11 @@ impl VerifyInner { success.virtual_machine.into_prototype(), ))) } - (Some(Some(_code)), heap_pages) => { + (Some((Some(_code), ())), heap_pages) => { let parent_runtime = success.virtual_machine.into_prototype(); let heap_pages = match heap_pages { - Some(heap_pages) => { + Some((heap_pages, ())) => { match executor::storage_heap_pages_to_value(heap_pages) { Ok(hp) => hp, Err(err) => { @@ -511,6 +517,7 @@ impl VerifyInner { logs: success.logs, offchain_storage_changes: success.offchain_storage_changes, storage_top_trie_changes: success.storage_top_trie_changes, + state_trie_version: success.state_trie_version, top_trie_root_calculation_cache: success .top_trie_root_calculation_cache, }); @@ -522,6 +529,7 @@ impl VerifyInner { new_runtime: None, consensus: self.consensus_success, storage_top_trie_changes: success.storage_top_trie_changes, + state_trie_version: success.state_trie_version, offchain_storage_changes: success.offchain_storage_changes, top_trie_root_calculation_cache: success.top_trie_root_calculation_cache, logs: success.logs, @@ -572,7 +580,10 @@ impl StorageGet { } /// Injects the corresponding storage value. - pub fn inject_value(self, value: Option>>) -> Verify { + pub fn inject_value( + self, + value: Option<(impl Iterator>, TrieEntryVersion)>, + ) -> Verify { VerifyInner { inner: self.inner.inject_value(value), execution_not_started: self.execution_not_started, @@ -647,6 +658,7 @@ impl StorageNextKey { pub struct RuntimeCompilation { parent_runtime: host::HostVmPrototype, storage_top_trie_changes: storage_diff::StorageDiff, + state_trie_version: TrieEntryVersion, offchain_storage_changes: storage_diff::StorageDiff, top_trie_root_calculation_cache: calculate_root::CalculationCache, logs: String, @@ -663,6 +675,7 @@ impl RuntimeCompilation { .storage_top_trie_changes .diff_get(&b":code"[..]) .unwrap() + .0 .unwrap(); let new_runtime = match host::HostVmPrototype::new(host::Config { @@ -685,6 +698,7 @@ impl RuntimeCompilation { new_runtime: Some(new_runtime), consensus: self.consensus_success, storage_top_trie_changes: self.storage_top_trie_changes, + state_trie_version: self.state_trie_version, offchain_storage_changes: self.offchain_storage_changes, top_trie_root_calculation_cache: self.top_trie_root_calculation_cache, logs: self.logs, diff --git a/light-base/src/json_rpc_service.rs b/light-base/src/json_rpc_service.rs index 12300c4ed4..433f287f0e 100644 --- a/light-base/src/json_rpc_service.rs +++ b/light-base/src/json_rpc_service.rs @@ -1650,7 +1650,7 @@ impl Background { break Err(RuntimeCallError::Call(err)); } }; - runtime_call = get.inject_value(storage_value.map(iter::once)); + runtime_call = get.inject_value(storage_value.map(|(v, _)| iter::once(v))); } read_only_runtime_host::RuntimeHostVm::NextKey(nk) => { // TODO: diff --git a/light-base/src/json_rpc_service/chain_head.rs b/light-base/src/json_rpc_service/chain_head.rs index 906be045f4..5ab60ff3d2 100644 --- a/light-base/src/json_rpc_service/chain_head.rs +++ b/light-base/src/json_rpc_service/chain_head.rs @@ -243,8 +243,10 @@ impl Background { .to_json_call_object_parameters(None); } }; - runtime_call = - get.inject_value(storage_value.map(iter::once)); + runtime_call = get.inject_value( + storage_value + .map(|(val, vers)| (iter::once(val), vers)), + ); } runtime_host::RuntimeHostVm::NextKey(nk) => { // TODO: implement somehow diff --git a/light-base/src/json_rpc_service/state_chain.rs b/light-base/src/json_rpc_service/state_chain.rs index 50ada998fa..49e8aa5f03 100644 --- a/light-base/src/json_rpc_service/state_chain.rs +++ b/light-base/src/json_rpc_service/state_chain.rs @@ -1186,7 +1186,7 @@ impl Background { spec_version: u64::from(runtime_spec.spec_version), impl_version: u64::from(runtime_spec.impl_version), transaction_version: runtime_spec.transaction_version.map(u64::from), - state_version: runtime_spec.state_version.map(u64::from), + state_version: runtime_spec.state_version.map(u8::from).map(u64::from), apis: runtime_spec .apis .map(|api| (methods::HexString(api.name_hash.to_vec()), api.version)) @@ -1373,7 +1373,10 @@ impl Background { transaction_version: runtime_spec .transaction_version .map(u64::from), - state_version: runtime_spec.state_version.map(u64::from), + state_version: runtime_spec + .state_version + .map(u8::from) + .map(u64::from), apis: runtime_spec .apis .map(|api| { diff --git a/light-base/src/runtime_service.rs b/light-base/src/runtime_service.rs index 202905694c..a1964685d3 100644 --- a/light-base/src/runtime_service.rs +++ b/light-base/src/runtime_service.rs @@ -83,7 +83,7 @@ use smoldot::{ executor, header, informant::{BytesDisplay, HashDisplay}, network::protocol, - trie::{self, proof_decode}, + trie::{self, proof_decode, TrieEntryVersion}, }; /// Configuration for a runtime service. @@ -850,7 +850,10 @@ impl<'a> RuntimeCallLock<'a> { /// Returns an error if the key couldn't be found in the proof, meaning that the proof is /// invalid. // TODO: if proof is invalid, we should give the option to fetch another call proof - pub fn storage_entry(&self, requested_key: &[u8]) -> Result, RuntimeCallError> { + pub fn storage_entry( + &self, + requested_key: &[u8], + ) -> Result, RuntimeCallError> { let call_proof = match &self.call_proof { Ok(p) => p, Err(err) => return Err(err.clone()), @@ -889,7 +892,7 @@ impl<'a> RuntimeCallLock<'a> { if matches!( node_info.storage_value, - proof_decode::StorageValue::Known(_) + proof_decode::StorageValue::Known { .. } | proof_decode::StorageValue::HashKnownValueMissing(_) ) { assert_eq!(key.len() % 2, 0); diff --git a/light-base/src/sync_service.rs b/light-base/src/sync_service.rs index b6be9adfb5..87319660b8 100644 --- a/light-base/src/sync_service.rs +++ b/light-base/src/sync_service.rs @@ -441,7 +441,7 @@ impl SyncService { decoded .storage_value(key.as_ref()) .ok_or(StorageQueryErrorDetail::MissingProofEntry)? - .map(|v| v.to_owned()), + .map(|(v, _)| v.to_owned()), ); } debug_assert_eq!(result.len(), result.capacity()); diff --git a/light-base/src/sync_service/parachain.rs b/light-base/src/sync_service/parachain.rs index 87fc03cdde..1e50a82447 100644 --- a/light-base/src/sync_service/parachain.rs +++ b/light-base/src/sync_service/parachain.rs @@ -1149,7 +1149,7 @@ async fn parahead( read_only_runtime_host::RuntimeHostVm::StorageGet(get) => { let storage_value = runtime_call_lock.storage_entry(get.key().as_ref()); let storage_value = match storage_value { - Ok(v) => v, + Ok(v) => v.map(|(v, _)| v), Err(err) => { runtime_call_lock.unlock( read_only_runtime_host::RuntimeHostVm::StorageGet(get).into_prototype(), diff --git a/light-base/src/transactions_service.rs b/light-base/src/transactions_service.rs index 78319c7e73..d942d67bdb 100644 --- a/light-base/src/transactions_service.rs +++ b/light-base/src/transactions_service.rs @@ -1206,7 +1206,8 @@ async fn validate_transaction( )); } }; - validation_in_progress = get.inject_value(storage_value.map(iter::once)); + validation_in_progress = + get.inject_value(storage_value.map(|(val, vers)| (iter::once(val), vers))); } validate::Query::NextKey(nk) => { // TODO: