diff --git a/ffi/firewood.h b/ffi/firewood.h index d5f1aebf5..69059aa0a 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -59,8 +59,9 @@ typedef struct ProposalHandle ProposalHandle; /** * FFI context for a proposed change proof. It is created from calling `propose` - * on a `VerifiedChangeProofContext` and stores the database and proposal handle. - * Calling `commit` on it will consume the proof. + * on a `VerifiedChangeProofContext` and stores the database, proposal handle, + * and other parameters need to implement `find_next_key`. Calling `commit` on it + * will consume the proof, but `find_next_key` can still be called on it. */ typedef struct ProposedChangeProofContext ProposedChangeProofContext; @@ -573,6 +574,10 @@ typedef struct CreateChangeProofArgs { uint32_t max_length; } CreateChangeProofArgs; +typedef struct CommittedChangeProofArgs { + struct ProposedChangeProofContext *proof; +} CommittedChangeProofArgs; + enum ProposedChangeProofResult_Tag { /** * The caller provided a null pointer to the input handle. @@ -704,44 +709,6 @@ typedef struct CreateRangeProofArgs { uint32_t max_length; } CreateRangeProofArgs; -/** - * Arguments for verifying a change proof. - */ -typedef struct VerifyChangeProofArgs { - /** - * The change proof to verify. If null, the function will return - * [`VoidResult::NullHandlePointer`]. We need a mutable reference to - * update the validation context. - */ - struct ChangeProofContext *proof; - /** - * The root hash of the starting revision. This must match the starting - * root of the proof. - */ - struct HashKey start_root; - /** - * The root hash of the ending revision. This must match the ending root of - * the proof. - */ - struct HashKey end_root; - /** - * The lower bound of the key range that the proof is expected to cover. If - * `None`, the proof is expected to cover from the start of the keyspace. - */ - struct Maybe_BorrowedBytes start_key; - /** - * The upper bound of the key range that the proof is expected to cover. If - * `None`, the proof is expected to cover to the end of the keyspace. - */ - struct Maybe_BorrowedBytes end_key; - /** - * The maximum number of key/value pairs that the proof is expected to cover. - * If the proof contains more items than this, it is considered invalid. If - * `0`, there is no limit. - */ - uint32_t max_length; -} VerifyChangeProofArgs; - /** * Arguments for verifying a range proof. */ @@ -1257,6 +1224,44 @@ typedef struct VerifiedChangeProofResult { }; } VerifiedChangeProofResult; +/** + * Arguments for verifying a change proof. + */ +typedef struct VerifyChangeProofArgs { + /** + * The change proof to verify. If null, the function will return + * [`VoidResult::NullHandlePointer`]. We need a mutable reference to + * update the validation context. + */ + struct ChangeProofContext *proof; + /** + * The root hash of the starting revision. This must match the starting + * root of the proof. + */ + struct HashKey start_root; + /** + * The root hash of the ending revision. This must match the ending root of + * the proof. + */ + struct HashKey end_root; + /** + * The lower bound of the key range that the proof is expected to cover. If + * `None`, the proof is expected to cover from the start of the keyspace. + */ + struct Maybe_BorrowedBytes start_key; + /** + * The upper bound of the key range that the proof is expected to cover. If + * `None`, the proof is expected to cover to the end of the keyspace. + */ + struct Maybe_BorrowedBytes end_key; + /** + * The maximum number of key/value pairs that the proof is expected to cover. + * If the proof contains more items than this, it is considered invalid. If + * `0`, there is no limit. + */ + uint32_t max_length; +} VerifyChangeProofArgs; + /** * Puts the given key-value pairs into the database. * @@ -1301,10 +1306,6 @@ struct VoidResult fwd_block_replay_flush(void); * Returns the next key range that should be fetched after processing the * current set of operations in a change proof that was truncated. * - * Can be called multiple times to get subsequent disjoint key ranges until - * it returns [`NextKeyRangeResult::None`], indicating there are no more keys to - * fetch and the proof is complete. - * * # Arguments * * - `proof` - A [`ChangeProofContext`] previously returned from the create @@ -1314,7 +1315,8 @@ struct VoidResult fwd_block_replay_flush(void); * * - [`NextKeyRangeResult::NullHandlePointer`] if the caller provided a null pointer. * - [`NextKeyRangeResult::NotPrepared`] if the proof has not been prepared into - * a proposal nor committed to the database. + * a proposal nor committed to the database. Should not be possible for a change + * proof due to its different interface compared to range proofs. * - [`NextKeyRangeResult::None`] if there are no more keys to fetch. * - [`NextKeyRangeResult::Some`] containing the next key range to fetch. * - [`NextKeyRangeResult::Err`] containing an error message if the next key range @@ -1327,7 +1329,7 @@ struct VoidResult fwd_block_replay_flush(void); * concurrently. The caller must ensure exclusive access to the proof context * for the duration of the call. */ -struct NextKeyRangeResult fwd_change_proof_find_next_key(struct ChangeProofContext *_proof); +struct NextKeyRangeResult fwd_change_proof_find_next_key_proposed(struct ProposedChangeProofContext *proof); /** * Deserialize a `ChangeProof` from bytes. @@ -1491,6 +1493,29 @@ struct HashResult fwd_commit_proposal(struct ProposalHandle *proposal); struct ChangeProofResult fwd_db_change_proof(const struct DatabaseHandle *db, struct CreateChangeProofArgs args); +/** + * Commit a change proof to the database. + * + * # Arguments + * + * - `args` - The arguments for verifying the change proof, which is just a `ProposedChangeProofContext`. + * + * # Returns + * + * - [`HashResult::NullHandlePointer`] if the caller provided a null pointer to the proof. + * - [`HashResult::None`] if the proof resulted in an empty database (i.e., all keys were deleted). + * - [`HashResult::Some`] containing the new root hash + * - [`HashResult::Err`] containing an error message if the proof could not be committed. + * + * # Thread Safety + * + * It is not safe to call this function concurrently with the same proof context + * nor is it safe to call any other function that accesses the same proof context + * concurrently. The caller must ensure exclusive access to the proof context + * for the duration of the call. + */ +struct HashResult fwd_db_commit_change_proof(struct CommittedChangeProofArgs args); + /** * Dumps the Trie structure of the latest revision of the database to a DOT * (Graphviz) format string for debugging. @@ -1560,40 +1585,6 @@ struct ProposedChangeProofResult fwd_db_propose_change_proof(const struct Databa struct RangeProofResult fwd_db_range_proof(const struct DatabaseHandle *db, struct CreateRangeProofArgs args); -/** - * Verify and commit a change proof to the database. - * - * If the proof has already been verified, the previously prepared proposal will be - * committed instead of re-verifying. If the proof has not been verified, it will be - * verified now. If the prepared proposal is no longer valid (e.g., the database has - * changed since it was prepared), a new proposal will be created and committed. - * - * The proof context will be updated with additional information about the committed - * proof to allow for optimized introspection of the committed changes. - * - * # Arguments - * - * - `db` - The database to commit the changes to. - * - `args` - The arguments for verifying the change proof. - * - * # Returns - * - * - [`HashResult::NullHandlePointer`] if the caller provided a null pointer to either - * the database or the proof. - * - [`HashResult::None`] if the proof resulted in an empty database (i.e., all keys were deleted). - * - [`HashResult::Some`] containing the new root hash if the proof was successfully verified - * - [`HashResult::Err`] containing an error message if the proof could not be verified or committed. - * - * # Thread Safety - * - * It is not safe to call this function concurrently with the same proof context - * nor is it safe to call any other function that accesses the same proof context - * concurrently. The caller must ensure exclusive access to the proof context - * for the duration of the call. - */ -struct HashResult fwd_db_verify_and_commit_change_proof(const struct DatabaseHandle *_db, - struct VerifyChangeProofArgs _args); - /** * Verify and commit a range proof to the database. * diff --git a/ffi/proofs.go b/ffi/proofs.go index 7aeed10e4..fdc1cb10b 100644 --- a/ffi/proofs.go +++ b/ffi/proofs.go @@ -74,6 +74,7 @@ type VerifiedChangeProof struct { // ProposedChangeProof contains a proposal for the ChangeProof. type ProposedChangeProof struct { handle *C.ProposedChangeProofContext + db *Database } // NextKeyRange represents a range of keys to fetch from the database. The start @@ -140,9 +141,9 @@ func (p *RangeProof) Verify( return getErrorFromVoidResult(C.fwd_range_proof_verify(args)) } -// VerifyChangeProof verifies the provided change [proof] proves the changes -// between [startRoot] and [endRoot] for keys in the range [startKey, endKey]. If -// the proof is valid, a proposal containing the changes is prepared. The +// VerifyRangeProof verifies the provided range range [proof] proves the values +// in the range [startKey, endKey] are included in the tree with the given +// [rootHash]. If the proof is valid, a proposal containing the values is prepared. The // call to [*Database.VerifyAndCommitRangeProof] will skip verification and commit the // prepared proposal. // @@ -390,9 +391,31 @@ func (db *Database) ProposeChangeProof( args := C.ProposedChangeProofArgs{ proof: proof.handle, } - return getProposedChangeProofFromProposedChangeProofResult(C.fwd_db_propose_change_proof(db.handle, args)) + return getProposedChangeProofFromProposedChangeProofResult(db, C.fwd_db_propose_change_proof(db.handle, args)) } +func (proof *ProposedChangeProof) CommitChangeProof() (Hash, error) { + proof.db.handleLock.RLock() + defer proof.db.handleLock.RUnlock() + if proof.db.handle == nil { + return EmptyRoot, errDBClosed + } + + var pinner runtime.Pinner + defer pinner.Unpin() + + args := C.CommittedChangeProofArgs{ + proof: proof.handle, + } + + return getHashKeyFromHashResult(C.fwd_db_commit_change_proof(args)) +} + +func (proof *ProposedChangeProof) FindNextKey() (*NextKeyRange, error) { + return getNextKeyRangeFromNextKeyRangeResult(C.fwd_change_proof_find_next_key_proposed(proof.handle)) +} + +/* // VerifyAndCommitChangeProof verifies the provided change [proof] proves the changes // between [startRoot] and [endRoot] for keys in the range [startKey, endKey]. If // the proof is valid, it is committed to the database and the new root hash is @@ -425,6 +448,7 @@ func (db *Database) VerifyAndCommitChangeProof( return getHashKeyFromHashResult(C.fwd_db_verify_and_commit_change_proof(db.handle, args)) } + // FindNextKey returns the next key range to fetch for this proof, if any. If the // proof has been fully processed, nil is returned. If an error occurs while // determining the next key range, that error is returned. @@ -434,6 +458,7 @@ func (db *Database) VerifyAndCommitChangeProof( func (p *ChangeProof) FindNextKey() (*NextKeyRange, error) { return getNextKeyRangeFromNextKeyRangeResult(C.fwd_change_proof_find_next_key(p.handle)) } +*/ // CodeHashes returns an iterator for the code hashes contained in the account nodes // of this proof. This list may contain duplicates and is not guaranteed to be in any particular order. @@ -664,13 +689,13 @@ func getVerifiedChangeProofFromVerifiedChangeProofResult(result C.VerifiedChange } } -func getProposedChangeProofFromProposedChangeProofResult(result C.ProposedChangeProofResult) (*ProposedChangeProof, error) { +func getProposedChangeProofFromProposedChangeProofResult(db *Database, result C.ProposedChangeProofResult) (*ProposedChangeProof, error) { switch result.tag { case C.ProposedChangeProofResult_NullHandlePointer: return nil, errDBClosed case C.ProposedChangeProofResult_Ok: ptr := *(**C.ProposedChangeProofContext)(unsafe.Pointer(&result.anon0)) - return &ProposedChangeProof{handle: ptr}, nil + return &ProposedChangeProof{handle: ptr, db: db}, nil case C.ProposedChangeProofResult_Err: err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() return nil, err diff --git a/ffi/proofs_test.go b/ffi/proofs_test.go index 8a4f0c814..dc1a03a21 100644 --- a/ffi/proofs_test.go +++ b/ffi/proofs_test.go @@ -15,6 +15,7 @@ const ( rangeProofLenUnbounded = 0 rangeProofLenTruncated = 10 changeProofLenUnbounded = 0 + changeProofLenTruncated = 10 ) type maybe struct { @@ -563,3 +564,192 @@ func TestVerifyEmptyChangeProofRange(t *testing.T) { r.NoError(err) t.Cleanup(func() { r.NoError(proposedChangeProof.Free()) }) } + +func TestVerifyAndCommitChangeProof(t *testing.T) { + r := require.New(t) + dbA := newTestDatabase(t) + dbB := newTestDatabase(t) + + // Insert some data. + keys, vals, batch := kvForTest(100) + root, err := dbA.Update(batch[:50]) + r.NoError(err) + _, err = dbB.Update(batch[:50]) + r.NoError(err) + + // Insert more data into dbA but not dbB. + rootAUpdated, err := dbA.Update(batch[50:]) + r.NoError(err) + + // Create a change proof from dbA. + changeProof, err := dbA.ChangeProof(root, rootAUpdated, nothing(), nothing(), changeProofLenUnbounded) + r.NoError(err) + t.Cleanup(func() { r.NoError(changeProof.Free()) }) + + // Verify the change proof. + verifiedChangeProof, err := changeProof.VerifyChangeProof(root, rootAUpdated, nothing(), nothing(), changeProofLenUnbounded) + r.NoError(err) + t.Cleanup(func() { r.NoError(verifiedChangeProof.Free()) }) + + // Propose change proof + proposedChangeProof, err := dbB.ProposeChangeProof(verifiedChangeProof) + r.NoError(err) + t.Cleanup(func() { r.NoError(proposedChangeProof.Free()) }) + + // Commit the proposal on dbB. + rootBUpdated, err := proposedChangeProof.CommitChangeProof() + r.NoError(err) + r.Equal(rootAUpdated, rootBUpdated) + + // Verify all keys are now in db2 + for i, key := range keys { + got, err := dbB.Get(key) + r.NoError(err, "Get key %d", i) + r.Equal(vals[i], got, "Value mismatch for key %d", i) + } +} + +func TestChangeProofFindNextKey(t *testing.T) { + r := require.New(t) + dbA := newTestDatabase(t) + dbB := newTestDatabase(t) + + // Insert first half of data in the first batch + _, _, batch := kvForTest(10000) + rootA, err := dbA.Update(batch[:5000]) + r.NoError(err) + + rootB, err := dbB.Update(batch[:5000]) + r.NoError(err) + + // Insert the rest in the second batch + rootAUpdated, err := dbA.Update(batch[5000:]) + r.NoError(err) + + proof, err := dbA.ChangeProof(rootA, rootAUpdated, nothing(), nothing(), changeProofLenTruncated) + r.NoError(err) + t.Cleanup(func() { r.NoError(proof.Free()) }) + + // Verify the change proof. + verifiedChangeProof, err := proof.VerifyChangeProof(rootB, rootAUpdated, nothing(), nothing(), changeProofLenTruncated) + r.NoError(err) + t.Cleanup(func() { r.NoError(verifiedChangeProof.Free()) }) + + // Propose change proof + proposedChangeProof, err := dbB.ProposeChangeProof(verifiedChangeProof) + r.NoError(err) + t.Cleanup(func() { r.NoError(proposedChangeProof.Free()) }) + + // FindNextKey is available after creating a proposal. + nextRange, err := proposedChangeProof.FindNextKey() + r.NoError(err) + r.NotNil(nextRange) + startKey := nextRange.StartKey() + r.NotEmpty(startKey) + r.NoError(nextRange.Free()) + + // Commit the proposal on dbB. + _, err = proposedChangeProof.CommitChangeProof() + r.NoError(err) + + // FindNextKey should still work after commit + nextRange, err = proposedChangeProof.FindNextKey() + r.NoError(err) + r.NotNil(nextRange) + r.Equal(nextRange.StartKey(), startKey) + r.NoError(nextRange.Free()) +} + +func TestMultiRoundChangeProof(t *testing.T) { + type TestStruct struct { + name string + hasDeletes bool + } + + tests := []TestStruct{ + {"Multi-round change proofs with no deletes", false}, + {"Multi-round change proofs With deletes", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := require.New(t) + dbA := newTestDatabase(t) + dbB := newTestDatabase(t) + + // Insert first half of data in the first batch + keys, vals, batch := kvForTest(100) + rootA, err := dbA.Update(batch[:50]) + r.NoError(err) + + rootB, err := dbB.Update(batch[:50]) + r.NoError(err) + + // Insert the rest in the second batch + rootAUpdated, err := dbA.Update(batch[50:]) + r.NoError(err) + + if tt.hasDeletes { + // Delete some of the keys. This will create Delete BatchOps in the + // change proof. + delKeys := make([]BatchOp, 20) + for i := range delKeys { + keyIdx := i * 2 + delKeys[i] = Delete(keys[keyIdx]) + keys[keyIdx] = nil + } + rootAUpdated, err = dbA.Update(delKeys) + r.NoError(err) + } + + // Create and commit multiple change proofs to update dbB to match dbA. + startKey := nothing() + + // Loop limit to help with debugging + for range 10 { + proof, err := dbA.ChangeProof(rootA, rootAUpdated, startKey, nothing(), changeProofLenTruncated) + r.NoError(err) + t.Cleanup(func() { r.NoError(proof.Free()) }) + + // Verify the proof + verifiedProof, err := proof.VerifyChangeProof(rootB, rootAUpdated, startKey, nothing(), changeProofLenTruncated) + r.NoError(err) + t.Cleanup(func() { r.NoError(verifiedProof.Free()) }) + + // Propose the proof + proposedProof, err := dbB.ProposeChangeProof(verifiedProof) + r.NoError(err) + t.Cleanup(func() { r.NoError(proposedProof.Free()) }) + + // Commit the proof + rootB, err = proposedProof.CommitChangeProof() + r.NoError(err) + + // Find the next start key + nextRange, err := proposedProof.FindNextKey() + r.NoError(err) + if nextRange == nil { + break + } + startKey = maybe{ + hasValue: true, + value: nextRange.StartKey(), + } + r.NoError(nextRange.Free()) + } + + // Verify that the root hashes match + r.Equal(rootAUpdated, rootB) + + // Verify all keys are now in dbB. Skip over any keys that has been deleted. + for i, key := range keys { + if key == nil { + continue + } + got, err := dbB.Get(key) + r.NoError(err, "Get key %d", i) + r.Equal(vals[i], got, "Value mismatch for %s", string(key)) + } + }) + } +} diff --git a/ffi/src/proofs/change.rs b/ffi/src/proofs/change.rs index c96f7d346..c0355d05a 100644 --- a/ffi/src/proofs/change.rs +++ b/ffi/src/proofs/change.rs @@ -3,6 +3,7 @@ use std::num::NonZeroUsize; +use firewood_metrics::firewood_increment; #[cfg(feature = "ethhash")] use firewood_storage::TrieHash; #[cfg(feature = "ethhash")] @@ -11,13 +12,13 @@ use rlp::Rlp; use firewood::{ ProofError, logger::warn, - v2::api::{self, FrozenChangeProof}, + v2::api::{self, DbView as _, FrozenChangeProof}, }; use std::cmp::Ordering; use crate::{ - BorrowedBytes, CResult, ChangeProofResult, DatabaseHandle, HashKey, HashResult, Maybe, + BorrowedBytes, ChangeProofResult, DatabaseHandle, HashKey, HashResult, KeyRange, Maybe, NextKeyRangeResult, OwnedBytes, ValueResult, VoidResult, results::{ProposedChangeProofResult, VerifiedChangeProofResult}, }; @@ -88,6 +89,13 @@ pub struct ProposedChangeProofArgs<'a> { pub proof: Option<&'a mut VerifiedChangeProofContext>, } +#[derive(Debug)] +#[repr(C)] +pub struct CommittedChangeProofArgs<'a> { + // The proposed change proof context that will be used to commit a proposal + pub proof: Option<&'a mut ProposedChangeProofContext<'a>>, +} + /// FFI context for a parsed or generated change proof. This change proof has not /// been verified. Calling `verify` on it will generate a `VerifiedChangeProofContext` /// and consume the `proof` and replacing it with None. @@ -183,27 +191,78 @@ impl VerifiedChangeProofContext { 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(std::convert::Into::into); Ok(ProposedChangeProofContext { - proof: Some(proof), + proof, db, - proposal: proposal.handle, + root_hash, + end_root: self.params.end_root, + end_key: self.params.end_key.clone(), + proposal: Some(proposal.handle), }) } } /// FFI context for a proposed change proof. It is created from calling `propose` -/// on a `VerifiedChangeProofContext` and stores the database and proposal handle. -/// Calling `commit` on it will consume the proof. +/// on a `VerifiedChangeProofContext` and stores the database, proposal handle, +/// and other parameters need to implement `find_next_key`. Calling `commit` on it +/// will consume the proof, but `find_next_key` can still be called on it. #[expect(unused)] #[derive(Debug)] pub struct ProposedChangeProofContext<'db> { - proof: Option, + proof: FrozenChangeProof, db: &'db DatabaseHandle, - proposal: crate::ProposalHandle<'db>, + root_hash: Option, + end_root: HashKey, + end_key: Option>, + proposal: Option>, +} + +impl<'db> ProposedChangeProofContext<'db> { + fn find_next_key(&mut self) -> Result, api::Error> { + let Some(last_op) = self.proof.batch_ops().last() else { + // no BatchOps in the proof, so we are done + return Ok(None); + }; + + if self.root_hash.as_ref() == Some(&self.end_root) { + // already at the target root, so we are done + return Ok(None); + } + + if self.proof.end_proof().is_empty() { + // unbounded, so we are done + return Ok(None); + } + + if let Some(ref end_key) = self.end_key + && **last_op.key() >= **end_key + { + // reached or exceeded the end key, so we are done + return Ok(None); + } + + Ok(Some((last_op.key().clone(), self.end_key.clone()))) + } + + /// Consumes proposal handle after being called once. + fn commit(&'db mut self) -> Result, api::Error> { + let Some(proposal_handle) = self.proposal.take() else { + return Err(api::Error::ProofError(ProofError::ProposalIsNone)); + }; + + let metrics_cb = |commit_time: coarsetime::Duration| { + firewood_increment!(crate::registry::COMMIT_MS, commit_time.as_millis()); + firewood_increment!(crate::registry::MERGE_COUNT, 1); + }; + + let result = proposal_handle.commit_proposal(metrics_cb); + let hash = result?.map(std::convert::Into::into); + Ok(hash) + } } /// FFI parameters for verifying a change proof -#[expect(unused)] #[derive(Debug)] struct VerificationParams { start_root: HashKey, @@ -412,28 +471,18 @@ pub extern "C" fn fwd_db_propose_change_proof<'db>( crate::invoke_with_handle(handle, |(db, ctx)| ctx.propose(db)) } -/// Verify and commit a change proof to the database. -/// -/// If the proof has already been verified, the previously prepared proposal will be -/// committed instead of re-verifying. If the proof has not been verified, it will be -/// verified now. If the prepared proposal is no longer valid (e.g., the database has -/// changed since it was prepared), a new proposal will be created and committed. -/// -/// The proof context will be updated with additional information about the committed -/// proof to allow for optimized introspection of the committed changes. +/// Commit a change proof to the database. /// /// # Arguments /// -/// - `db` - The database to commit the changes to. -/// - `args` - The arguments for verifying the change proof. +/// - `args` - The arguments for verifying the change proof, which is just a `ProposedChangeProofContext`. /// /// # Returns /// -/// - [`HashResult::NullHandlePointer`] if the caller provided a null pointer to either -/// the database or the proof. +/// - [`HashResult::NullHandlePointer`] if the caller provided a null pointer to the proof. /// - [`HashResult::None`] if the proof resulted in an empty database (i.e., all keys were deleted). -/// - [`HashResult::Some`] containing the new root hash if the proof was successfully verified -/// - [`HashResult::Err`] containing an error message if the proof could not be verified or committed. +/// - [`HashResult::Some`] containing the new root hash +/// - [`HashResult::Err`] containing an error message if the proof could not be committed. /// /// # Thread Safety /// @@ -442,20 +491,16 @@ pub extern "C" fn fwd_db_propose_change_proof<'db>( /// concurrently. The caller must ensure exclusive access to the proof context /// for the duration of the call. #[unsafe(no_mangle)] -pub extern "C" fn fwd_db_verify_and_commit_change_proof( - _db: Option<&DatabaseHandle>, - _args: VerifyChangeProofArgs, -) -> HashResult { - CResult::from_err("not yet implemented") +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(std::convert::Into::into)) + }) } /// Returns the next key range that should be fetched after processing the /// current set of operations in a change proof that was truncated. /// -/// Can be called multiple times to get subsequent disjoint key ranges until -/// it returns [`NextKeyRangeResult::None`], indicating there are no more keys to -/// fetch and the proof is complete. -/// /// # Arguments /// /// - `proof` - A [`ChangeProofContext`] previously returned from the create @@ -465,7 +510,8 @@ pub extern "C" fn fwd_db_verify_and_commit_change_proof( /// /// - [`NextKeyRangeResult::NullHandlePointer`] if the caller provided a null pointer. /// - [`NextKeyRangeResult::NotPrepared`] if the proof has not been prepared into -/// a proposal nor committed to the database. +/// a proposal nor committed to the database. Should not be possible for a change +/// proof due to its different interface compared to range proofs. /// - [`NextKeyRangeResult::None`] if there are no more keys to fetch. /// - [`NextKeyRangeResult::Some`] containing the next key range to fetch. /// - [`NextKeyRangeResult::Err`] containing an error message if the next key range @@ -478,10 +524,10 @@ pub extern "C" fn fwd_db_verify_and_commit_change_proof( /// concurrently. The caller must ensure exclusive access to the proof context /// for the duration of the call. #[unsafe(no_mangle)] -pub extern "C" fn fwd_change_proof_find_next_key( - _proof: Option<&mut ChangeProofContext>, +pub extern "C" fn fwd_change_proof_find_next_key_proposed( + proof: Option<&mut ProposedChangeProofContext>, ) -> NextKeyRangeResult { - CResult::from_err("not yet implemented") + crate::invoke_with_handle(proof, ProposedChangeProofContext::find_next_key) } /// Serialize a `ChangeProof` to bytes. diff --git a/firewood/src/proofs/types.rs b/firewood/src/proofs/types.rs index 89223df85..886411939 100644 --- a/firewood/src/proofs/types.rs +++ b/firewood/src/proofs/types.rs @@ -140,6 +140,9 @@ pub enum ProofError { #[error("the proof is None as it has been consumed")] ProofIsNone, + + #[error("the proposal for a change proof is None as it has been consumed")] + ProposalIsNone, } #[derive(Clone, PartialEq, Eq)]