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
144 changes: 144 additions & 0 deletions x/firewood/proof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package firewood

import (
"runtime"

"github.com/ava-labs/firewood-go-ethhash/ffi"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/maybe"

xsync "github.com/ava-labs/avalanchego/x/sync"
)

var (
_ xsync.Marshaler[*RangeProof] = RangeProofMarshaler{}
_ xsync.Marshaler[*ChangeProof] = ChangeProofMarshaler{}
)

type RangeProofMarshaler struct{}

func (RangeProofMarshaler) Marshal(r *RangeProof) ([]byte, error) {
if r == nil {
return nil, errNilProof
}
if r.proof == nil {
return nil, nil
}

data, err := r.proof.MarshalBinary()
return data, err
}

func (RangeProofMarshaler) Unmarshal(data []byte) (*RangeProof, error) {
proof := new(ffi.RangeProof)

Check failure on line 37 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: ffi.RangeProof

Check failure on line 37 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (macos-14)

undefined: ffi.RangeProof

Check failure on line 37 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-22.04)

undefined: ffi.RangeProof

Check failure on line 37 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-noble)

undefined: ffi.RangeProof

Check failure on line 37 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-jammy)

undefined: ffi.RangeProof

Check failure on line 37 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-24.04)

undefined: ffi.RangeProof
if err := proof.UnmarshalBinary(data); err != nil {
return nil, err
}
return newRangeProof(proof), nil
}

type RangeProof struct {
proof *ffi.RangeProof

Check failure on line 45 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: ffi.RangeProof

Check failure on line 45 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (macos-14)

undefined: ffi.RangeProof

Check failure on line 45 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-22.04)

undefined: ffi.RangeProof

Check failure on line 45 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-noble)

undefined: ffi.RangeProof

Check failure on line 45 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-jammy)

undefined: ffi.RangeProof

Check failure on line 45 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-24.04)

undefined: ffi.RangeProof
root ids.ID
maxLength int
}

// Wrap the ffi proof in our proof type.
func newRangeProof(proof *ffi.RangeProof) *RangeProof {

Check failure on line 51 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: ffi.RangeProof

Check failure on line 51 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (macos-14)

undefined: ffi.RangeProof

Check failure on line 51 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-22.04)

undefined: ffi.RangeProof

Check failure on line 51 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-noble)

undefined: ffi.RangeProof

Check failure on line 51 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-jammy)

undefined: ffi.RangeProof

Check failure on line 51 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-24.04)

undefined: ffi.RangeProof
rangeProof := &RangeProof{
proof: proof,
}
// Once this struct is out of scope, free the underlying proof.
runtime.AddCleanup(rangeProof, func(ffiProof *ffi.RangeProof) {

Check failure on line 56 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: ffi.RangeProof

Check failure on line 56 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (macos-14)

undefined: ffi.RangeProof

Check failure on line 56 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-22.04)

undefined: ffi.RangeProof

Check failure on line 56 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-noble)

undefined: ffi.RangeProof

Check failure on line 56 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-jammy)

undefined: ffi.RangeProof

Check failure on line 56 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-24.04)

undefined: ffi.RangeProof
if ffiProof != nil {
_ = ffiProof.Free()
}
}, rangeProof.proof)
return rangeProof
}

func (r *RangeProof) FindNextKey() (maybe.Maybe[[]byte], error) {
// We can now get the FindNextKey iterator.
nextKeyRange, err := r.proof.FindNextKey()
if err != nil {
return maybe.Nothing[[]byte](), err
}

// TODO: this panics
startKey := maybe.Some(nextKeyRange.StartKey())

// Done using nextKeyRange
if err := nextKeyRange.Free(); err != nil {
return maybe.Nothing[[]byte](), err
}

return startKey, nil
}

type ChangeProofMarshaler struct{}

func (ChangeProofMarshaler) Marshal(r *ChangeProof) ([]byte, error) {
if r == nil {
return nil, errNilProof
}
if r.proof == nil {
return nil, nil
}

data, err := r.proof.MarshalBinary()
return data, err
}

func (ChangeProofMarshaler) Unmarshal(data []byte) (*ChangeProof, error) {
proof := new(ffi.ChangeProof)

Check failure on line 97 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: ffi.ChangeProof

Check failure on line 97 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (macos-14)

undefined: ffi.ChangeProof

Check failure on line 97 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-22.04)

undefined: ffi.ChangeProof

Check failure on line 97 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-noble)

undefined: ffi.ChangeProof

Check failure on line 97 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-jammy)

undefined: ffi.ChangeProof

Check failure on line 97 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-24.04)

undefined: ffi.ChangeProof
if err := proof.UnmarshalBinary(data); err != nil {
return nil, err
}
return newChangeProof(proof), nil
}

type ChangeProof struct {
proof *ffi.ChangeProof

Check failure on line 105 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: ffi.ChangeProof

Check failure on line 105 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (macos-14)

undefined: ffi.ChangeProof

Check failure on line 105 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-22.04)

undefined: ffi.ChangeProof

Check failure on line 105 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-noble)

undefined: ffi.ChangeProof

Check failure on line 105 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-jammy)

