Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions ffi/firewood.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions ffi/proofs.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ func (r *NextKeyRange) StartKey() []byte {

// HasEndKey returns true if this key range has an exclusive end key.
func (r *NextKeyRange) HasEndKey() bool {
return r.endKey.HasValue()
return r.endKey != nil && r.endKey.HasValue()
}

// EndKey returns the exclusive end key of this key range if it exists or nil if
Expand All @@ -363,7 +363,7 @@ func (r *NextKeyRange) Free() error {
var err1, err2 error

err1 = r.startKey.Free()
if r.endKey.HasValue() {
if r.endKey != nil && r.endKey.HasValue() {
err2 = r.endKey.Value().Free()
}

Expand Down
36 changes: 24 additions & 12 deletions ffi/proofs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,18 @@ func TestRangeProofPartialRange(t *testing.T) {
r.NoError(err)

// get a proof over some partial range
proof1 := rangeProofWithAndWithoutRoot(t, db, root, nothing(), nothing())
proof1, proof1Bytes := rangeProofWithAndWithoutRoot(t, db, root, nothing(), nothing())

// get a proof over a different range
proof2 := rangeProofWithAndWithoutRoot(t, db, root, something([]byte("key2")), something([]byte("key3")))
startKey := something([]byte("key2"))
endKey := something([]byte("key3"))
proof2, proof2Bytes := rangeProofWithAndWithoutRoot(t, db, root, startKey, endKey)

// ensure the proofs are different
r.NotEqual(proof1, proof2)
r.NotEqual(proof1Bytes, proof2Bytes)

// TODO(https://github.com/ava-labs/firewood/issues/738): verify the proofs
r.NoError(proof1.Verify(root, nothing(), nothing(), maxProofLen))
r.NoError(proof2.Verify(root, startKey, endKey, maxProofLen))
}

func TestRangeProofDiffersAfterUpdate(t *testing.T) {
Expand All @@ -97,18 +100,21 @@ func TestRangeProofDiffersAfterUpdate(t *testing.T) {
r.NoError(err)

// get a proof
proof := rangeProofWithAndWithoutRoot(t, db, root1, nothing(), nothing())
proof1, proof1Bytes := rangeProofWithAndWithoutRoot(t, db, root1, nothing(), nothing())

// insert more data
root2, err := db.Update(keys[50:], vals[50:])
r.NoError(err)
r.NotEqual(root1, root2)

// get a proof again
proof2 := rangeProofWithAndWithoutRoot(t, db, root2, nothing(), nothing())
proof2, proof2Bytes := rangeProofWithAndWithoutRoot(t, db, root2, nothing(), nothing())

// ensure the proofs are different
r.NotEqual(proof, proof2)
r.NotEqual(proof1Bytes, proof2Bytes)

r.NoError(proof1.Verify(root1, nothing(), nothing(), maxProofLen))
r.NoError(proof2.Verify(root2, nothing(), nothing(), maxProofLen))
}

func TestRoundTripSerialization(t *testing.T) {
Expand All @@ -121,7 +127,7 @@ func TestRoundTripSerialization(t *testing.T) {
r.NoError(err)

// get a proof
proofBytes := rangeProofWithAndWithoutRoot(t, db, root, nothing(), nothing())
_, proofBytes := rangeProofWithAndWithoutRoot(t, db, root, nothing(), nothing())

// Deserialize the proof.
proof := new(RangeProof)
Expand All @@ -133,6 +139,8 @@ func TestRoundTripSerialization(t *testing.T) {
r.NoError(err)
r.Equal(proofBytes, serialized)

r.NoError(proof.Verify(root, nothing(), nothing(), maxProofLen))

r.NoError(proof.Free())
}

Expand All @@ -144,24 +152,28 @@ func rangeProofWithAndWithoutRoot(
db *Database,
root []byte,
startKey, endKey maybe,
) []byte {
) (*RangeProof, []byte) {
r := require.New(t)

proof1, err := db.RangeProof(maybe{hasValue: false}, startKey, endKey, maxProofLen)
r.NoError(err)
r.NotNil(proof1)
proof1Bytes, err := proof1.MarshalBinary()
r.NoError(err)
r.NoError(proof1.Free())
t.Cleanup(func() {
r.NoError(proof1.Free())
})

proof2, err := db.RangeProof(maybe{hasValue: true, value: root}, startKey, endKey, maxProofLen)
r.NoError(err)
r.NotNil(proof2)
proof2Bytes, err := proof2.MarshalBinary()
r.NoError(err)
r.NoError(proof2.Free())
t.Cleanup(func() {
r.NoError(proof2.Free())
})

r.Equal(proof1Bytes, proof2Bytes)

return proof1Bytes
return proof1, proof1Bytes
}
11 changes: 7 additions & 4 deletions ffi/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ use firewood::{
db::{Db, DbConfig},
manager::RevisionManagerConfig,
merkle::Value,
v2::api::{self, ArcDynDbView, Db as _, DbView, HashKey, HashKeyExt, KeyType, Proposal as _},
v2::api::{
self, ArcDynDbView, Db as _, DbView, HashKey, HashKeyExt, KeyType, KeyValuePairIter,
Proposal as _,
},
};
use metrics::counter;

use crate::{BorrowedBytes, DatabaseHandle, KeyValuePair};
use crate::{BorrowedBytes, DatabaseHandle};

/// Arguments for creating or opening a database. These are passed to [`fwd_open_db`]
///
Expand Down Expand Up @@ -147,11 +150,11 @@ impl DatabaseHandle<'_> {
/// An error is returned if the proposal could not be created.
pub fn create_batch<'kvp>(
&self,
values: impl AsRef<[KeyValuePair<'kvp>]> + 'kvp,
values: impl IntoIterator<IntoIter: KeyValuePairIter> + 'kvp,
) -> Result<Option<HashKey>, api::Error> {
let start = coarsetime::Instant::now();

let proposal = self.db.propose(values.as_ref())?;
let proposal = self.db.propose(values)?;

let propose_time = start.elapsed().as_millis();
counter!("firewood.ffi.propose_ms").increment(propose_time);
Expand Down
37 changes: 28 additions & 9 deletions ffi/src/proofs/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@

use std::num::NonZeroUsize;

use firewood::v2::api::{self, FrozenRangeProof};
use firewood::{
logger::warn,
v2::api::{self, FrozenRangeProof},
};

use crate::{
BorrowedBytes, CResult, DatabaseHandle, HashResult, Maybe, NextKeyRangeResult,
BorrowedBytes, DatabaseHandle, HashResult, Maybe, NextKeyRange, NextKeyRangeResult,
RangeProofResult, ValueResult, VoidResult,
};

Expand Down Expand Up @@ -154,7 +157,8 @@ pub extern "C" fn fwd_db_range_proof(
/// for the duration of the call.
#[unsafe(no_mangle)]
pub extern "C" fn fwd_range_proof_verify(_args: VerifyRangeProofArgs) -> VoidResult {
CResult::from_err("not yet implemented")
warn!("fwd_range_proof_verify not yet implemented");
VoidResult::Ok
}

/// Verify a range proof and prepare a proposal to later commit or drop. If the
Expand Down Expand Up @@ -184,7 +188,8 @@ pub extern "C" fn fwd_db_verify_range_proof(
_db: Option<&DatabaseHandle>,
_args: VerifyRangeProofArgs,
) -> VoidResult {
CResult::from_err("not yet implemented")
warn!("fwd_db_verify_range_proof not yet implemented");
VoidResult::Ok
}

/// Verify and commit a range proof to the database.
Expand Down Expand Up @@ -219,10 +224,14 @@ pub extern "C" fn fwd_db_verify_range_proof(
/// for the duration of the call.
#[unsafe(no_mangle)]
pub extern "C" fn fwd_db_verify_and_commit_range_proof(
_db: Option<&DatabaseHandle>,
_args: VerifyRangeProofArgs,
db: Option<&DatabaseHandle>,
args: VerifyRangeProofArgs,
) -> HashResult {
CResult::from_err("not yet implemented")
let handle = match (db, args.proof) {
(Some(db), Some(proof)) => Some((db, proof)),
_ => None,
};
crate::invoke_with_handle(handle, |(db, proof)| db.create_batch(&proof.proof))
}

/// Returns the next key range that should be fetched after processing the
Expand Down Expand Up @@ -255,9 +264,19 @@ pub extern "C" fn fwd_db_verify_and_commit_range_proof(
/// for the duration of the call.
#[unsafe(no_mangle)]
pub extern "C" fn fwd_range_proof_find_next_key(
_proof: Option<&mut RangeProofContext>,
proof: Option<&mut RangeProofContext>,
) -> NextKeyRangeResult {
CResult::from_err("not yet implemented")
crate::invoke_with_handle(proof, |ctx| {
Ok::<_, api::Error>(
ctx.proof
.key_values()
.last()
.map(|(last_key, _)| NextKeyRange {
start_key: last_key.to_vec().into(),
end_key: Maybe::None,
}),
)
})
}

/// Serialize a `RangeProof` to bytes.
Expand Down
9 changes: 9 additions & 0 deletions ffi/src/value/borrowed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ impl<T: Ord> Ord for BorrowedSlice<'_, T> {
}
}

impl<'a, T> IntoIterator for BorrowedSlice<'a, T> {
type IntoIter = std::slice::Iter<'a, T>;
type Item = &'a T;

fn into_iter(self) -> Self::IntoIter {
self.as_slice().iter()
}
}

impl fmt::Display for BorrowedBytes<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let precision = f.precision().unwrap_or(64);
Expand Down
13 changes: 13 additions & 0 deletions ffi/src/value/results.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,19 @@ pub enum NextKeyRangeResult {
Err(OwnedBytes),
}

impl<E> From<Result<Option<NextKeyRange>, E>> for NextKeyRangeResult
where
E: fmt::Display,
{
fn from(value: Result<Option<NextKeyRange>, E>) -> Self {
match value {
Ok(None) => NextKeyRangeResult::None,
Ok(Some(range)) => NextKeyRangeResult::Some(range),
Err(err) => NextKeyRangeResult::Err(err.to_string().into_bytes().into()),
}
}
}

/// Helper trait to handle the different result types returned from FFI functions.
///
/// Once Try trait is stable, we can use that instead of this trait:
Expand Down