From a8158f4fa2952165c1f0e3a5267bdb7a8fc54fe0 Mon Sep 17 00:00:00 2001 From: Aleksandr Logunov Date: Thu, 31 Oct 2024 15:41:41 +0400 Subject: [PATCH] fix: generic trie value updates (#12344) Preparation step for #12324. Introduce `generic_store_value` and `generic_delete_value`. These are different ways for MemTrie and TrieStorage to process value updates. TLDR: memtries can work with inlined values; trie storages must always have full values. Also the way to record accessed nodes is different. Also, solving some issues on the way. For example, interfaces of `Trie::insert` and `MemTrieUpdate::insert_impl` currently diverge. The latter needs both `FlatStateValue` for memtrie changes and `Option>` for disk changes. There is a good reason for that - if memtrie is loaded from flat state, we don't need to produce disk trie changes, so it is enough to load `FlatStateValue`s. However, the current interface is quite loose, so it is a net improvement to make it more strict by requiring everyone to give `ValueUpdate::MemtrieAndDisk` or `::MemtrieOnly`. The drawback is that `Trie::insert` technically can receive `ValueUpdate::MemtrieOnly`, it just will panic. But well... it still looks better than unclear memtrie interface with two values and hashes on the way. --- core/primitives/src/state.rs | 11 ++ core/store/src/trie/insert_delete.rs | 41 ++---- core/store/src/trie/mem/loading.rs | 4 +- core/store/src/trie/mem/resharding.rs | 2 +- core/store/src/trie/mem/updating.rs | 193 ++++++++++++++++++-------- core/store/src/trie/mod.rs | 28 ++-- 6 files changed, 175 insertions(+), 104 deletions(-) diff --git a/core/primitives/src/state.rs b/core/primitives/src/state.rs index 243d40e6a5f..ec67b7ec9ac 100644 --- a/core/primitives/src/state.rs +++ b/core/primitives/src/state.rs @@ -110,3 +110,14 @@ impl FlatStateValue { } } } + +/// Value to insert to trie or update existing value in the trie. +#[derive(Debug, Clone)] +pub enum GenericTrieValue { + /// Value to update both memtrie and trie storage. Full value is required + /// for that. + MemtrieAndDisk(Vec), + /// Value to update only memtrie. In such case it is enough to have a + /// `FlatStateValue`. + MemtrieOnly(FlatStateValue), +} diff --git a/core/store/src/trie/insert_delete.rs b/core/store/src/trie/insert_delete.rs index 8f80ea9da5b..27a17299695 100644 --- a/core/store/src/trie/insert_delete.rs +++ b/core/store/src/trie/insert_delete.rs @@ -1,5 +1,5 @@ use super::TrieRefcountDeltaMap; -use crate::trie::mem::updating::GenericTrieUpdateSquash; +use crate::trie::mem::updating::{GenericTrieUpdate, GenericTrieUpdateSquash}; use crate::trie::nibble_slice::NibbleSlice; use crate::trie::{ Children, NodeHandle, RawTrieNode, RawTrieNodeWithSize, StorageHandle, StorageValueHandle, @@ -8,11 +8,11 @@ use crate::trie::{ use crate::{StorageError, Trie, TrieChanges}; use borsh::BorshSerialize; use near_primitives::hash::{hash, CryptoHash}; -use near_primitives::state::ValueRef; +use near_primitives::state::{GenericTrieValue, ValueRef}; pub(crate) struct NodesStorage<'a> { nodes: Vec>, - values: Vec>>, + pub(crate) values: Vec>>, pub(crate) refcount_changes: TrieRefcountDeltaMap, pub(crate) trie: &'a Trie, } @@ -59,12 +59,6 @@ impl<'a> NodesStorage<'a> { StorageHandle(self.nodes.len() - 1) } - pub(crate) fn store_value(&mut self, value: Vec) -> StorageValueHandle { - let value_len = value.len(); - self.values.push(Some(value)); - StorageValueHandle(self.values.len() - 1, value_len) - } - pub(crate) fn value_ref(&self, handle: StorageValueHandle) -> &[u8] { self.values .get(handle.0) @@ -93,11 +87,10 @@ impl Trie { memory: &mut NodesStorage, node: StorageHandle, partial: NibbleSlice<'_>, - value: Vec, + value: GenericTrieValue, ) -> Result { let root_handle = node; let mut handle = node; - let mut value = Some(value); let mut partial = partial; let mut path = Vec::new(); loop { @@ -106,11 +99,8 @@ impl Trie { let children_memory_usage = memory_usage - node.memory_usage_direct(memory); match node { TrieNode::Empty => { - let value_handle = memory.store_value(value.take().unwrap()); - let leaf_node = TrieNode::Leaf( - partial.encoded(true).into_vec(), - ValueHandle::InMemory(value_handle), - ); + let value_handle = memory.generic_store_value(value); + let leaf_node = TrieNode::Leaf(partial.encoded(true).into_vec(), value_handle); let memory_usage = leaf_node.memory_usage_direct(memory); memory.store_at(handle, TrieNodeWithSize { node: leaf_node, memory_usage }); break; @@ -118,12 +108,11 @@ impl Trie { TrieNode::Branch(mut children, existing_value) => { // If the key ends here, store the value in branch's value. if partial.is_empty() { - if let Some(value) = &existing_value { - self.delete_value(memory, value)?; + if let Some(value) = existing_value { + memory.generic_delete_value(value)?; } - let value_handle = memory.store_value(value.take().unwrap()); - let new_node = - TrieNode::Branch(children, Some(ValueHandle::InMemory(value_handle))); + let value_handle = memory.generic_store_value(value); + let new_node = TrieNode::Branch(children, Some(value_handle)); let new_memory_usage = children_memory_usage + new_node.memory_usage_direct(memory); memory.store_at(handle, TrieNodeWithSize::new(new_node, new_memory_usage)); @@ -155,9 +144,9 @@ impl Trie { let common_prefix = partial.common_prefix(&existing_key); if common_prefix == existing_key.len() && common_prefix == partial.len() { // Equivalent leaf. - self.delete_value(memory, &existing_value)?; - let value_handle = memory.store_value(value.take().unwrap()); - let node = TrieNode::Leaf(key, ValueHandle::InMemory(value_handle)); + memory.generic_delete_value(existing_value)?; + let value_handle = memory.generic_store_value(value); + let node = TrieNode::Leaf(key, value_handle); let memory_usage = node.memory_usage_direct(memory); memory.store_at(handle, TrieNodeWithSize { node, memory_usage }); break; @@ -337,7 +326,7 @@ impl Trie { } TrieNode::Leaf(key, value) => { if NibbleSlice::from_encoded(&key).0 == partial { - self.delete_value(memory, &value)?; + memory.generic_delete_value(value)?; memory.store_at(handle, TrieNodeWithSize::empty()); break; } else { @@ -362,7 +351,7 @@ impl Trie { key_deleted = false; break; } - self.delete_value(memory, &value.unwrap())?; + memory.generic_delete_value(value.unwrap())?; Trie::calc_memory_usage_and_store( memory, handle, diff --git a/core/store/src/trie/mem/loading.rs b/core/store/src/trie/mem/loading.rs index 35b2b3c4cda..376a5e48caa 100644 --- a/core/store/src/trie/mem/loading.rs +++ b/core/store/src/trie/mem/loading.rs @@ -158,9 +158,9 @@ pub fn load_trie_from_flat_state_and_delta( for (key, value) in changes.0 { match value { Some(value) => { - trie_update.insert_memtrie_only(&key, value); + trie_update.insert_memtrie_only(&key, value)?; } - None => trie_update.delete(&key), + None => trie_update.delete(&key)?, }; } diff --git a/core/store/src/trie/mem/resharding.rs b/core/store/src/trie/mem/resharding.rs index c46037e5320..b6f984bdcd1 100644 --- a/core/store/src/trie/mem/resharding.rs +++ b/core/store/src/trie/mem/resharding.rs @@ -346,7 +346,7 @@ mod tests { let mut memtries = MemTries::new(ShardUId::single_shard()); let mut update = memtries.update(Trie::EMPTY_ROOT, false).unwrap(); for (key, value) in initial_entries { - update.insert(&key, value); + update.insert(&key, value).unwrap(); } let memtrie_changes = update.to_mem_trie_changes_only(); let state_root = memtries.apply_memtrie_changes(0, &memtrie_changes); diff --git a/core/store/src/trie/mem/updating.rs b/core/store/src/trie/mem/updating.rs index a7dcde48243..c079878d8c2 100644 --- a/core/store/src/trie/mem/updating.rs +++ b/core/store/src/trie/mem/updating.rs @@ -13,14 +13,14 @@ use super::metrics::MEM_TRIE_NUM_NODES_CREATED_FROM_UPDATES; use super::node::{InputMemTrieNode, MemTrieNodeId, MemTrieNodeView}; use crate::trie::insert_delete::NodesStorage; use crate::trie::{ - Children, MemTrieChanges, NodeHandle, StorageHandle, TrieNode, TrieNodeWithSize, - TrieRefcountDeltaMap, ValueHandle, TRIE_COSTS, + Children, MemTrieChanges, NodeHandle, StorageHandle, StorageValueHandle, TrieNode, + TrieNodeWithSize, TrieRefcountDeltaMap, ValueHandle, TRIE_COSTS, }; use crate::{NibbleSlice, RawTrieNode, RawTrieNodeWithSize, TrieChanges}; use near_primitives::errors::StorageError; use near_primitives::hash::{hash, CryptoHash}; -use near_primitives::state::FlatStateValue; -use std::collections::HashMap; +use near_primitives::state::{FlatStateValue, GenericTrieValue}; +use std::collections::{BTreeMap, HashMap}; use std::sync::Arc; /// For updated nodes, the ID is simply the index into the array of updated nodes we keep. @@ -198,6 +198,12 @@ pub(crate) trait GenericTrieUpdate<'a, GenericTrieNodePtr, GenericValueHandle> { &self, node_id: GenericUpdatedNodeId, ) -> GenericUpdatedTrieNodeWithSize; + + /// Stores a state value in the trie. + fn generic_store_value(&mut self, value: GenericTrieValue) -> GenericValueHandle; + + /// Deletes a state value from the trie. + fn generic_delete_value(&mut self, value: GenericValueHandle) -> Result<(), StorageError>; } /// Keeps values and internal nodes accessed on updating memtrie. @@ -212,8 +218,13 @@ pub struct TrieAccesses { /// Tracks intermediate trie changes, final version of which is to be committed /// to disk after finishing trie update. struct TrieChangesTracker { - /// Changes of reference count on disk for each impacted node. - refcount_changes: TrieRefcountDeltaMap, + /// Counts hashes deleted so far. + /// Includes hashes of both trie nodes and state values! + refcount_deleted_hashes: BTreeMap, + /// Counts state values inserted so far. + /// Separated from `refcount_deleted_hashes` to postpone hash computation + /// as far as possible. + refcount_inserted_values: BTreeMap, u32>, /// All observed values and internal nodes. /// Needed to prepare recorded storage. /// Note that negative `refcount_changes` does not fully cover it, as node @@ -222,6 +233,20 @@ struct TrieChangesTracker { accesses: TrieAccesses, } +impl TrieChangesTracker { + /// Prepare final refcount difference and also return all trie accesses. + fn finalize(self) -> (TrieRefcountDeltaMap, TrieAccesses) { + let mut refcount_delta_map = TrieRefcountDeltaMap::new(); + for (value, rc) in self.refcount_inserted_values { + refcount_delta_map.add(hash(&value), value, rc); + } + for (hash, rc) in self.refcount_deleted_hashes { + refcount_delta_map.subtract(hash, rc); + } + (refcount_delta_map, self.accesses) + } +} + /// Structure to build an update to the in-memory trie. pub struct MemTrieUpdate<'a, M: ArenaMemory> { /// The original root before updates. It is None iff the original trie had no keys. @@ -310,6 +335,43 @@ impl<'a, M: ArenaMemory> GenericTrieUpdate<'a, MemTrieNodeId, FlatStateValue> memory_usage: 0, } } + + fn generic_store_value(&mut self, value: GenericTrieValue) -> FlatStateValue { + // First, set the value which will be stored in memtrie. + let flat_value = match &value { + GenericTrieValue::MemtrieOnly(value) => return value.clone(), + GenericTrieValue::MemtrieAndDisk(value) => FlatStateValue::on_disk(value.as_slice()), + }; + + // Then, record disk changes if needed. + let Some(tracked_node_changes) = self.tracked_trie_changes.as_mut() else { + return flat_value; + }; + let GenericTrieValue::MemtrieAndDisk(value) = value else { + return flat_value; + }; + tracked_node_changes + .refcount_inserted_values + .entry(value) + .and_modify(|rc| *rc += 1) + .or_insert(1); + + flat_value + } + + fn generic_delete_value(&mut self, value: FlatStateValue) -> Result<(), StorageError> { + if let Some(tracked_node_changes) = self.tracked_trie_changes.as_mut() { + let hash = value.to_value_ref().hash; + tracked_node_changes.accesses.values.insert(hash, value); + tracked_node_changes + .refcount_deleted_hashes + .entry(hash) + .and_modify(|rc| *rc += 1) + .or_insert(1); + } + + Ok(()) + } } pub(crate) type TrieStorageNodePtr = CryptoHash; @@ -422,6 +484,32 @@ impl<'a> GenericTrieUpdate<'a, TrieStorageNodePtr, ValueHandle> for NodesStorage memory_usage, } } + + fn generic_store_value(&mut self, value: GenericTrieValue) -> ValueHandle { + let GenericTrieValue::MemtrieAndDisk(value) = value else { + unimplemented!( + "NodesStorage for Trie doesn't support value {value:?} \ + because disk updates must be generated." + ); + }; + + let value_len = value.len(); + self.values.push(Some(value)); + ValueHandle::InMemory(StorageValueHandle(self.values.len() - 1, value_len)) + } + + fn generic_delete_value(&mut self, value: ValueHandle) -> Result<(), StorageError> { + match value { + ValueHandle::HashAndSize(value) => { + self.trie.internal_retrieve_trie_node(&value.hash, true, true)?; + self.refcount_changes.subtract(value.hash, 1); + } + ValueHandle::InMemory(_) => { + // do nothing + } + } + Ok(()) + } } impl<'a, M: ArenaMemory> MemTrieUpdate<'a, M> { @@ -438,7 +526,8 @@ impl<'a, M: ArenaMemory> MemTrieUpdate<'a, M> { updated_nodes: vec![], tracked_trie_changes: if track_trie_changes { Some(TrieChangesTracker { - refcount_changes: TrieRefcountDeltaMap::new(), + refcount_inserted_values: BTreeMap::new(), + refcount_deleted_hashes: BTreeMap::new(), accesses: TrieAccesses { nodes: HashMap::new(), values: HashMap::new() }, }) } else { @@ -489,7 +578,11 @@ impl<'a, M: ArenaMemory> MemTrieUpdate<'a, M> { .accesses .nodes .insert(node_hash, raw_node_serialized.into()); - tracked_trie_changes.refcount_changes.subtract(node_hash, 1); + tracked_trie_changes + .refcount_deleted_hashes + .entry(node_hash) + .and_modify(|rc| *rc += 1) + .or_insert(1); } self.new_updated_node(UpdatedMemTrieNode::from_existing_node_view( node.as_ptr(self.memory).view(), @@ -505,29 +598,19 @@ impl<'a, M: ArenaMemory> MemTrieUpdate<'a, M> { } } - fn add_refcount_to_value(&mut self, hash: CryptoHash, value: Option>) { - if let Some(tracked_node_changes) = self.tracked_trie_changes.as_mut() { - tracked_node_changes.refcount_changes.add(hash, value.unwrap(), 1); - } - } - - fn subtract_refcount_for_value(&mut self, value: FlatStateValue) { - if let Some(tracked_node_changes) = self.tracked_trie_changes.as_mut() { - let hash = value.to_value_ref().hash; - tracked_node_changes.accesses.values.insert(hash, value); - tracked_node_changes.refcount_changes.subtract(hash, 1); - } - } - /// Inserts the given key value pair into the trie. - pub fn insert(&mut self, key: &[u8], value: Vec) { - self.insert_impl(key, FlatStateValue::on_disk(&value), Some(value)); + pub fn insert(&mut self, key: &[u8], value: Vec) -> Result<(), StorageError> { + self.insert_impl(key, GenericTrieValue::MemtrieAndDisk(value)) } /// Inserts the given key value pair into the trie, but the value may be a reference. /// This is used to update the in-memory trie only, without caring about on-disk changes. - pub fn insert_memtrie_only(&mut self, key: &[u8], value: FlatStateValue) { - self.insert_impl(key, value, None); + pub fn insert_memtrie_only( + &mut self, + key: &[u8], + value: FlatStateValue, + ) -> Result<(), StorageError> { + self.insert_impl(key, GenericTrieValue::MemtrieOnly(value)) } /// Insertion logic. We descend from the root down to whatever node corresponds to @@ -535,13 +618,9 @@ impl<'a, M: ArenaMemory> MemTrieUpdate<'a, M> { /// the way to achieve that. This takes care of refcounting changes for existing /// nodes as well as values, but will not yet increment refcount for any newly /// created nodes - that's done at the end. - /// - /// Note that `value` must be Some if we're keeping track of on-disk changes, but can - /// be None if we're only keeping track of in-memory changes. - fn insert_impl(&mut self, key: &[u8], flat_value: FlatStateValue, value: Option>) { + fn insert_impl(&mut self, key: &[u8], value: GenericTrieValue) -> Result<(), StorageError> { let mut node_id = 0; // root let mut partial = NibbleSlice::new(key); - let value_ref = flat_value.to_value_ref(); loop { // Take out the current node; we'd have to change it no matter what. @@ -549,27 +628,27 @@ impl<'a, M: ArenaMemory> MemTrieUpdate<'a, M> { match node { UpdatedMemTrieNode::Empty => { // There was no node here, create a new leaf. + let value_handle = self.generic_store_value(value); self.place_node( node_id, UpdatedMemTrieNode::Leaf { extension: partial.encoded(true).into_vec().into_boxed_slice(), - value: flat_value, + value: value_handle, }, ); - self.add_refcount_to_value(value_ref.hash, value); break; } UpdatedMemTrieNode::Branch { children, value: old_value } => { if partial.is_empty() { // This branch node is exactly where the value should be added. if let Some(value) = old_value { - self.subtract_refcount_for_value(value); + self.generic_delete_value(value)?; } + let value_handle = self.generic_store_value(value); self.place_node( node_id, - UpdatedMemTrieNode::Branch { children, value: Some(flat_value) }, + UpdatedMemTrieNode::Branch { children, value: Some(value_handle) }, ); - self.add_refcount_to_value(value_ref.hash, value); break; } else { // Continue descending into the branch, possibly adding a new child. @@ -594,12 +673,12 @@ impl<'a, M: ArenaMemory> MemTrieUpdate<'a, M> { let common_prefix = partial.common_prefix(&existing_key); if common_prefix == existing_key.len() && common_prefix == partial.len() { // We're at the exact leaf. Rewrite the value at this leaf. - self.subtract_refcount_for_value(old_value); + self.generic_delete_value(old_value)?; + let value_handle = self.generic_store_value(value); self.place_node( node_id, - UpdatedMemTrieNode::Leaf { extension, value: flat_value }, + UpdatedMemTrieNode::Leaf { extension, value: value_handle }, ); - self.add_refcount_to_value(value_ref.hash, value); break; } else if common_prefix == 0 { // Convert the leaf to an equivalent branch. We are not adding @@ -707,6 +786,8 @@ impl<'a, M: ArenaMemory> MemTrieUpdate<'a, M> { } } } + + Ok(()) } /// Deletes a key from the trie. @@ -716,7 +797,7 @@ impl<'a, M: ArenaMemory> MemTrieUpdate<'a, M> { /// consistent by changing the types of any nodes along the way. /// /// Deleting a non-existent key is allowed, and is a no-op. - pub fn delete(&mut self, key: &[u8]) { + pub fn delete(&mut self, key: &[u8]) -> Result<(), StorageError> { let mut node_id = 0; // root let mut partial = NibbleSlice::new(key); let mut path = vec![]; // for squashing at the end. @@ -729,17 +810,17 @@ impl<'a, M: ArenaMemory> MemTrieUpdate<'a, M> { UpdatedMemTrieNode::Empty => { // Nothing to delete. self.place_node(node_id, UpdatedMemTrieNode::Empty); - return; + return Ok(()); } UpdatedMemTrieNode::Leaf { extension, value } => { if NibbleSlice::from_encoded(&extension).0 == partial { - self.subtract_refcount_for_value(value); + self.generic_delete_value(value)?; self.place_node(node_id, UpdatedMemTrieNode::Empty); break; } else { // Key being deleted doesn't exist. self.place_node(node_id, UpdatedMemTrieNode::Leaf { extension, value }); - return; + return Ok(()); } } UpdatedMemTrieNode::Branch { children: old_children, value } => { @@ -750,9 +831,9 @@ impl<'a, M: ArenaMemory> MemTrieUpdate<'a, M> { node_id, UpdatedMemTrieNode::Branch { children: old_children, value }, ); - return; + return Ok(()); }; - self.subtract_refcount_for_value(value.unwrap()); + self.generic_delete_value(value.unwrap())?; self.place_node( node_id, UpdatedMemTrieNode::Branch { children: old_children, value: None }, @@ -770,7 +851,7 @@ impl<'a, M: ArenaMemory> MemTrieUpdate<'a, M> { node_id, UpdatedMemTrieNode::Branch { children: old_children, value }, ); - return; + return Ok(()); } }; let new_child_id = self.ensure_updated(old_child_id); @@ -809,7 +890,7 @@ impl<'a, M: ArenaMemory> MemTrieUpdate<'a, M> { node_id, UpdatedMemTrieNode::Extension { extension, child }, ); - return; + return Ok(()); } } } @@ -819,6 +900,7 @@ impl<'a, M: ArenaMemory> MemTrieUpdate<'a, M> { for node_id in path.into_iter().rev() { self.squash_node(node_id).unwrap(); } + Ok(()) } } @@ -1174,10 +1256,11 @@ impl<'a, M: ArenaMemory> MemTrieUpdate<'a, M> { pub(crate) fn to_trie_changes(mut self) -> (TrieChanges, TrieAccesses) { let old_root = self.root.map(|root| root.as_ptr(self.memory).view().node_hash()).unwrap_or_default(); - let TrieChangesTracker { mut refcount_changes, accesses } = self + let (mut refcount_changes, accesses) = self .tracked_trie_changes .take() - .expect("Cannot to_trie_changes for memtrie changes only"); + .expect("Cannot to_trie_changes for memtrie changes only") + .finalize(); let (mem_trie_changes, hashes_and_serialized) = self.to_mem_trie_changes_internal(); // We've accounted for the dereferenced nodes, as well as value addition/subtractions. @@ -1299,9 +1382,9 @@ mod tests { }); for (key, value) in changes { if let Some(value) = value { - update.insert(&key, value); + update.insert(&key, value).unwrap(); } else { - update.delete(&key); + update.delete(&key).unwrap(); } } update.to_trie_changes().0 @@ -1316,9 +1399,9 @@ mod tests { }); for (key, value) in changes { if let Some(value) = value { - update.insert_memtrie_only(&key, FlatStateValue::on_disk(&value)); + update.insert_memtrie_only(&key, FlatStateValue::on_disk(&value)).unwrap(); } else { - update.delete(&key); + update.delete(&key).unwrap(); } } update.to_mem_trie_changes_only() @@ -1696,9 +1779,9 @@ mod tests { for (key, value) in changes { if let Some(value) = value { - update.insert_memtrie_only(&key, FlatStateValue::on_disk(&value)); + update.insert_memtrie_only(&key, FlatStateValue::on_disk(&value)).unwrap(); } else { - update.delete(&key); + update.delete(&key).unwrap(); } } diff --git a/core/store/src/trie/mod.rs b/core/store/src/trie/mod.rs index 23994365748..d939b6342fd 100644 --- a/core/store/src/trie/mod.rs +++ b/core/store/src/trie/mod.rs @@ -888,23 +888,6 @@ impl Trie { memory_usage } - fn delete_value( - &self, - memory: &mut NodesStorage, - value: &ValueHandle, - ) -> Result<(), StorageError> { - match value { - ValueHandle::HashAndSize(value) => { - self.internal_retrieve_trie_node(&value.hash, true, true)?; - memory.refcount_changes.subtract(value.hash, 1); - } - ValueHandle::InMemory(_) => { - // do nothing - } - } - Ok(()) - } - /// Prints the trie nodes starting from `hash`, up to `max_depth` depth. The node hash can be any node in the trie. /// Depending on arguments provided, can limit output to no more than `limit` entries, /// show only subtree for a given `record_type`, or skip subtrees where `AccountId` is less than `from` or greater than `to`. @@ -1623,8 +1606,8 @@ impl Trie { let mut trie_update = guard.update(self.root, true)?; for (key, value) in changes { match value { - Some(arr) => trie_update.insert(&key, arr), - None => trie_update.delete(&key), + Some(arr) => trie_update.insert(&key, arr)?, + None => trie_update.delete(&key)?, } } let (trie_changes, trie_accesses) = trie_update.to_trie_changes(); @@ -1672,7 +1655,12 @@ impl Trie { for (key, value) in changes { let key = NibbleSlice::new(&key); root_node = match value { - Some(arr) => self.insert(&mut memory, root_node, key, arr), + Some(arr) => self.insert( + &mut memory, + root_node, + key, + near_primitives::state::GenericTrieValue::MemtrieAndDisk(arr), + ), None => self.delete(&mut memory, root_node, key), }?; }