Skip to content

Commit 34c131b

Browse files
committed
wip draft of firewood sync
1 parent 47550e9 commit 34c131b

File tree

3 files changed

+429
-0
lines changed

3 files changed

+429
-0
lines changed

x/firewood/proof.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package firewood
5+
6+
import (
7+
"runtime"
8+
9+
"github.com/ava-labs/firewood-go-ethhash/ffi"
10+
11+
"github.com/ava-labs/avalanchego/ids"
12+
"github.com/ava-labs/avalanchego/utils/maybe"
13+
14+
xsync "github.com/ava-labs/avalanchego/x/sync"
15+
)
16+
17+
var (
18+
_ xsync.Marshaler[*RangeProof] = RangeProofMarshaler{}
19+
_ xsync.Marshaler[*ChangeProof] = ChangeProofMarshaler{}
20+
)
21+
22+
type RangeProofMarshaler struct{}
23+
24+
func (RangeProofMarshaler) Marshal(r *RangeProof) ([]byte, error) {
25+
if r == nil {
26+
return nil, errNilProof
27+
}
28+
if r.proof == nil {
29+
return nil, nil
30+
}
31+
32+
data, err := r.proof.MarshalBinary()
33+
return data, err
34+
}
35+
36+
func (RangeProofMarshaler) Unmarshal(data []byte) (*RangeProof, error) {
37+
proof := new(ffi.RangeProof)
38+
if err := proof.UnmarshalBinary(data); err != nil {
39+
return nil, err
40+
}
41+
return newRangeProof(proof), nil
42+
}
43+
44+
type RangeProof struct {
45+
proof *ffi.RangeProof
46+
root ids.ID
47+
maxLength int
48+
}
49+
50+
// Wrap the ffi proof in our proof type.
51+
func newRangeProof(proof *ffi.RangeProof) *RangeProof {
52+
rangeProof := &RangeProof{
53+
proof: proof,
54+
}
55+
// Once this struct is out of scope, free the underlying proof.
56+
runtime.AddCleanup(rangeProof, func(ffiProof *ffi.RangeProof) {
57+
if ffiProof != nil {
58+
_ = ffiProof.Free()
59+
}
60+
}, rangeProof.proof)
61+
return rangeProof
62+
}
63+
64+
func (r *RangeProof) FindNextKey() (maybe.Maybe[[]byte], error) {
65+
// We can now get the FindNextKey iterator.
66+
nextKeyRange, err := r.proof.FindNextKey()
67+
if err != nil {
68+
return maybe.Nothing[[]byte](), err
69+
}
70+
71+
// TODO: this panics
72+
startKey := maybe.Some(nextKeyRange.StartKey())
73+
74+
// Done using nextKeyRange
75+
if err := nextKeyRange.Free(); err != nil {
76+
return maybe.Nothing[[]byte](), err
77+
}
78+
79+
return startKey, nil
80+
}
81+
82+
type ChangeProofMarshaler struct{}
83+
84+
func (ChangeProofMarshaler) Marshal(r *ChangeProof) ([]byte, error) {
85+
if r == nil {
86+
return nil, errNilProof
87+
}
88+
if r.proof == nil {
89+
return nil, nil
90+
}
91+
92+
data, err := r.proof.MarshalBinary()
93+
return data, err
94+
}
95+
96+
func (ChangeProofMarshaler) Unmarshal(data []byte) (*ChangeProof, error) {
97+
proof := new(ffi.ChangeProof)
98+
if err := proof.UnmarshalBinary(data); err != nil {
99+
return nil, err
100+
}
101+
return newChangeProof(proof), nil
102+
}
103+
104+
type ChangeProof struct {
105+
proof *ffi.ChangeProof
106+
startRoot ids.ID
107+
endRoot ids.ID
108+
startKey maybe.Maybe[[]byte]
109+
maxLength int
110+
}
111+
112+
// Wrap the ffi proof in our proof type.
113+
func newChangeProof(proof *ffi.ChangeProof) *ChangeProof {
114+
changeProof := &ChangeProof{
115+
proof: proof,
116+
}
117+
118+
// Once this struct is out of scope, free the underlying proof.
119+
runtime.AddCleanup(changeProof, func(ffiProof *ffi.ChangeProof) {
120+
if ffiProof != nil {
121+
_ = ffiProof.Free()
122+
}
123+
}, changeProof.proof)
124+
125+
return changeProof
126+
}
127+
128+
func (c *ChangeProof) FindNextKey() (maybe.Maybe[[]byte], error) {
129+
// We can now get the FindNextKey iterator.
130+
nextKeyRange, err := c.proof.FindNextKey()
131+
if err != nil {
132+
return maybe.Nothing[[]byte](), err
133+
}
134+
135+
// TODO: this panics
136+
startKey := maybe.Some(nextKeyRange.StartKey())
137+
138+
// Done using nextKeyRange
139+
if err := nextKeyRange.Free(); err != nil {
140+
return maybe.Nothing[[]byte](), err
141+
}
142+
143+
return startKey, nil
144+
}

