use std::convert::Into;
use std::num::NonZeroUsize;
use firewood_metrics::firewood_increment;
#[cfg(feature = "ethhash")]
use firewood_storage::TrieHash;
#[cfg(feature = "ethhash")]
use rlp::Rlp;
use firewood::{
ProofError,
api::{self, DbView as _, FrozenChangeProof},
logger::warn,
};
use std::cmp::Ordering;
use crate::{
BorrowedBytes, ChangeProofResult, DatabaseHandle, HashKey, HashResult, KeyRange, Maybe,
NextKeyRangeResult, OwnedBytes, ValueResult, VoidResult,
results::{ProposedChangeProofResult, VerifiedChangeProofResult},
};
#[cfg(feature = "ethhash")]
const EMPTY_CODE_HASH: [u8; 32] = [
0xc5, 0xd2, 0x46, 0x01, 0x86, 0xf7, 0x23, 0x3c, 0x92, 0x7e, 0x7d, 0xb2, 0xdc, 0xc7, 0x03, 0xc0,
0xe5, 0x00, 0xb6, 0x53, 0xca, 0x82, 0x27, 0x3b, 0x7b, 0xfa, 0xd8, 0x04, 0x5d, 0x85, 0xa4, 0x70,
];
#[derive(Debug)]
#[repr(C)]
pub struct CreateChangeProofArgs<'a> {
pub start_root: HashKey,
pub end_root: HashKey,
pub start_key: Maybe<BorrowedBytes<'a>>,
pub end_key: Maybe<BorrowedBytes<'a>>,
pub max_length: u32,
}
#[derive(Debug)]
#[repr(C)]
pub struct VerifyChangeProofArgs<'a> {
pub proof: Option<&'a mut ChangeProofContext>,
pub start_root: HashKey,
pub end_root: HashKey,
pub start_key: Maybe<BorrowedBytes<'a>>,
pub end_key: Maybe<BorrowedBytes<'a>>,
pub max_length: u32,
}
#[derive(Debug)]
#[repr(C)]
pub struct ProposedChangeProofArgs<'a> {
pub proof: Option<&'a mut VerifiedChangeProofContext>,
}
#[derive(Debug)]
#[repr(C)]
pub struct CommittedChangeProofArgs<'a> {
pub proof: Option<&'a mut ProposedChangeProofContext<'a>>,
}
#[derive(Debug)]
pub struct ChangeProofContext {
proof: Option<FrozenChangeProof>,
}
impl From<FrozenChangeProof> for ChangeProofContext {
fn from(proof: FrozenChangeProof) -> Self {
Self { proof: Some(proof) }
}
}
impl ChangeProofContext {
fn verify(
&mut self,
params: VerificationParams,
) -> Result<VerifiedChangeProofContext, api::Error> {
let Some(proof) = self.proof.take() else {
return Err(api::Error::ProofError(ProofError::ProofIsNone));
};
let batch_ops = proof.batch_ops();
if let Some(max_length) = params.max_length
&& batch_ops.len() > max_length.into()
{
return Err(api::Error::ProofError(
ProofError::ProofIsLargerThanMaxLength,
));
}
if let (Some(start_key), Some(first_key)) = (¶ms.start_key, batch_ops.first())
&& start_key.cmp(first_key.key()) == Ordering::Greater
{
return Err(api::Error::ProofError(
ProofError::StartKeyLargerThanFirstKey,
));
}
if let (Some(end_key), Some(last_key)) = (¶ms.end_key, batch_ops.last())
&& end_key.cmp(last_key.key()) == Ordering::Less
{
return Err(api::Error::ProofError(ProofError::EndKeyLessThanLastKey));
}
if batch_ops
.iter()
.is_sorted_by(|a, b| b.key().cmp(a.key()) == Ordering::Greater)
{
warn!("change proof verification not yet implemented");
Ok(VerifiedChangeProofContext {
proof: Some(proof),
params,
})
} else {
Err(api::Error::ProofError(ProofError::ChangeProofKeysNotSorted))
}
}
}
#[derive(Debug)]
pub struct VerifiedChangeProofContext {
proof: Option<FrozenChangeProof>,
params: VerificationParams,
}
impl VerifiedChangeProofContext {
fn propose<'db>(
&'db mut self,
db: &'db DatabaseHandle,
) -> Result<ProposedChangeProofContext<'db>, api::Error> {
let Some(proof) = self.proof.take() else {
return Err(api::Error::ProofError(ProofError::ProofIsNone));
};
let proposal = db.apply_change_proof_to_parent(self.params.start_root.into(), &proof)?;
let root_hash = proposal.handle.root_hash().map(Into::into);
Ok(ProposedChangeProofContext {
proof,
db,
root_hash,
end_root: self.params.end_root,
end_key: self.params.end_key.clone(),
proposal: Some(proposal.handle),
})
}
}
#[expect(unused)]
#[derive(Debug)]
pub struct ProposedChangeProofContext<'db> {
proof: FrozenChangeProof,
db: &'db DatabaseHandle,
root_hash: Option<HashKey>,
end_root: HashKey,
end_key: Option<Box<[u8]>>,
proposal: Option<crate::ProposalHandle<'db>>,
}
impl<'db> ProposedChangeProofContext<'db> {
fn find_next_key(&mut self) -> Result<Option<KeyRange>, api::Error> {
let Some(last_op) = self.proof.batch_ops().last() else {
return Ok(None);
};
if self.proof.end_proof().is_empty() {
return Ok(None);
}
if let Some(ref end_key) = self.end_key
&& **last_op.key() >= **end_key
{
return Ok(None);
}
Ok(Some((last_op.key().clone(), self.end_key.clone())))
}
fn commit(&'db mut self) -> Result<Option<HashKey>, api::Error> {
let Some(proposal_handle) = self.proposal.take() else {
return Err(api::Error::ProofError(ProofError::ProposalIsNone));
};
let result = proposal_handle.commit_proposal();
let hash = result?.map(Into::into);
firewood_increment!(crate::registry::MERGE_COUNT, 1, "change" => "commit");
Ok(hash)
}
}
#[derive(Debug)]
struct VerificationParams {
start_root: HashKey,
end_root: HashKey,
start_key: Option<Box<[u8]>>,
end_key: Option<Box<[u8]>>,
max_length: Option<NonZeroUsize>,
}
#[derive(Debug)]
#[repr(C)]
pub struct NextKeyRange {
pub start_key: OwnedBytes,
pub end_key: Maybe<OwnedBytes>,
}
#[derive(Debug)]
#[non_exhaustive]
pub struct CodeIteratorHandle<'a> {
#[cfg(feature = "ethhash")]
inner: std::slice::Iter<'a, KeyValuePair>,
#[cfg(not(feature = "ethhash"))]
void: std::convert::Infallible,
#[cfg(not(feature = "ethhash"))]
marker: std::marker::PhantomData<&'a ()>,
}
type KeyValuePair = (Box<[u8]>, Box<[u8]>);
impl Iterator for CodeIteratorHandle<'_> {
type Item = Result<HashKey, api::Error>;
fn next(&mut self) -> Option<Self::Item> {
#[cfg(not(feature = "ethhash"))]
match self.void {}
#[cfg(feature = "ethhash")]
self.inner.find_map(|(key, value)| {
if key.len() != 32 {
return None;
}
let Ok(code_hash_slice) = Rlp::new(value).at(3).and_then(|r| r.data()) else {
return Some(Err(api::Error::ProofError(ProofError::InvalidValueFormat)));
};
let code_hash: HashKey = TrieHash::try_from(code_hash_slice).ok()?.into();
if code_hash == TrieHash::from(EMPTY_CODE_HASH).into() {
return None;
}
Some(Ok(code_hash))
})
}
}
impl<'a> CodeIteratorHandle<'a> {
#[cfg_attr(feature = "ethhash", allow(clippy::missing_const_for_fn))]
#[cfg_attr(not(feature = "ethhash"), allow(unused_variables))]
pub fn new(key_values: &'a [KeyValuePair]) -> Result<Self, api::Error> {
#[cfg(not(feature = "ethhash"))]
{
Err(api::Error::FeatureNotSupported(
"ethhash code hash iterator".to_string(),
))
}
#[cfg(feature = "ethhash")]
{
Ok(CodeIteratorHandle {
inner: key_values.iter(),
})
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_db_change_proof(
db: Option<&DatabaseHandle>,
args: CreateChangeProofArgs,
) -> ChangeProofResult {
crate::invoke_with_handle(db, |db| {
db.change_proof(
args.start_root.into(),
args.end_root.into(),
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_verify_change_proof(
args: VerifyChangeProofArgs,
) -> VerifiedChangeProofResult {
crate::invoke_with_handle(args.proof, |ctx| {
let context = VerificationParams {
start_root: args.start_root,
end_root: args.end_root,
start_key: args.start_key.into_option().as_deref().map(Box::from),
end_key: args.end_key.into_option().as_deref().map(Box::from),
max_length: NonZeroUsize::new(args.max_length as usize),
};
ctx.verify(context)
})
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_db_propose_change_proof<'db>(
db: Option<&'db DatabaseHandle>,
args: ProposedChangeProofArgs<'db>,
) -> ProposedChangeProofResult<'db> {
let handle = db.and_then(|db| args.proof.map(|p| (db, p)));
crate::invoke_with_handle(handle, |(db, ctx)| ctx.propose(db))
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_db_commit_change_proof(args: CommittedChangeProofArgs<'_>) -> HashResult {
crate::invoke_with_handle(args.proof, |ctx| {
ctx.commit().map(|hash_key| hash_key.map(Into::into))
})
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_change_proof_find_next_key_proposed(
proof: Option<&mut ProposedChangeProofContext>,
) -> NextKeyRangeResult {
crate::invoke_with_handle(proof, ProposedChangeProofContext::find_next_key)
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_change_proof_to_bytes(proof: Option<&ChangeProofContext>) -> ValueResult {
crate::invoke_with_handle(proof, |ctx| {
let mut vec = Vec::new();
if let Some(proof) = &ctx.proof {
proof.write_to_vec(&mut vec);
}
vec
})
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_change_proof_from_bytes(bytes: BorrowedBytes) -> ChangeProofResult {
crate::invoke(move || {
FrozenChangeProof::from_slice(&bytes)
.map_err(|err| api::Error::ProofError(ProofError::Deserialization(err)))
})
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_free_change_proof(proof: Option<Box<ChangeProofContext>>) -> VoidResult {
crate::invoke_with_handle(proof, drop)
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_free_verified_change_proof(
proof: Option<Box<VerifiedChangeProofContext>>,
) -> VoidResult {
crate::invoke_with_handle(proof, drop)
}
#[unsafe(no_mangle)]
pub extern "C" fn fwd_free_proposed_change_proof(
proof: Option<Box<ProposedChangeProofContext>>,
) -> VoidResult {
crate::invoke_with_handle(proof, drop)
}
impl crate::MetricsContextExt for ChangeProofContext {
fn metrics_context(&self) -> Option<firewood_metrics::MetricsContext> {
None
}
}
impl crate::MetricsContextExt for VerifiedChangeProofContext {
fn metrics_context(&self) -> Option<firewood_metrics::MetricsContext> {
None
}
}
impl crate::MetricsContextExt for ProposedChangeProofContext<'_> {
fn metrics_context(&self) -> Option<firewood_metrics::MetricsContext> {
None
}
}
impl crate::MetricsContextExt for (&DatabaseHandle, &mut ChangeProofContext) {
fn metrics_context(&self) -> Option<firewood_metrics::MetricsContext> {
self.0.metrics_context()
}
}
impl crate::MetricsContextExt for (&DatabaseHandle, &mut VerifiedChangeProofContext) {
fn metrics_context(&self) -> Option<firewood_metrics::MetricsContext> {
self.0.metrics_context()
}
}
impl crate::MetricsContextExt for CodeIteratorHandle<'_> {
fn metrics_context(&self) -> Option<firewood_metrics::MetricsContext> {
None
}
}