diff --git a/crates/mpt/Cargo.toml b/crates/mpt/Cargo.toml index b8b81313..9a9e8135 100644 --- a/crates/mpt/Cargo.toml +++ b/crates/mpt/Cargo.toml @@ -17,6 +17,7 @@ alloy-primitives = { workspace = true, features = ["rlp"] } # External alloy-trie = { version = "0.3.1", default-features = false } alloy-rlp = { version = "0.3.4", default-features = false } +alloy-consensus = { git = "https://github.com/alloy-rs/alloy", default-features = false } smallvec = "1.13" revm-primitives = { version = "3.1.1", default-features = false } revm = { version = "8.0.0", default-features = false } diff --git a/crates/mpt/README.md b/crates/mpt/README.md index 44e4889e..1d1f8ac5 100644 --- a/crates/mpt/README.md +++ b/crates/mpt/README.md @@ -1,3 +1,3 @@ # `kona-mpt` -Utilities for interacting with and iterating through a merkle patricia trie +Utilities for interacting with a merkle patricia trie in the client program. diff --git a/crates/mpt/src/db/account.rs b/crates/mpt/src/db/account.rs new file mode 100644 index 00000000..9fa11f8c --- /dev/null +++ b/crates/mpt/src/db/account.rs @@ -0,0 +1,48 @@ +//! This module contains the [TrieAccount] struct. + +use alloy_consensus::constants::KECCAK_EMPTY; +use alloy_primitives::{B256, U256}; +use alloy_rlp::{RlpDecodable, RlpEncodable}; +use revm_primitives::{Account, AccountInfo}; + +/// An Ethereum account as represented in the trie. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, RlpEncodable, RlpDecodable)] +pub struct TrieAccount { + /// Account nonce. + nonce: u64, + /// Account balance. + balance: U256, + /// Account's storage root. + storage_root: B256, + /// Hash of the account's bytecode. + code_hash: B256, +} + +impl From<(Account, B256)> for TrieAccount { + fn from((account, storage_root): (Account, B256)) -> Self { + Self { + nonce: account.info.nonce, + balance: account.info.balance, + storage_root, + code_hash: account.info.code_hash, + } + } +} + +impl From<(AccountInfo, B256)> for TrieAccount { + fn from((account, storage_root): (AccountInfo, B256)) -> Self { + Self { + nonce: account.nonce, + balance: account.balance, + storage_root, + code_hash: account.code_hash, + } + } +} + +impl TrieAccount { + /// Get account's storage root. + pub fn storage_root(&self) -> B256 { + self.storage_root + } +} diff --git a/crates/mpt/src/db/mod.rs b/crates/mpt/src/db/mod.rs index 54e28c2d..6ab86c83 100644 --- a/crates/mpt/src/db/mod.rs +++ b/crates/mpt/src/db/mod.rs @@ -1,8 +1,9 @@ -//! This module contains an implementation of an in-memory Trie DB, that allows for incremental updates through fetching -//! node preimages on the fly. +//! This module contains an implementation of an in-memory Trie DB for [revm], that allows for +//! incremental updates through fetching node preimages on the fly during execution. #![allow(dead_code, unused)] +use crate::{NodeElement, TrieNode}; use alloc::collections::VecDeque; use alloy_primitives::{keccak256, Address, Bytes, B256, U256}; use alloy_rlp::Decodable; @@ -12,21 +13,25 @@ use core::marker::PhantomData; use revm::{db::DbAccount, Database, DatabaseCommit, DatabaseRef, InMemoryDB}; use revm_primitives::{hash_map::Entry, Account, AccountInfo, Bytecode, HashMap}; -use crate::{NodeElement, TrieNode}; +mod account; +pub use account::TrieAccount; -/// A Trie DB that caches account state in-memory. When accounts that don't already exist within the cache are queried, -/// the database fetches the preimages of the trie nodes on the path to the account using the `PreimageFetcher` -/// (`PF` generic) and `CodeHashFetcher` (`CHF` generic). This allows for data to be fetched in a verifiable manner -/// given an initial trusted state root as it is needed during execution. +/// A Trie DB that caches account state in-memory. When accounts that don't already exist within the +/// cache are queried, the database fetches the preimages of the trie nodes on the path to the +/// account using the `PreimageFetcher` (`PF` generic) and `CodeHashFetcher` (`CHF` generic). This +/// allows for data to be fetched in a verifiable manner given an initial trusted state root as it +/// is needed during execution. /// /// **Behavior**: -/// - When an account is queried and it does not already exist in the inner cache database, we fall through to the -/// `PreimageFetcher` to fetch the preimages of the trie nodes on the path to the account. After it has been fetched, -/// the account is inserted into the cache database and will be read from there on subsequent queries. -/// - When querying for the code hash of an account, the `CodeHashFetcher` is consulted to fetch the code hash of the -/// account. -/// - When a changeset is committed to the database, the changes are first applied to the cache database and then the -/// trie is recomputed. The root hash of the trie is then persisted as +/// - When an account is queried and it does not already exist in the inner cache database, we fall +/// through to the `PreimageFetcher` to fetch the preimages of the trie nodes on the path to the +/// account. After it has been fetched, the account is inserted into the cache database and will +/// be read from there on subsequent queries. +/// - When querying for the code hash of an account, the `CodeHashFetcher` is consulted to fetch the +/// code hash of the account. +/// - When a changeset is committed to the database, the changes are first applied to the cache +/// database and then the trie hash is recomputed. The root hash of the trie is then persisted to +/// the struct. #[derive(Debug, Default, Clone)] pub struct TrieCacheDB { /// The underlying DB that stores the account state in-memory. @@ -88,78 +93,6 @@ where todo!() } - - /// Walks down the trie to a leaf value with the given key, if it exists. Preimages for blinded nodes along the - /// path are fetched using the `fetcher` function. - /// - /// TODO: Fix nibble relations - fn get_trie( - &self, - item_key: &Bytes, - trie_node: TrieNode, - mut pos: usize, - fetcher: PF, - ) -> Result<(Bytes, Bytes)> { - match trie_node { - TrieNode::Branch { stack } => { - let next = item_key[pos]; - extern crate std; - std::dbg!(next); - - // for (i, node) in stack.into_iter().enumerate() { - // match node { - // NodeElement::String(s) => { - // // If the string is a hash, we need to grab the preimage for it and - // // continue recursing. - // let hash: B256 = s.as_ref().try_into().map_err(|e| anyhow!("Conversion error: {e}"))?; - // let trie_node = TrieNode::decode(&mut fetcher(hash)?.as_ref()).map_err(|e| anyhow!(e))?; - // - // // If the value was found in the blinded node, return it. - // if let Ok((key, value)) = self.get_trie(item_key, trie_node, pos, fetcher) { - // return Ok((key, value)); - // } - // } - // list @ NodeElement::List(_) => { - // let trie_node = list.try_list_into_node()?; - // - // // If the value was found in the blinded node, return it. - // if let Ok((key, value)) = self.get_trie(item_key, trie_node, pos, fetcher) { - // return Ok((key, value)); - // } - // } - // _ => { /* Skip over empty lists and strings; We're looking for leaves */ } - // }; - // } - - anyhow::bail!("Key does not exist in trie"); - } - TrieNode::Leaf { key, value } => { - let shared_nibbles = key[1..].as_ref(); - let item_key_nibbles = item_key[pos..pos + shared_nibbles.len()].as_ref(); - if item_key_nibbles == shared_nibbles { - Ok((key, value)) - } else { - anyhow::bail!("Key does not exist in trie"); - } - } - TrieNode::Extension { prefix, node } => { - let shared_nibbles = prefix[1..].as_ref(); - let item_key_nibbles = item_key[pos..pos + shared_nibbles.len()].as_ref(); - if item_key_nibbles == shared_nibbles { - // Increase the offset within the key by the length of the shared nibbles - pos += shared_nibbles.len(); - - // Follow extension branch - let hash = B256::from_slice(node.as_ref()); - let extension_link = - TrieNode::decode(&mut fetcher(hash)?.as_ref()).map_err(|e| anyhow!(e))?; - self.get_trie(item_key, extension_link, pos, fetcher) - } else { - anyhow::bail!("Key does not exist in trie"); - } - } - } - } } impl DatabaseCommit for TrieCacheDB diff --git a/crates/mpt/src/lib.rs b/crates/mpt/src/lib.rs index 6df04dd6..bc9884de 100644 --- a/crates/mpt/src/lib.rs +++ b/crates/mpt/src/lib.rs @@ -13,7 +13,10 @@ mod list_walker; pub use list_walker::OrderedListWalker; mod db; -pub use db::TrieCacheDB; +pub use db::{TrieAccount, TrieCacheDB}; + +mod retrieval; +pub use retrieval::retrieve; #[cfg(test)] mod test_util; diff --git a/crates/mpt/src/node.rs b/crates/mpt/src/node.rs index 4126a96c..105ae1fc 100644 --- a/crates/mpt/src/node.rs +++ b/crates/mpt/src/node.rs @@ -55,10 +55,6 @@ impl TrieNode { match path[0] >> 4 { PREFIX_EXTENSION_EVEN | PREFIX_EXTENSION_ODD => { // extension node - { - extern crate std; - std::dbg!("FOUND EXTENSION NODE", &path, &value); - } Ok(TrieNode::Extension { prefix: path, node: value }) } PREFIX_LEAF_EVEN | PREFIX_LEAF_ODD => { diff --git a/crates/mpt/src/retrieval.rs b/crates/mpt/src/retrieval.rs new file mode 100644 index 00000000..df249d47 --- /dev/null +++ b/crates/mpt/src/retrieval.rs @@ -0,0 +1,138 @@ +//! Contains the [retrieve] function, allowing for retrieving values from leaves within a Merkle +//! Patricia Trie by key. + +use crate::{NodeElement, TrieNode}; +use alloy_primitives::{Bytes, B256}; +use alloy_rlp::Decodable; +use alloy_trie::Nibbles; +use anyhow::{anyhow, Result}; + +/// Walks down the trie to a leaf value with the given key, if it exists. Preimages for blinded +/// nodes along the path are fetched using the `fetcher` function. +/// +/// ## Takes +/// - `item_key` - The nibbles representation of the key being retrieved +/// - `trie_node` - The root trie node +/// - `pos` - The number of nibbles that have already been traversed in the `item_key` +/// - `fetcher` - The preimage fetcher for intermediate blinded nodes +/// +/// ## Returns +/// - `Err(_)` - Could not retrieve the node with the given key from the trie. +/// - `Ok((_, _))` - The key and value of the node +pub fn retrieve( + item_key: &Nibbles, + trie_node: TrieNode, + mut pos: usize, + fetcher: impl Fn(B256) -> Result + Copy, +) -> Result { + match trie_node { + TrieNode::Branch { mut stack } => { + let branch_nibble = item_key[pos]; + pos += 1; + + match stack + .remove(branch_nibble as usize) + .ok_or(anyhow!("Key does not exist in trie"))? + { + NodeElement::String(s) => { + // If the string is a hash, we need to grab the preimage for it and + // continue recursing. + let hash: B256 = + s.as_ref().try_into().map_err(|e| anyhow!("Conversion error: {e}"))?; + let trie_node = + TrieNode::decode(&mut fetcher(hash)?.as_ref()).map_err(|e| anyhow!(e))?; + + // If the value was found in the blinded node, return it. + if let Ok(value) = retrieve(item_key, trie_node, pos, fetcher) { + return Ok(value); + } + } + list @ NodeElement::List(_) => { + let trie_node = list.try_list_into_node()?; + + // If the value was found in the blinded node, return it. + if let Ok(value) = retrieve(item_key, trie_node, pos, fetcher) { + return Ok(value); + } + } + _ => { /* Skip over empty lists and strings; We're looking for leaves */ } + }; + + anyhow::bail!("Key does not exist in trie"); + } + TrieNode::Leaf { key, value } => { + // If the key length is one, it only contains the prefix and no shared nibbles. Return + // the key and value. + if key.len() == 1 { + return Ok(value); + } + + let key_nibbles = Nibbles::unpack(key.clone()); + let shared_nibbles = key_nibbles[1..].as_ref(); + let item_key_nibbles = item_key[pos..pos + shared_nibbles.len()].as_ref(); + + if item_key_nibbles == shared_nibbles { + Ok(value) + } else { + anyhow::bail!("Key does not exist in trie"); + } + } + TrieNode::Extension { prefix, node } => { + let prefix_nibbles = Nibbles::unpack(prefix); + let shared_nibbles = prefix_nibbles[1..].as_ref(); + let item_key_nibbles = item_key[pos..pos + shared_nibbles.len()].as_ref(); + if item_key_nibbles == shared_nibbles { + // Increase the offset within the key by the length of the shared nibbles + pos += shared_nibbles.len(); + + // Follow extension branch + let hash = B256::from_slice(node.as_ref()); + let extension_link = + TrieNode::decode(&mut fetcher(hash)?.as_ref()).map_err(|e| anyhow!(e))?; + retrieve(item_key, extension_link, pos, fetcher) + } else { + anyhow::bail!("Key does not exist in trie"); + } + } + } +} + +#[cfg(test)] +mod test { + use alloc::{collections::BTreeMap, vec::Vec}; + use alloy_primitives::{keccak256, Bytes, B256}; + use alloy_rlp::{Decodable, Encodable, EMPTY_STRING_CODE}; + use alloy_trie::Nibbles; + use anyhow::{anyhow, Result}; + + use crate::{retrieve, test_util::ordered_trie_with_encoder, TrieNode}; + + #[test] + fn test_retrieve_from_trie_simple() { + const VALUES: [&str; 5] = ["yeah", "dog", ", ", "laminar", "flow"]; + + let mut trie = ordered_trie_with_encoder(&VALUES, |v, buf| v.encode(buf)); + let root = trie.root(); + + let preimages = + trie.take_proofs().into_iter().fold(BTreeMap::default(), |mut acc, (_, value)| { + acc.insert(keccak256(value.as_ref()), value); + acc + }); + let fetcher = |h: B256| -> Result { + preimages.get(&h).cloned().ok_or(anyhow!("Failed to find preimage")) + }; + + let root = TrieNode::decode(&mut fetcher(root).unwrap().as_ref()).unwrap(); + + for (i, value) in VALUES.iter().enumerate() { + let key_nibbles = Nibbles::unpack([if i == 0 { EMPTY_STRING_CODE } else { i as u8 }]); + let v = retrieve(&key_nibbles, root.clone(), 0, fetcher).unwrap(); + + let mut encoded_value = Vec::with_capacity(value.length()); + value.encode(&mut encoded_value); + + assert_eq!(v, encoded_value); + } + } +} diff --git a/crates/mpt/src/test_util.rs b/crates/mpt/src/test_util.rs index b1cb13ab..454dc59e 100644 --- a/crates/mpt/src/test_util.rs +++ b/crates/mpt/src/test_util.rs @@ -2,25 +2,64 @@ extern crate std; -use std::dbg; - -use alloc::{ - collections::BTreeMap, - vec::{self, Vec}, -}; +use alloc::{collections::BTreeMap, vec::Vec}; use alloy_consensus::{Receipt, ReceiptEnvelope, ReceiptWithBloom, TxEnvelope, TxType}; -use alloy_primitives::{b256, keccak256, Bytes, Log, B256}; +use alloy_primitives::{keccak256, Bytes, Log, B256}; use alloy_provider::{network::eip2718::Encodable2718, Provider, ProviderBuilder}; -use alloy_rlp::{BufMut, Decodable, Encodable}; +use alloy_rlp::{BufMut, Encodable}; use alloy_rpc_types::BlockTransactions; use alloy_trie::{HashBuilder, Nibbles}; use anyhow::{anyhow, Result}; use reqwest::Url; -use crate::{NodeElement, TrieNode}; - const RPC_URL: &str = "https://docs-demo.quiknode.pro/"; +/// Compute a trie root of the collection of items with a custom encoder. +pub(crate) fn ordered_trie_with_encoder(items: &[T], mut encode: F) -> HashBuilder +where + F: FnMut(&T, &mut dyn BufMut), +{ + let mut index_buffer = Vec::new(); + let mut value_buffer = Vec::new(); + let items_len = items.len(); + + // Store preimages for all intermediates + let path_nibbles = (0..items_len) + .map(|i| { + let i = adjust_index_for_rlp(i, items_len); + index_buffer.clear(); + i.encode(&mut index_buffer); + Nibbles::unpack(&index_buffer) + }) + .collect::>(); + + let mut hb = HashBuilder::default().with_proof_retainer(path_nibbles); + for i in 0..items_len { + let index = adjust_index_for_rlp(i, items_len); + + index_buffer.clear(); + index.encode(&mut index_buffer); + + value_buffer.clear(); + encode(&items[index], &mut value_buffer); + + hb.add_leaf(Nibbles::unpack(&index_buffer), &value_buffer); + } + + hb +} + +/// Adjust the index of an item for rlp encoding. +pub(crate) const fn adjust_index_for_rlp(i: usize, len: usize) -> usize { + if i > 0x7f { + i + } else if i == 0x7f || i + 1 == len { + 0 + } else { + i + 1 + } +} + /// Grabs a live merkleized receipts list within a block header. pub(crate) async fn get_live_derivable_receipts_list( ) -> Result<(B256, BTreeMap, Vec)> { @@ -126,226 +165,3 @@ pub(crate) async fn get_live_derivable_transactions_list( Ok((root, preimages, consensus_txs)) } - -/// Compute a trie root of the collection of items with a custom encoder. -pub(crate) fn ordered_trie_with_encoder(items: &[T], mut encode: F) -> HashBuilder -where - F: FnMut(&T, &mut dyn BufMut), -{ - let mut index_buffer = Vec::new(); - let mut value_buffer = Vec::new(); - let items_len = items.len(); - - // Store preimages for all intermediates - let path_nibbles = (0..items_len) - .map(|i| { - let i = adjust_index_for_rlp(i, items_len); - index_buffer.clear(); - i.encode(&mut index_buffer); - Nibbles::unpack(&index_buffer) - }) - .collect::>(); - - let mut hb = HashBuilder::default().with_proof_retainer(path_nibbles); - for i in 0..items_len { - let index = adjust_index_for_rlp(i, items_len); - - index_buffer.clear(); - index.encode(&mut index_buffer); - - value_buffer.clear(); - encode(&items[index], &mut value_buffer); - - hb.add_leaf(Nibbles::unpack(&index_buffer), &value_buffer); - } - - hb -} - -/// Adjust the index of an item for rlp encoding. -pub(crate) const fn adjust_index_for_rlp(i: usize, len: usize) -> usize { - if i > 0x7f { - i - } else if i == 0x7f || i + 1 == len { - 0 - } else { - i + 1 - } -} - -#[test] -fn test_trie() { - use alloc::vec; - - let mut hb = HashBuilder::default().with_proof_retainer(vec![ - Nibbles::unpack(&[0x80]), - Nibbles::unpack(&[0x01]), - Nibbles::unpack(&[0xFF]), - ]); - - hb.add_leaf(Nibbles::unpack(&[0x01]), b"test two"); - hb.add_leaf(Nibbles::unpack(&[0x88]), b"test one"); - hb.add_leaf(Nibbles::unpack(&[0xFF]), b"test three"); - // hb.add_branch( - // Nibbles::unpack(&[0x00]), - // b256!("f4ae7801fd7296c9cb9f2387149e93079bd7c74158fea76d978947fddbead8b7"), - // true, - // ); - // hb.add_branch( - // Nibbles::unpack(&[0x01]), - // b256!("91c0bc2b7771df00372f3b3ec799e2586115046fabc2b406c94b4d793ff1669c"), - // true, - // ); - std::dbg!(hb.root()); - - let proofs = hb.take_proofs(); - let preimages = proofs.into_iter().fold(BTreeMap::default(), |mut acc, (_, v)| { - acc.insert(keccak256(v.as_ref()), v); - acc - }); - let fetcher = |hash: B256| -> Result { Ok(preimages.get(&hash).cloned().unwrap()) }; - - let root = TrieNode::decode(&mut fetcher(hb.root()).unwrap().as_ref()).unwrap(); - std::dbg!(get_trie(&Nibbles::unpack(&[0x01]), root, 0, fetcher).unwrap()); -} - -fn adjust_index_for_read(index: usize) -> usize { - match index.cmp(&0x80) { - core::cmp::Ordering::Less => index, - core::cmp::Ordering::Equal => 0x00, - core::cmp::Ordering::Greater => index - 1, - } -} - -#[tokio::test] -async fn test_trie_get() { - // Initialize the provider. - let provider = - ProviderBuilder::new().on_http(Url::parse(RPC_URL).expect("invalid rpc url")).unwrap(); - - let block_number = 19005266; - let block = provider.get_block(block_number.into(), true).await.unwrap().unwrap(); - - let BlockTransactions::Full(txs) = block.transactions else { - panic!("Did not fetch full block"); - }; - let consensus_txs = txs - .into_iter() - .map(|tx| TxEnvelope::try_from(tx).map_err(|e| anyhow!(e))) - .collect::>>() - .unwrap(); - - // Compute the derivable list - let mut list = - ordered_trie_with_encoder(consensus_txs.as_ref(), |rlp, buf| rlp.encode_2718(buf)); - let root = list.root(); - - // Sanity check transaction root is correct - assert_eq!(block.header.transactions_root, root); - - // Construct the mapping of hashed intermediates -> raw intermediates - let proofs = list.take_proofs(); - let preimages = proofs.into_iter().fold(BTreeMap::default(), |mut acc, (_, value)| { - acc.insert(keccak256(value.as_ref()), value); - acc - }); - - let fetcher = |hash: B256| -> Result { Ok(preimages.get(&hash).cloned().unwrap()) }; - - let root = TrieNode::decode(&mut fetcher(root).unwrap().as_ref()).unwrap(); - for i in 1..135 { - let (_, v) = get_trie( - &Nibbles::unpack(alloc::vec![i as u8]), - root.clone(), - 0, - fetcher, - ) - .unwrap(); - let mut rlp_buf = Vec::new(); - consensus_txs[adjust_index_for_read(i)].encode_2718(&mut rlp_buf); - assert_eq!(v.as_ref(), rlp_buf.as_slice(), "Failed at index: {}", i); - } - dbg!(block.header.number); - dbg!(consensus_txs[0x0].tx_hash()); -} - -/// Walks down the trie to a leaf value with the given key, if it exists. Preimages for blinded nodes along the -/// path are fetched using the `fetcher` function. -fn get_trie( - item_key: &Nibbles, - trie_node: TrieNode, - mut pos: usize, - fetcher: impl Fn(B256) -> Result + Copy, -) -> Result<(Bytes, Bytes)> { - match trie_node { - TrieNode::Branch { mut stack } => { - let branch_nibble = item_key[pos]; - pos += 1; - - match stack - .remove(branch_nibble as usize) - .ok_or(anyhow!("Key does not exist in trie"))? - { - NodeElement::String(s) => { - // If the string is a hash, we need to grab the preimage for it and - // continue recursing. - let hash: B256 = - s.as_ref().try_into().map_err(|e| anyhow!("Conversion error: {e}"))?; - let trie_node = - TrieNode::decode(&mut fetcher(hash)?.as_ref()).map_err(|e| anyhow!(e))?; - - // If the value was found in the blinded node, return it. - if let Ok((key, value)) = get_trie(item_key, trie_node, pos, fetcher) { - return Ok((key, value)); - } - } - list @ NodeElement::List(_) => { - let trie_node = list.try_list_into_node()?; - - // If the value was found in the blinded node, return it. - if let Ok((key, value)) = get_trie(item_key, trie_node, pos, fetcher) { - return Ok((key, value)); - } - } - _ => { /* Skip over empty lists and strings; We're looking for leaves */ } - }; - - anyhow::bail!("Key does not exist in trie"); - } - TrieNode::Leaf { key, value } => { - // If the key length is one, it only contains the prefix and no shared nibbles. Return the - // key and value. - if key.len() == 1 { - return Ok((key, value)); - } - - let key_nibbles = Nibbles::unpack(key.clone()); - let shared_nibbles = key_nibbles[1..].as_ref(); - let item_key_nibbles = item_key[pos..pos + shared_nibbles.len()].as_ref(); - - if item_key_nibbles == shared_nibbles { - Ok((key, value)) - } else { - anyhow::bail!("Key does not exist in trie"); - } - } - TrieNode::Extension { prefix, node } => { - std::dbg!(&prefix); - let prefix_nibbles = Nibbles::unpack(prefix); - let shared_nibbles = prefix_nibbles[1..].as_ref(); - let item_key_nibbles = item_key[pos..pos + shared_nibbles.len()].as_ref(); - if item_key_nibbles == shared_nibbles { - // Increase the offset within the key by the length of the shared nibbles - pos += shared_nibbles.len(); - - // Follow extension branch - let hash = B256::from_slice(node.as_ref()); - let extension_link = - TrieNode::decode(&mut fetcher(hash)?.as_ref()).map_err(|e| anyhow!(e))?; - get_trie(item_key, extension_link, pos, fetcher) - } else { - anyhow::bail!("Key does not exist in trie"); - } - } - } -}