x/firewood/sync_db.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package firewood
5+
6+
import (
7+
"bytes"
8+
"context"
9+
"errors"
10+
"sync"
11+
12+
"github.com/ava-labs/firewood-go-ethhash/ffi"
13+
14+
"github.com/ava-labs/avalanchego/ids"
15+
"github.com/ava-labs/avalanchego/utils/maybe"
16+
17+
xsync "github.com/ava-labs/avalanchego/x/sync"
18+
)
19+
20+
var (
21+
_ xsync.DB[*RangeProof, *ChangeProof] = (*syncDB)(nil)
22+
23+
errNilProof = errors.New("nil proof")
24+
)
25+
26+
type syncDB struct {
27+
fw *ffi.Database
28+
lock sync.Mutex
29+
}
30+
31+
func New(db *ffi.Database) *syncDB {
32+
return &syncDB{fw: db}
33+
}
34+
35+
func (db *syncDB) GetMerkleRoot(context.Context) (ids.ID, error) {
36+
root, err := db.fw.Root()
37+
if err != nil {
38+
return ids.ID{}, err
39+
}
40+
return ids.ID(root), nil
41+
}
42+
43+
// TODO: implement
44+
func (db *syncDB) CommitChangeProof(_ context.Context, end maybe.Maybe[[]byte], proof *ChangeProof) (nextKey maybe.Maybe[[]byte], err error) {
45+
// Set up cleanup.
46+
var nextKeyRange *ffi.NextKeyRange
47+
defer func() {
48+
// If we got a nextKeyRange, free it too.
49+
if nextKeyRange != nil {
50+
err = errors.Join(err, nextKeyRange.Free())
51+
}
52+
}()
53+
54+
// TODO: Remove copy. Currently necessary to avoid passing pointer to a stack variable?
55+
startRootBytes := make([]byte, ids.IDLen)
56+
copy(startRootBytes, proof.startRoot[:])
57+
endRootBytes := make([]byte, ids.IDLen)
58+
copy(endRootBytes, proof.endRoot[:])
59+
60+
// Verify and commit the proof in a single step (TODO: separate these steps).
61+
// CommitRangeProof will verify the proof as part of committing it.
62+
db.lock.Lock()
63+
defer db.lock.Unlock()
64+
newRoot, err := db.fw.VerifyAndCommitChangeProof(proof.proof, startRootBytes, endRootBytes, proof.startKey, end, uint32(proof.maxLength))
65+
if err != nil {
66+
return maybe.Nothing[[]byte](), err
67+
}
68+
69+
// TODO: This case should be handled by `FindNextKey`.
70+
if bytes.Equal(newRoot, endRootBytes) {
71+
return maybe.Nothing[[]byte](), nil
72+
}
73+
74+
return proof.FindNextKey()
75+
}
76+
77+
// Commit the range proof to the database.
78+
// TODO: This should only commit the range proof, not verify it.
79+
// This will be resolved once the Firewood supports that.
80+
// This is the last call to the proof, so it and any resources should be freed.
81+
func (db *syncDB) CommitRangeProof(_ context.Context, start, end maybe.Maybe[[]byte], proof *RangeProof) (nextKey maybe.Maybe[[]byte], err error) {
82+
// Set up cleanup.
83+
// TODO: Remove copy. Currently necessary to avoid passing pointer to a stack variable?
84+
rootBytes := make([]byte, ids.IDLen)
85+
copy(rootBytes, proof.root[:])
86+
87+
// Verify and commit the proof in a single step (TODO: separate these steps).
88+
// CommitRangeProof will verify the proof as part of committing it.
89+
db.lock.Lock()
90+
defer db.lock.Unlock()
91+
newRoot, err := db.fw.VerifyAndCommitRangeProof(proof.proof, start, end, rootBytes, uint32(proof.maxLength))
92+
if err != nil {
93+
return maybe.Nothing[[]byte](), err
94+
}
95+
96+
// TODO: This case should be handled by `FindNextKey`.
97+
if bytes.Equal(newRoot, rootBytes) {
98+
return maybe.Nothing[[]byte](), nil
99+
}
100+
101+
return proof.FindNextKey()
102+
}
103+
104+
// TODO: implement
105+
func (db *syncDB) GetChangeProof(_ context.Context, startRootID ids.ID, endRootID ids.ID, start maybe.Maybe[[]byte], end maybe.Maybe[[]byte], maxLength int) (*ChangeProof, error) {
106+
proof, err := db.fw.ChangeProof(startRootID[:], endRootID[:], start, end, uint32(maxLength))
107+
if err != nil {
108+
return nil, err
109+
}
110+
111+
return newChangeProof(proof), nil
112+
}
113+
114+
// Get the range proof between [start, end].
115+
// The returned proof must be freed when no longer needed.
116+
// Since this method is only called prior to marshalling the proof for sending over the
117+
// network, the proof will be freed when marshalled.
118+
func (db *syncDB) GetRangeProofAtRoot(_ context.Context, rootID ids.ID, start maybe.Maybe[[]byte], end maybe.Maybe[[]byte], maxLength int) (*RangeProof, error) {
119+
proof, err := db.fw.RangeProof(maybe.Some(rootID[:]), start, end, uint32(maxLength))
120+
if err != nil {
121+
return nil, err
122+
}
123+
124+
return newRangeProof(proof), nil
125+
}
126+
127+
// TODO: implement
128+
// Right now, we verify the proof as part of committing it, making this function a no-op.
129+
// We must only pass the necessary data to CommitChangeProof so it can verify the proof.
130+
//
131+
//nolint:revive
132+
func (db *syncDB) VerifyChangeProof(_ context.Context, proof *ChangeProof, start maybe.Maybe[[]byte], end maybe.Maybe[[]byte], expectedEndRootID ids.ID, maxLength int) error {
133+
if proof.proof == nil {
134+
return errNilProof
135+
}
136+
137+
// TODO: once firewood can verify separately from committing, do that here.
138+
// For now, pass any necessary data to be done in CommitChangeProof.
139+
// Namely, the start root, end root, and max length.
140+
proof.startRoot = expectedEndRootID
141+
proof.endRoot = expectedEndRootID
142+
proof.maxLength = maxLength
143+
return nil
144+
}
145+
146+
// TODO: implement
147+
// Right now, we verify the proof as part of committing it, making this function a no-op.
148+
// We must only pass the necessary data to CommitRangeProof so it can verify the proof.
149+
//
150+
//nolint:revive
151+
func (db *syncDB) VerifyRangeProof(_ context.Context, proof *RangeProof, start maybe.Maybe[[]byte], end maybe.Maybe[[]byte], expectedEndRootID ids.ID, maxLength int) error {
152+
if proof.proof == nil {
153+
return errNilProof
154+
}
155+
156+
// TODO: once firewood can verify separately from committing, do that here.
157+
// For now, pass any necessary data to be done in CommitRangeProof.
158+
// Namely, the max length and root.
159+
proof.root = expectedEndRootID
160+
proof.maxLength = maxLength
161+
return nil
162+
}
163+
164+
// TODO: implement
165+
// No error is returned to ensure some tests pass.
166+
//
167+
//nolint:revive
168+
func (db *syncDB) Clear() error {
169+
return nil
170+
}

0 commit comments

Comments
 (0)