firewood_ffi/proofs/change.rs
1// Copyright (C) 2025, Ava Labs, Inc. All rights reserved.
2// See the file LICENSE.md for licensing terms.
3
4use std::convert::Into;
5use std::num::NonZeroUsize;
6
7use firewood_metrics::firewood_increment;
8#[cfg(feature = "ethhash")]
9use firewood_storage::TrieHash;
10#[cfg(feature = "ethhash")]
11use rlp::Rlp;
12
13use firewood::{
14 ProofError,
15 api::{self, DbView as _, FrozenChangeProof},
16 logger::warn,
17};
18
19use std::cmp::Ordering;
20
21use crate::{
22 BorrowedBytes, ChangeProofResult, DatabaseHandle, HashKey, HashResult, KeyRange, Maybe,
23 NextKeyRangeResult, OwnedBytes, ValueResult, VoidResult,
24 results::{ProposedChangeProofResult, VerifiedChangeProofResult},
25};
26
27#[cfg(feature = "ethhash")]
28const EMPTY_CODE_HASH: [u8; 32] = [
29 // "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
30 0xc5, 0xd2, 0x46, 0x01, 0x86, 0xf7, 0x23, 0x3c, 0x92, 0x7e, 0x7d, 0xb2, 0xdc, 0xc7, 0x03, 0xc0,
31 0xe5, 0x00, 0xb6, 0x53, 0xca, 0x82, 0x27, 0x3b, 0x7b, 0xfa, 0xd8, 0x04, 0x5d, 0x85, 0xa4, 0x70,
32];
33
34/// Arguments for creating a change proof.
35#[derive(Debug)]
36#[repr(C)]
37pub struct CreateChangeProofArgs<'a> {
38 /// The root hash of the starting revision. This must be provided.
39 /// If the root is not found in the database, the function will return
40 /// [`ChangeProofResult::StartRevisionNotFound`].
41 pub start_root: HashKey,
42 /// The root hash of the ending revision. This must be provided.
43 /// If the root is not found in the database, the function will return
44 /// [`ChangeProofResult::EndRevisionNotFound`].
45 pub end_root: HashKey,
46 /// The start key of the range to create the proof for. If `None`, the range
47 /// starts from the beginning of the keyspace.
48 pub start_key: Maybe<BorrowedBytes<'a>>,
49 /// The end key of the range to create the proof for. If `None`, the range
50 /// ends at the end of the keyspace or until `max_length` items have been
51 /// included in the proof.
52 pub end_key: Maybe<BorrowedBytes<'a>>,
53 /// The maximum number of key/value pairs to include in the proof. If the
54 /// range contains more items than this, the proof will be truncated. If
55 /// `0`, there is no limit.
56 pub max_length: u32,
57}
58
59/// Arguments for verifying a change proof.
60#[derive(Debug)]
61#[repr(C)]
62pub struct VerifyChangeProofArgs<'a> {
63 /// The change proof to verify. If null, the function will return
64 /// [`VoidResult::NullHandlePointer`]. We need a mutable reference to
65 /// update the validation context.
66 pub proof: Option<&'a mut ChangeProofContext>,
67 /// The root hash of the starting revision. This must match the starting
68 /// root of the proof.
69 pub start_root: HashKey,
70 /// The root hash of the ending revision. This must match the ending root of
71 /// the proof.
72 pub end_root: HashKey,
73 /// The lower bound of the key range that the proof is expected to cover. If
74 /// `None`, the proof is expected to cover from the start of the keyspace.
75 pub start_key: Maybe<BorrowedBytes<'a>>,
76 /// The upper bound of the key range that the proof is expected to cover. If
77 /// `None`, the proof is expected to cover to the end of the keyspace.
78 pub end_key: Maybe<BorrowedBytes<'a>>,
79 /// The maximum number of key/value pairs that the proof is expected to cover.
80 /// If the proof contains more items than this, it is considered invalid. If
81 /// `0`, there is no limit.
82 pub max_length: u32,
83}
84
85// Arguments for creating a proposal from a verified change proof
86#[derive(Debug)]
87#[repr(C)]
88pub struct ProposedChangeProofArgs<'a> {
89 /// The verified change proof context that will be used to create a proposal.
90 pub proof: Option<&'a mut VerifiedChangeProofContext>,
91}
92
93#[derive(Debug)]
94#[repr(C)]
95pub struct CommittedChangeProofArgs<'a> {
96 // The proposed change proof context that will be used to commit a proposal
97 pub proof: Option<&'a mut ProposedChangeProofContext<'a>>,
98}
99
100/// FFI context for a parsed or generated change proof. This change proof has not
101/// been verified. Calling `verify` on it will generate a `VerifiedChangeProofContext`
102/// and consume the `proof` and replacing it with None.
103#[derive(Debug)]
104pub struct ChangeProofContext {
105 proof: Option<FrozenChangeProof>,
106}
107
108impl From<FrozenChangeProof> for ChangeProofContext {
109 fn from(proof: FrozenChangeProof) -> Self {
110 Self { proof: Some(proof) }
111 }
112}
113
114impl ChangeProofContext {
115 /// Verifies the `ChangeProofContext` and creates a `VerifiedChangeProofContext`
116 /// on success. Calling `verify` consumes the proof, and calling it again will
117 /// return a `ProofIsNone` error.
118 ///
119 /// Currently only performs a cursory verification, such as whether
120 /// the keys in the change proof is sorted.
121 fn verify(
122 &mut self,
123 params: VerificationParams,
124 ) -> Result<VerifiedChangeProofContext, api::Error> {
125 let Some(proof) = self.proof.take() else {
126 return Err(api::Error::ProofError(ProofError::ProofIsNone));
127 };
128
129 let batch_ops = proof.batch_ops();
130
131 // Check to make sure the BatchOp array size is less than or equal to `max_length`
132 if let Some(max_length) = params.max_length
133 && batch_ops.len() > max_length.into()
134 {
135 return Err(api::Error::ProofError(
136 ProofError::ProofIsLargerThanMaxLength,
137 ));
138 }
139
140 // Check the start key is not greater than the first key in the proof.
141 if let (Some(start_key), Some(first_key)) = (¶ms.start_key, batch_ops.first())
142 && start_key.cmp(first_key.key()) == Ordering::Greater
143 {
144 return Err(api::Error::ProofError(
145 ProofError::StartKeyLargerThanFirstKey,
146 ));
147 }
148
149 // Check the end key is not less than the last key in the proof.
150 if let (Some(end_key), Some(last_key)) = (¶ms.end_key, batch_ops.last())
151 && end_key.cmp(last_key.key()) == Ordering::Less
152 {
153 return Err(api::Error::ProofError(ProofError::EndKeyLessThanLastKey));
154 }
155
156 // Verify the keys are in sorted order.
157 if batch_ops
158 .iter()
159 .is_sorted_by(|a, b| b.key().cmp(a.key()) == Ordering::Greater)
160 {
161 warn!("change proof verification not yet implemented");
162 Ok(VerifiedChangeProofContext {
163 proof: Some(proof),
164 params,
165 })
166 } else {
167 Err(api::Error::ProofError(ProofError::ChangeProofKeysNotSorted))
168 }
169 }
170}
171
172/// FFI context for a verified change proof. It is created from calling `verify`
173/// on a `ChangeProofContext` and stores the parameters of that call in `params`.
174/// Calling `propose` on it will consume the proof to create a
175/// `ProposedChangeProofContext`.
176#[derive(Debug)]
177pub struct VerifiedChangeProofContext {
178 proof: Option<FrozenChangeProof>,
179 params: VerificationParams,
180}
181
182impl VerifiedChangeProofContext {
183 /// Creates a proposal from the verified change proof context, and stores the change
184 /// proof, database handle, and proposal handle in a `ProposedChangeProofContext`.
185 /// Calling `propose` consumes the proof, and calling it again will return a
186 /// `ProofIsNone` error.
187 fn propose<'db>(
188 &'db mut self,
189 db: &'db DatabaseHandle,
190 ) -> Result<ProposedChangeProofContext<'db>, api::Error> {
191 let Some(proof) = self.proof.take() else {
192 return Err(api::Error::ProofError(ProofError::ProofIsNone));
193 };
194 let proposal = db.apply_change_proof_to_parent(self.params.start_root.into(), &proof)?;
195 let root_hash = proposal.handle.root_hash().map(Into::into);
196 Ok(ProposedChangeProofContext {
197 proof,
198 db,
199 root_hash,
200 end_root: self.params.end_root,
201 end_key: self.params.end_key.clone(),
202 proposal: Some(proposal.handle),
203 })
204 }
205}
206
207/// FFI context for a proposed change proof. It is created from calling `propose`
208/// on a `VerifiedChangeProofContext` and stores the database, proposal handle,
209/// and other parameters need to implement `find_next_key`. Calling `commit` on it
210/// will consume the proof, but `find_next_key` can still be called on it.
211#[expect(unused)]
212#[derive(Debug)]
213pub struct ProposedChangeProofContext<'db> {
214 proof: FrozenChangeProof,
215 db: &'db DatabaseHandle,
216 root_hash: Option<HashKey>,
217 end_root: HashKey,
218 end_key: Option<Box<[u8]>>,
219 proposal: Option<crate::ProposalHandle<'db>>,
220}
221
222impl<'db> ProposedChangeProofContext<'db> {
223 fn find_next_key(&mut self) -> Result<Option<KeyRange>, api::Error> {
224 let Some(last_op) = self.proof.batch_ops().last() else {
225 // no BatchOps in the proof, so we are done
226 return Ok(None);
227 };
228
229 if self.proof.end_proof().is_empty() {
230 // unbounded, so we are done
231 return Ok(None);
232 }
233
234 if let Some(ref end_key) = self.end_key
235 && **last_op.key() >= **end_key
236 {
237 // reached or exceeded the end key, so we are done
238 return Ok(None);
239 }
240
241 Ok(Some((last_op.key().clone(), self.end_key.clone())))
242 }
243
244 /// Consumes proposal handle after being called once.
245 fn commit(&'db mut self) -> Result<Option<HashKey>, api::Error> {
246 let Some(proposal_handle) = self.proposal.take() else {
247 return Err(api::Error::ProofError(ProofError::ProposalIsNone));
248 };
249
250 let result = proposal_handle.commit_proposal();
251 let hash = result?.map(Into::into);
252 firewood_increment!(crate::registry::MERGE_COUNT, 1, "change" => "commit");
253 Ok(hash)
254 }
255}
256
257/// FFI parameters for verifying a change proof
258#[derive(Debug)]
259struct VerificationParams {
260 start_root: HashKey,
261 end_root: HashKey,
262 start_key: Option<Box<[u8]>>,
263 end_key: Option<Box<[u8]>>,
264 max_length: Option<NonZeroUsize>,
265}
266
267/// A key range that should be fetched to continue iterating through a range
268/// or change proof that was truncated. Represents a half-open range
269/// `[start_key, end_key)`. If `end_key` is `None`, the range is unbounded
270/// and continues to the end of the keyspace.
271#[derive(Debug)]
272#[repr(C)]
273pub struct NextKeyRange {
274 /// The start key of the next range to fetch.
275 pub start_key: OwnedBytes,
276
277 /// If set, a non-inclusive upper bound for the next range to fetch. If not
278 /// set, the range is unbounded (this is the final range).
279 pub end_key: Maybe<OwnedBytes>,
280}
281
282#[derive(Debug)]
283#[non_exhaustive]
284pub struct CodeIteratorHandle<'a> {
285 #[cfg(feature = "ethhash")]
286 inner: std::slice::Iter<'a, KeyValuePair>,
287 // uninhabitable fields make the struct impossible to construct when the feature is disabled
288 #[cfg(not(feature = "ethhash"))]
289 void: std::convert::Infallible,
290 #[cfg(not(feature = "ethhash"))]
291 marker: std::marker::PhantomData<&'a ()>,
292}
293
294type KeyValuePair = (Box<[u8]>, Box<[u8]>);
295
296impl Iterator for CodeIteratorHandle<'_> {
297 type Item = Result<HashKey, api::Error>;
298
299 fn next(&mut self) -> Option<Self::Item> {
300 #[cfg(not(feature = "ethhash"))]
301 match self.void {}
302
303 #[cfg(feature = "ethhash")]
304 self.inner.find_map(|(key, value)| {
305 if key.len() != 32 {
306 return None;
307 }
308
309 let Ok(code_hash_slice) = Rlp::new(value).at(3).and_then(|r| r.data()) else {
310 return Some(Err(api::Error::ProofError(ProofError::InvalidValueFormat)));
311 };
312 let code_hash: HashKey = TrieHash::try_from(code_hash_slice).ok()?.into();
313 if code_hash == TrieHash::from(EMPTY_CODE_HASH).into() {
314 return None;
315 }
316
317 Some(Ok(code_hash))
318 })
319 }
320}
321
322impl<'a> CodeIteratorHandle<'a> {
323 /// Create a new code hash iterator from the given key/value pairs.
324 /// The key/value pairs should be the raw entries from the
325 /// underlying proof.
326 ///
327 /// The iterator must be freed after use.
328 ///
329 /// Arguments:
330 /// - `key_values` - The key/value pairs from the proof.
331 ///
332 /// Returns:
333 /// - `Ok(CodeIteratorHandle)` if the iterator was successfully created.
334 /// - `Err(api::Error)` if the iterator could not be created.
335 ///
336 /// # Errors
337 ///
338 /// - Returns `api::Error::FeatureNotSupported` if the `ethhash` feature
339 /// is not enabled.
340 #[cfg_attr(feature = "ethhash", allow(clippy::missing_const_for_fn))]
341 #[cfg_attr(not(feature = "ethhash"), allow(unused_variables))]
342 pub fn new(key_values: &'a [KeyValuePair]) -> Result<Self, api::Error> {
343 #[cfg(not(feature = "ethhash"))]
344 {
345 Err(api::Error::FeatureNotSupported(
346 "ethhash code hash iterator".to_string(),
347 ))
348 }
349
350 #[cfg(feature = "ethhash")]
351 {
352 Ok(CodeIteratorHandle {
353 inner: key_values.iter(),
354 })
355 }
356 }
357}
358
359/// Create a change proof for the given range of keys between two roots.
360///
361/// # Arguments
362///
363/// - `db` - The database to create the proof from.
364/// - `args` - The arguments for creating the change proof.
365///
366/// # Returns
367///
368/// - [`ChangeProofResult::NullHandlePointer`] if the caller provided a null pointer.
369/// - [`ChangeProofResult::StartRevisionNotFound`] if the caller provided a start root
370/// that was not found in the database. The missing root hash is included in the result.
371/// If both the start root and end root are missing, then only the end root is
372/// reported.
373/// - [`ChangeProofResult::EndRevisionNotFound`] if the caller provided an end root
374/// that was not found in the database. The missing root hash is included in the result.
375/// If both the start root and end root are missing, then only the end root is
376/// reported.
377/// - [`ChangeProofResult::Ok`] containing a pointer to the `ChangeProofContext` if the proof
378/// was successfully created.
379/// - [`ChangeProofResult::Err`] containing an error message if the proof could not be created.
380#[unsafe(no_mangle)]
381pub extern "C" fn fwd_db_change_proof(
382 db: Option<&DatabaseHandle>,
383 args: CreateChangeProofArgs,
384) -> ChangeProofResult {
385 crate::invoke_with_handle(db, |db| {
386 db.change_proof(
387 args.start_root.into(),
388 args.end_root.into(),
389 args.start_key
390 .as_ref()
391 .map(BorrowedBytes::as_slice)
392 .into_option(),
393 args.end_key
394 .as_ref()
395 .map(BorrowedBytes::as_slice)
396 .into_option(),
397 NonZeroUsize::new(args.max_length as usize),
398 )
399 })
400}
401
402/// Verify a change proof and return a `VerifiedChangeProofResult`.
403///
404/// # Arguments
405///
406/// - `args` - The arguments for verifying the change proof.
407///
408/// # Returns
409///
410/// - [`VerifiedChangeProofResult::NullHandlePointer`] if the caller provided a null pointer to the
411/// proof.
412/// - [`VerifiedChangeProofResult::Ok`] if the proof was successfully verified.
413/// - [`VerifiedChangeProofResult::Err`] containing an error message if the proof could not be verified
414///
415/// # Thread Safety
416///
417/// It is not safe to call this function concurrently with the same proof context
418/// nor is it safe to call any other function that accesses the same proof context
419/// concurrently. The caller must ensure exclusive access to the proof context
420/// for the duration of the call.
421#[unsafe(no_mangle)]
422pub extern "C" fn fwd_verify_change_proof(
423 args: VerifyChangeProofArgs,
424) -> VerifiedChangeProofResult {
425 crate::invoke_with_handle(args.proof, |ctx| {
426 let context = VerificationParams {
427 start_root: args.start_root,
428 end_root: args.end_root,
429 start_key: args.start_key.into_option().as_deref().map(Box::from),
430 end_key: args.end_key.into_option().as_deref().map(Box::from),
431 max_length: NonZeroUsize::new(args.max_length as usize),
432 };
433 ctx.verify(context)
434 })
435}
436
437/// Create a proposal from a change proof and return a `ProposedChangeProofResult`.
438///
439/// # Arguments
440///
441/// - `db` - The database to create the proposal.
442/// - `args` - The arguments for verifying the change proof.
443///
444/// # Returns
445///
446/// - [`ProposedChangeProofResult::NullHandlePointer`] if the caller provided a null pointer to either
447/// the database or the proof.
448/// - [`ProposedChangeProofResult::Ok`] if a proposal was successfully created.
449/// - [`ProposedChangeProofResult::Err`] containing an error message if the proposal could not be created.
450///
451/// # Thread Safety
452///
453/// It is not safe to call this function concurrently with the same proof context
454/// nor is it safe to call any other function that accesses the same proof context
455/// concurrently. The caller must ensure exclusive access to the proof context
456/// for the duration of the call.
457#[unsafe(no_mangle)]
458pub extern "C" fn fwd_db_propose_change_proof<'db>(
459 db: Option<&'db DatabaseHandle>,
460 args: ProposedChangeProofArgs<'db>,
461) -> ProposedChangeProofResult<'db> {
462 let handle = db.and_then(|db| args.proof.map(|p| (db, p)));
463 crate::invoke_with_handle(handle, |(db, ctx)| ctx.propose(db))
464}
465
466/// Commit a change proof to the database.
467///
468/// # Arguments
469///
470/// - `args` - The arguments for verifying the change proof, which is just a `ProposedChangeProofContext`.
471///
472/// # Returns
473///
474/// - [`HashResult::NullHandlePointer`] if the caller provided a null pointer to the proof.
475/// - [`HashResult::None`] if the proof resulted in an empty database (i.e., all keys were deleted).
476/// - [`HashResult::Some`] containing the new root hash
477/// - [`HashResult::Err`] containing an error message if the proof could not be committed.
478///
479/// # Thread Safety
480///
481/// It is not safe to call this function concurrently with the same proof context
482/// nor is it safe to call any other function that accesses the same proof context
483/// concurrently. The caller must ensure exclusive access to the proof context
484/// for the duration of the call.
485#[unsafe(no_mangle)]
486pub extern "C" fn fwd_db_commit_change_proof(args: CommittedChangeProofArgs<'_>) -> HashResult {
487 crate::invoke_with_handle(args.proof, |ctx| {
488 ctx.commit().map(|hash_key| hash_key.map(Into::into))
489 })
490}
491
492/// Returns the next key range that should be fetched after processing the
493/// current set of operations in a change proof that was truncated.
494///
495/// # Arguments
496///
497/// - `proof` - A [`ChangeProofContext`] previously returned from the create
498/// methods and has been prepared into a proposal or already committed.
499///
500/// # Returns
501///
502/// - [`NextKeyRangeResult::NullHandlePointer`] if the caller provided a null pointer.
503/// - [`NextKeyRangeResult::NotPrepared`] if the proof has not been prepared into
504/// a proposal nor committed to the database. Should not be possible for a change
505/// proof due to its different interface compared to range proofs.
506/// - [`NextKeyRangeResult::None`] if there are no more keys to fetch.
507/// - [`NextKeyRangeResult::Some`] containing the next key range to fetch.
508/// - [`NextKeyRangeResult::Err`] containing an error message if the next key range
509/// could not be determined.
510///
511/// # Thread Safety
512///
513/// It is not safe to call this function concurrently with the same proof context
514/// nor is it safe to call any other function that accesses the same proof context
515/// concurrently. The caller must ensure exclusive access to the proof context
516/// for the duration of the call.
517#[unsafe(no_mangle)]
518pub extern "C" fn fwd_change_proof_find_next_key_proposed(
519 proof: Option<&mut ProposedChangeProofContext>,
520) -> NextKeyRangeResult {
521 crate::invoke_with_handle(proof, ProposedChangeProofContext::find_next_key)
522}
523
524/// Serialize a `ChangeProof` to bytes.
525///
526/// # Arguments
527///
528/// - `proof` - A [`ChangeProofContext`] previously returned from the create
529/// method. If from a parsed proof, the proof will not be verified before
530/// serialization.
531///
532/// # Returns
533///
534/// - [`ValueResult::NullHandlePointer`] if the caller provided a null pointer.
535/// - [`ValueResult::Some`] containing the serialized bytes if successful.
536/// - [`ValueResult::Err`] if the caller provided a null pointer.
537///
538/// The other [`ValueResult`] variants are not used.
539#[unsafe(no_mangle)]
540pub extern "C" fn fwd_change_proof_to_bytes(proof: Option<&ChangeProofContext>) -> ValueResult {
541 crate::invoke_with_handle(proof, |ctx| {
542 let mut vec = Vec::new();
543 if let Some(proof) = &ctx.proof {
544 proof.write_to_vec(&mut vec);
545 }
546 vec
547 })
548}
549
550/// Deserialize a `ChangeProof` from bytes.
551///
552/// # Arguments
553///
554/// * `bytes` - The bytes to deserialize the proof from.
555///
556/// # Returns
557///
558/// - [`ChangeProofResult::NullHandlePointer`] if the caller provided a null or zero-length slice.
559/// - [`ChangeProofResult::Ok`] containing a pointer to the `ChangeProofContext` if the proof
560/// was successfully parsed. This does not imply that the proof is valid, only that it is
561/// well-formed. The verify method must be called to ensure the proof is cryptographically valid.
562/// - [`ChangeProofResult::Err`] containing an error message if the proof could not be parsed.
563#[unsafe(no_mangle)]
564pub extern "C" fn fwd_change_proof_from_bytes(bytes: BorrowedBytes) -> ChangeProofResult {
565 crate::invoke(move || {
566 FrozenChangeProof::from_slice(&bytes)
567 .map_err(|err| api::Error::ProofError(ProofError::Deserialization(err)))
568 })
569}
570
571/// Frees the memory associated with a `ChangeProofContext`.
572///
573/// # Arguments
574///
575/// * `proof` - The `ChangeProofContext` to free, previously returned from any Rust function.
576///
577/// # Returns
578///
579/// - [`VoidResult::Ok`] if the memory was successfully freed.
580/// - [`VoidResult::Err`] if the process panics while freeing the memory.
581#[unsafe(no_mangle)]
582pub extern "C" fn fwd_free_change_proof(proof: Option<Box<ChangeProofContext>>) -> VoidResult {
583 crate::invoke_with_handle(proof, drop)
584}
585
586/// Frees the memory associated with a `VerifiedChangeProofContext`.
587///
588/// # Arguments
589///
590/// * `proof` - The `VerifiedChangeProofContext` to free, previously returned from any Rust function.
591///
592/// # Returns
593///
594/// - [`VoidResult::Ok`] if the memory was successfully freed.
595/// - [`VoidResult::Err`] if the process panics while freeing the memory.
596#[unsafe(no_mangle)]
597pub extern "C" fn fwd_free_verified_change_proof(
598 proof: Option<Box<VerifiedChangeProofContext>>,
599) -> VoidResult {
600 crate::invoke_with_handle(proof, drop)
601}
602
603/// Frees the memory associated with a `ProposedChangeProofContext`.
604///
605/// # Arguments
606///
607/// * `proof` - The `ProposedChangeProofContext` to free, previously returned from any Rust function.
608///
609/// # Returns
610///
611/// - [`VoidResult::Ok`] if the memory was successfully freed.
612/// - [`VoidResult::Err`] if the process panics while freeing the memory.
613#[unsafe(no_mangle)]
614pub extern "C" fn fwd_free_proposed_change_proof(
615 proof: Option<Box<ProposedChangeProofContext>>,
616) -> VoidResult {
617 crate::invoke_with_handle(proof, drop)
618}
619
620impl crate::MetricsContextExt for ChangeProofContext {
621 fn metrics_context(&self) -> Option<firewood_metrics::MetricsContext> {
622 None
623 }
624}
625
626impl crate::MetricsContextExt for VerifiedChangeProofContext {
627 fn metrics_context(&self) -> Option<firewood_metrics::MetricsContext> {
628 None
629 }
630}
631
632impl crate::MetricsContextExt for ProposedChangeProofContext<'_> {
633 fn metrics_context(&self) -> Option<firewood_metrics::MetricsContext> {
634 None
635 }
636}
637
638impl crate::MetricsContextExt for (&DatabaseHandle, &mut ChangeProofContext) {
639 fn metrics_context(&self) -> Option<firewood_metrics::MetricsContext> {
640 self.0.metrics_context()
641 }
642}
643
644impl crate::MetricsContextExt for (&DatabaseHandle, &mut VerifiedChangeProofContext) {
645 fn metrics_context(&self) -> Option<firewood_metrics::MetricsContext> {
646 self.0.metrics_context()
647 }
648}
649
650impl crate::MetricsContextExt for CodeIteratorHandle<'_> {
651 fn metrics_context(&self) -> Option<firewood_metrics::MetricsContext> {
652 None
653 }
654}