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

Implement trie version 1 #2277

Merged
merged 9 commits into from
May 16, 2022
Merged
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
49 changes: 39 additions & 10 deletions bin/light-base/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,10 +348,11 @@ impl<TChain, TPlat: Platform> Client<TChain, TPlat> {
// Load the information about the chain from the chain spec. If a light sync state (also
// known as a checkpoint) is present in the chain spec, it is possible to start syncing at
// the finalized block it describes.
let chain_information = {
// TODO: clean up that block
let (chain_information, genesis_block_header) = {
match (
chain_spec
.as_chain_information() // TODO: very expensive, don't always call?
.as_chain_information()
.map(|(ci, _)| chain::chain_information::ValidChainInformation::try_from(ci)), // TODO: don't just throw away the runtime
chain_spec.light_sync_state().map(|s| {
chain::chain_information::ValidChainInformation::try_from(
Expand All @@ -360,7 +361,22 @@ impl<TChain, TPlat: Platform> Client<TChain, TPlat> {
}),
finalized_serialize::decode_chain(config.database_content),
) {
(_, _, Ok((ci, _))) => ci,
(Ok(Ok(genesis_ci)), _, Ok((ci, _))) => {
let genesis_header = genesis_ci.as_ref().finalized_block_header.clone();
(ci, genesis_header.into())
}

(Err(chain_spec::FromGenesisStorageError::UnknownStorageItems), _, Ok((ci, _))) => {
let genesis_header = header::Header {
parent_hash: [0; 32],
number: 0,
state_root: *chain_spec.genesis_storage().into_trie_root_hash().unwrap(),
extrinsics_root: smoldot::trie::empty_trie_merkle_value(),
digest: header::DigestRef::empty().into(),
};

(ci, genesis_header)
}

(Err(chain_spec::FromGenesisStorageError::UnknownStorageItems), None, _) => {
// TODO: we can in theory support chain specs that have neither a checkpoint nor the genesis storage, but it's complicated
Expand All @@ -376,7 +392,17 @@ impl<TChain, TPlat: Platform> Client<TChain, TPlat> {
Err(chain_spec::FromGenesisStorageError::UnknownStorageItems),
Some(Ok(ci)),
_,
) => ci,
) => {
let genesis_header = header::Header {
parent_hash: [0; 32],
number: 0,
state_root: *chain_spec.genesis_storage().into_trie_root_hash().unwrap(),
extrinsics_root: smoldot::trie::empty_trie_merkle_value(),
digest: header::DigestRef::empty().into(),
};

(ci, genesis_header)
}

(Err(err), _, _) => {
return ChainId(self.public_api_chains.insert(PublicApiChain::Erroneous {
Expand All @@ -399,16 +425,19 @@ impl<TChain, TPlat: Platform> Client<TChain, TPlat> {
}));
}

(_, Some(Ok(ci)), _) => ci,
(Ok(Ok(genesis_ci)), Some(Ok(ci)), _) => {
let genesis_header = genesis_ci.as_ref().finalized_block_header.clone();
(ci, genesis_header.into())
}

(Ok(Ok(ci)), None, _) => ci,
(Ok(Ok(ci)), None, _) => {
let genesis_header =
header::Header::from(ci.as_ref().finalized_block_header.clone());
(ci, genesis_header)
}
}
};

// Even with a checkpoint, knowing the genesis block header is necessary for various
// reasons.
let genesis_block_header = smoldot::calculate_genesis_block_header(&chain_spec);

// If the chain specification specifies a parachain, find the corresponding relay chain
// in the list of potential relay chains passed by the user.
// If no relay chain can be found, the chain creation fails. Exactly one matching relay
Expand Down
6 changes: 5 additions & 1 deletion bin/light-base/src/runtime_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -822,7 +822,11 @@ impl<'a> RuntimeCallLock<'a> {
})
.map_err(RuntimeCallError::StorageRetrieval)?;

if node_info.storage_value.is_some() {
if matches!(
node_info.storage_value,
proof_verify::StorageValue::Known(_)
| proof_verify::StorageValue::HashKnownValueMissing(_)
) {
assert_eq!(key.len() % 2, 0);
output.push(trie::nibbles_to_bytes_extend(key.iter().copied()).collect::<Vec<_>>());
}
Expand Down
4 changes: 4 additions & 0 deletions bin/wasm-node/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Added

- Added support for version 1 of the trie. Previously, it wasn't possible to connect to chains that were using version 1. ([#2277](https://github.com/paritytech/smoldot/pull/2277))

### Changed

- The runtime of the genesis block is now only compiled once when a chain is added, decreasing the time this operation takes. ([#2270](https://github.com/paritytech/smoldot/pull/2270))
Expand Down
24 changes: 5 additions & 19 deletions src/author/runtime/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,12 @@ fn block_building_works() {
.unwrap();
let genesis_storage = chain_specs.genesis_storage().into_genesis_items().unwrap();

let parent_runtime = {
let code = genesis_storage
.iter()
.find(|(k, _)| k == b":code")
.unwrap()
.1;
crate::executor::host::HostVmPrototype::new(crate::executor::host::Config {
module: code,
heap_pages: crate::executor::DEFAULT_HEAP_PAGES,
exec_hint: crate::executor::vm::ExecHint::Oneshot,
allow_unresolved_imports: false,
})
.unwrap()
};

let parent_hash = crate::calculate_genesis_block_header(&chain_specs).hash();
let (chain_info, genesis_runtime) = chain_specs.as_chain_information().unwrap();
let genesis_hash = chain_info.finalized_block_header.hash();

let mut builder = super::build_block(super::Config {
parent_runtime,
parent_hash: &parent_hash,
parent_runtime: genesis_runtime,
parent_hash: &genesis_hash,
parent_number: 0,
block_body_capacity: 0,
consensus_digest_log_item: super::ConfigPreRuntime::Aura(crate::header::AuraPreDigest {
Expand All @@ -61,7 +47,7 @@ fn block_building_works() {
super::BlockBuild::Finished(Ok(success)) => {
let decoded = crate::header::decode(&success.scale_encoded_header).unwrap();
assert_eq!(decoded.number, 1);
assert_eq!(*decoded.parent_hash, parent_hash);
assert_eq!(*decoded.parent_hash, genesis_hash);
break;
}
super::BlockBuild::Finished(Err(err)) => panic!("{}", err),
Expand Down
67 changes: 65 additions & 2 deletions src/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use crate::{
aura_config, babe_genesis_config, grandpa_genesis_config, BabeEpochInformation,
ChainInformation, ChainInformationConsensus, ChainInformationFinality,
},
executor, libp2p,
executor, header, libp2p, trie,
};

use alloc::{
Expand Down Expand Up @@ -179,8 +179,59 @@ impl ChainSpec {
(finality, vm_prototype)
};

let (state_version, vm_prototype) = {
match executor::core_version(vm_prototype) {
(Ok(runtime_spec), vm_prototype) => {
let state_version = match runtime_spec.decode().state_version {
Some(0) | None => trie::TrieEntryVersion::V0,
Some(1) => trie::TrieEntryVersion::V1,
Some(_) => return Err(FromGenesisStorageError::UnknownStateVersion),
};

(state_version, vm_prototype)
}
(Err(err), _) => return Err(FromGenesisStorageError::CoreVersionLoad(err)),
}
};

let chain_info = ChainInformation {
finalized_block_header: crate::calculate_genesis_block_header(self),
finalized_block_header: {
let state_root = match self.genesis_storage() {
GenesisStorage::TrieRootHash(hash) => *hash,
GenesisStorage::Items(genesis_storage) => {
let mut calculation = trie::calculate_root::root_merkle_value(None);

loop {
match calculation {
trie::calculate_root::RootMerkleValueCalculation::Finished {
hash,
..
} => break hash,
trie::calculate_root::RootMerkleValueCalculation::AllKeys(keys) => {
calculation = keys.inject(
genesis_storage.iter().map(|(k, _)| k.iter().copied()),
);
}
trie::calculate_root::RootMerkleValueCalculation::StorageValue(
val,
) => {
let key: alloc::vec::Vec<u8> = val.key().collect();
let value = genesis_storage.value(&key[..]);
calculation = val.inject(state_version, value);
}
}
}
}
};

header::Header {
parent_hash: [0; 32],
number: 0,
state_root,
extrinsics_root: trie::empty_trie_merkle_value(),
digest: header::DigestRef::empty().into(),
}
},
consensus,
finality,
};
Expand Down Expand Up @@ -353,6 +404,14 @@ impl<'a> GenesisStorage<'a> {
GenesisStorage::TrieRootHash(_) => None,
}
}

/// Returns `Some` for [`GenesisStorage::TrieRootHash`], and `None` otherwise.
pub fn into_trie_root_hash(self) -> Option<&'a [u8; 32]> {
match self {
GenesisStorage::Items(_) => None,
GenesisStorage::TrieRootHash(hash) => Some(hash),
}
}
}

/// See [`GenesisStorage`].
Expand Down Expand Up @@ -475,6 +534,10 @@ pub enum FromGenesisStorageError {
AuraConfigLoad(aura_config::FromVmPrototypeError),
/// Error when retrieving the Babe algorithm configuration.
BabeConfigLoad(babe_genesis_config::FromVmPrototypeError),
/// Failed to retrieve the core version of the runtime.
CoreVersionLoad(executor::CoreVersionError),
/// State version in runtime specification is not supported.
UnknownStateVersion,
/// Multiple consensus algorithms have been detected.
MultipleConsensusAlgorithms,
/// Chain specification doesn't contain the list of storage items.
Expand Down
40 changes: 19 additions & 21 deletions src/executor/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -791,8 +791,8 @@ impl ReadyToRun {
macro_rules! expect_state_version {
($num:expr) => {{
match &params[$num] {
vm::WasmValue::I32(0) => 0,
vm::WasmValue::I32(1) => 1,
vm::WasmValue::I32(0) => trie::TrieEntryVersion::V0,
vm::WasmValue::I32(1) => trie::TrieEntryVersion::V1,
v => {
return HostVm::Error {
error: Error::WrongParamTy {
Expand Down Expand Up @@ -932,9 +932,10 @@ impl ReadyToRun {
HostFunction::ext_storage_root_version_2 => {
let state_version = expect_state_version!(0);
match state_version {
0 => HostVm::ExternalStorageRoot(ExternalStorageRoot { inner: self.inner }),
1 => host_fn_not_implemented!(), // TODO: https://github.com/paritytech/smoldot/issues/1967
_ => unreachable!(),
trie::TrieEntryVersion::V0 => {
HostVm::ExternalStorageRoot(ExternalStorageRoot { inner: self.inner })
}
trie::TrieEntryVersion::V1 => host_fn_not_implemented!(), // TODO: https://github.com/paritytech/smoldot/issues/1967
}
}
HostFunction::ext_storage_changes_root_version_1 => {
Expand Down Expand Up @@ -1481,13 +1482,12 @@ impl ReadyToRun {
HostFunction::ext_sandbox_get_global_val_version_1 => host_fn_not_implemented!(),
HostFunction::ext_trie_blake2_256_root_version_1
| HostFunction::ext_trie_blake2_256_root_version_2 => {
if matches!(host_fn, HostFunction::ext_trie_blake2_256_root_version_2) {
match expect_state_version!(1) {
0 => {}
1 => host_fn_not_implemented!(), // TODO: https://github.com/paritytech/smoldot/issues/1967
_ => unreachable!(),
}
}
let state_version =
if matches!(host_fn, HostFunction::ext_trie_blake2_256_root_version_2) {
melekes marked this conversation as resolved.
Show resolved Hide resolved
expect_state_version!(1)
} else {
trie::TrieEntryVersion::V0
};

let result = {
let input = expect_pointer_size!(0);
Expand All @@ -1514,7 +1514,7 @@ impl ReadyToRun {
.map(|(_, parse_result)| parse_result);

match parsing_result {
Ok(elements) => Ok(trie::trie_root(&elements[..])),
Ok(elements) => Ok(trie::trie_root(state_version, &elements[..])),
Err(_) => Err(()),
}
};
Expand All @@ -1531,16 +1531,14 @@ impl ReadyToRun {
}
HostFunction::ext_trie_blake2_256_ordered_root_version_1
| HostFunction::ext_trie_blake2_256_ordered_root_version_2 => {
if matches!(
let state_version = if matches!(
host_fn,
HostFunction::ext_trie_blake2_256_ordered_root_version_2
) {
match expect_state_version!(1) {
0 => {}
1 => host_fn_not_implemented!(), // TODO: https://github.com/paritytech/smoldot/issues/1967
_ => unreachable!(),
}
}
expect_state_version!(1)
} else {
trie::TrieEntryVersion::V0
};

let result = {
let input = expect_pointer_size!(0);
Expand All @@ -1561,7 +1559,7 @@ impl ReadyToRun {
.map(|(_, parse_result)| parse_result);

match parsing_result {
Ok(elements) => Ok(trie::ordered_root(&elements[..])),
Ok(elements) => Ok(trie::ordered_root(state_version, &elements[..])),
Err(_) => Err(()),
}
};
Expand Down
10 changes: 7 additions & 3 deletions src/executor/runtime_host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

use crate::{
executor::{self, host, storage_diff, vm},
trie::calculate_root,
trie::{self, calculate_root},
util,
};

Expand Down Expand Up @@ -259,7 +259,9 @@ impl StorageGet {
if let calculate_root::RootMerkleValueCalculation::StorageValue(value_request) =
self.inner.root_calculation.take().unwrap()
{
self.inner.root_calculation = Some(value_request.inject(value));
// 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));
} else {
// We only create a `StorageGet` if the state is `StorageValue`.
panic!()
Expand Down Expand Up @@ -621,7 +623,9 @@ impl Inner {
.top_trie_changes
.diff_get(&value_request.key().collect::<Vec<_>>())
{
self.root_calculation = Some(value_request.inject(overlay));
// 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));
} else {
self.root_calculation =
Some(calculate_root::RootMerkleValueCalculation::StorageValue(
Expand Down
3 changes: 2 additions & 1 deletion src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ pub fn hash_from_scale_encoded_header_vectored(
/// Returns the value appropriate for [`Header::extrinsics_root`]. Must be passed the list of
/// transactions in that block.
pub fn extrinsics_root(transactions: &[impl AsRef<[u8]>]) -> [u8; 32] {
trie::ordered_root(transactions)
// The extrinsics root is always calculated with V0 of the trie.
trie::ordered_root(trie::TrieEntryVersion::V0, transactions)
}

/// Attempt to decode the given SCALE-encoded header.
Expand Down
Loading