undefined: ffi.ChangeProof

Check failure on line 105 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-24.04)

undefined: ffi.ChangeProof
startRoot ids.ID
endRoot ids.ID
startKey maybe.Maybe[[]byte]
maxLength int
}

// Wrap the ffi proof in our proof type.
func newChangeProof(proof *ffi.ChangeProof) *ChangeProof {

Check failure on line 113 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: ffi.ChangeProof

Check failure on line 113 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (macos-14)

undefined: ffi.ChangeProof

Check failure on line 113 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-22.04)

undefined: ffi.ChangeProof

Check failure on line 113 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-noble)

undefined: ffi.ChangeProof

Check failure on line 113 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-jammy)

undefined: ffi.ChangeProof

Check failure on line 113 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-24.04)

undefined: ffi.ChangeProof
changeProof := &ChangeProof{
proof: proof,
}

// Once this struct is out of scope, free the underlying proof.
runtime.AddCleanup(changeProof, func(ffiProof *ffi.ChangeProof) {

Check failure on line 119 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: ffi.ChangeProof

Check failure on line 119 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (macos-14)

undefined: ffi.ChangeProof

Check failure on line 119 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-22.04)

undefined: ffi.ChangeProof

Check failure on line 119 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-noble)

undefined: ffi.ChangeProof

Check failure on line 119 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-jammy)

undefined: ffi.ChangeProof

Check failure on line 119 in x/firewood/proof.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-24.04)

undefined: ffi.ChangeProof
if ffiProof != nil {
_ = ffiProof.Free()
}
}, changeProof.proof)

return changeProof
}

func (c *ChangeProof) FindNextKey() (maybe.Maybe[[]byte], error) {
// We can now get the FindNextKey iterator.
nextKeyRange, err := c.proof.FindNextKey()
if err != nil {
return maybe.Nothing[[]byte](), err
}

// TODO: this panics
startKey := maybe.Some(nextKeyRange.StartKey())

// Done using nextKeyRange
if err := nextKeyRange.Free(); err != nil {
return maybe.Nothing[[]byte](), err
}

return startKey, nil
}
170 changes: 170 additions & 0 deletions x/firewood/sync_db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package firewood

import (
"bytes"
"context"
"errors"
"sync"

"github.com/ava-labs/firewood-go-ethhash/ffi"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/maybe"

xsync "github.com/ava-labs/avalanchego/x/sync"
)

var (
_ xsync.DB[*RangeProof, *ChangeProof] = (*syncDB)(nil)

errNilProof = errors.New("nil proof")
)

type syncDB struct {
fw *ffi.Database
lock sync.Mutex
}

func New(db *ffi.Database) *syncDB {
return &syncDB{fw: db}
}

func (db *syncDB) GetMerkleRoot(context.Context) (ids.ID, error) {
root, err := db.fw.Root()
if err != nil {
return ids.ID{}, err
}
return ids.ID(root), nil
}

// TODO: implement
func (db *syncDB) CommitChangeProof(_ context.Context, end maybe.Maybe[[]byte], proof *ChangeProof) (nextKey maybe.Maybe[[]byte], err error) {
// Set up cleanup.
var nextKeyRange *ffi.NextKeyRange

Check failure on line 46 in x/firewood/sync_db.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: ffi.NextKeyRange

Check failure on line 46 in x/firewood/sync_db.go

View workflow job for this annotation

GitHub Actions / Unit (macos-14)

undefined: ffi.NextKeyRange

Check failure on line 46 in x/firewood/sync_db.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-22.04)

undefined: ffi.NextKeyRange

Check failure on line 46 in x/firewood/sync_db.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-noble)

undefined: ffi.NextKeyRange

Check failure on line 46 in x/firewood/sync_db.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-jammy)

undefined: ffi.NextKeyRange

Check failure on line 46 in x/firewood/sync_db.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-24.04)

undefined: ffi.NextKeyRange
defer func() {
// If we got a nextKeyRange, free it too.
if nextKeyRange != nil {
err = errors.Join(err, nextKeyRange.Free())
}
}()

// TODO: Remove copy. Currently necessary to avoid passing pointer to a stack variable?
startRootBytes := make([]byte, ids.IDLen)
copy(startRootBytes, proof.startRoot[:])
endRootBytes := make([]byte, ids.IDLen)
copy(endRootBytes, proof.endRoot[:])

// Verify and commit the proof in a single step (TODO: separate these steps).
// CommitRangeProof will verify the proof as part of committing it.
db.lock.Lock()
defer db.lock.Unlock()
newRoot, err := db.fw.VerifyAndCommitChangeProof(proof.proof, startRootBytes, endRootBytes, proof.startKey, end, uint32(proof.maxLength))

Check failure on line 64 in x/firewood/sync_db.go

View workflow job for this annotation

GitHub Actions / Lint

db.fw.VerifyAndCommitChangeProof undefined (type *ffi.Database has no field or method VerifyAndCommitChangeProof)

Check failure on line 64 in x/firewood/sync_db.go

View workflow job for this annotation

GitHub Actions / Unit (macos-14)

