Skip to main content

firewood_ffi/
lib.rs

1// Copyright (C) 2025, Ava Labs, Inc. All rights reserved.
2// See the file LICENSE.md for licensing terms.
3
4// HINT WHEN REFERENCING TYPES OUTSIDE THIS LIBRARY:
5// - Anything that is outside the crate must be included as a `type` alias (not just
6//   a `use`) in order for cbindgen to generate an opaque forward declaration. The type
7//   alias can have a doc comment which will be included in the generated header file.
8// - The value must be boxed, or otherwise used via a pointer. This is because only
9//   a forward declaration is generated and callers will be unable to instantiate the
10//   type without a complete definition.
11
12#![doc = include_str!("../README.md")]
13#![expect(
14    unsafe_code,
15    reason = "This is an FFI library, so unsafe code is expected."
16)]
17#![cfg_attr(
18    not(target_pointer_width = "64"),
19    forbid(
20        clippy::cast_possible_truncation,
21        reason = "non-64 bit target likely to cause issues during u64 to usize conversions"
22    )
23)]
24
25mod arc_cache;
26mod handle;
27mod iterator;
28mod logging;
29mod metrics;
30mod proofs;
31mod proposal;
32mod reconstructed;
33mod registry;
34#[cfg(feature = "block-replay")]
35mod replay;
36mod revision;
37mod value;
38
39use firewood::api::DbView;
40use firewood_metrics::set_metrics_context;
41
42pub use crate::handle::*;
43pub use crate::iterator::*;
44pub use crate::logging::*;
45use crate::metrics::MetricsContextExt;
46pub use crate::proofs::*;
47pub use crate::proposal::*;
48pub use crate::reconstructed::*;
49pub use crate::revision::*;
50pub use crate::value::*;
51
52#[global_allocator]
53#[doc(hidden)]
54static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
55
56/// Invokes a closure and returns the result as a [`CResult`].
57///
58/// If the closure panics, it will return [`CResult::from_panic`] with the panic
59/// information.
60#[inline]
61fn invoke<T: CResult, V: Into<T>>(once: impl FnOnce() -> V) -> T {
62    #[cfg(panic = "unwind")]
63    match std::panic::catch_unwind(std::panic::AssertUnwindSafe(once)) {
64        Ok(result) => result.into(),
65        Err(panic) => T::from_panic(panic),
66    }
67
68    #[cfg(not(panic = "unwind"))]
69    {
70        once().into()
71    }
72}
73
74/// Invokes a closure with context from the handle.
75#[inline]
76fn invoke_with_handle<H: MetricsContextExt, T: NullHandleResult, V: Into<T>>(
77    handle: Option<H>,
78    once: impl FnOnce(H) -> V,
79) -> T {
80    match handle {
81        Some(handle) => {
82            let _guard = set_metrics_context(handle.metrics_context());
83            invoke(move || once(handle))
84        }
85        None => T::null_handle_pointer_error(),
86    }
87}
88
89/// Gets the value associated with the given key from the database for the
90/// latest revision.
91///
92/// # Arguments
93///
94/// * `db` - The database handle returned by [`fwd_open_db`]
95/// * `key` - The key to look up as a [`BorrowedBytes`]
96///
97/// # Returns
98///
99/// - [`ValueResult::NullHandlePointer`] if the provided database handle is null.
100/// - [`ValueResult::RevisionNotFound`] if no revision was found for the root
101///   (i.e., there is no current root).
102/// - [`ValueResult::None`] if the key was not found.
103/// - [`ValueResult::Some`] if the key was found with the associated value.
104/// - [`ValueResult::Err`] if an error occurred while retrieving the value.
105///
106/// # Safety
107///
108/// The caller must:
109/// * ensure that `db` is a valid pointer to a [`DatabaseHandle`].
110/// * ensure that `key` is valid for [`BorrowedBytes`]
111/// * call [`fwd_free_owned_bytes`] to free the memory associated with the
112///   returned error or value.
113///
114/// [`BorrowedBytes`]: crate::value::BorrowedBytes
115#[unsafe(no_mangle)]
116pub extern "C" fn fwd_get_latest(db: Option<&DatabaseHandle>, key: BorrowedBytes) -> ValueResult {
117    #[cfg(feature = "block-replay")]
118    if db.is_some() {
119        replay::record_get_latest(key);
120    }
121
122    invoke_with_handle(db, move |db| db.get_latest(key))
123}
124
125/// Returns an iterator optionally starting from a key in the provided revision.
126///
127/// # Arguments
128///
129/// * `revision` - The revision handle returned by [`fwd_get_revision`].
130/// * `key` - The key to look up as a [`BorrowedBytes`]
131///
132/// # Returns
133///
134/// - [`IteratorResult::NullHandlePointer`] if the provided revision handle is null.
135/// - [`IteratorResult::Ok`] if the iterator was created, with the iterator handle.
136/// - [`IteratorResult::Err`] if an error occurred while creating the iterator.
137///
138/// # Safety
139///
140/// The caller must:
141/// * ensure that `revision` is a valid pointer to a [`RevisionHandle`]
142/// * ensure that `key` is a valid [`BorrowedBytes`]
143/// * call [`fwd_free_iterator`] to free the memory associated with the iterator.
144///
145#[unsafe(no_mangle)]
146pub extern "C" fn fwd_iter_on_revision<'view>(
147    revision: Option<&'view RevisionHandle<'_>>,
148    key: BorrowedBytes,
149) -> IteratorResult<'view> {
150    invoke_with_handle(revision, move |rev| rev.iter_from(Some(key.as_slice())))
151}
152
153/// Returns an iterator on the provided proposal optionally starting from a key
154///
155/// # Arguments
156///
157/// * `handle` - The proposal handle returned by [`fwd_propose_on_db`] or
158///   [`fwd_propose_on_proposal`].
159/// * `key` - The key to look up as a [`BorrowedBytes`]
160///
161/// # Returns
162///
163/// - [`IteratorResult::NullHandlePointer`] if the provided proposal handle is null.
164/// - [`IteratorResult::Ok`] if the iterator was created, with the iterator handle.
165/// - [`IteratorResult::Err`] if an error occurred while creating the iterator.
166///
167/// # Safety
168///
169/// The caller must:
170/// * ensure that `handle` is a valid pointer to a [`ProposalHandle`]
171/// * ensure that `key` is a valid for [`BorrowedBytes`]
172/// * call [`fwd_free_iterator`] to free the memory associated with the iterator.
173///
174#[unsafe(no_mangle)]
175pub extern "C" fn fwd_iter_on_proposal<'p>(
176    handle: Option<&'p ProposalHandle<'_>>,
177    key: BorrowedBytes,
178) -> IteratorResult<'p> {
179    invoke_with_handle(handle, move |p| p.iter_from(Some(key.as_slice())))
180}
181
182/// Returns an iterator on the provided reconstructed view optionally starting from a key.
183///
184/// # Arguments
185///
186/// * `handle` - The reconstructed handle returned by [`fwd_reconstruct_on_revision`] or
187///   [`fwd_reconstruct_on_reconstructed`].
188/// * `key` - The key to start iterating from as a [`BorrowedBytes`].
189///
190/// # Returns
191///
192/// - [`IteratorResult::NullHandlePointer`] if the provided handle is null.
193/// - [`IteratorResult::Ok`] if the iterator was created, with the iterator handle.
194/// - [`IteratorResult::Err`] if an error occurred while creating the iterator.
195///
196/// # Safety
197///
198/// The caller must:
199/// * ensure that `handle` is a valid pointer to a [`ReconstructedHandle`]
200/// * ensure that `key` is a valid [`BorrowedBytes`]
201/// * call [`fwd_free_iterator`] to free the memory associated with the iterator.
202#[unsafe(no_mangle)]
203pub extern "C" fn fwd_iter_on_reconstructed<'p>(
204    handle: Option<&'p ReconstructedHandle<'_>>,
205    key: BorrowedBytes,
206) -> IteratorResult<'p> {
207    invoke_with_handle(handle, move |h| h.iter_from(Some(key.as_slice())))
208}
209
210/// Retrieves the next item from the iterator.
211///
212/// # Arguments
213///
214/// * `handle` - The iterator handle returned by [`fwd_iter_on_revision`] or
215///   [`fwd_iter_on_proposal`].
216///
217/// # Returns
218///
219/// - [`KeyValueResult::NullHandlePointer`] if the provided iterator handle is null.
220/// - [`KeyValueResult::None`] if the iterator is exhausted (no remaining values). Once returned,
221///   subsequent calls will continue returning [`KeyValueResult::None`]. You may still call this
222///   safely, but freeing the iterator with [`fwd_free_iterator`] is recommended.
223/// - [`KeyValueResult::Some`] if the next item on iterator was retrieved, with the associated
224///   key value pair.
225/// - [`KeyValueResult::Err`] if an I/O error occurred while retrieving the next item. Most
226///   iterator errors are non-reentrant. Once returned, the iterator should be considered
227///   invalid and must be freed with [`fwd_free_iterator`].
228///
229/// # Safety
230///
231/// The caller must:
232/// * ensure that `handle` is a valid pointer to a [`IteratorHandle`].
233/// * call [`fwd_free_owned_kv_pair`] on returned [`OwnedKeyValuePair`]
234///   to free the memory associated with the returned value.
235///
236#[unsafe(no_mangle)]
237pub extern "C" fn fwd_iter_next(handle: Option<&mut IteratorHandle<'_>>) -> KeyValueResult {
238    invoke_with_handle(handle, Iterator::next)
239}
240
241/// Retrieves the next batch of items from the iterator.
242///
243/// # Arguments
244///
245/// * `handle` - The iterator handle returned by [`fwd_iter_on_revision`] or
246///   [`fwd_iter_on_proposal`].
247///
248/// # Returns
249///
250/// - [`KeyValueBatchResult::NullHandlePointer`] if the provided iterator handle is null.
251/// - [`KeyValueBatchResult::Some`] with up to `n` key/value pairs. If the iterator is
252///   exhausted, this may be fewer than `n`, including zero items.
253/// - [`KeyValueBatchResult::Err`] if an I/O error occurred while retrieving items. Most
254///   iterator errors are non-reentrant. Once returned, the iterator should be considered
255///   invalid and must be freed with [`fwd_free_iterator`].
256///
257/// Once an empty batch or items fewer than `n` is returned (iterator exhausted), subsequent calls
258/// will continue returning empty batches. You may still call this safely, but freeing the
259/// iterator with [`fwd_free_iterator`] is recommended.
260///
261/// # Safety
262///
263/// The caller must:
264/// * ensure that `handle` is a valid pointer to a [`IteratorHandle`].
265/// * call [`fwd_free_owned_key_value_batch`] on the returned batch to free any allocated memory.
266///
267#[unsafe(no_mangle)]
268pub extern "C" fn fwd_iter_next_n(
269    handle: Option<&mut IteratorHandle<'_>>,
270    n: usize,
271) -> KeyValueBatchResult {
272    invoke_with_handle(handle, |it| it.iter_next_n(n))
273}
274
275/// Consumes the [`IteratorHandle`], destroys the iterator, and frees the memory.
276///
277/// # Arguments
278///
279/// * `iterator` - A pointer to a [`IteratorHandle`] previously returned from a
280///   function from this library.
281///
282/// # Returns
283///
284/// - [`VoidResult::NullHandlePointer`] if the provided iterator handle is null.
285/// - [`VoidResult::Ok`] if the iterator was successfully freed.
286/// - [`VoidResult::Err`] if the process panics while freeing the memory.
287///
288/// # Safety
289///
290/// The caller must ensure that the `iterator` is not null and that it points to
291/// a valid [`IteratorHandle`] previously returned by a function from this library.
292///
293#[unsafe(no_mangle)]
294pub extern "C" fn fwd_free_iterator(iterator: Option<Box<IteratorHandle<'_>>>) -> VoidResult {
295    invoke_with_handle(iterator, drop)
296}
297
298/// Gets a handle to the revision identified by the provided root hash.
299///
300/// # Arguments
301///
302/// * `db` - The database handle returned by [`fwd_open_db`].
303/// * `root` - The hash of the revision as a [`BorrowedBytes`].
304///
305/// # Returns
306///
307/// - [`RevisionResult::NullHandlePointer`] if the provided database handle is null.
308/// - [`RevisionResult::Ok`] containing a [`RevisionHandle`] and root hash if the revision exists.
309/// - [`RevisionResult::Err`] if the revision cannot be fetched or the root hash is invalid.
310///
311/// # Safety
312///
313/// The caller must:
314/// * ensure that `db` is a valid pointer to a [`DatabaseHandle`].
315/// * ensure that `root` is valid for [`BorrowedBytes`].
316/// * call [`fwd_free_revision`] to free the returned handle when it is no longer needed.
317/// * ensure that the [`DatabaseHandle`] remains valid (not closed via [`fwd_close_db`])
318///   for as long as the returned [`RevisionHandle`] (and any derived [`ReconstructedHandle`])
319///   is in use.
320///
321/// [`BorrowedBytes`]: crate::value::BorrowedBytes
322/// [`RevisionHandle`]: crate::revision::RevisionHandle
323#[unsafe(no_mangle)]
324pub extern "C" fn fwd_get_revision(
325    db: Option<&DatabaseHandle>,
326    root: HashKey,
327) -> RevisionResult<'_> {
328    invoke_with_handle(db, move |db| db.get_revision(root.into()))
329}
330
331/// Gets the value associated with the given key from the provided revision handle.
332///
333/// # Arguments
334///
335/// * `revision` - The revision handle returned by [`fwd_get_revision`].
336/// * `key` - The key to look up as a [`BorrowedBytes`].
337///
338/// # Returns
339///
340/// - [`ValueResult::NullHandlePointer`] if the provided revision handle is null.
341/// - [`ValueResult::None`] if the key was not found in the revision.
342/// - [`ValueResult::Some`] if the key was found with the associated value.
343/// - [`ValueResult::Err`] if an error occurred while retrieving the value.
344///
345/// # Safety
346///
347/// The caller must:
348/// * ensure that `revision` is a valid pointer to a [`RevisionHandle`].
349/// * ensure that `key` is valid for [`BorrowedBytes`].
350/// * call [`fwd_free_owned_bytes`] to free the memory associated with the [`OwnedBytes`]
351///   returned in the result.
352#[unsafe(no_mangle)]
353pub extern "C" fn fwd_get_from_revision(
354    revision: Option<&RevisionHandle<'_>>,
355    key: BorrowedBytes,
356) -> ValueResult {
357    invoke_with_handle(revision, move |rev| rev.val(key))
358}
359
360/// Consumes the [`RevisionHandle`] and frees the memory associated with it.
361///
362/// # Arguments
363///
364/// * `revision` - A pointer to a [`RevisionHandle`] previously returned by
365///   [`fwd_get_revision`].
366///
367/// # Returns
368///
369/// - [`VoidResult::NullHandlePointer`] if the provided revision handle is null.
370/// - [`VoidResult::Ok`] if the revision handle was successfully freed.
371/// - [`VoidResult::Err`] if the process panics while freeing the memory.
372///
373/// # Safety
374///
375/// The caller must ensure that the revision handle is valid and is not used again after
376/// this function is called.
377#[unsafe(no_mangle)]
378pub extern "C" fn fwd_free_revision(revision: Option<Box<RevisionHandle<'_>>>) -> VoidResult {
379    invoke_with_handle(revision, drop)
380}
381
382/// Gets the value associated with the given key from the proposal provided.
383///
384/// # Arguments
385///
386/// * `handle` - The proposal handle returned by [`fwd_propose_on_db`] or
387///   [`fwd_propose_on_proposal`].
388/// * `key` - The key to look up, as a [`BorrowedBytes`].
389///
390/// # Returns
391///
392/// - [`ValueResult::NullHandlePointer`] if the provided database handle is null.
393/// - [`ValueResult::None`] if the key was not found.
394/// - [`ValueResult::Some`] if the key was found with the associated value.
395/// - [`ValueResult::Err`] if an error occurred while retrieving the value.
396///
397/// # Safety
398///
399/// The caller must:
400/// * ensure that `handle` is a valid pointer to a [`ProposalHandle`]
401/// * ensure that `key` is valid for [`BorrowedBytes`]
402/// * call [`fwd_free_owned_bytes`] to free the memory associated [`OwnedBytes`]
403///   returned in the result.
404#[unsafe(no_mangle)]
405pub extern "C" fn fwd_get_from_proposal(
406    handle: Option<&ProposalHandle<'_>>,
407    key: BorrowedBytes,
408) -> ValueResult {
409    #[cfg(feature = "block-replay")]
410    replay::record_get_from_proposal(handle, key);
411
412    invoke_with_handle(handle, move |handle| handle.val(key))
413}
414
415/// Gets the value associated with the given key from the reconstructed view provided.
416///
417/// # Arguments
418///
419/// * `handle` - The reconstructed handle returned by [`fwd_reconstruct_on_revision`] or
420///   [`fwd_reconstruct_on_reconstructed`].
421/// * `key` - The key to look up as a [`BorrowedBytes`].
422///
423/// # Returns
424///
425/// - [`ValueResult::NullHandlePointer`] if the provided handle is null.
426/// - [`ValueResult::None`] if the key was not found in the reconstructed view.
427/// - [`ValueResult::Some`] if the key was found with the associated value.
428/// - [`ValueResult::Err`] if an error occurred while retrieving the value.
429///
430/// # Safety
431///
432/// The caller must:
433/// * ensure that `handle` is a valid pointer to a [`ReconstructedHandle`].
434/// * ensure that `key` is valid for [`BorrowedBytes`].
435/// * call [`fwd_free_owned_bytes`] to free the memory associated with the [`OwnedBytes`]
436///   returned in the result.
437#[unsafe(no_mangle)]
438pub extern "C" fn fwd_get_from_reconstructed(
439    handle: Option<&ReconstructedHandle<'_>>,
440    key: BorrowedBytes,
441) -> ValueResult {
442    invoke_with_handle(handle, move |handle| handle.val(key))
443}
444
445/// Puts the given key-value pairs into the database.
446///
447/// # Arguments
448///
449/// * `db` - The database handle returned by [`fwd_open_db`]
450/// * `values` - A [`BorrowedBatchOps`] containing the batch operations to apply.
451///
452/// # Returns
453///
454/// - [`HashResult::NullHandlePointer`] if the provided database handle is null.
455/// - [`HashResult::None`] if the commit resulted in an empty database.
456/// - [`HashResult::Some`] if the commit was successful, containing the new root hash.
457/// - [`HashResult::Err`] if an error occurred while committing the batch.
458///
459/// # Safety
460///
461/// The caller must:
462/// * ensure that `db` is a valid pointer to a [`DatabaseHandle`]
463/// * ensure that `values` is valid for [`BorrowedBatchOps`]
464/// * call [`fwd_free_owned_bytes`] to free the memory associated with the
465///   returned error ([`HashKey`] does not need to be freed as it is returned by
466///   value).
467#[unsafe(no_mangle)]
468pub extern "C" fn fwd_batch(
469    db: Option<&DatabaseHandle>,
470    values: BorrowedBatchOps<'_>,
471) -> HashResult {
472    #[cfg(feature = "block-replay")]
473    if db.is_some() {
474        replay::record_batch(values);
475    }
476
477    invoke_with_handle(db, move |db| db.create_batch(values))
478}
479
480/// Proposes a batch of operations to the database.
481///
482/// # Arguments
483///
484/// * `db` - The database handle returned by [`fwd_open_db`]
485/// * `values` - A [`BorrowedBatchOps`] containing the batch operations to apply.
486///
487/// # Returns
488///
489/// - [`ProposalResult::NullHandlePointer`] if the provided database handle is null.
490/// - [`ProposalResult::Ok`] if the proposal was created, with the proposal handle
491///   and calculated root hash.
492/// - [`ProposalResult::Err`] if an error occurred while creating the proposal.
493///
494/// # Safety
495///
496/// The caller must:
497/// * ensure that `db` is a valid pointer to a [`DatabaseHandle`]
498/// * ensure that `values` is valid for [`BorrowedBatchOps`]
499/// * call [`fwd_commit_proposal`] or [`fwd_free_proposal`] to free the memory
500///   associated with the proposal. And, the caller must ensure this is done
501///   before calling [`fwd_close_db`] to avoid memory leaks or undefined behavior.
502#[unsafe(no_mangle)]
503pub extern "C" fn fwd_propose_on_db<'db>(
504    db: Option<&'db DatabaseHandle>,
505    values: BorrowedBatchOps<'_>,
506) -> ProposalResult<'db> {
507    let result = invoke_with_handle(db, move |db| db.create_proposal_handle(values));
508
509    #[cfg(feature = "block-replay")]
510    if db.is_some() {
511        replay::record_propose_on_db(&result, values);
512    }
513
514    result
515}
516
517/// Proposes a batch of operations to the database on top of an existing proposal.
518///
519/// # Arguments
520///
521/// * `handle` - The proposal handle returned by [`fwd_propose_on_db`] or
522///   [`fwd_propose_on_proposal`].
523/// * `values` - A [`BorrowedBatchOps`] containing the batch operations to apply.
524///
525/// # Returns
526///
527/// - [`ProposalResult::NullHandlePointer`] if the provided database handle is null.
528/// - [`ProposalResult::Ok`] if the proposal was created, with the proposal handle
529///   and calculated root hash.
530/// - [`ProposalResult::Err`] if an error occurred while creating the proposal.
531///
532/// # Safety
533///
534/// The caller must:
535/// * ensure that `handle` is a valid pointer to a [`ProposalHandle`]
536/// * ensure that `values` is valid for [`BorrowedBatchOps`]
537/// * call [`fwd_commit_proposal`] or [`fwd_free_proposal`] to free the memory
538///   associated with the proposal. And, the caller must ensure this is done
539///   before calling [`fwd_close_db`] to avoid memory leaks or undefined behavior.
540#[unsafe(no_mangle)]
541pub extern "C" fn fwd_propose_on_proposal<'db>(
542    handle: Option<&ProposalHandle<'db>>,
543    values: BorrowedBatchOps<'_>,
544) -> ProposalResult<'db> {
545    let result = invoke_with_handle(handle, move |p| p.create_proposal_handle(values));
546
547    #[cfg(feature = "block-replay")]
548    replay::record_propose_on_proposal(handle, &result, values);
549
550    result
551}
552
553/// Reconstructs a batch of operations on top of a historical revision.
554///
555/// # Arguments
556///
557/// * `handle` - The revision handle returned by [`fwd_get_revision`].
558/// * `values` - A [`BorrowedBatchOps`] containing the batch operations to apply.
559///
560/// # Returns
561///
562/// - [`ReconstructedResult::NullHandlePointer`] if the provided handle is null.
563/// - [`ReconstructedResult::Ok`] if reconstruction succeeded, with a [`ReconstructedHandle`].
564/// - [`ReconstructedResult::Err`] if reconstruction failed (e.g., the revision is not historical).
565///
566/// # Safety
567///
568/// The caller must:
569/// * ensure that `handle` is a valid pointer to a [`RevisionHandle`].
570/// * ensure that `values` is valid for [`BorrowedBatchOps`].
571/// * call [`fwd_free_reconstructed`] to free the returned handle when it is no longer needed.
572/// * ensure that the underlying [`DatabaseHandle`] remains valid (not closed via [`fwd_close_db`])
573///   for as long as the returned [`ReconstructedHandle`] is in use.
574#[unsafe(no_mangle)]
575pub extern "C" fn fwd_reconstruct_on_revision<'db>(
576    handle: Option<&'db RevisionHandle<'db>>,
577    values: BorrowedBatchOps<'_>,
578) -> ReconstructedResult<'db> {
579    invoke_with_handle(handle, move |h| h.reconstruct(values))
580}
581
582/// Reconstructs a batch of operations on top of an existing reconstructed view.
583///
584/// This function consumes the previous reconstructed handle.
585///
586/// # Arguments
587///
588/// * `handle` - The reconstructed handle returned by a previous call to
589///   [`fwd_reconstruct_on_revision`] or [`fwd_reconstruct_on_reconstructed`].
590/// * `values` - A [`BorrowedBatchOps`] containing the batch operations to apply.
591///
592/// # Returns
593///
594/// - [`ReconstructedResult::NullHandlePointer`] if the provided handle is null.
595/// - [`ReconstructedResult::Ok`] if reconstruction succeeded, with a new [`ReconstructedHandle`].
596/// - [`ReconstructedResult::Err`] if reconstruction failed.
597///
598/// # Safety
599///
600/// The caller must:
601/// * ensure that `handle` is a valid pointer to a [`ReconstructedHandle`].
602/// * ensure that `values` is valid for [`BorrowedBatchOps`].
603/// * call [`fwd_free_reconstructed`] to free the returned handle when it is no longer needed.
604/// * ensure that the underlying [`DatabaseHandle`] remains valid (not closed via [`fwd_close_db`])
605///   for as long as the returned [`ReconstructedHandle`] is in use.
606/// * not use the consumed `handle` after this call.
607#[unsafe(no_mangle)]
608pub extern "C" fn fwd_reconstruct_on_reconstructed<'db>(
609    handle: Option<Box<ReconstructedHandle<'db>>>,
610    values: BorrowedBatchOps<'_>,
611) -> ReconstructedResult<'db> {
612    invoke_with_handle(handle, move |h| h.reconstruct(values))
613}
614
615/// Commits a proposal to the database.
616///
617/// This function will consume the proposal regardless of whether the commit
618/// is successful.
619///
620/// # Arguments
621///
622/// * `handle` - The proposal handle returned by [`fwd_propose_on_db`] or
623///   [`fwd_propose_on_proposal`].
624///
625/// # Returns
626///
627/// # Returns
628///
629/// - [`HashResult::NullHandlePointer`] if the provided database handle is null.
630/// - [`HashResult::None`] if the commit resulted in an empty database.
631/// - [`HashResult::Some`] if the commit was successful, containing the new root hash.
632/// - [`HashResult::Err`] if an error occurred while committing the batch.
633///
634/// # Safety
635///
636/// The caller must:
637/// * ensure that `handle` is a valid pointer to a [`ProposalHandle`]
638/// * ensure that `handle` is not used again after this function is called.
639/// * call [`fwd_free_owned_bytes`] to free the memory associated with the
640///   returned error ([`HashKey`] does not need to be freed as it is returned
641///   by value).
642#[unsafe(no_mangle)]
643pub extern "C" fn fwd_commit_proposal(proposal: Option<Box<ProposalHandle<'_>>>) -> HashResult {
644    #[cfg(feature = "block-replay")]
645    let proposal_ptr = proposal.as_ref().map(|h| std::ptr::from_ref(&**h));
646
647    let result = invoke_with_handle(proposal, move |proposal| proposal.commit_proposal());
648
649    #[cfg(feature = "block-replay")]
650    replay::record_commit(proposal_ptr, &result);
651
652    result
653}
654
655/// Consumes the [`ProposalHandle`], cancels the proposal, and frees the memory.
656///
657/// # Arguments
658///
659/// * `proposal` - A pointer to a [`ProposalHandle`] previously returned from a
660///   function from this library.
661///
662/// # Returns
663///
664/// - [`VoidResult::NullHandlePointer`] if the provided proposal handle is null.
665/// - [`VoidResult::Ok`] if the proposal was successfully cancelled and freed.
666/// - [`VoidResult::Err`] if the process panics while freeing the memory.
667///
668/// # Safety
669///
670/// The caller must ensure that the `proposal` is not null and that it points to
671/// a valid [`ProposalHandle`] previously returned by a function from this library.
672///
673/// The caller must ensure that the proposal was not committed. [`fwd_commit_proposal`]
674/// will consume the proposal automatically.
675#[unsafe(no_mangle)]
676pub extern "C" fn fwd_free_proposal(proposal: Option<Box<ProposalHandle<'_>>>) -> VoidResult {
677    invoke_with_handle(proposal, drop)
678}
679
680/// Consumes the [`ReconstructedHandle`] and frees the memory associated with it.
681///
682/// # Arguments
683///
684/// * `reconstructed` - A pointer to a [`ReconstructedHandle`] previously returned by
685///   [`fwd_reconstruct_on_revision`] or [`fwd_reconstruct_on_reconstructed`].
686///
687/// # Returns
688///
689/// - [`VoidResult::NullHandlePointer`] if the provided handle is null.
690/// - [`VoidResult::Ok`] if the handle was successfully freed.
691/// - [`VoidResult::Err`] if the process panics while freeing the memory.
692///
693/// # Safety
694///
695/// The caller must:
696/// * ensure that `reconstructed` is a valid pointer to a [`ReconstructedHandle`].
697/// * not use `reconstructed` after this function is called.
698/// * free all reconstructed handles before closing the database with [`fwd_close_db`].
699#[unsafe(no_mangle)]
700pub extern "C" fn fwd_free_reconstructed(
701    reconstructed: Option<Box<ReconstructedHandle<'_>>>,
702) -> VoidResult {
703    invoke_with_handle(reconstructed, drop)
704}
705
706/// Get the root hash of the reconstructed view.
707///
708/// # Arguments
709///
710/// * `reconstructed` - The reconstructed handle returned by reconstruction APIs.
711///
712/// # Returns
713///
714/// - [`HashResult::NullHandlePointer`] if the provided handle is null.
715/// - [`HashResult::None`] if the reconstructed view is empty.
716/// - [`HashResult::Some`] with the root hash of the reconstructed view.
717///
718/// # Safety
719///
720/// * ensure that `reconstructed` is a valid pointer to a [`ReconstructedHandle`]
721/// * call [`fwd_free_owned_bytes`] to free the memory associated with the
722///   returned error ([`HashKey`] does not need to be freed as it is returned
723///   by value).
724#[unsafe(no_mangle)]
725pub extern "C" fn fwd_reconstructed_root_hash(
726    reconstructed: Option<&ReconstructedHandle<'_>>,
727) -> HashResult {
728    invoke_with_handle(reconstructed, firewood::api::DbView::root_hash)
729}
730
731/// Get the root hash of the latest version of the database
732///
733/// # Argument
734///
735/// * `db` - The database handle returned by [`fwd_open_db`]
736///
737/// # Returns
738///
739/// - [`HashResult::NullHandlePointer`] if the provided database handle is null.
740/// - [`HashResult::None`] if the database is empty.
741/// - [`HashResult::Some`] with the root hash of the database.
742///
743/// # Safety
744///
745/// * ensure that `db` is a valid pointer to a [`DatabaseHandle`]
746/// * call [`fwd_free_owned_bytes`] to free the memory associated with the
747///   returned error ([`HashKey`] does not need to be freed as it is returned
748///   by value).
749#[unsafe(no_mangle)]
750pub extern "C" fn fwd_root_hash(db: Option<&DatabaseHandle>) -> HashResult {
751    invoke_with_handle(db, DatabaseHandle::current_root_hash)
752}
753
754/// Start metrics recorder for this process.
755///
756/// # Returns
757///
758/// - [`VoidResult::Ok`] if the recorder was initialized.
759/// - [`VoidResult::Err`] if an error occurs during initialization.
760#[unsafe(no_mangle)]
761pub extern "C" fn fwd_start_metrics() -> VoidResult {
762    invoke(metrics::setup_metrics)
763}
764
765/// Start metrics recorder and exporter for this process.
766///
767/// # Arguments
768///
769/// * `metrics_port` - the port where metrics will be exposed at
770///
771/// # Returns
772///
773/// - [`VoidResult::Ok`] if the recorder was initialized.
774/// - [`VoidResult::Err`] if an error occurs during initialization.
775///
776/// # Safety
777///
778/// The caller must:
779/// * call [`fwd_free_owned_bytes`] to free the memory associated with the
780///   returned error (if any).
781#[unsafe(no_mangle)]
782pub extern "C" fn fwd_start_metrics_with_exporter(metrics_port: u16) -> VoidResult {
783    invoke(move || metrics::setup_metrics_with_exporter(metrics_port))
784}
785
786/// Gather latest metrics for this process.
787///
788/// # Returns
789///
790/// - [`ValueResult::None`] if the gathered metrics resulted in an empty string.
791/// - [`ValueResult::Some`] the gathered metrics as an [`OwnedBytes`] (with
792///   guaranteed to be utf-8 data, not null terminated).
793/// - [`ValueResult::Err`] if an error occurred while retrieving the value.
794///
795/// # Safety
796///
797/// The caller must:
798/// * call [`fwd_free_owned_bytes`] to free the memory associated with the
799///   returned error or value.
800#[unsafe(no_mangle)]
801pub extern "C" fn fwd_gather() -> ValueResult {
802    invoke(metrics::gather_metrics)
803}
804
805/// Open a database with the given arguments.
806///
807/// # Arguments
808///
809/// See [`DatabaseHandleArgs`].
810///
811/// # Returns
812///
813/// - [`HandleResult::Ok`] with the database handle if successful.
814/// - [`HandleResult::Err`] if an error occurs while opening the database.
815///
816/// # Safety
817///
818/// The caller must:
819/// - ensure that the database is freed with [`fwd_close_db`] when no longer needed.
820/// - ensure that the database handle is freed only after freeing or committing
821///   all proposals created on it.
822#[unsafe(no_mangle)]
823pub extern "C" fn fwd_open_db(args: DatabaseHandleArgs) -> HandleResult {
824    invoke(move || DatabaseHandle::new(args))
825}
826
827/// Start logs for this process.
828///
829/// # Arguments
830///
831/// See [`LogArgs`].
832///
833/// # Returns
834///
835/// - [`VoidResult::Ok`] if the recorder was initialized.
836/// - [`VoidResult::Err`] if an error occurs during initialization.
837///
838/// # Safety
839///
840/// The caller must:
841/// * call [`fwd_free_owned_bytes`] to free the memory associated with the
842///   returned error (if any).
843#[unsafe(no_mangle)]
844pub extern "C" fn fwd_start_logs(args: LogArgs) -> VoidResult {
845    invoke(move || args.start_logging())
846}
847
848/// Close and free the memory for a database handle
849///
850/// This also stops the background persistence thread.
851///
852/// # Arguments
853///
854/// * `db` - The database handle to close, previously returned from a call to [`fwd_open_db`].
855///
856/// # Returns
857///
858/// - [`VoidResult::NullHandlePointer`] if the provided database handle is null.
859/// - [`VoidResult::Ok`] if the database handle was successfully closed and freed.
860/// - [`VoidResult::Err`] if the background persistence worker thread panics while
861///   closing the database handle or if the background persistence worker thread
862///   errored.
863///
864/// # Safety
865///
866/// Callers must ensure that:
867///
868/// - `db` is a valid pointer to a [`DatabaseHandle`] returned by [`fwd_open_db`].
869/// - There are no handles to any open proposals. If so, they must be freed first
870///   using [`fwd_free_proposal`].
871/// - Freeing the database handle does not free outstanding [`RevisionHandle`]s
872///   returned by [`fwd_get_revision`]. To prevent leaks, free them separately
873///   with [`fwd_free_revision`].
874/// - The database handle is not used after this function is called.
875#[unsafe(no_mangle)]
876pub extern "C" fn fwd_close_db(db: Option<Box<DatabaseHandle>>) -> VoidResult {
877    #[cfg(feature = "block-replay")]
878    let _ = replay::flush_to_disk();
879
880    invoke_with_handle(db, |db| db.close())
881}
882
883/// Flushes buffered block replay operations to disk.
884///
885/// This function is only meaningful when the `block-replay` feature is enabled
886/// and the `FIREWOOD_BLOCK_REPLAY_PATH` environment variable is set. Otherwise,
887/// it is a no-op.
888///
889/// # Returns
890///
891/// - [`VoidResult::Ok`] if the flush succeeded or was a no-op.
892/// - [`VoidResult::Err`] if an I/O error occurred during the flush.
893#[unsafe(no_mangle)]
894#[allow(clippy::missing_const_for_fn)] // Can't be const when block-replay is enabled
895pub extern "C" fn fwd_block_replay_flush() -> VoidResult {
896    #[cfg(feature = "block-replay")]
897    {
898        invoke(replay::flush_to_disk)
899    }
900
901    #[cfg(not(feature = "block-replay"))]
902    {
903        VoidResult::Ok
904    }
905}
906
907/// Consumes the [`OwnedBytes`] and frees the memory associated with it.
908///
909/// # Arguments
910///
911/// * `bytes` - The [`OwnedBytes`] struct to free, previously returned from any
912///   function from this library.
913///
914/// # Returns
915///
916/// - [`VoidResult::Ok`] if the memory was successfully freed.
917/// - [`VoidResult::Err`] if the process panics while freeing the memory.
918///
919/// # Safety
920///
921/// The caller must ensure that the `bytes` struct is valid and that the memory
922/// it points to is uniquely owned by this object. However, if `bytes.ptr` is null,
923/// this function does nothing.
924#[unsafe(no_mangle)]
925pub extern "C" fn fwd_free_owned_bytes(bytes: OwnedBytes) -> VoidResult {
926    invoke(move || drop(bytes))
927}
928
929/// Consumes the [`OwnedKeyValueBatch`] and frees the memory associated with it.
930///
931/// # Arguments
932///
933/// * `batch` - The [`OwnedKeyValueBatch`] struct to free, previously returned from any
934///   function from this library.
935///
936/// # Returns
937///
938/// - [`VoidResult::Ok`] if the memory was successfully freed.
939/// - [`VoidResult::Err`] if the process panics while freeing the memory.
940///
941/// # Safety
942///
943/// The caller must ensure that the `batch` struct is valid and that the memory
944/// it points to is uniquely owned by this object. However, if `batch.ptr` is null,
945/// this function does nothing.
946#[unsafe(no_mangle)]
947pub extern "C" fn fwd_free_owned_key_value_batch(batch: OwnedKeyValueBatch) -> VoidResult {
948    invoke(move || drop(batch))
949}
950
951/// Consumes the [`OwnedKeyValuePair`] and frees the memory associated with it.
952///
953/// # Arguments
954///
955/// * `kv` - The [`OwnedKeyValuePair`] struct to free, previously returned from any
956///   function from this library.
957///
958/// # Returns
959///
960/// - [`VoidResult::Ok`] if the memory was successfully freed.
961/// - [`VoidResult::Err`] if the process panics while freeing the memory.
962///
963/// # Safety
964///
965/// The caller must ensure that the `kv` struct is valid.
966#[unsafe(no_mangle)]
967pub extern "C" fn fwd_free_owned_kv_pair(kv: OwnedKeyValuePair) -> VoidResult {
968    invoke(move || drop(kv))
969}
970
971/// Dumps the Trie structure of the latest revision of the database to a DOT
972/// (Graphviz) format string for debugging.
973///
974/// # Arguments
975///
976/// * `db` - The database handle returned by [`fwd_open_db`]
977///
978/// # Returns
979///
980/// - [`ValueResult::NullHandlePointer`] if the provided database handle is null.
981/// - [`ValueResult::Some`] with the DOT format string if successful (the data is
982///   guaranteed to be utf-8 data, not null terminated).
983/// - [`ValueResult::Err`] if an error occurred while dumping the database.
984///
985/// # Safety
986///
987/// The caller must:
988/// * ensure that `db` is a valid pointer to a [`DatabaseHandle`].
989/// * call [`fwd_free_owned_bytes`] to free the memory associated with the
990///   returned value.
991#[unsafe(no_mangle)]
992pub extern "C" fn fwd_db_dump(db: Option<&DatabaseHandle>) -> ValueResult {
993    invoke_with_handle(db, handle::DatabaseHandle::dump_to_string)
994}
995
996/// Dumps the Trie structure of a revision to a DOT (Graphviz) format string for debugging.
997///
998/// # Arguments
999///
1000/// * `revision` - A pointer to a [`RevisionHandle`] previously returned by
1001///   [`fwd_get_revision`].
1002///
1003/// # Returns
1004///
1005/// - [`ValueResult::NullHandlePointer`] if the provided revision handle is null.
1006/// - [`ValueResult::Some`] with the DOT format string if successful (the data is
1007///   guaranteed to be utf-8 data, not null terminated).
1008/// - [`ValueResult::Err`] if an error occurred while dumping the revision.
1009///
1010/// # Safety
1011///
1012/// The caller must:
1013/// * ensure that `revision` is a valid pointer to a [`RevisionHandle`].
1014/// * call [`fwd_free_owned_bytes`] to free the memory associated with the
1015///   returned value.
1016#[unsafe(no_mangle)]
1017pub extern "C" fn fwd_revision_dump(revision: Option<&RevisionHandle<'_>>) -> ValueResult {
1018    invoke_with_handle(revision, firewood::api::DbView::dump_to_string)
1019}
1020
1021/// Dumps the Trie structure of a proposal to a DOT (Graphviz) format string for debugging.
1022///
1023/// # Arguments
1024///
1025/// * `proposal` - The proposal handle returned by [`fwd_propose_on_db`] or
1026///   [`fwd_propose_on_proposal`].
1027///
1028/// # Returns
1029///
1030/// - [`ValueResult::NullHandlePointer`] if the provided proposal handle is null.
1031/// - [`ValueResult::Some`] with the DOT format string if successful (the data is
1032///   guaranteed to be utf-8 data, not null terminated).
1033/// - [`ValueResult::Err`] if an error occurred while dumping the proposal.
1034///
1035/// # Safety
1036///
1037/// The caller must:
1038/// * ensure that `proposal` is a valid pointer to a [`ProposalHandle`].
1039/// * call [`fwd_free_owned_bytes`] to free the memory associated with the
1040///   returned value.
1041#[unsafe(no_mangle)]
1042pub extern "C" fn fwd_proposal_dump(proposal: Option<&ProposalHandle>) -> ValueResult {
1043    invoke_with_handle(proposal, firewood::api::DbView::dump_to_string)
1044}
1045
1046/// Dumps the Trie structure of a reconstructed view to a DOT (Graphviz) format string
1047/// for debugging.
1048///
1049/// # Arguments
1050///
1051/// * `reconstructed` - The reconstructed handle returned by [`fwd_reconstruct_on_revision`]
1052///   or [`fwd_reconstruct_on_reconstructed`].
1053///
1054/// # Returns
1055///
1056/// - [`ValueResult::NullHandlePointer`] if the provided handle is null.
1057/// - [`ValueResult::Some`] with the DOT format string if successful (the data is
1058///   guaranteed to be utf-8 data, not null terminated).
1059/// - [`ValueResult::Err`] if an error occurred while dumping the reconstructed view.
1060///
1061/// # Safety
1062///
1063/// The caller must:
1064/// * ensure that `reconstructed` is a valid pointer to a [`ReconstructedHandle`].
1065/// * call [`fwd_free_owned_bytes`] to free the memory associated with the
1066///   returned value.
1067#[unsafe(no_mangle)]
1068pub extern "C" fn fwd_reconstructed_dump(
1069    reconstructed: Option<&ReconstructedHandle<'_>>,
1070) -> ValueResult {
1071    invoke_with_handle(reconstructed, firewood::api::DbView::dump_to_string)
1072}