use std::num::NonZeroUsize;
use firewood::{
ProofError,
api::{self, DbView, FrozenRangeProof, HashKey},
logger::warn,
};
use firewood_metrics::{MetricsContext, firewood_increment};
use crate::{
BorrowedBytes, CodeIteratorHandle, CodeIteratorResult, DatabaseHandle, HashResult, Maybe,
NextKeyRangeResult, RangeProofResult, ValueResult, VoidResult,
};
pub type KeyRange = (Box<[u8]>, Option<Box<[u8]>>);
#[derive(Debug)]
#[repr(C)]
pub struct CreateRangeProofArgs<'a> {
pub root: crate::HashKey,
pub start_key: Maybe<BorrowedBytes<'a>>,
pub end_key: Maybe<BorrowedBytes<'a>>,
pub max_length: u32,
}
#[derive(Debug)]
#[repr(C)]
pub struct VerifyRangeProofArgs<'a, 'db> {
pub proof: Option<&'a mut RangeProofContext<'db>>,
pub root: crate::HashKey,
pub start_key: Maybe<BorrowedBytes<'a>>,
pub end_key: Maybe<BorrowedBytes<'a>>,
pub max_length: u32,
}
#[derive(Debug)]
pub struct RangeProofContext<'db> {
proof: FrozenRangeProof,
verification: Option<VerificationContext>,
proposal_state: Option<ProposalState<'db>>,
}
#[derive(Debug)]
struct VerificationContext {
root: HashKey,
start_key: Option<Box<[u8]>>,
end_key: Option<Box<[u8]>>,
max_length: Option<NonZeroUsize>,
}
#[derive(Debug)]
enum ProposalState<'db> {
Proposed(crate::ProposalHandle<'db>),
Committed(Option<HashKey>),
}
impl From<FrozenRangeProof> for RangeProofContext<'_> {
fn from(proof: FrozenRangeProof) -> Self {
Self {
proof,
verification: None,
proposal_state: None,
}
}
}
impl<'db> RangeProofContext<'db> {
fn verify(
&mut self,
root: HashKey,
start_key: Option<&[u8]>,
end_key: Option<&[u8]>,
max_length: Option<NonZeroUsize>,
) -> Result<(), api::Error> {
if let Some(ref ctx) = self.verification {
if ctx.root == root
&& ctx.start_key.as_deref() == start_key
&& ctx.end_key.as_deref() == end_key
&& ctx.max_length == max_length
{
return Ok(());
}
return Err(api::Error::ProofError(ProofError::ValueMismatch));
}
debug_assert!(self.verification.is_none());
warn!("range proof verification not yet implemented");
self.verification = Some(VerificationContext {
root,
start_key: start_key.map(Box::from),
end_key: end_key.map(Box::from),
max_length,
});
Ok(())
}
fn verify_and_propose(
&mut self,
db: &'db crate::DatabaseHandle,
root: HashKey,
start_key: Option<&[u8]>,
end_key: Option<&[u8]>,
max_length: Option<NonZeroUsize>,
) -> Result<(), api::Error> {
self.verify(root, start_key, end_key, max_length)?;
if self.proposal_state.is_some() {
return Ok(());
}
let proposal = db.merge_key_value_range(start_key, end_key, self.proof.key_values())?;
self.proposal_state = Some(ProposalState::Proposed(proposal.handle));
Ok(())
}
fn verify_and_commit(
&mut self,
db: &'db crate::DatabaseHandle,
root: HashKey,
start_key: Option<&[u8]>,
end_key: Option<&[u8]>,
max_length: Option<NonZeroUsize>,
) -> Result<Option<HashKey>, api::Error> {
self.verify(root, start_key, end_key, max_length)?;
let mut allow_rebase = true;
let proposal_handle = match self.proposal_state.take() {
Some(ProposalState::Committed(hash)) => {
self.proposal_state = Some(ProposalState::Committed(hash.clone()));
return Ok(hash);
}
Some(ProposalState::Proposed(proposal)) => proposal,
None => {
allow_rebase = false;
db.merge_key_value_range(start_key, end_key, self.proof.key_values())?
.handle
}
};
let result = proposal_handle.commit_proposal();
let result = if let Err(api::Error::ParentNotLatest { .. }) = result
&& allow_rebase
{
let proposal_handle = db
.merge_key_value_range(start_key, end_key, self.proof.key_values())?
.handle;
proposal_handle.commit_proposal()
} else {
result
};
let hash = result?;
firewood_increment!(crate::registry::MERGE_COUNT, 1);
self.proposal_state = Some(ProposalState::Committed(hash.clone()));
Ok(hash)
}
fn find_next_key(&mut self) -> Result<Option<KeyRange>, api::Error> {
let verification = self
.verification
.as_ref()
.ok_or(api::Error::ProofError(ProofError::Unverified))?;
let Some((last_key, _)) = self.proof.key_values().last() else {
return Ok(None);
};
let root_hash = match self.proposal_state {
Some(ProposalState::Committed(ref hash)) => Ok(hash.clone()),
Some(ProposalState::Proposed(ref proposal)) => Ok(proposal.root_hash()),
None => Err(api::Error::ProofError(ProofError::Unverified)),
}?;
if root_hash.as_ref() == Some(&verification.root) {
return Ok(None);
}
if self.proof.end_proof().is_empty() {
return Ok(None);
}
if let Some(ref end_key) = verification.end_key
&& **last_key >= **end_key
{
return Ok(None);
}
Ok(Some((last_key.clone(), verification.end_key.clone())))
}
fn code_hash_iter(&self) -> Result<CodeIteratorHandle<'_>, api::Error> {
CodeIteratorHandle::new(self.proof.key_values())
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_db_range_proof(
db: Option<&DatabaseHandle>,
args: CreateRangeProofArgs,
) -> RangeProofResult<'static> {
crate::invoke_with_handle(db, |db| {
let view = db.get_root(args.root.into())?;
view.range_proof(
args.start_key
.as_ref()
.map(BorrowedBytes::as_slice)
.into_option(),
args.end_key
.as_ref()
.map(BorrowedBytes::as_slice)
.into_option(),
NonZeroUsize::new(args.max_length as usize),
)
})
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_range_proof_verify(args: VerifyRangeProofArgs) -> VoidResult {
let VerifyRangeProofArgs {
proof,
root,
start_key,
end_key,
max_length,
} = args;
crate::invoke_with_handle(proof, |ctx| {
let start_key = start_key.into_option();
let end_key = end_key.into_option();
ctx.verify(
root.into(),
start_key.as_deref(),
end_key.as_deref(),
NonZeroUsize::new(max_length as usize),
)
})
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_db_verify_range_proof<'db>(
db: Option<&'db DatabaseHandle>,
args: VerifyRangeProofArgs<'_, 'db>,
) -> VoidResult {
let VerifyRangeProofArgs {
proof,
root,
start_key,
end_key,
max_length,
} = args;
let handle = db.and_then(|db| proof.map(|p| (db, p)));
crate::invoke_with_handle(handle, |(db, ctx)| {
let start_key = start_key.into_option();
let end_key = end_key.into_option();
ctx.verify_and_propose(
db,
root.into(),
start_key.as_deref(),
end_key.as_deref(),
NonZeroUsize::new(max_length as usize),
)
})
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_db_verify_and_commit_range_proof<'db>(
db: Option<&'db DatabaseHandle>,
args: VerifyRangeProofArgs<'_, 'db>,
) -> HashResult {
let VerifyRangeProofArgs {
proof,
root,
start_key,
end_key,
max_length,
} = args;
let handle = db.and_then(|db| proof.map(|p| (db, p)));
crate::invoke_with_handle(handle, |(db, ctx)| {
let start_key = start_key.into_option();
let end_key = end_key.into_option();
ctx.verify_and_commit(
db,
root.into(),
start_key.as_deref(),
end_key.as_deref(),
NonZeroUsize::new(max_length as usize),
)
})
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_range_proof_find_next_key(
proof: Option<&mut RangeProofContext>,
) -> NextKeyRangeResult {
crate::invoke_with_handle(proof, RangeProofContext::find_next_key)
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_range_proof_code_hash_iter<'a>(
proof: Option<&'a RangeProofContext>,
) -> CodeIteratorResult<'a> {
crate::invoke_with_handle(proof, RangeProofContext::code_hash_iter)
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_code_hash_iter_next<'a>(
iter: Option<&'a mut CodeIteratorHandle<'a>>,
) -> HashResult {
crate::invoke_with_handle(iter, CodeIteratorHandle::next)
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_code_hash_iter_free(iter: Option<Box<CodeIteratorHandle>>) -> VoidResult {
crate::invoke_with_handle(iter, drop)
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_range_proof_to_bytes(proof: Option<&RangeProofContext>) -> ValueResult {
crate::invoke_with_handle(proof, |ctx| {
let mut vec = Vec::new();
ctx.proof.write_to_vec(&mut vec);
vec
})
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_range_proof_from_bytes(
bytes: BorrowedBytes<'_>,
) -> RangeProofResult<'static> {
crate::invoke(move || {
FrozenRangeProof::from_slice(&bytes)
.map_err(|err| api::Error::ProofError(ProofError::Deserialization(err)))
})
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_free_range_proof(proof: Option<Box<RangeProofContext>>) -> VoidResult {
crate::invoke_with_handle(proof, drop)
}
impl crate::MetricsContextExt for RangeProofContext<'_> {
fn metrics_context(&self) -> Option<MetricsContext> {
None
}
}
impl<'a> crate::MetricsContextExt for (&'a DatabaseHandle, &mut RangeProofContext<'a>) {
fn metrics_context(&self) -> Option<MetricsContext> {
self.0.metrics_context()
}
}