diff --git a/shared/src/ledger/native_vp/mod.rs b/shared/src/ledger/native_vp/mod.rs index e14378c3d3..231405dde5 100644 --- a/shared/src/ledger/native_vp/mod.rs +++ b/shared/src/ledger/native_vp/mod.rs @@ -176,7 +176,7 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - type PrefixIter<'iter> = >::PrefixIter where Self: 'iter; + type PrefixIter<'iter> = storage::PrefixIter<'iter, DB> where Self: 'iter; fn read_bytes( &self, @@ -198,18 +198,21 @@ where vp_host_fns::has_key_pre( &mut self.ctx.gas_meter.borrow_mut(), self.ctx.storage, + self.ctx.write_log, key, ) .into_storage_result() } - fn iter_next<'iter>( + fn iter_prefix<'iter>( &'iter self, - iter: &mut Self::PrefixIter<'iter>, - ) -> Result)>, storage_api::Error> { - vp_host_fns::iter_pre_next::( + prefix: &crate::types::storage::Key, + ) -> Result, storage_api::Error> { + vp_host_fns::iter_prefix_pre( &mut self.ctx.gas_meter.borrow_mut(), - iter, + self.ctx.write_log, + self.ctx.storage, + prefix, ) .into_storage_result() } @@ -217,11 +220,12 @@ where // ---- Methods below are implemented in `self.ctx`, because they are // the same in `pre/post` ---- - fn iter_prefix<'iter>( + fn iter_next<'iter>( &'iter self, - prefix: &crate::types::storage::Key, - ) -> Result, storage_api::Error> { - self.ctx.iter_prefix(prefix) + iter: &mut Self::PrefixIter<'iter>, + ) -> Result)>, storage_api::Error> { + vp_host_fns::iter_next::(&mut self.ctx.gas_meter.borrow_mut(), iter) + .into_storage_result() } fn get_chain_id(&self) -> Result { @@ -256,7 +260,7 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - type PrefixIter<'iter> = >::PrefixIter where Self:'iter; + type PrefixIter<'iter> = storage::PrefixIter<'iter, DB> where Self: 'iter; fn read_bytes( &self, @@ -284,14 +288,15 @@ where .into_storage_result() } - fn iter_next<'iter>( + fn iter_prefix<'iter>( &'iter self, - iter: &mut Self::PrefixIter<'iter>, - ) -> Result)>, storage_api::Error> { - vp_host_fns::iter_post_next::( + prefix: &crate::types::storage::Key, + ) -> Result, storage_api::Error> { + vp_host_fns::iter_prefix_post( &mut self.ctx.gas_meter.borrow_mut(), self.ctx.write_log, - iter, + self.ctx.storage, + prefix, ) .into_storage_result() } @@ -299,11 +304,12 @@ where // ---- Methods below are implemented in `self.ctx`, because they are // the same in `pre/post` ---- - fn iter_prefix<'iter>( + fn iter_next<'iter>( &'iter self, - prefix: &crate::types::storage::Key, - ) -> Result, storage_api::Error> { - self.ctx.iter_prefix(prefix) + iter: &mut Self::PrefixIter<'iter>, + ) -> Result)>, storage_api::Error> { + vp_host_fns::iter_next::(&mut self.ctx.gas_meter.borrow_mut(), iter) + .into_storage_result() } fn get_chain_id(&self) -> Result { @@ -339,7 +345,7 @@ where { type Post = CtxPostStorageRead<'view, 'a, DB, H, CA>; type Pre = CtxPreStorageRead<'view, 'a, DB, H, CA>; - type PrefixIter<'iter> = >::PrefixIter where Self: 'iter; + type PrefixIter<'iter> = storage::PrefixIter<'iter, DB> where Self: 'iter; fn pre(&'view self) -> Self::Pre { CtxPreStorageRead { ctx: self } @@ -426,8 +432,9 @@ where &'iter self, prefix: &Key, ) -> Result, storage_api::Error> { - vp_host_fns::iter_prefix( + vp_host_fns::iter_prefix_pre( &mut self.gas_meter.borrow_mut(), + self.write_log, self.storage, prefix, ) diff --git a/shared/src/ledger/vp_host_fns.rs b/shared/src/ledger/vp_host_fns.rs index 336981b2af..5bcbd69cf4 100644 --- a/shared/src/ledger/vp_host_fns.rs +++ b/shared/src/ledger/vp_host_fns.rs @@ -311,71 +311,51 @@ where Ok(storage.native_token.clone()) } -/// Storage prefix iterator, ordered by storage keys. It will try to get an -/// iterator from the storage. -pub fn iter_prefix<'a, DB, H>( +/// Storage prefix iterator for prior state (before tx execution), ordered by +/// storage keys. It will try to get an iterator from the storage. +pub fn iter_prefix_pre<'a, DB, H>( gas_meter: &mut VpGasMeter, + write_log: &'a WriteLog, storage: &'a Storage, prefix: &Key, -) -> EnvResult<>::PrefixIter> +) -> EnvResult> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, { - let (iter, gas) = storage.iter_prefix(prefix); + let (iter, gas) = storage::iter_prefix_pre(write_log, storage, prefix); add_gas(gas_meter, gas)?; Ok(iter) } -/// Storage prefix iterator for prior state (before tx execution). It will try -/// to read from the storage. -pub fn iter_pre_next( +/// Storage prefix iterator for posterior state (after tx execution), ordered by +/// storage keys. It will try to get an iterator from the storage. +pub fn iter_prefix_post<'a, DB, H>( gas_meter: &mut VpGasMeter, - iter: &mut >::PrefixIter, -) -> EnvResult)>> + write_log: &'a WriteLog, + storage: &'a Storage, + prefix: &Key, +) -> EnvResult> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, { - if let Some((key, val, gas)) = iter.next() { - add_gas(gas_meter, gas)?; - return Ok(Some((key, val))); - } - Ok(None) + let (iter, gas) = storage::iter_prefix_post(write_log, storage, prefix); + add_gas(gas_meter, gas)?; + Ok(iter) } -/// Storage prefix iterator next for posterior state (after tx execution). It -/// will try to read from the write log first and if no entry found then from -/// the storage. -pub fn iter_post_next( +/// Get the next item in a storage prefix iterator (pre or post). +pub fn iter_next( gas_meter: &mut VpGasMeter, - write_log: &WriteLog, - iter: &mut >::PrefixIter, + iter: &mut storage::PrefixIter, ) -> EnvResult)>> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, { - for (key, val, iter_gas) in iter { - let (log_val, log_gas) = write_log.read( - &Key::parse(key.clone()).map_err(RuntimeError::StorageDataError)?, - ); - add_gas(gas_meter, iter_gas + log_gas)?; - match log_val { - Some(&write_log::StorageModification::Write { ref value }) => { - return Ok(Some((key, value.clone()))); - } - Some(&write_log::StorageModification::Delete) => { - // check the next because the key has already deleted - continue; - } - Some(&write_log::StorageModification::InitAccount { .. }) => { - // a VP of a new account doesn't need to be iterated - continue; - } - Some(&write_log::StorageModification::Temp { .. }) => { - return Err(RuntimeError::ReadTemporaryValueError); - } - None => return Ok(Some((key, val))), - } + if let Some((key, val, gas)) = iter.next() { + add_gas(gas_meter, gas)?; + return Ok(Some((key, val))); } Ok(None) } diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index ee8e8a3601..2ceaee746f 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -696,7 +696,7 @@ pub fn tx_iter_prefix( ) -> TxResult where MEM: VmMemory, - DB: storage::DB + for<'iter> storage::DBIter<'iter>, + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, CA: WasmCacheAccess, { @@ -706,15 +706,17 @@ where .map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?; tx_add_gas(env, gas)?; - tracing::debug!("tx_iter_prefix {}, prefix {}", prefix, prefix_ptr); + tracing::debug!("tx_iter_prefix {}", prefix); let prefix = Key::parse(prefix).map_err(TxRuntimeError::StorageDataError)?; + let write_log = unsafe { env.ctx.write_log.get() }; let storage = unsafe { env.ctx.storage.get() }; - let iterators = unsafe { env.ctx.iterators.get() }; - let (iter, gas) = storage.iter_prefix(&prefix); + let (iter, gas) = storage::iter_prefix_post(write_log, storage, &prefix); tx_add_gas(env, gas)?; + + let iterators = unsafe { env.ctx.iterators.get() }; Ok(iterators.insert(iter).id()) } @@ -1183,7 +1185,9 @@ where let key = Key::parse(key).map_err(vp_host_fns::RuntimeError::StorageDataError)?; let storage = unsafe { env.ctx.storage.get() }; - let present = vp_host_fns::has_key_pre(gas_meter, storage, &key)?; + let write_log = unsafe { env.ctx.write_log.get() }; + let present = + vp_host_fns::has_key_pre(gas_meter, storage, write_log, &key)?; Ok(HostEnvResult::from(present).to_i64()) } @@ -1220,17 +1224,18 @@ where Ok(HostEnvResult::from(present).to_i64()) } -/// Storage prefix iterator function exposed to the wasm VM VP environment. -/// It will try to get an iterator from the storage and return the corresponding -/// ID of the iterator, ordered by storage keys. -pub fn vp_iter_prefix( +/// Storage prefix iterator function for prior state (before tx execution) +/// exposed to the wasm VM VP environment. It will try to get an iterator from +/// the storage and return the corresponding ID of the iterator, ordered by +/// storage keys. +pub fn vp_iter_prefix_pre( env: &VpVmEnv, prefix_ptr: u64, prefix_len: u64, ) -> vp_host_fns::EnvResult where MEM: VmMemory, - DB: storage::DB + for<'iter> storage::DBIter<'iter>, + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, EVAL: VpEvaluator, CA: WasmCacheAccess, @@ -1242,63 +1247,63 @@ where let gas_meter = unsafe { env.ctx.gas_meter.get() }; vp_host_fns::add_gas(gas_meter, gas)?; + tracing::debug!("vp_iter_prefix_pre {}", prefix); + let prefix = Key::parse(prefix) .map_err(vp_host_fns::RuntimeError::StorageDataError)?; - tracing::debug!("vp_iter_prefix {}", prefix); + let write_log = unsafe { env.ctx.write_log.get() }; let storage = unsafe { env.ctx.storage.get() }; - let iter = vp_host_fns::iter_prefix(gas_meter, storage, &prefix)?; + let iter = + vp_host_fns::iter_prefix_pre(gas_meter, write_log, storage, &prefix)?; + let iterators = unsafe { env.ctx.iterators.get() }; Ok(iterators.insert(iter).id()) } -/// Storage prefix iterator for prior state (before tx execution) function -/// exposed to the wasm VM VP environment. It will try to read from the storage. -/// -/// Returns `-1` when the key is not present, or the length of the data when -/// the key is present (the length may be `0`). -pub fn vp_iter_pre_next( +/// Storage prefix iterator function for posterior state (after tx execution) +/// exposed to the wasm VM VP environment. It will try to get an iterator from +/// the storage and return the corresponding ID of the iterator, ordered by +/// storage keys. +pub fn vp_iter_prefix_post( env: &VpVmEnv, - iter_id: u64, -) -> vp_host_fns::EnvResult + prefix_ptr: u64, + prefix_len: u64, +) -> vp_host_fns::EnvResult where MEM: VmMemory, - DB: storage::DB + for<'iter> storage::DBIter<'iter>, + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, EVAL: VpEvaluator, CA: WasmCacheAccess, { - tracing::debug!("vp_iter_pre_next iter_id {}", iter_id); + let (prefix, gas) = env + .memory + .read_string(prefix_ptr, prefix_len as _) + .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; + let gas_meter = unsafe { env.ctx.gas_meter.get() }; + vp_host_fns::add_gas(gas_meter, gas)?; + + tracing::debug!("vp_iter_prefix_post {}", prefix); + + let prefix = Key::parse(prefix) + .map_err(vp_host_fns::RuntimeError::StorageDataError)?; + + let write_log = unsafe { env.ctx.write_log.get() }; + let storage = unsafe { env.ctx.storage.get() }; + let iter = + vp_host_fns::iter_prefix_post(gas_meter, write_log, storage, &prefix)?; let iterators = unsafe { env.ctx.iterators.get() }; - let iter_id = PrefixIteratorId::new(iter_id); - if let Some(iter) = iterators.get_mut(iter_id) { - let gas_meter = unsafe { env.ctx.gas_meter.get() }; - if let Some((key, val)) = - vp_host_fns::iter_pre_next::(gas_meter, iter)? - { - let key_val = KeyVal { key, val } - .try_to_vec() - .map_err(vp_host_fns::RuntimeError::EncodingError)?; - let len: i64 = key_val - .len() - .try_into() - .map_err(vp_host_fns::RuntimeError::NumConversionError)?; - let result_buffer = unsafe { env.ctx.result_buffer.get() }; - result_buffer.replace(key_val); - return Ok(len); - } - } - Ok(HostEnvResult::Fail.to_i64()) + Ok(iterators.insert(iter).id()) } -/// Storage prefix iterator next for posterior state (after tx execution) -/// function exposed to the wasm VM VP environment. It will try to read from the -/// write log first and if no entry found then from the storage. +/// Storage prefix iterator for prior or posterior state function +/// exposed to the wasm VM VP environment. /// /// Returns `-1` when the key is not present, or the length of the data when /// the key is present (the length may be `0`). -pub fn vp_iter_post_next( +pub fn vp_iter_next( env: &VpVmEnv, iter_id: u64, ) -> vp_host_fns::EnvResult @@ -1309,16 +1314,13 @@ where EVAL: VpEvaluator, CA: WasmCacheAccess, { - tracing::debug!("vp_iter_post_next iter_id {}", iter_id); + tracing::debug!("vp_iter_next iter_id {}", iter_id); let iterators = unsafe { env.ctx.iterators.get() }; let iter_id = PrefixIteratorId::new(iter_id); if let Some(iter) = iterators.get_mut(iter_id) { let gas_meter = unsafe { env.ctx.gas_meter.get() }; - let write_log = unsafe { env.ctx.write_log.get() }; - if let Some((key, val)) = - vp_host_fns::iter_post_next::(gas_meter, write_log, iter)? - { + if let Some((key, val)) = vp_host_fns::iter_next(gas_meter, iter)? { let key_val = KeyVal { key, val } .try_to_vec() .map_err(vp_host_fns::RuntimeError::EncodingError)?; diff --git a/shared/src/vm/prefix_iter.rs b/shared/src/vm/prefix_iter.rs index e30cc72864..49e58b33dd 100644 --- a/shared/src/vm/prefix_iter.rs +++ b/shared/src/vm/prefix_iter.rs @@ -3,6 +3,8 @@ use std::collections::HashMap; +use namada_core::ledger::storage::PrefixIter; + use crate::ledger::storage; /// A temporary iterators storage, used during a wasm run after which it's @@ -10,18 +12,18 @@ use crate::ledger::storage; #[derive(Debug)] pub struct PrefixIterators<'iter, DB> where - DB: storage::DBIter<'iter>, + DB: storage::DB + storage::DBIter<'iter>, { index: PrefixIteratorId, - iterators: HashMap, + iterators: HashMap>, } impl<'iter, DB> PrefixIterators<'iter, DB> where - DB: storage::DBIter<'iter>, + DB: storage::DB + storage::DBIter<'iter>, { /// Insert a new prefix iterator to the temporary storage. - pub fn insert(&mut self, iter: DB::PrefixIter) -> PrefixIteratorId { + pub fn insert(&mut self, iter: PrefixIter<'iter, DB>) -> PrefixIteratorId { let id = self.index; self.iterators.insert(id, iter); self.index = id.next_id(); @@ -32,7 +34,7 @@ where pub fn next( &mut self, id: PrefixIteratorId, - ) -> Option<::Item> { + ) -> Option< as Iterator>::Item> { self.iterators.get_mut(&id).and_then(|i| i.next()) } @@ -40,14 +42,14 @@ where pub fn get_mut( &mut self, id: PrefixIteratorId, - ) -> Option<&mut DB::PrefixIter> { + ) -> Option<&mut PrefixIter<'iter, DB>> { self.iterators.get_mut(&id) } } impl<'iter, DB> Default for PrefixIterators<'iter, DB> where - DB: storage::DBIter<'iter>, + DB: storage::DB + storage::DBIter<'iter>, { fn default() -> Self { Self { diff --git a/shared/src/vm/wasm/host_env.rs b/shared/src/vm/wasm/host_env.rs index 28fb2cc12a..74c87ed69b 100644 --- a/shared/src/vm/wasm/host_env.rs +++ b/shared/src/vm/wasm/host_env.rs @@ -108,9 +108,9 @@ where "namada_vp_result_buffer" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_result_buffer), "namada_vp_has_key_pre" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_has_key_pre), "namada_vp_has_key_post" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_has_key_post), - "namada_vp_iter_prefix" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_iter_prefix), - "namada_vp_iter_pre_next" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_iter_pre_next), - "namada_vp_iter_post_next" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_iter_post_next), + "namada_vp_iter_prefix_pre" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_iter_prefix_pre), + "namada_vp_iter_prefix_post" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_iter_prefix_pre), + "namada_vp_iter_next" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_iter_next), "namada_vp_get_chain_id" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_chain_id), "namada_vp_get_tx_index" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_tx_index), "namada_vp_get_block_height" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_block_height), diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 233a5f72a0..0efdac499c 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -290,7 +290,7 @@ fn run_vp( } /// Validity predicate wasm evaluator for `eval` host function calls. -#[derive(Default)] +#[derive(Default, Debug)] pub struct VpEvalWasm where DB: storage::DB + for<'iter> storage::DBIter<'iter>, diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index b38feb83f2..e275c840d1 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -139,23 +139,26 @@ pub mod vp { // Returns 1 if the key is present in posterior state, -1 otherwise. pub fn namada_vp_has_key_post(key_ptr: u64, key_len: u64) -> i64; - // Get an ID of a data iterator with key prefix, ordered by storage - // keys. - pub fn namada_vp_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64; - - // Read variable-length prior state when we don't know the size - // up-front, returns the size of the value (can be 0), or -1 if - // the key is not present. If a value is found, it will be placed in the - // result buffer, because we cannot allocate a buffer for it before - // we know its size. - pub fn namada_vp_iter_pre_next(iter_id: u64) -> i64; - - // Read variable-length posterior state when we don't know the size - // up-front, returns the size of the value (can be 0), or -1 if the - // key is not present. If a value is found, it will be placed in the - // result buffer, because we cannot allocate a buffer for it before - // we know its size. - pub fn namada_vp_iter_post_next(iter_id: u64) -> i64; + // Get an ID of a data iterator with key prefix in prior state, ordered + // by storage keys. + pub fn namada_vp_iter_prefix_pre( + prefix_ptr: u64, + prefix_len: u64, + ) -> u64; + + // Get an ID of a data iterator with key prefix in posterior state, + // ordered by storage keys. + pub fn namada_vp_iter_prefix_post( + prefix_ptr: u64, + prefix_len: u64, + ) -> u64; + + // Read variable-length iterator's next value when we don't know the + // size up-front, returns the size of the value (can be 0), or + // -1 if the key is not present. If a value is found, it will be + // placed in the result buffer, because we cannot allocate a + // buffer for it before we know its size. + pub fn namada_vp_iter_next(iter_id: u64) -> i64; // Get the chain ID pub fn namada_vp_get_chain_id(result_ptr: u64); diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index d67f0d980b..87e98f1af8 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -260,8 +260,7 @@ impl<'view> VpEnv<'view> for Ctx { &'iter self, prefix: &storage::Key, ) -> Result, Error> { - // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl - iter_prefix_impl(prefix) + iter_prefix_pre_impl(prefix) } fn eval( @@ -332,26 +331,26 @@ impl StorageRead for CtxPreStorageRead<'_> { Ok(HostEnvResult::is_success(found)) } + fn iter_prefix<'iter>( + &'iter self, + prefix: &storage::Key, + ) -> Result, Error> { + iter_prefix_pre_impl(prefix) + } + + // ---- Methods below share the same implementation in `pre/post` ---- + fn iter_next<'iter>( &'iter self, iter: &mut Self::PrefixIter<'iter>, ) -> Result)>, Error> { - let read_result = unsafe { namada_vp_iter_pre_next(iter.0) }; + let read_result = unsafe { namada_vp_iter_next(iter.0) }; Ok(read_key_val_bytes_from_buffer( read_result, namada_vp_result_buffer, )) } - // ---- Methods below share the same implementation in `pre/post` ---- - - fn iter_prefix<'iter>( - &'iter self, - prefix: &storage::Key, - ) -> Result, Error> { - iter_prefix_impl(prefix) - } - fn get_chain_id(&self) -> Result { get_chain_id() } @@ -395,26 +394,26 @@ impl StorageRead for CtxPostStorageRead<'_> { Ok(HostEnvResult::is_success(found)) } + fn iter_prefix<'iter>( + &'iter self, + prefix: &storage::Key, + ) -> Result, Error> { + iter_prefix_post_impl(prefix) + } + + // ---- Methods below share the same implementation in `pre/post` ---- + fn iter_next<'iter>( &'iter self, iter: &mut Self::PrefixIter<'iter>, ) -> Result)>, Error> { - let read_result = unsafe { namada_vp_iter_post_next(iter.0) }; + let read_result = unsafe { namada_vp_iter_next(iter.0) }; Ok(read_key_val_bytes_from_buffer( read_result, namada_vp_result_buffer, )) } - // ---- Methods below share the same implementation in `pre/post` ---- - - fn iter_prefix<'iter>( - &'iter self, - prefix: &storage::Key, - ) -> Result, Error> { - iter_prefix_impl(prefix) - } - fn get_chain_id(&self) -> Result { get_chain_id() } @@ -440,12 +439,22 @@ impl StorageRead for CtxPostStorageRead<'_> { } } -fn iter_prefix_impl( +fn iter_prefix_pre_impl( + prefix: &storage::Key, +) -> Result)>, Error> { + let prefix = prefix.to_string(); + let iter_id = unsafe { + namada_vp_iter_prefix_pre(prefix.as_ptr() as _, prefix.len() as _) + }; + Ok(KeyValIterator(iter_id, PhantomData)) +} + +fn iter_prefix_post_impl( prefix: &storage::Key, ) -> Result)>, Error> { let prefix = prefix.to_string(); let iter_id = unsafe { - namada_vp_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) + namada_vp_iter_prefix_post(prefix.as_ptr() as _, prefix.len() as _) }; Ok(KeyValIterator(iter_id, PhantomData)) }