diff --git a/Cargo.lock b/Cargo.lock index 3c3797bc9..ca36a178b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1088,6 +1088,7 @@ dependencies = [ "cbindgen", "chrono", "coarsetime", + "derive-where", "env_logger", "firewood", "firewood-storage", diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index af176c996..6e6db493a 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -31,6 +31,7 @@ chrono = "0.4.42" oxhttp = "0.3.1" # Optional dependencies env_logger = { workspace = true, optional = true } +derive-where = "1.6.0" [target.'cfg(unix)'.dependencies] tikv-jemallocator = "0.6.0" diff --git a/ffi/firewood.h b/ffi/firewood.h index 5f5c285eb..88c8ae1eb 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -24,6 +24,11 @@ typedef struct ChangeProofContext ChangeProofContext; */ typedef struct DatabaseHandle DatabaseHandle; +/** + * An opaque wrapper around a [`BoxKeyValueIter`]. + */ +typedef struct IteratorHandle IteratorHandle; + /** * An opaque wrapper around a Proposal that also retains a reference to the * database handle it was created from. @@ -647,6 +652,17 @@ typedef struct VerifyRangeProofArgs { uint32_t max_length; } VerifyRangeProofArgs; +/** + * Owned version of `KeyValuePair`, returned to ffi callers. + * + * C callers must free this using [`crate::fwd_free_owned_kv_pair`], + * not the C standard library's `free` function. + */ +typedef struct OwnedKeyValuePair { + OwnedBytes key; + OwnedBytes value; +} OwnedKeyValuePair; + /** * A result type returned from FFI functions that get a revision */ @@ -703,6 +719,94 @@ typedef struct RevisionResult { }; } RevisionResult; +/** + * A result type returned from iterator FFI functions + */ +typedef enum KeyValueResult_Tag { + /** + * The caller provided a null pointer to an iterator handle. + */ + KeyValueResult_NullHandlePointer, + /** + * The iterator is exhausted + */ + KeyValueResult_None, + /** + * The next item is returned. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with the key and the value of this pair. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + KeyValueResult_Some, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. The + * value is guaranteed to contain only valid UTF-8. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this error. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + KeyValueResult_Err, +} KeyValueResult_Tag; + +typedef struct KeyValueResult { + KeyValueResult_Tag tag; + union { + struct { + struct OwnedKeyValuePair some; + }; + struct { + OwnedBytes err; + }; + }; +} KeyValueResult; + +/** + * A result type returned from FFI functions that create an iterator + */ +typedef enum IteratorResult_Tag { + /** + * The caller provided a null pointer to a revision/proposal handle. + */ + IteratorResult_NullHandlePointer, + /** + * Building the iterator was successful and the iterator handle is returned + */ + IteratorResult_Ok, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this error. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + IteratorResult_Err, +} IteratorResult_Tag; + +typedef struct IteratorResult_Ok_Body { + /** + * An opaque pointer to the [`IteratorHandle`]. + * The value should be freed with [`fwd_free_iterator`] + * + * [`fwd_free_iterator`]: crate::fwd_free_iterator + */ + struct IteratorHandle *handle; +} IteratorResult_Ok_Body; + +typedef struct IteratorResult { + IteratorResult_Tag tag; + union { + IteratorResult_Ok_Body ok; + struct { + OwnedBytes err; + }; + }; +} IteratorResult; + /** * The result type returned from the open or create database functions. */ @@ -1192,6 +1296,28 @@ struct VoidResult fwd_db_verify_range_proof(const struct DatabaseHandle *_db, */ struct VoidResult fwd_free_change_proof(struct ChangeProofContext *proof); +/** + * Consumes the [`IteratorHandle`], destroys the iterator, and frees the memory. + * + * # Arguments + * + * * `iterator` - A pointer to a [`IteratorHandle`] previously returned from a + * function from this library. + * + * # Returns + * + * - [`VoidResult::NullHandlePointer`] if the provided iterator handle is null. + * - [`VoidResult::Ok`] if the iterator was successfully freed. + * - [`VoidResult::Err`] if the process panics while freeing the memory. + * + * # Safety + * + * The caller must ensure that the `iterator` is not null and that it points to + * a valid [`IteratorHandle`] previously returned by a function from this library. + * + */ +struct VoidResult fwd_free_iterator(struct IteratorHandle *iterator); + /** * Consumes the [`OwnedBytes`] and frees the memory associated with it. * @@ -1213,6 +1339,25 @@ struct VoidResult fwd_free_change_proof(struct ChangeProofContext *proof); */ struct VoidResult fwd_free_owned_bytes(OwnedBytes bytes); +/** + * Consumes the [`OwnedKeyValuePair`] and frees the memory associated with it. + * + * # Arguments + * + * * `kv` - The [`OwnedKeyValuePair`] struct to free, previously returned from any + * function from this library. + * + * # Returns + * + * - [`VoidResult::Ok`] if the memory was successfully freed. + * - [`VoidResult::Err`] if the process panics while freeing the memory. + * + * # Safety + * + * The caller must ensure that the `kv` struct is valid. + */ +struct VoidResult fwd_free_owned_kv_pair(struct OwnedKeyValuePair kv); + /** * Consumes the [`ProposalHandle`], cancels the proposal, and frees the memory. * @@ -1429,6 +1574,82 @@ struct ValueResult fwd_get_latest(const struct DatabaseHandle *db, BorrowedBytes */ struct RevisionResult fwd_get_revision(const struct DatabaseHandle *db, BorrowedBytes root); +/** + * Retrieves the next item from the iterator + * + * # Arguments + * + * * `handle` - The iterator handle returned by [`fwd_iter_on_revision`] or + * [`fwd_iter_on_proposal`]. + * + * # Returns + * + * - [`KeyValueResult::NullHandlePointer`] if the provided iterator handle is null. + * - [`KeyValueResult::None`] if the iterator doesn't have any remaining values/exhausted. + * - [`KeyValueResult::Some`] if the next item on iterator was retrieved, with the associated + * key value pair. + * - [`KeyValueResult::Err`] if an error occurred while retrieving the next item on iterator. + * + * # Safety + * + * The caller must: + * * ensure that `handle` is a valid pointer to a [`IteratorHandle`]. + * * call [`fwd_free_owned_bytes`] on [`OwnedKeyValuePair::key`] and [`OwnedKeyValuePair::value`] + * to free the memory associated with the returned error or value. + * + */ +struct KeyValueResult fwd_iter_next(struct IteratorHandle *handle); + +/** + * Returns an iterator on the provided proposal optionally starting from a key + * + * # Arguments + * + * * `handle` - The proposal handle returned by [`fwd_propose_on_db`] or + * [`fwd_propose_on_proposal`]. + * * `key` - The key to look up as a [`BorrowedBytes`] + * + * # Returns + * + * - [`IteratorResult::NullHandlePointer`] if the provided proposal handle is null. + * - [`IteratorResult::Ok`] if the iterator was created, with the iterator handle. + * - [`IteratorResult::Err`] if an error occurred while creating the iterator. + * + * # Safety + * + * The caller must: + * * ensure that `handle` is a valid pointer to a [`ProposalHandle`] + * * ensure that `key` is a valid for [`BorrowedBytes`] + * * call [`fwd_free_iterator`] to free the memory associated with the iterator. + * + */ +struct IteratorResult fwd_iter_on_proposal(const struct ProposalHandle *handle, BorrowedBytes key); + +/** + * Returns an iterator optionally starting from a key in the provided revision. + * + * # Arguments + * + * * `revision` - The revision handle returned by [`fwd_get_revision`]. + * * `key` - The key to look up as a [`BorrowedBytes`] + * + * # Returns + * + * - [`IteratorResult::NullHandlePointer`] if the provided revision handle is null. + * - [`IteratorResult::Ok`] if the iterator was created, with the iterator handle. + * - [`IteratorResult::Err`] if an error occurred while creating the iterator. + * + * # Safety + * + * The caller must: + * * ensure that `revision` is a valid pointer to a [`RevisionHandle`] + * * ensure that `key` is a valid [`BorrowedBytes`] + * * call [`fwd_free_iterator`] to free the memory associated with the iterator. + * + */ +struct IteratorResult fwd_iter_on_revision(const struct RevisionHandle *revision, + BorrowedBytes key); + /** * Open a database with the given arguments. * diff --git a/ffi/src/iterator.rs b/ffi/src/iterator.rs new file mode 100644 index 000000000..487caf1cb --- /dev/null +++ b/ffi/src/iterator.rs @@ -0,0 +1,35 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::ops::{Deref, DerefMut}; + +use derive_where::derive_where; +use firewood::v2::api::BoxKeyValueIter; + +/// An opaque wrapper around a [`BoxKeyValueIter`]. +#[derive_where(Debug)] +#[derive_where(skip_inner)] +pub struct IteratorHandle<'view>(BoxKeyValueIter<'view>); + +impl<'view> From> for IteratorHandle<'view> { + fn from(value: BoxKeyValueIter<'view>) -> Self { + IteratorHandle(value) + } +} + +impl<'view> Deref for IteratorHandle<'view> { + type Target = BoxKeyValueIter<'view>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for IteratorHandle<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[derive(Debug)] +pub struct CreateIteratorResult<'db>(pub IteratorHandle<'db>); diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 8bd2e23aa..4d54bdb7e 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -24,6 +24,7 @@ mod arc_cache; mod handle; +mod iterator; mod logging; mod metrics_setup; mod proofs; @@ -34,6 +35,7 @@ mod value; use firewood::v2::api::DbView; pub use crate::handle::*; +pub use crate::iterator::*; pub use crate::logging::*; pub use crate::proofs::*; pub use crate::proposal::*; @@ -109,6 +111,115 @@ pub unsafe extern "C" fn fwd_get_latest( invoke_with_handle(db, move |db| db.get_latest(key)) } +/// Returns an iterator optionally starting from a key in the provided revision. +/// +/// # Arguments +/// +/// * `revision` - The revision handle returned by [`fwd_get_revision`]. +/// * `key` - The key to look up as a [`BorrowedBytes`] +/// +/// # Returns +/// +/// - [`IteratorResult::NullHandlePointer`] if the provided revision handle is null. +/// - [`IteratorResult::Ok`] if the iterator was created, with the iterator handle. +/// - [`IteratorResult::Err`] if an error occurred while creating the iterator. +/// +/// # Safety +/// +/// The caller must: +/// * ensure that `revision` is a valid pointer to a [`RevisionHandle`] +/// * ensure that `key` is a valid [`BorrowedBytes`] +/// * call [`fwd_free_iterator`] to free the memory associated with the iterator. +/// +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_iter_on_revision<'view>( + revision: Option<&'view RevisionHandle>, + key: BorrowedBytes, +) -> IteratorResult<'view> { + invoke_with_handle(revision, move |rev| rev.iter_from(Some(key.as_slice()))) +} + +/// Returns an iterator on the provided proposal optionally starting from a key +/// +/// # Arguments +/// +/// * `handle` - The proposal handle returned by [`fwd_propose_on_db`] or +/// [`fwd_propose_on_proposal`]. +/// * `key` - The key to look up as a [`BorrowedBytes`] +/// +/// # Returns +/// +/// - [`IteratorResult::NullHandlePointer`] if the provided proposal handle is null. +/// - [`IteratorResult::Ok`] if the iterator was created, with the iterator handle. +/// - [`IteratorResult::Err`] if an error occurred while creating the iterator. +/// +/// # Safety +/// +/// The caller must: +/// * ensure that `handle` is a valid pointer to a [`ProposalHandle`] +/// * ensure that `key` is a valid for [`BorrowedBytes`] +/// * call [`fwd_free_iterator`] to free the memory associated with the iterator. +/// +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_iter_on_proposal<'p>( + handle: Option<&'p ProposalHandle<'_>>, + key: BorrowedBytes, +) -> IteratorResult<'p> { + invoke_with_handle(handle, move |p| p.iter_from(Some(key.as_slice()))) +} + +/// Retrieves the next item from the iterator +/// +/// # Arguments +/// +/// * `handle` - The iterator handle returned by [`fwd_iter_on_revision`] or +/// [`fwd_iter_on_proposal`]. +/// +/// # Returns +/// +/// - [`KeyValueResult::NullHandlePointer`] if the provided iterator handle is null. +/// - [`KeyValueResult::None`] if the iterator doesn't have any remaining values/exhausted. +/// - [`KeyValueResult::Some`] if the next item on iterator was retrieved, with the associated +/// key value pair. +/// - [`KeyValueResult::Err`] if an error occurred while retrieving the next item on iterator. +/// +/// # Safety +/// +/// The caller must: +/// * ensure that `handle` is a valid pointer to a [`IteratorHandle`]. +/// * call [`fwd_free_owned_bytes`] on [`OwnedKeyValuePair::key`] and [`OwnedKeyValuePair::value`] +/// to free the memory associated with the returned error or value. +/// +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_iter_next(handle: Option<&mut IteratorHandle<'_>>) -> KeyValueResult { + invoke_with_handle(handle, |it| it.next()) +} + +/// Consumes the [`IteratorHandle`], destroys the iterator, and frees the memory. +/// +/// # Arguments +/// +/// * `iterator` - A pointer to a [`IteratorHandle`] previously returned from a +/// function from this library. +/// +/// # Returns +/// +/// - [`VoidResult::NullHandlePointer`] if the provided iterator handle is null. +/// - [`VoidResult::Ok`] if the iterator was successfully freed. +/// - [`VoidResult::Err`] if the process panics while freeing the memory. +/// +/// # Safety +/// +/// The caller must ensure that the `iterator` is not null and that it points to +/// a valid [`IteratorHandle`] previously returned by a function from this library. +/// +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_free_iterator( + iterator: Option>>, +) -> VoidResult { + invoke_with_handle(iterator, drop) +} + /// Gets a handle to the revision identified by the provided root hash. /// /// # Arguments @@ -581,3 +692,23 @@ pub unsafe extern "C" fn fwd_close_db(db: Option>) -> VoidRe pub unsafe extern "C" fn fwd_free_owned_bytes(bytes: OwnedBytes) -> VoidResult { invoke(move || drop(bytes)) } + +/// Consumes the [`OwnedKeyValuePair`] and frees the memory associated with it. +/// +/// # Arguments +/// +/// * `kv` - The [`OwnedKeyValuePair`] struct to free, previously returned from any +/// function from this library. +/// +/// # Returns +/// +/// - [`VoidResult::Ok`] if the memory was successfully freed. +/// - [`VoidResult::Err`] if the process panics while freeing the memory. +/// +/// # Safety +/// +/// The caller must ensure that the `kv` struct is valid. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_free_owned_kv_pair(kv: OwnedKeyValuePair) -> VoidResult { + invoke(move || drop(kv)) +} diff --git a/ffi/src/proposal.rs b/ffi/src/proposal.rs index 2c7dcdad6..1281a9993 100644 --- a/ffi/src/proposal.rs +++ b/ffi/src/proposal.rs @@ -1,10 +1,11 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use firewood::v2::api::{self, DbView, HashKey, Proposal as _}; +use firewood::v2::api::{self, BoxKeyValueIter, DbView, HashKey, Proposal as _}; use crate::value::KeyValuePair; +use crate::iterator::CreateIteratorResult; use metrics::counter; /// An opaque wrapper around a Proposal that also retains a reference to the @@ -97,8 +98,17 @@ impl ProposalHandle<'_> { Ok(hash_key) } -} + /// Creates an iterator on the proposal starting from the given key. + #[must_use] + #[allow(clippy::missing_panics_doc)] + pub fn iter_from(&self, first_key: Option<&[u8]>) -> CreateIteratorResult<'_> { + let it = self + .iter_option(first_key) + .expect("infallible; see issue #1329"); + CreateIteratorResult((Box::new(it) as BoxKeyValueIter<'_>).into()) + } +} #[derive(Debug)] pub struct CreateProposalResult<'db> { pub handle: ProposalHandle<'db>, diff --git a/ffi/src/revision.rs b/ffi/src/revision.rs index ee23014f6..eb73f15e1 100644 --- a/ffi/src/revision.rs +++ b/ffi/src/revision.rs @@ -1,6 +1,7 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::CreateIteratorResult; use firewood::v2::api; use firewood::v2::api::{ArcDynDbView, BoxKeyValueIter, DbView, HashKey}; @@ -14,6 +15,17 @@ impl RevisionHandle { pub(crate) fn new(view: ArcDynDbView) -> RevisionHandle { RevisionHandle { view } } + + /// Creates an iterator on the revision starting from the given key. + #[must_use] + #[allow(clippy::missing_panics_doc)] + pub fn iter_from(&self, first_key: Option<&[u8]>) -> CreateIteratorResult<'_> { + let it = self + .view + .iter_option(first_key) + .expect("infallible; see issue #1329"); + CreateIteratorResult(it.into()) + } } impl DbView for RevisionHandle { diff --git a/ffi/src/value.rs b/ffi/src/value.rs index 39894b9a4..f9f5ee7f3 100644 --- a/ffi/src/value.rs +++ b/ffi/src/value.rs @@ -11,12 +11,12 @@ mod results; pub use self::borrowed::{BorrowedBytes, BorrowedKeyValuePairs, BorrowedSlice}; use self::display_hex::DisplayHex; pub use self::hash_key::HashKey; -pub use self::kvp::KeyValuePair; +pub use self::kvp::{KeyValuePair, OwnedKeyValuePair}; pub use self::owned::{OwnedBytes, OwnedSlice}; pub(crate) use self::results::{CResult, NullHandleResult}; pub use self::results::{ - ChangeProofResult, HandleResult, HashResult, NextKeyRangeResult, ProposalResult, - RangeProofResult, RevisionResult, ValueResult, VoidResult, + ChangeProofResult, HandleResult, HashResult, IteratorResult, KeyValueResult, + NextKeyRangeResult, ProposalResult, RangeProofResult, RevisionResult, ValueResult, VoidResult, }; /// Maybe is a C-compatible optional type using a tagged union pattern. diff --git a/ffi/src/value/kvp.rs b/ffi/src/value/kvp.rs index 98471147a..c5255cc40 100644 --- a/ffi/src/value/kvp.rs +++ b/ffi/src/value/kvp.rs @@ -3,9 +3,9 @@ use std::fmt; -use firewood::v2::api; - +use crate::OwnedBytes; use crate::value::BorrowedBytes; +use firewood::v2::api; /// A `KeyValue` represents a key-value pair, passed to the FFI. #[repr(C)] @@ -61,3 +61,23 @@ impl<'a> api::KeyValuePair for &KeyValuePair<'a> { (*self).into_batch() } } + +/// Owned version of `KeyValuePair`, returned to ffi callers. +/// +/// C callers must free this using [`crate::fwd_free_owned_kv_pair`], +/// not the C standard library's `free` function. +#[repr(C)] +#[derive(Debug, Clone)] +pub struct OwnedKeyValuePair { + pub key: OwnedBytes, + pub value: OwnedBytes, +} + +impl From<(Box<[u8]>, Box<[u8]>)> for OwnedKeyValuePair { + fn from(value: (Box<[u8]>, Box<[u8]>)) -> Self { + OwnedKeyValuePair { + key: value.0.into(), + value: value.1.into(), + } + } +} diff --git a/ffi/src/value/results.rs b/ffi/src/value/results.rs index 139ad9ae4..a351f79d8 100644 --- a/ffi/src/value/results.rs +++ b/ffi/src/value/results.rs @@ -1,14 +1,14 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::fmt; - +use firewood::merkle; use firewood::v2::api; +use std::fmt; use crate::revision::{GetRevisionResult, RevisionHandle}; use crate::{ - ChangeProofContext, CreateProposalResult, HashKey, NextKeyRange, OwnedBytes, ProposalHandle, - RangeProofContext, + ChangeProofContext, CreateIteratorResult, CreateProposalResult, HashKey, IteratorHandle, + NextKeyRange, OwnedBytes, OwnedKeyValuePair, ProposalHandle, RangeProofContext, }; /// The result type returned from an FFI function that returns no value but may @@ -307,6 +307,83 @@ pub enum ProposalResult<'db> { Err(OwnedBytes), } +/// A result type returned from FFI functions that create an iterator +#[derive(Debug)] +#[repr(C)] +pub enum IteratorResult<'db> { + /// The caller provided a null pointer to a revision/proposal handle. + NullHandlePointer, + /// Building the iterator was successful and the iterator handle is returned + Ok { + /// An opaque pointer to the [`IteratorHandle`]. + /// The value should be freed with [`fwd_free_iterator`] + /// + /// [`fwd_free_iterator`]: crate::fwd_free_iterator + handle: Box>, + }, + /// An error occurred and the message is returned as an [`OwnedBytes`]. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this error. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Err(OwnedBytes), +} + +/// A result type returned from iterator FFI functions +#[derive(Debug)] +#[repr(C)] +pub enum KeyValueResult { + /// The caller provided a null pointer to an iterator handle. + NullHandlePointer, + /// The iterator is exhausted + None, + /// The next item is returned. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with the key and the value of this pair. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Some(OwnedKeyValuePair), + /// An error occurred and the message is returned as an [`OwnedBytes`]. The + /// value is guaranteed to contain only valid UTF-8. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this error. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Err(OwnedBytes), +} + +impl From>> for KeyValueResult { + fn from(value: Option>) -> Self { + match value { + Some(value) => match value { + Ok(value) => KeyValueResult::Some(value.into()), + Err(err) => KeyValueResult::Err(err.to_string().into_bytes().into()), + }, + None => KeyValueResult::None, + } + } +} + +impl<'db> From> for IteratorResult<'db> { + fn from(value: CreateIteratorResult<'db>) -> Self { + IteratorResult::Ok { + handle: Box::new(value.0), + } + } +} + +impl<'db, E: fmt::Display> From, E>> for IteratorResult<'db> { + fn from(value: Result, E>) -> Self { + match value { + Ok(res) => res.into(), + Err(err) => IteratorResult::Err(err.to_string().into_bytes().into()), + } + } +} + /// A result type returned from FFI functions that get a revision #[derive(Debug)] #[repr(C)] @@ -434,7 +511,9 @@ impl_null_handle_result!( ChangeProofResult, NextKeyRangeResult, ProposalResult<'_>, + IteratorResult<'_>, RevisionResult, + KeyValueResult, ); impl_cresult!( @@ -446,7 +525,9 @@ impl_cresult!( ChangeProofResult, NextKeyRangeResult, ProposalResult<'_>, + IteratorResult<'_>, RevisionResult, + KeyValueResult, ); enum Panic {