Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Multiple storage root support #902

Merged
merged 34 commits into from
Oct 18, 2018
Merged

Multiple storage root support #902

merged 34 commits into from
Oct 18, 2018

Conversation

sorpaas
Copy link
Member

@sorpaas sorpaas commented Oct 12, 2018

rel #872

This adds support for multiple storage root. The basic idea is that we compute multiple tries (maybe of different types) for the same runtime, and in the end, merge the changeset to commit the results. This allows children trie to get its own storage root.

The top-level storage contains a WELL_KNOWN_KEYS prefix :child_storage:, within which we store all child storage roots. A child storage root is updated whenever Ext::child_storage_root is called, or Ext::storage_root is called. Ext::place_storage and other mutation operations are forbidden to modify storage values under :child_storage:.

Currently it's a non-generic version, but it already works -- even for multiple trie types. (I'm hoping to get a generic version, together with generic tries, in a separate PR.) The trick is that one can specify [patch] for substrate-trie to overwrite the default implementation, and then write a customized child_delta_trie_root. The idea is that that function should dispatch and use different trie types to compute the transaction based on the child "storage key". A customized implementation can also use is_child_trie_key_valid to make it so that only a subset of the :child_storage: namespace is valid.

For the default implementation right now in substrate-trie, it builds all child tries using the same trie type.

@sorpaas sorpaas added the A0-please_review Pull request needs code review. label Oct 12, 2018
@gavofyork gavofyork requested a review from arkpar October 14, 2018 10:29
/// Fetch child storage root together with its transaction.
fn child_storage_root_transaction(&mut self, storage_key: &[u8]) -> (Vec<u8>, B::Transaction) {
self.mark_dirty();
let storage_key = storage_key.to_vec();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you convert it to an Vec at this position?
The Vec is required in sync_child_storage_root and this function clones the incoming value. So, we make one allocation that is not required at all. If you would call to_vec in sync_child_storage_root instead of clone(). You could reduce the number of allocations to 1.

.insert(extrinsic);
}

for (_, entry) in map_entry.1.iter_mut() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick:
Could be changed to:
map_entry.1.values_mut().for_each(|e| e = None)

Copy link
Contributor

@svyatonik svyatonik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