db.fw.VerifyAndCommitChangeProof undefined (type *ffi.Database has no field or method VerifyAndCommitChangeProof)

Check failure on line 64 in x/firewood/sync_db.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-22.04)

db.fw.VerifyAndCommitChangeProof undefined (type *ffi.Database has no field or method VerifyAndCommitChangeProof)

Check failure on line 64 in x/firewood/sync_db.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-noble)

db.fw.VerifyAndCommitChangeProof undefined (type *ffi.Database has no field or method VerifyAndCommitChangeProof)

Check failure on line 64 in x/firewood/sync_db.go

View workflow job for this annotation

GitHub Actions / Unit (custom-arm64-jammy)

db.fw.VerifyAndCommitChangeProof undefined (type *ffi.Database has no field or method VerifyAndCommitChangeProof)

Check failure on line 64 in x/firewood/sync_db.go

View workflow job for this annotation

GitHub Actions / Unit (ubuntu-24.04)

db.fw.VerifyAndCommitChangeProof undefined (type *ffi.Database has no field or method VerifyAndCommitChangeProof)
if err != nil {
return maybe.Nothing[[]byte](), err
}

// TODO: This case should be handled by `FindNextKey`.
if bytes.Equal(newRoot, endRootBytes) {
return maybe.Nothing[[]byte](), nil
}

return proof.FindNextKey()
}

// Commit the range proof to the database.
// TODO: This should only commit the range proof, not verify it.
// This will be resolved once the Firewood supports that.
// This is the last call to the proof, so it and any resources should be freed.
func (db *syncDB) CommitRangeProof(_ context.Context, start, end maybe.Maybe[[]byte], proof *RangeProof) (nextKey maybe.Maybe[[]byte], err error) {
// Set up cleanup.
// TODO: Remove copy. Currently necessary to avoid passing pointer to a stack variable?
rootBytes := make([]byte, ids.IDLen)
copy(rootBytes, proof.root[:])

// Verify and commit the proof in a single step (TODO: separate these steps).
// CommitRangeProof will verify the proof as part of committing it.
db.lock.Lock()
defer db.lock.Unlock()
newRoot, err := db.fw.VerifyAndCommitRangeProof(proof.proof, start, end, rootBytes, uint32(proof.maxLength))
if err != nil {
return maybe.Nothing[[]byte](), err
}

// TODO: This case should be handled by `FindNextKey`.
if bytes.Equal(newRoot, rootBytes) {
return maybe.Nothing[[]byte](), nil
}

return proof.FindNextKey()
}

// TODO: implement
func (db *syncDB) GetChangeProof(_ context.Context, startRootID ids.ID, endRootID ids.ID, start maybe.Maybe[[]byte], end maybe.Maybe[[]byte], maxLength int) (*ChangeProof, error) {
proof, err := db.fw.ChangeProof(startRootID[:], endRootID[:], start, end, uint32(maxLength))
if err != nil {
return nil, err
}

return newChangeProof(proof), nil
}

// Get the range proof between [start, end].
// The returned proof must be freed when no longer needed.
// Since this method is only called prior to marshalling the proof for sending over the
// network, the proof will be freed when marshalled.
func (db *syncDB) GetRangeProofAtRoot(_ context.Context, rootID ids.ID, start maybe.Maybe[[]byte], end maybe.Maybe[[]byte], maxLength int) (*RangeProof, error) {
proof, err := db.fw.RangeProof(maybe.Some(rootID[:]), start, end, uint32(maxLength))
if err != nil {
return nil, err
}

return newRangeProof(proof), nil
}

// TODO: implement
// Right now, we verify the proof as part of committing it, making this function a no-op.
// We must only pass the necessary data to CommitChangeProof so it can verify the proof.
//
//nolint:revive
func (db *syncDB) VerifyChangeProof(_ context.Context, proof *ChangeProof, start maybe.Maybe[[]byte], end maybe.Maybe[[]byte], expectedEndRootID ids.ID, maxLength int) error {
if proof.proof == nil {
return errNilProof
}

// TODO: once firewood can verify separately from committing, do that here.
// For now, pass any necessary data to be done in CommitChangeProof.
// Namely, the start root, end root, and max length.
proof.startRoot = expectedEndRootID
proof.endRoot = expectedEndRootID
proof.maxLength = maxLength
return nil
}

// TODO: implement
// Right now, we verify the proof as part of committing it, making this function a no-op.
// We must only pass the necessary data to CommitRangeProof so it can verify the proof.
//
//nolint:revive
func (db *syncDB) VerifyRangeProof(_ context.Context, proof *RangeProof, start maybe.Maybe[[]byte], end maybe.Maybe[[]byte], expectedEndRootID ids.ID, maxLength int) error {
if proof.proof == nil {
return errNilProof
}

// TODO: once firewood can verify separately from committing, do that here.
// For now, pass any necessary data to be done in CommitRangeProof.
// Namely, the max length and root.
proof.root = expectedEndRootID
proof.maxLength = maxLength
return nil
}

// TODO: implement
// No error is returned to ensure some tests pass.
//
//nolint:revive
func (db *syncDB) Clear() error {
return nil
}
Loading
Loading