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
30 changes: 22 additions & 8 deletions database/merkle/firewood/syncer/evm_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package syncer

import (
"context"
"errors"

"github.com/ava-labs/firewood-go-ethhash/ffi"
"github.com/ava-labs/libevm/common"
Expand All @@ -16,7 +15,7 @@ import (
"github.com/ava-labs/avalanchego/utils/maybe"
)

var _ sync.DB[*RangeProof, struct{}] = (*evmDB)(nil)
var _ sync.DB[*RangeProof, *ChangeProof] = (*evmDB)(nil)

type CodeQueue interface {
AddCode(context.Context, []common.Hash) error
Expand All @@ -33,11 +32,11 @@ func NewEVM(
codeQueue CodeQueue,
targetRoot ids.ID,
proofClient *p2p.Client,
) (*sync.Syncer[*RangeProof, struct{}], error) {
) (*sync.Syncer[*RangeProof, *ChangeProof], error) {
return newWithDB(
config,
&evmDB{
db: &database{db},
db: &database{db: db},
codeQueue: codeQueue,
},
targetRoot,
Expand Down Expand Up @@ -66,8 +65,23 @@ func (e *evmDB) CommitRangeProof(ctx context.Context, start, end maybe.Maybe[[]b
return nextKey, nil
}

func (*evmDB) CommitChangeProof(context.Context, maybe.Maybe[[]byte], struct{}) (maybe.Maybe[[]byte], error) {
return maybe.Nothing[[]byte](), errors.New("change proof code hashes not implemented")
func (e *evmDB) CommitChangeProof(ctx context.Context, end maybe.Maybe[[]byte], proof *ChangeProof) (maybe.Maybe[[]byte], error) {
// First enqueue any code hashes found in the proof.
// If an error occurs here, we don't want to commit the proof to the database, because the
// code hashes will never be requested.
// Note: ffi.ChangeProof.CodeHashes() is currently a no-op in firewood; the loop is
// kept so we pick up real code hashes automatically once firewood implements it.
var codeHashes []common.Hash
for h, err := range proof.cp.CodeHashes() {
if err != nil {
return maybe.Nothing[[]byte](), err
}
codeHashes = append(codeHashes, common.Hash(h))
}
if err := e.codeQueue.AddCode(ctx, codeHashes); err != nil {
return maybe.Nothing[[]byte](), err
}
return e.db.CommitChangeProof(ctx, end, proof)
}

func (e *evmDB) GetMerkleRoot(ctx context.Context) (ids.ID, error) {
Expand All @@ -78,15 +92,15 @@ func (e *evmDB) Clear() error {
return e.db.Clear()
}

func (e *evmDB) GetChangeProof(ctx context.Context, startRootID ids.ID, endRootID ids.ID, start maybe.Maybe[[]byte], end maybe.Maybe[[]byte], maxLength int) (struct{}, error) {
func (e *evmDB) GetChangeProof(ctx context.Context, startRootID ids.ID, endRootID ids.ID, start maybe.Maybe[[]byte], end maybe.Maybe[[]byte], maxLength int) (*ChangeProof, error) {
return e.db.GetChangeProof(ctx, startRootID, endRootID, start, end, maxLength)
}

func (e *evmDB) GetRangeProofAtRoot(ctx context.Context, rootID ids.ID, start maybe.Maybe[[]byte], end maybe.Maybe[[]byte], maxLength int) (*RangeProof, error) {
return e.db.GetRangeProofAtRoot(ctx, rootID, start, end, maxLength)
}

func (e *evmDB) VerifyChangeProof(ctx context.Context, proof struct{}, start maybe.Maybe[[]byte], end maybe.Maybe[[]byte], expectedEndRootID ids.ID, maxLength int) error {
func (e *evmDB) VerifyChangeProof(ctx context.Context, proof *ChangeProof, start maybe.Maybe[[]byte], end maybe.Maybe[[]byte], expectedEndRootID ids.ID, maxLength int) error {
return e.db.VerifyChangeProof(ctx, proof, start, end, expectedEndRootID, maxLength)
}

Expand Down
2 changes: 1 addition & 1 deletion database/merkle/firewood/syncer/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ import (

// NewGetProofHandler returns a handler that services proof requests
// using the provided Firewood database for p2p connections.
func NewGetProofHandler(db *ffi.Database) *sync.ProofHandler[*RangeProof, struct{}] {
func NewGetProofHandler(db *ffi.Database) *sync.ProofHandler[*RangeProof, *ChangeProof] {
return sync.NewProofHandler(&database{db: db}, rangeProofMarshaler{}, changeProofMarshaler{})
}
27 changes: 18 additions & 9 deletions database/merkle/firewood/syncer/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@
package syncer

import (
"errors"

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

"github.com/ava-labs/avalanchego/database/merkle/sync"
"github.com/ava-labs/avalanchego/ids"
)

var (
_ sync.Marshaler[*RangeProof] = rangeProofMarshaler{}
_ sync.Marshaler[struct{}] = changeProofMarshaler{}
_ sync.Marshaler[*RangeProof] = rangeProofMarshaler{}
_ sync.Marshaler[*ChangeProof] = changeProofMarshaler{}
)

type rangeProofMarshaler struct{}
Expand All @@ -39,13 +37,24 @@ type RangeProof struct {
maxLength int
}

// TODO: implement an actual ChangeProof marshaler.
type changeProofMarshaler struct{}

func (changeProofMarshaler) Marshal(struct{}) ([]byte, error) {
return nil, errors.New("not implemented")
func (changeProofMarshaler) Marshal(p *ChangeProof) ([]byte, error) {
return p.cp.MarshalBinary()
}

func (changeProofMarshaler) Unmarshal(data []byte) (*ChangeProof, error) {
proof := new(ffi.ChangeProof)
if err := proof.UnmarshalBinary(data); err != nil {
return nil, err
}
return &ChangeProof{cp: proof}, nil
}

func (changeProofMarshaler) Unmarshal([]byte) (struct{}, error) {
return struct{}{}, errors.New("not implemented")
type ChangeProof struct {
cp *ffi.ChangeProof
// proposal is populated by VerifyChangeProof on the client side and
// consumed by CommitChangeProof. It is nil for proofs returned from
// GetChangeProof on the server side.
proposal *ffi.Proposal
}
63 changes: 48 additions & 15 deletions database/merkle/firewood/syncer/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
)

var (
_ sync.DB[*RangeProof, struct{}] = (*database)(nil)
_ sync.DB[*RangeProof, *ChangeProof] = (*database)(nil)

defaultSimultaneousWorkLimit = 8
)
Expand All @@ -37,16 +37,16 @@ type Config struct {
Registerer prometheus.Registerer
}

func New(config Config, db *ffi.Database, targetRoot ids.ID, proofClient *p2p.Client) (*sync.Syncer[*RangeProof, struct{}], error) {
func New(config Config, db *ffi.Database, targetRoot ids.ID, proofClient *p2p.Client) (*sync.Syncer[*RangeProof, *ChangeProof], error) {
return newWithDB(
config,
&database{db},
&database{db: db},
targetRoot,
proofClient,
)
}

func newWithDB(config Config, db sync.DB[*RangeProof, struct{}], targetRoot ids.ID, proofClient *p2p.Client) (*sync.Syncer[*RangeProof, struct{}], error) {
func newWithDB(config Config, db sync.DB[*RangeProof, *ChangeProof], targetRoot ids.ID, proofClient *p2p.Client) (*sync.Syncer[*RangeProof, *ChangeProof], error) {
if config.Registerer == nil {
config.Registerer = prometheus.NewRegistry()
}
Expand All @@ -58,7 +58,7 @@ func newWithDB(config Config, db sync.DB[*RangeProof, struct{}], targetRoot ids.
}
return sync.NewSyncer(
db,
sync.Config[*RangeProof, struct{}]{
sync.Config[*RangeProof, *ChangeProof]{
RangeProofMarshaler: rangeProofMarshaler{},
ChangeProofMarshaler: changeProofMarshaler{},
EmptyRoot: ids.ID(types.EmptyRootHash),
Expand Down Expand Up @@ -124,20 +124,53 @@ func (db *database) CommitRangeProof(_ context.Context, start, end maybe.Maybe[[
return maybe.Some(nextKey), nil
}

// TODO: Use change proofs to optimize syncing.
// Returning the sentinel error suggests to the server handler to serve a full range proof instead.
func (*database) GetChangeProof(context.Context, ids.ID, ids.ID, maybe.Maybe[[]byte], maybe.Maybe[[]byte], int) (struct{}, error) {
return struct{}{}, sync.ErrInsufficientHistory
func (db *database) GetChangeProof(_ context.Context, startRootID ids.ID, endRootID ids.ID, start maybe.Maybe[[]byte], end maybe.Maybe[[]byte], maxLength int) (*ChangeProof, error) {
proof, err := db.db.ChangeProof(ffi.Hash(startRootID), ffi.Hash(endRootID), start, end, uint32(maxLength))
switch {
case errors.Is(err, ffi.ErrStartRevisionNotFound):
return nil, sync.ErrInsufficientHistory
case errors.Is(err, ffi.ErrEndRevisionNotFound):
return nil, sync.ErrNoEndRoot
case err != nil:
return nil, err
}
return &ChangeProof{cp: proof}, nil
}

// TODO: implement this method.
func (*database) VerifyChangeProof(context.Context, struct{}, maybe.Maybe[[]byte], maybe.Maybe[[]byte], ids.ID, int) error {
return errors.New("change proofs are not implemented")
func (db *database) VerifyChangeProof(_ context.Context, proof *ChangeProof, start maybe.Maybe[[]byte], end maybe.Maybe[[]byte], expectedEndRootID ids.ID, maxLength int) error {
proposal, err := db.db.VerifyChangeProof(proof.cp, ffi.Hash(expectedEndRootID), start, end, uint32(maxLength))
if err != nil {
return err
}
proof.proposal = proposal
return nil
}

// TODO: implement this method.
func (*database) CommitChangeProof(context.Context, maybe.Maybe[[]byte], struct{}) (maybe.Maybe[[]byte], error) {
return maybe.Nothing[[]byte](), errors.New("change proofs are not implemented")
func (*database) CommitChangeProof(_ context.Context, end maybe.Maybe[[]byte], proof *ChangeProof) (maybe.Maybe[[]byte], error) {
if proof.proposal == nil {
return maybe.Nothing[[]byte](), errors.New("change proof was not verified before commit")
}
if _, err := proof.proposal.CommitWithRebase(); err != nil {
return maybe.Nothing[[]byte](), err
}

nextKeyRange, err := proof.cp.FindNextKey(end)
if err != nil {
return maybe.Nothing[[]byte](), err
}
if nextKeyRange == nil {
return maybe.Nothing[[]byte](), nil
}

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

if end.HasValue() && bytes.Compare(nextKey, end.Value()) > 0 {
return maybe.Nothing[[]byte](), nil
}
return maybe.Some(nextKey), nil
}

func (db *database) Clear() error {
Expand Down
2 changes: 1 addition & 1 deletion database/merkle/firewood/syncer/syncer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func testSyncWithUpdate(t *testing.T, clientKeys int, serverKeys int, numRequest
}()
wantRoot := fillDB(t, r, serverDB, serverKeys)

var syncer *sync.Syncer[*RangeProof, struct{}]
var syncer *sync.Syncer[*RangeProof, *ChangeProof]
ctx, cancel := context.WithCancelCause(t.Context())
defer cancel(nil)

Expand Down
1 change: 1 addition & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/ava-labs/firewood-go-ethhash/ffi v0.3.0/go.mod h1:71C76bo47zlDX9gWnn3p/0QZQXkTQ/GNqUNI6fchvjs=
github.com/ava-labs/firewood-go-ethhash/ffi v0.5.0/go.mod h1:MoUHYlFrwaflfLpHt8nmQUHoLy2CCHaFNH/n7vazUuI=
github.com/ava-labs/simplex v0.0.0-20260320130759-afe09323fdd1 h1:Y9UdRQj28/t+GNzVSlP6nvoNLtzNRo3fNXLUc/NscVM=
github.com/ava-labs/simplex v0.0.0-20260320130759-afe09323fdd1/go.mod h1:GVzumIo3zR23/qGRN2AdnVkIPHcKMq/D89EGWZfMGQ0=
github.com/aws/aws-sdk-go-v2 v1.21.2 h1:+LXZ0sgo8quN9UOKXXzAWRT3FWd4NxeXWOZom9pE7GA=
Expand Down
2 changes: 1 addition & 1 deletion graft/evm/sync/evmstate/firewood_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ var (
)

type FirewoodSyncer struct {
s *merklesync.Syncer[*syncer.RangeProof, struct{}]
s *merklesync.Syncer[*syncer.RangeProof, *syncer.ChangeProof]
cancel context.CancelFunc
codeQueue *code.Queue
// finalizeOnce is initialized in the constructor to make Finalize idempotent.
Expand Down
Loading