.into_iter()
.flat_map(|map| map.1.iter().map(|(k, v)| (k.clone(), v.clone())))
.chain(self.overlay.prospective.children.get(storage_key)
.into_iter()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spaces -> tabs

@svyatonik svyatonik added F1-panic and removed A0-please_review Pull request needs code review. F1-panic labels Oct 18, 2018
if let Some(ref b) = maybe_value {
format!("{}", HexDisplay::from(b))
} else {
"<empty>".to_owned()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can remove the .to_owned() when you change the first if branch to &format!("{}", HexDisplay::from(b)).
So you don't need to allocate in the else branch :)

if let Some(ref b) = maybe_value {
format!("{}", HexDisplay::from(b))
} else {
"<empty>".to_owned()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as below.

let key = this.memory.get(key_data, key_len as usize).map_err(|_| UserError("Invalid attempt to determine key in ext_set_child_storage"))?;
let value = this.memory.get(value_data, value_len as usize).map_err(|_| UserError("Invalid attempt to determine value in ext_set_child_storage"))?;
if let Some(_preimage) = this.hash_lookup.get(&key) {
debug_trace!(target: "wasm-trace", "*** Setting child storage: {} -> %{} -> {} [k={}]", ::primitives::hexdisplay::ascii_format(&storage_key), ::primitives::hexdisplay::ascii_format(&_preimage), HexDisplay::from(&value), HexDisplay::from(&key));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you maybe break these two debug_trace! into multiple lines?

ext::with(|ext| ext.child_storage(storage_key, key).map(|value| {
let value = &value[value_offset..];
let written = ::std::cmp::min(value.len(), value_out.len());
value_out[0..written].copy_from_slice(&value[0..written]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: You can remove the leading 0 in [0..written].

/// Set storage entry `key` of current contract being called (effective immediately).
fn set_storage(&mut self, key: Vec<u8>, value: Vec<u8>) {
self.place_storage(key, Some(value));
}

/// Set child storage entry `key` of current contract being called (effective immediately).
fn set_child_storage(&mut self, storage_key: Vec<u8>, key: Vec<u8>, value: Vec<u8>) -> bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe taking just a slice of u8 for each of these values would be better.
You have cases calling this function, where the data is not required to be an Vec<u8> and so we could reduce the number allocations.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only one I found where it may be redundant is kill_child_storage, which I think would be fine because it doesn't mean to be a fast operation.

I would actually hope to keep this to use Vec<u8> because it is a public interface. We also have set_storage and many other interface here that use Vec<u8>.

if length == u32::max_value() {
None
} else {
Some(Vec::from_raw_parts(ptr, length as usize, length as usize))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, as above.

core/state-machine/src/backend.rs Show resolved Hide resolved
core/state-machine/src/backend.rs Show resolved Hide resolved
}
let root = match root {
Some(root) => root,
None => insert_into_memory_db::<H, _>(&mut mdb, HashMap::new().into_iter())?,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think HashMap::new().into_iter() can be replaced by https://doc.rust-lang.org/std/iter/fn.empty.html

core/state-machine/src/ext.rs Show resolved Hide resolved
Ok(())
},
ext_clear_child_storage(storage_key_data: *const u8, storage_key_len: u32, key_data: *const u8, key_len: u32) => {
let storage_key = this.memory.get(storage_key_data, storage_key_len as usize).map_err(|_| UserError("Invalid attempt to determine storage_key in ext_clear_child_storage"))?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this code is wider than 120 maximum. Can we wrap this code?

@@ -196,11 +221,21 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
let key = this.memory.get(key_data, key_len as usize).map_err(|_| UserError("Invalid attempt to determine key in ext_exists_storage"))?;
Ok(if this.ext.exists_storage(&key) { 1 } else { 0 })
},
ext_exists_child_storage(storage_key_data: *const u8, storage_key_len: u32, key_data: *const u8, key_len: u32) -> u32 => {
let storage_key = this.memory.get(storage_key_data, storage_key_len as usize).map_err(|_| UserError("Invalid attempt to determine storage_key in ext_exists_child_storage"))?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this code is wider than 120 maximum. Can we wrap this code?

ext_clear_prefix(prefix_data: *const u8, prefix_len: u32) => {
let prefix = this.memory.get(prefix_data, prefix_len as usize).map_err(|_| UserError("Invalid attempt to determine prefix in ext_clear_prefix"))?;
this.ext.clear_prefix(&prefix);
Ok(())
},
ext_kill_child_storage(storage_key_data: *const u8, storage_key_len: u32) => {
let storage_key = this.memory.get(storage_key_data, storage_key_len as usize).map_err(|_| UserError("Invalid attempt to determine storage_key in ext_kill_child_storage"))?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this code is wider than 120 maximum. Can we wrap this code?

@@ -232,6 +267,39 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
Ok(0)
}
},
// return 0 and place u32::max_value() into written_out if no value exists for the key.
ext_get_allocated_child_storage(storage_key_data: *const u8, storage_key_len: u32, key_data: *const u8, key_len: u32, written_out: *mut u32) -> *mut u8 => {
let storage_key = this.memory.get(storage_key_data, storage_key_len as usize).map_err(|_| UserError("Invalid attempt to determine storage_key in ext_get_allocated_child_storage"))?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this code is wider than 120 maximum. Can we wrap this code?

@@ -259,11 +327,55 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
Ok(u32::max_value())
}
},
// return u32::max_value() if no value exists for the key.
ext_get_child_storage_into(storage_key_data: *const u8, storage_key_len: u32, key_data: *const u8, key_len: u32, value_data: *mut u8, value_len: u32, value_offset: u32) -> u32 => {
let storage_key = this.memory.get(storage_key_data, storage_key_len as usize).map_err(|_| UserError("Invalid attempt to determine storage_key in ext_get_child_storage_into"))?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this code is wider than 120 maximum. Can we wrap this code?

if length == u32::max_value() {
None
} else {
Some(slice::from_raw_parts(ptr, length as usize).to_vec())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last time ^^
@pepyakin can't we call ext_free here for freeing ptr?
If that not works, I would at least propose to add TODO over these lines that we do not forget these memory leaks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is the only possible way, as long as ext_* make allocations with it.

Copy link
Member

@bkchr bkchr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me :)

@gavofyork gavofyork merged commit 234e31d into master Oct 18, 2018
@bkchr bkchr deleted the sp-multi-trie2 branch October 18, 2018 19:43
lamafab pushed a commit to lamafab/substrate that referenced this pull request Jun 16, 2020
liuchengxu pushed a commit to chainx-org/substrate that referenced this pull request Aug 23, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants