Conversation
| /// 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(); |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
Nitpick:
Could be changed to:
map_entry.1.values_mut().for_each(|e| e = None)
| .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() |
| if let Some(ref b) = maybe_value { | ||
| format!("{}", HexDisplay::from(b)) | ||
| } else { | ||
| "<empty>".to_owned() |
There was a problem hiding this comment.
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() |
| 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)); |
There was a problem hiding this comment.
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]); |
There was a problem hiding this comment.
Nitpick: You can remove the leading 0 in [0..written].
| } | ||
|
|
||
| /// 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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)) |
| } | ||
| let root = match root { | ||
| Some(root) => root, | ||
| None => insert_into_memory_db::<H, _>(&mut mdb, HashMap::new().into_iter())?, |
There was a problem hiding this comment.
I think HashMap::new().into_iter() can be replaced by https://doc.rust-lang.org/std/iter/fn.empty.html
| 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"))?; |
There was a problem hiding this comment.
It looks like this code is wider than 120 maximum. Can we wrap this code?
| 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"))?; |
There was a problem hiding this comment.
It looks like this code is wider than 120 maximum. Can we wrap this code?
| 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"))?; |
There was a problem hiding this comment.
It looks like this code is wider than 120 maximum. Can we wrap this code?
| }, | ||
| // 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"))?; |
There was a problem hiding this comment.
It looks like this code is wider than 120 maximum. Can we wrap this code?
| }, | ||
| // 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"))?; |
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Yes, this is the only possible way, as long as ext_* make allocations with it.
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_KEYSprefix:child_storage:, within which we store all child storage roots. A child storage root is updated wheneverExt::child_storage_rootis called, orExt::storage_rootis called.Ext::place_storageand 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]forsubstrate-trieto overwrite the default implementation, and then write a customizedchild_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 useis_child_trie_key_validto 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.