From 581a67395f8c06c49e1bdb97bf90a8cf589b29f5 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Tue, 23 May 2023 14:02:34 -0400 Subject: [PATCH] add interface for MerkleDB (#1519) --- scripts/mocks.mockgen.txt | 3 +- x/merkledb/batch.go | 2 +- x/merkledb/db.go | 315 ++++++++++++++++++++------ x/merkledb/db_test.go | 6 +- x/merkledb/iterator.go | 2 +- x/merkledb/mock_db.go | 462 ++++++++++++++++++++++++++++++++++++++ x/merkledb/proof.go | 147 +----------- x/merkledb/proof_test.go | 18 +- x/merkledb/trie_test.go | 2 +- x/merkledb/trieview.go | 6 +- x/sync/client.go | 6 +- x/sync/client_test.go | 10 +- x/sync/mock_client.go | 5 +- x/sync/network_server.go | 4 +- x/sync/sync_test.go | 16 +- x/sync/syncmanager.go | 2 +- 16 files changed, 753 insertions(+), 253 deletions(-) create mode 100644 x/merkledb/mock_db.go diff --git a/scripts/mocks.mockgen.txt b/scripts/mocks.mockgen.txt index cc95dc0e5a7..047e54259b1 100644 --- a/scripts/mocks.mockgen.txt +++ b/scripts/mocks.mockgen.txt @@ -48,4 +48,5 @@ github.com/ava-labs/avalanchego/vms/registry=VMGetter=vms/registry/mock_vm_gette github.com/ava-labs/avalanchego/vms/registry=VMRegisterer=vms/registry/mock_vm_registerer.go github.com/ava-labs/avalanchego/vms/registry=VMRegistry=vms/registry/mock_vm_registry.go github.com/ava-labs/avalanchego/vms=Factory,Manager=vms/mock_manager.go -github.com/ava-labs/avalanchego/x/sync=Client=x/sync/mock_client.go \ No newline at end of file +github.com/ava-labs/avalanchego/x/merkledb=MerkleDB=x/merkledb/mock_db.go +github.com/ava-labs/avalanchego/x/sync=Client=x/sync/mock_client.go diff --git a/x/merkledb/batch.go b/x/merkledb/batch.go index c7927ac91aa..6c8d318500f 100644 --- a/x/merkledb/batch.go +++ b/x/merkledb/batch.go @@ -12,7 +12,7 @@ var _ database.Batch = (*batch)(nil) type batch struct { database.BatchOps - db *Database + db *merkleDB } // apply all operations in order to the database and write the result to disk diff --git a/x/merkledb/db.go b/x/merkledb/db.go index a001494fee5..591eeceaff3 100644 --- a/x/merkledb/db.go +++ b/x/merkledb/db.go @@ -38,8 +38,8 @@ const ( ) var ( - _ TrieView = (*Database)(nil) - _ database.Database = (*Database)(nil) + _ TrieView = (*merkleDB)(nil) + _ MerkleDB = (*merkleDB)(nil) Codec, Version = newCodec() @@ -65,8 +65,64 @@ type Config struct { Tracer trace.Tracer } -// Database can only be edited by committing changes from a trieView. -type Database struct { +type MerkleDB interface { + database.Database + Trie + + // GetChangeProof returns a proof for a subset of the key/value changes in key range + // [start, end] that occurred between [startRootID] and [endRootID]. + // Returns at most [maxLength] key/value pairs. + GetChangeProof( + ctx context.Context, + startRootID ids.ID, + endRootID ids.ID, + start []byte, + end []byte, + maxLength int, + ) (*ChangeProof, error) + + // Returns nil iff all of the following hold: + // - [start] <= [end]. + // - [proof] is non-empty iff [proof.HadRootsInHistory]. + // - All keys in [proof.KeyValues] and [proof.DeletedKeys] are in [start, end]. + // If [start] is empty, all keys are considered > [start]. + // If [end] is empty, all keys are considered < [end]. + // - [proof.KeyValues] and [proof.DeletedKeys] are sorted in order of increasing key. + // - [proof.StartProof] and [proof.EndProof] are well-formed. + // - When the keys in [proof.KeyValues] are added to [db] and the keys in [proof.DeletedKeys] + // are removed from [db], the root ID of [db] is [expectedEndRootID]. + // + // Assumes [db.lock] isn't held. + // This is defined on Database instead of ChangeProof because it accesses + // database internals. + VerifyChangeProof( + ctx context.Context, + proof *ChangeProof, + start []byte, + end []byte, + expectedEndRootID ids.ID, + ) error + + // CommitChangeProof commits the key/value pairs within the [proof] to the db. + CommitChangeProof(ctx context.Context, proof *ChangeProof) error + + // GetRangeProofAtRoot returns a proof for the key/value pairs in this trie within the range + // [start, end] when the root of the trie was [rootID]. + GetRangeProofAtRoot( + ctx context.Context, + rootID ids.ID, + start, + end []byte, + maxLength int, + ) (*RangeProof, error) + + // CommitRangeProof commits the key/value pairs within the [proof] to the db. + // [start] is the smallest key in the range this [proof] covers. + CommitRangeProof(ctx context.Context, start []byte, proof *RangeProof) error +} + +// merkleDB can only be edited by committing changes from a trieView. +type merkleDB struct { // Must be held when reading/writing fields. lock sync.RWMutex @@ -110,8 +166,8 @@ func newDatabase( db database.Database, config Config, metrics merkleMetrics, -) (*Database, error) { - trieDB := &Database{ +) (*merkleDB, error) { + trieDB := &merkleDB{ metrics: metrics, nodeDB: versiondb.New(prefixdb.New(nodePrefix, db)), metadataDB: prefixdb.New(metadataPrefix, db), @@ -158,7 +214,7 @@ func newDatabase( // Deletes every intermediate node and rebuilds them by re-adding every key/value. // TODO: make this more efficient by only clearing out the stale portions of the trie. -func (db *Database) rebuild(ctx context.Context) error { +func (db *merkleDB) rebuild(ctx context.Context) error { db.root = newNode(nil, RootPath) if err := db.nodeDB.Delete(rootKey); err != nil { return err @@ -217,7 +273,7 @@ func (db *Database) rebuild(ctx context.Context) error { } // New returns a new merkle database. -func New(ctx context.Context, db database.Database, config Config) (*Database, error) { +func New(ctx context.Context, db database.Database, config Config) (*merkleDB, error) { metrics, err := newMetrics("merkleDB", config.Reg) if err != nil { return nil, err @@ -225,8 +281,7 @@ func New(ctx context.Context, db database.Database, config Config) (*Database, e return newDatabase(ctx, db, config, metrics) } -// CommitChangeProof commits the key/value pairs within the [proof] to the db. -func (db *Database) CommitChangeProof(ctx context.Context, proof *ChangeProof) error { +func (db *merkleDB) CommitChangeProof(ctx context.Context, proof *ChangeProof) error { db.commitLock.Lock() defer db.commitLock.Unlock() @@ -241,9 +296,7 @@ func (db *Database) CommitChangeProof(ctx context.Context, proof *ChangeProof) e return view.commitToDB(ctx) } -// CommitRangeProof commits the key/value pairs within the [proof] to the db. -// [start] is the smallest key in the range this [proof] covers. -func (db *Database) CommitRangeProof(ctx context.Context, start []byte, proof *RangeProof) error { +func (db *merkleDB) CommitRangeProof(ctx context.Context, start []byte, proof *RangeProof) error { db.commitLock.Lock() defer db.commitLock.Unlock() @@ -258,11 +311,11 @@ func (db *Database) CommitRangeProof(ctx context.Context, start []byte, proof *R return view.commitToDB(ctx) } -func (db *Database) Compact(start []byte, limit []byte) error { +func (db *merkleDB) Compact(start []byte, limit []byte) error { return db.nodeDB.Compact(start, limit) } -func (db *Database) Close() error { +func (db *merkleDB) Close() error { db.commitLock.Lock() defer db.commitLock.Unlock() @@ -302,19 +355,19 @@ func (db *Database) Close() error { return db.metadataDB.Put(cleanShutdownKey, hadCleanShutdown) } -func (db *Database) Delete(key []byte) error { +func (db *merkleDB) Delete(key []byte) error { // this is a duplicate because the database interface doesn't support // contexts, which are used for tracing return db.Remove(context.Background(), key) } -func (db *Database) Get(key []byte) ([]byte, error) { +func (db *merkleDB) Get(key []byte) ([]byte, error) { // this is a duplicate because the database interface doesn't support // contexts, which are used for tracing return db.GetValue(context.Background(), key) } -func (db *Database) GetValues(ctx context.Context, keys [][]byte) ([][]byte, []error) { +func (db *merkleDB) GetValues(ctx context.Context, keys [][]byte) ([][]byte, []error) { _, span := db.tracer.Start(ctx, "MerkleDB.GetValues", oteltrace.WithAttributes( attribute.Int("keyCount", len(keys)), )) @@ -334,7 +387,7 @@ func (db *Database) GetValues(ctx context.Context, keys [][]byte) ([][]byte, []e // GetValue returns the value associated with [key]. // Returns database.ErrNotFound if it doesn't exist. -func (db *Database) GetValue(ctx context.Context, key []byte) ([]byte, error) { +func (db *merkleDB) GetValue(ctx context.Context, key []byte) ([]byte, error) { _, span := db.tracer.Start(ctx, "MerkleDB.GetValue") defer span.End() @@ -343,7 +396,7 @@ func (db *Database) GetValue(ctx context.Context, key []byte) ([]byte, error) { // getValueCopy returns a copy of the value for the given [key]. // Returns database.ErrNotFound if it doesn't exist. -func (db *Database) getValueCopy(key path, lock bool) ([]byte, error) { +func (db *merkleDB) getValueCopy(key path, lock bool) ([]byte, error) { val, err := db.getValue(key, lock) if err != nil { return nil, err @@ -355,7 +408,7 @@ func (db *Database) getValueCopy(key path, lock bool) ([]byte, error) { // Returns database.ErrNotFound if it doesn't exist. // If [lock], [db.lock]'s read lock is acquired. // Otherwise, assumes [db.lock] is already held. -func (db *Database) getValue(key path, lock bool) ([]byte, error) { +func (db *merkleDB) getValue(key path, lock bool) ([]byte, error) { if lock { db.lock.RLock() defer db.lock.RUnlock() @@ -375,8 +428,7 @@ func (db *Database) getValue(key path, lock bool) ([]byte, error) { return n.value.value, nil } -// GetMerkleRoot returns the ID of the root node of the merkle trie. -func (db *Database) GetMerkleRoot(ctx context.Context) (ids.ID, error) { +func (db *merkleDB) GetMerkleRoot(ctx context.Context) (ids.ID, error) { _, span := db.tracer.Start(ctx, "MerkleDB.GetMerkleRoot") defer span.End() @@ -390,23 +442,20 @@ func (db *Database) GetMerkleRoot(ctx context.Context) (ids.ID, error) { return db.getMerkleRoot(), nil } -// Returns the ID of the root node of the merkle trie. // Assumes [db.lock] is read locked. -func (db *Database) getMerkleRoot() ids.ID { +func (db *merkleDB) getMerkleRoot() ids.ID { return db.root.id } -// GetProof returns a proof of the existence/non-existence of [key] in this trie. -func (db *Database) GetProof(ctx context.Context, key []byte) (*Proof, error) { +func (db *merkleDB) GetProof(ctx context.Context, key []byte) (*Proof, error) { db.commitLock.RLock() defer db.commitLock.RUnlock() return db.getProof(ctx, key) } -// Returns a proof of the existence/non-existence of [key] in this trie. // Assumes [db.commitLock] is read locked. -func (db *Database) getProof(ctx context.Context, key []byte) (*Proof, error) { +func (db *merkleDB) getProof(ctx context.Context, key []byte) (*Proof, error) { if db.closed { return nil, database.ErrClosed } @@ -421,7 +470,7 @@ func (db *Database) getProof(ctx context.Context, key []byte) (*Proof, error) { // GetRangeProof returns a proof for the key/value pairs in this trie within the range // [start, end]. -func (db *Database) GetRangeProof( +func (db *merkleDB) GetRangeProof( ctx context.Context, start, end []byte, @@ -435,7 +484,7 @@ func (db *Database) GetRangeProof( // GetRangeProofAtRoot returns a proof for the key/value pairs in this trie within the range // [start, end] when the root of the trie was [rootID]. -func (db *Database) GetRangeProofAtRoot( +func (db *merkleDB) GetRangeProofAtRoot( ctx context.Context, rootID ids.ID, start, @@ -449,7 +498,7 @@ func (db *Database) GetRangeProofAtRoot( } // Assumes [db.commitLock] is read locked. -func (db *Database) getRangeProofAtRoot( +func (db *merkleDB) getRangeProofAtRoot( ctx context.Context, rootID ids.ID, start, @@ -470,10 +519,7 @@ func (db *Database) getRangeProofAtRoot( return historicalView.GetRangeProof(ctx, start, end, maxLength) } -// GetChangeProof returns a proof for a subset of the key/value changes in key range -// [start, end] that occurred between [startRootID] and [endRootID]. -// Returns at most [maxLength] key/value pairs. -func (db *Database) GetChangeProof( +func (db *merkleDB) GetChangeProof( ctx context.Context, startRootID ids.ID, endRootID ids.ID, @@ -580,14 +626,14 @@ func (db *Database) GetChangeProof( // NewView returns a new view on top of this trie. // Changes made to the view will only be reflected in the original trie if Commit is called. // Assumes [db.lock] isn't held. -func (db *Database) NewView() (TrieView, error) { +func (db *merkleDB) NewView() (TrieView, error) { return db.NewPreallocatedView(defaultPreallocationSize) } // Returns a new view that isn't tracked in [db.childViews]. // For internal use only, namely in methods that create short-lived views. // Assumes [db.lock] and/or [db.commitLock] is read locked. -func (db *Database) newUntrackedView(estimatedSize int) (*trieView, error) { +func (db *merkleDB) newUntrackedView(estimatedSize int) (*trieView, error) { return newTrieView(db, db, db.root.clone(), estimatedSize) } @@ -595,7 +641,7 @@ func (db *Database) newUntrackedView(estimatedSize int) (*trieView, error) { // If more changes are made, additional memory will be allocated. // The returned view is added to [db.childViews]. // Assumes [db.lock] isn't held. -func (db *Database) NewPreallocatedView(estimatedSize int) (TrieView, error) { +func (db *merkleDB) NewPreallocatedView(estimatedSize int) (TrieView, error) { db.lock.Lock() defer db.lock.Unlock() @@ -611,7 +657,7 @@ func (db *Database) NewPreallocatedView(estimatedSize int) (TrieView, error) { return newView, nil } -func (db *Database) Has(k []byte) (bool, error) { +func (db *merkleDB) Has(k []byte) (bool, error) { db.lock.RLock() defer db.lock.RUnlock() @@ -626,7 +672,7 @@ func (db *Database) Has(k []byte) (bool, error) { return err == nil, err } -func (db *Database) HealthCheck(ctx context.Context) (interface{}, error) { +func (db *merkleDB) HealthCheck(ctx context.Context) (interface{}, error) { db.lock.RLock() defer db.lock.RUnlock() @@ -636,7 +682,7 @@ func (db *Database) HealthCheck(ctx context.Context) (interface{}, error) { return db.nodeDB.HealthCheck(ctx) } -func (db *Database) Insert(ctx context.Context, k, v []byte) error { +func (db *merkleDB) Insert(ctx context.Context, k, v []byte) error { db.commitLock.Lock() defer db.commitLock.Unlock() @@ -655,34 +701,34 @@ func (db *Database) Insert(ctx context.Context, k, v []byte) error { return view.commitToDB(ctx) } -func (db *Database) NewBatch() database.Batch { +func (db *merkleDB) NewBatch() database.Batch { return &batch{ db: db, } } -func (db *Database) NewIterator() database.Iterator { +func (db *merkleDB) NewIterator() database.Iterator { return &iterator{ nodeIter: db.nodeDB.NewIterator(), db: db, } } -func (db *Database) NewIteratorWithStart(start []byte) database.Iterator { +func (db *merkleDB) NewIteratorWithStart(start []byte) database.Iterator { return &iterator{ nodeIter: db.nodeDB.NewIteratorWithStart(newPath(start).Bytes()), db: db, } } -func (db *Database) NewIteratorWithPrefix(prefix []byte) database.Iterator { +func (db *merkleDB) NewIteratorWithPrefix(prefix []byte) database.Iterator { return &iterator{ nodeIter: db.nodeDB.NewIteratorWithPrefix(newPath(prefix).Bytes()), db: db, } } -func (db *Database) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator { +func (db *merkleDB) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator { startBytes := newPath(start).Bytes() prefixBytes := newPath(prefix).Bytes() return &iterator{ @@ -696,7 +742,7 @@ func (db *Database) NewIteratorWithStartAndPrefix(start, prefix []byte) database // the movement of [node] from [db.nodeCache] to [db.nodeDB] is atomic. // As soon as [db.nodeCache] no longer has [node], [db.nodeDB] does. // Non-nil error is fatal -- causes [db] to close. -func (db *Database) onEviction(node *node) error { +func (db *merkleDB) onEviction(node *node) error { if node == nil || node.hasValue() { // only persist intermediary nodes return nil @@ -722,11 +768,11 @@ func (db *Database) onEviction(node *node) error { } // Put upserts the key/value pair into the db. -func (db *Database) Put(k, v []byte) error { +func (db *merkleDB) Put(k, v []byte) error { return db.Insert(context.Background(), k, v) } -func (db *Database) Remove(ctx context.Context, key []byte) error { +func (db *merkleDB) Remove(ctx context.Context, key []byte) error { db.commitLock.Lock() defer db.commitLock.Unlock() @@ -745,7 +791,7 @@ func (db *Database) Remove(ctx context.Context, key []byte) error { return view.commitToDB(ctx) } -func (db *Database) commitBatch(ops []database.BatchOp) error { +func (db *merkleDB) commitBatch(ops []database.BatchOp) error { db.commitLock.Lock() defer db.commitLock.Unlock() @@ -761,17 +807,17 @@ func (db *Database) commitBatch(ops []database.BatchOp) error { } // CommitToParent is a no-op for the db because it has no parent -func (*Database) CommitToParent(context.Context) error { +func (*merkleDB) CommitToParent(context.Context) error { return nil } // commitToDB is a no-op for the db because it is the db -func (*Database) commitToDB(context.Context) error { +func (*merkleDB) commitToDB(context.Context) error { return nil } // commitChanges commits the changes in trieToCommit to the db -func (db *Database) commitChanges(ctx context.Context, trieToCommit *trieView) error { +func (db *merkleDB) commitChanges(ctx context.Context, trieToCommit *trieView) error { db.lock.Lock() defer db.lock.Unlock() @@ -870,7 +916,7 @@ func (db *Database) commitChanges(ctx context.Context, trieToCommit *trieView) e // moveChildViewsToDB removes any child views from the trieToCommit and moves them to the db // assumes [db.lock] is held -func (db *Database) moveChildViewsToDB(trieToCommit *trieView) { +func (db *merkleDB) moveChildViewsToDB(trieToCommit *trieView) { trieToCommit.validityTrackingLock.Lock() defer trieToCommit.validityTrackingLock.Unlock() @@ -883,13 +929,146 @@ func (db *Database) moveChildViewsToDB(trieToCommit *trieView) { // CommitToDB is a no-op for db since it is already in sync with itself. // This exists to satisfy the TrieView interface. -func (*Database) CommitToDB(context.Context) error { +func (*merkleDB) CommitToDB(context.Context) error { + return nil +} + +func (db *merkleDB) VerifyChangeProof( + ctx context.Context, + proof *ChangeProof, + start []byte, + end []byte, + expectedEndRootID ids.ID, +) error { + if len(end) > 0 && bytes.Compare(start, end) > 0 { + return ErrStartAfterEnd + } + + if !proof.HadRootsInHistory { + // The node we requested the proof from didn't have sufficient + // history to fulfill this request. + if !proof.Empty() { + // cannot have any changes if the root was missing + return ErrDataInMissingRootProof + } + return nil + } + + switch { + case proof.Empty(): + return ErrNoMerkleProof + case len(end) > 0 && len(proof.EndProof) == 0: + // We requested an end proof but didn't get one. + return ErrNoEndProof + case len(start) > 0 && len(proof.StartProof) == 0 && len(proof.EndProof) == 0: + // We requested a start proof but didn't get one. + // Note that we also have to check that [proof.EndProof] is empty + // to handle the case that the start proof is empty because all + // its nodes are also in the end proof, and those nodes are omitted. + return ErrNoStartProof + } + + // Make sure the key-value pairs are sorted and in [start, end]. + if err := verifyKeyChanges(proof.KeyChanges, start, end); err != nil { + return err + } + + smallestPath := newPath(start) + + // Make sure the start proof, if given, is well-formed. + if err := verifyProofPath(proof.StartProof, smallestPath); err != nil { + return err + } + + // Find the greatest key in [proof.KeyChanges] + // Note that [proof.EndProof] is a proof for this key. + // [largestPath] is also used when we add children of proof nodes to [trie] below. + largestKey := end + if len(proof.KeyChanges) > 0 { + // If [proof] has key-value pairs, we should insert children + // greater than [end] to ancestors of the node containing [end] + // so that we get the expected root ID. + largestKey = proof.KeyChanges[len(proof.KeyChanges)-1].Key + } + largestPath := newPath(largestKey) + + // Make sure the end proof, if given, is well-formed. + if err := verifyProofPath(proof.EndProof, largestPath); err != nil { + return err + } + + keyValues := make(map[path]Maybe[[]byte], len(proof.KeyChanges)) + for _, keyValue := range proof.KeyChanges { + keyValues[newPath(keyValue.Key)] = keyValue.Value + } + + // want to prevent commit writes to DB, but not prevent db reads + db.commitLock.RLock() + defer db.commitLock.RUnlock() + + if err := verifyAllChangeProofKeyValuesPresent( + ctx, + db, + proof.StartProof, + smallestPath, + largestPath, + keyValues, + ); err != nil { + return err + } + + if err := verifyAllChangeProofKeyValuesPresent( + ctx, + db, + proof.EndProof, + smallestPath, + largestPath, + keyValues, + ); err != nil { + return err + } + + // Don't need to lock [view] because nobody else has a reference to it. + view, err := db.newUntrackedView(len(proof.KeyChanges)) + if err != nil { + return err + } + + // Insert the key-value pairs into the trie. + for _, kv := range proof.KeyChanges { + if kv.Value.IsNothing() { + if err := view.removeFromTrie(newPath(kv.Key)); err != nil { + return err + } + } else if _, err := view.insertIntoTrie(newPath(kv.Key), kv.Value); err != nil { + return err + } + } + + // For all the nodes along the edges of the proof, insert children < [start] and > [largestKey] + // into the trie so that we get the expected root ID (if this proof is valid). + if err := addPathInfo(view, proof.StartProof, smallestPath, largestPath); err != nil { + return err + } + if err := addPathInfo(view, proof.EndProof, smallestPath, largestPath); err != nil { + return err + } + + // Make sure we get the expected root. + calculatedRoot, err := view.getMerkleRoot(ctx) + if err != nil { + return err + } + if expectedEndRootID != calculatedRoot { + return fmt.Errorf("%w:[%s], expected:[%s]", ErrInvalidProof, calculatedRoot, expectedEndRootID) + } + return nil } // Invalidates and removes any child views that aren't [exception]. // Assumes [db.lock] is held. -func (db *Database) invalidateChildrenExcept(exception *trieView) { +func (db *merkleDB) invalidateChildrenExcept(exception *trieView) { isTrackedView := false for _, childView := range db.childViews { @@ -905,7 +1084,7 @@ func (db *Database) invalidateChildrenExcept(exception *trieView) { } } -func (db *Database) initializeRootIfNeeded() (ids.ID, error) { +func (db *merkleDB) initializeRootIfNeeded() (ids.ID, error) { // ensure that root exists nodeBytes, err := db.nodeDB.Get(rootKey) if err == nil { @@ -945,7 +1124,7 @@ func (db *Database) initializeRootIfNeeded() (ids.ID, error) { // Returns a view of the trie as it was when it had root [rootID] for keys within range [start, end]. // Assumes [db.commitLock] is read locked. -func (db *Database) getHistoricalViewForRange( +func (db *merkleDB) getHistoricalViewForRange( rootID ids.ID, start []byte, end []byte, @@ -967,7 +1146,7 @@ func (db *Database) getHistoricalViewForRange( // Returns all of the keys in range [start, end] that aren't in [keySet]. // If [start] is nil, then the range has no lower bound. // If [end] is nil, then the range has no upper bound. -func (db *Database) getKeysNotInSet(start, end []byte, keySet set.Set[string]) ([][]byte, error) { +func (db *merkleDB) getKeysNotInSet(start, end []byte, keySet set.Set[string]) ([][]byte, error) { db.lock.RLock() defer db.lock.RUnlock() @@ -991,7 +1170,7 @@ func (db *Database) getKeysNotInSet(start, end []byte, keySet set.Set[string]) ( // This copy may be edited by the caller without affecting the database state. // Returns database.ErrNotFound if the node doesn't exist. // Assumes [db.lock] isn't held. -func (db *Database) getEditableNode(key path) (*node, error) { +func (db *merkleDB) getEditableNode(key path) (*node, error) { db.lock.RLock() defer db.lock.RUnlock() @@ -1006,7 +1185,7 @@ func (db *Database) getEditableNode(key path) (*node, error) { // Editing the returned node affects the database state. // Returns database.ErrNotFound if the node doesn't exist. // Assumes [db.lock] is read locked. -func (db *Database) getNode(key path) (*node, error) { +func (db *merkleDB) getNode(key path) (*node, error) { if db.closed { return nil, database.ErrClosed } @@ -1046,7 +1225,7 @@ func (db *Database) getNode(key path) (*node, error) { // If [lock], grabs [db.lock]'s read lock. // Otherwise assumes [db.lock] is already read locked. -func (db *Database) getKeyValues( +func (db *merkleDB) getKeyValues( start []byte, end []byte, maxLength int, @@ -1095,7 +1274,7 @@ func (db *Database) getKeyValues( // Returns a new view atop [db] with the changes in [ops] applied to it. // Assumes [db.commitLock] is read locked. -func (db *Database) prepareBatchView(ops []database.BatchOp) (*trieView, error) { +func (db *merkleDB) prepareBatchView(ops []database.BatchOp) (*trieView, error) { view, err := db.newUntrackedView(len(ops)) if err != nil { return nil, err @@ -1119,7 +1298,7 @@ func (db *Database) prepareBatchView(ops []database.BatchOp) (*trieView, error) // Returns a new view atop [db] with the key/value pairs in [proof.KeyValues] // inserted and the key/value pairs in [proof.DeletedKeys] removed. // Assumes [db.commitLock] is locked. -func (db *Database) prepareChangeProofView(proof *ChangeProof) (*trieView, error) { +func (db *merkleDB) prepareChangeProofView(proof *ChangeProof) (*trieView, error) { view, err := db.newUntrackedView(len(proof.KeyChanges)) if err != nil { return nil, err @@ -1141,7 +1320,7 @@ func (db *Database) prepareChangeProofView(proof *ChangeProof) (*trieView, error // Returns a new view atop [db] with the key/value pairs in [proof.KeyValues] added and // any existing key-value pairs in the proof's range but not in the proof removed. // Assumes [db.commitLock] is locked. -func (db *Database) prepareRangeProofView(start []byte, proof *RangeProof) (*trieView, error) { +func (db *merkleDB) prepareRangeProofView(start []byte, proof *RangeProof) (*trieView, error) { // Don't need to lock [view] because nobody else has a reference to it. view, err := db.newUntrackedView(len(proof.KeyValues)) if err != nil { @@ -1172,14 +1351,14 @@ func (db *Database) prepareRangeProofView(start []byte, proof *RangeProof) (*tri } // Non-nil error is fatal -- [db] will close. -func (db *Database) putNodeInCache(key path, n *node) error { +func (db *merkleDB) putNodeInCache(key path, n *node) error { // TODO Cache metrics // Note that this may cause a node to be evicted from the cache, // which will call [OnEviction]. return db.nodeCache.Put(key, n) } -func (db *Database) getNodeInCache(key path) (*node, bool) { +func (db *merkleDB) getNodeInCache(key path) (*node, bool) { // TODO Cache metrics if node, ok := db.nodeCache.Get(key); ok { return node, true diff --git a/x/merkledb/db_test.go b/x/merkledb/db_test.go index 8ff11b91646..4babc62a106 100644 --- a/x/merkledb/db_test.go +++ b/x/merkledb/db_test.go @@ -700,7 +700,7 @@ type testOperation struct { delete bool } -func applyOperations(t *Database, ops []*testOperation) (Trie, error) { +func applyOperations(t *merkleDB, ops []*testOperation) (Trie, error) { view, err := t.NewView() if err != nil { return nil, err @@ -810,9 +810,9 @@ func runRandDBTest(require *require.Assertions, r *rand.Rand, rt randTest) { require.NoError(err) changeProofDB, err := getBasicDB() require.NoError(err) - err = changeProof.Verify( + err = changeProofDB.VerifyChangeProof( context.Background(), - changeProofDB, + changeProof, step.key, step.value, root, diff --git a/x/merkledb/iterator.go b/x/merkledb/iterator.go index 7a170b38c7f..7b7612d599e 100644 --- a/x/merkledb/iterator.go +++ b/x/merkledb/iterator.go @@ -8,7 +8,7 @@ import "github.com/ava-labs/avalanchego/database" var _ database.Iterator = (*iterator)(nil) type iterator struct { - db *Database + db *merkleDB nodeIter database.Iterator current *node err error diff --git a/x/merkledb/mock_db.go b/x/merkledb/mock_db.go new file mode 100644 index 00000000000..f89f5d35939 --- /dev/null +++ b/x/merkledb/mock_db.go @@ -0,0 +1,462 @@ +// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ava-labs/avalanchego/x/merkledb (interfaces: MerkleDB) + +// Package merkledb is a generated GoMock package. +package merkledb + +import ( + context "context" + reflect "reflect" + + database "github.com/ava-labs/avalanchego/database" + ids "github.com/ava-labs/avalanchego/ids" + set "github.com/ava-labs/avalanchego/utils/set" + gomock "github.com/golang/mock/gomock" +) + +// MockMerkleDB is a mock of MerkleDB interface. +type MockMerkleDB struct { + ctrl *gomock.Controller + recorder *MockMerkleDBMockRecorder +} + +// MockMerkleDBMockRecorder is the mock recorder for MockMerkleDB. +type MockMerkleDBMockRecorder struct { + mock *MockMerkleDB +} + +// NewMockMerkleDB creates a new mock instance. +func NewMockMerkleDB(ctrl *gomock.Controller) *MockMerkleDB { + mock := &MockMerkleDB{ctrl: ctrl} + mock.recorder = &MockMerkleDBMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMerkleDB) EXPECT() *MockMerkleDBMockRecorder { + return m.recorder +} + +// Close mocks base method. +func (m *MockMerkleDB) Close() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Close") + ret0, _ := ret[0].(error) + return ret0 +} + +// Close indicates an expected call of Close. +func (mr *MockMerkleDBMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockMerkleDB)(nil).Close)) +} + +// CommitChangeProof mocks base method. +func (m *MockMerkleDB) CommitChangeProof(arg0 context.Context, arg1 *ChangeProof) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CommitChangeProof", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// CommitChangeProof indicates an expected call of CommitChangeProof. +func (mr *MockMerkleDBMockRecorder) CommitChangeProof(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitChangeProof", reflect.TypeOf((*MockMerkleDB)(nil).CommitChangeProof), arg0, arg1) +} + +// CommitRangeProof mocks base method. +func (m *MockMerkleDB) CommitRangeProof(arg0 context.Context, arg1 []byte, arg2 *RangeProof) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CommitRangeProof", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// CommitRangeProof indicates an expected call of CommitRangeProof. +func (mr *MockMerkleDBMockRecorder) CommitRangeProof(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitRangeProof", reflect.TypeOf((*MockMerkleDB)(nil).CommitRangeProof), arg0, arg1, arg2) +} + +// Compact mocks base method. +func (m *MockMerkleDB) Compact(arg0, arg1 []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Compact", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Compact indicates an expected call of Compact. +func (mr *MockMerkleDBMockRecorder) Compact(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Compact", reflect.TypeOf((*MockMerkleDB)(nil).Compact), arg0, arg1) +} + +// Delete mocks base method. +func (m *MockMerkleDB) Delete(arg0 []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockMerkleDBMockRecorder) Delete(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockMerkleDB)(nil).Delete), arg0) +} + +// Get mocks base method. +func (m *MockMerkleDB) Get(arg0 []byte) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockMerkleDBMockRecorder) Get(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockMerkleDB)(nil).Get), arg0) +} + +// GetChangeProof mocks base method. +func (m *MockMerkleDB) GetChangeProof(arg0 context.Context, arg1, arg2 ids.ID, arg3, arg4 []byte, arg5 int) (*ChangeProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetChangeProof", arg0, arg1, arg2, arg3, arg4, arg5) + ret0, _ := ret[0].(*ChangeProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetChangeProof indicates an expected call of GetChangeProof. +func (mr *MockMerkleDBMockRecorder) GetChangeProof(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChangeProof", reflect.TypeOf((*MockMerkleDB)(nil).GetChangeProof), arg0, arg1, arg2, arg3, arg4, arg5) +} + +// GetMerkleRoot mocks base method. +func (m *MockMerkleDB) GetMerkleRoot(arg0 context.Context) (ids.ID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMerkleRoot", arg0) + ret0, _ := ret[0].(ids.ID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMerkleRoot indicates an expected call of GetMerkleRoot. +func (mr *MockMerkleDBMockRecorder) GetMerkleRoot(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMerkleRoot", reflect.TypeOf((*MockMerkleDB)(nil).GetMerkleRoot), arg0) +} + +// GetProof mocks base method. +func (m *MockMerkleDB) GetProof(arg0 context.Context, arg1 []byte) (*Proof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetProof", arg0, arg1) + ret0, _ := ret[0].(*Proof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetProof indicates an expected call of GetProof. +func (mr *MockMerkleDBMockRecorder) GetProof(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProof", reflect.TypeOf((*MockMerkleDB)(nil).GetProof), arg0, arg1) +} + +// GetRangeProof mocks base method. +func (m *MockMerkleDB) GetRangeProof(arg0 context.Context, arg1, arg2 []byte, arg3 int) (*RangeProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRangeProof", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*RangeProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRangeProof indicates an expected call of GetRangeProof. +func (mr *MockMerkleDBMockRecorder) GetRangeProof(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRangeProof", reflect.TypeOf((*MockMerkleDB)(nil).GetRangeProof), arg0, arg1, arg2, arg3) +} + +// GetRangeProofAtRoot mocks base method. +func (m *MockMerkleDB) GetRangeProofAtRoot(arg0 context.Context, arg1 ids.ID, arg2, arg3 []byte, arg4 int) (*RangeProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRangeProofAtRoot", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(*RangeProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRangeProofAtRoot indicates an expected call of GetRangeProofAtRoot. +func (mr *MockMerkleDBMockRecorder) GetRangeProofAtRoot(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRangeProofAtRoot", reflect.TypeOf((*MockMerkleDB)(nil).GetRangeProofAtRoot), arg0, arg1, arg2, arg3, arg4) +} + +// GetValue mocks base method. +func (m *MockMerkleDB) GetValue(arg0 context.Context, arg1 []byte) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetValue", arg0, arg1) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetValue indicates an expected call of GetValue. +func (mr *MockMerkleDBMockRecorder) GetValue(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValue", reflect.TypeOf((*MockMerkleDB)(nil).GetValue), arg0, arg1) +} + +// GetValues mocks base method. +func (m *MockMerkleDB) GetValues(arg0 context.Context, arg1 [][]byte) ([][]byte, []error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetValues", arg0, arg1) + ret0, _ := ret[0].([][]byte) + ret1, _ := ret[1].([]error) + return ret0, ret1 +} + +// GetValues indicates an expected call of GetValues. +func (mr *MockMerkleDBMockRecorder) GetValues(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValues", reflect.TypeOf((*MockMerkleDB)(nil).GetValues), arg0, arg1) +} + +// Has mocks base method. +func (m *MockMerkleDB) Has(arg0 []byte) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Has", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Has indicates an expected call of Has. +func (mr *MockMerkleDBMockRecorder) Has(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Has", reflect.TypeOf((*MockMerkleDB)(nil).Has), arg0) +} + +// HealthCheck mocks base method. +func (m *MockMerkleDB) HealthCheck(arg0 context.Context) (interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HealthCheck", arg0) + ret0, _ := ret[0].(interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HealthCheck indicates an expected call of HealthCheck. +func (mr *MockMerkleDBMockRecorder) HealthCheck(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockMerkleDB)(nil).HealthCheck), arg0) +} + +// Insert mocks base method. +func (m *MockMerkleDB) Insert(arg0 context.Context, arg1, arg2 []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Insert", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// Insert indicates an expected call of Insert. +func (mr *MockMerkleDBMockRecorder) Insert(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockMerkleDB)(nil).Insert), arg0, arg1, arg2) +} + +// NewBatch mocks base method. +func (m *MockMerkleDB) NewBatch() database.Batch { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewBatch") + ret0, _ := ret[0].(database.Batch) + return ret0 +} + +// NewBatch indicates an expected call of NewBatch. +func (mr *MockMerkleDBMockRecorder) NewBatch() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewBatch", reflect.TypeOf((*MockMerkleDB)(nil).NewBatch)) +} + +// NewIterator mocks base method. +func (m *MockMerkleDB) NewIterator() database.Iterator { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewIterator") + ret0, _ := ret[0].(database.Iterator) + return ret0 +} + +// NewIterator indicates an expected call of NewIterator. +func (mr *MockMerkleDBMockRecorder) NewIterator() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewIterator", reflect.TypeOf((*MockMerkleDB)(nil).NewIterator)) +} + +// NewIteratorWithPrefix mocks base method. +func (m *MockMerkleDB) NewIteratorWithPrefix(arg0 []byte) database.Iterator { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewIteratorWithPrefix", arg0) + ret0, _ := ret[0].(database.Iterator) + return ret0 +} + +// NewIteratorWithPrefix indicates an expected call of NewIteratorWithPrefix. +func (mr *MockMerkleDBMockRecorder) NewIteratorWithPrefix(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewIteratorWithPrefix", reflect.TypeOf((*MockMerkleDB)(nil).NewIteratorWithPrefix), arg0) +} + +// NewIteratorWithStart mocks base method. +func (m *MockMerkleDB) NewIteratorWithStart(arg0 []byte) database.Iterator { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewIteratorWithStart", arg0) + ret0, _ := ret[0].(database.Iterator) + return ret0 +} + +// NewIteratorWithStart indicates an expected call of NewIteratorWithStart. +func (mr *MockMerkleDBMockRecorder) NewIteratorWithStart(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewIteratorWithStart", reflect.TypeOf((*MockMerkleDB)(nil).NewIteratorWithStart), arg0) +} + +// NewIteratorWithStartAndPrefix mocks base method. +func (m *MockMerkleDB) NewIteratorWithStartAndPrefix(arg0, arg1 []byte) database.Iterator { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewIteratorWithStartAndPrefix", arg0, arg1) + ret0, _ := ret[0].(database.Iterator) + return ret0 +} + +// NewIteratorWithStartAndPrefix indicates an expected call of NewIteratorWithStartAndPrefix. +func (mr *MockMerkleDBMockRecorder) NewIteratorWithStartAndPrefix(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewIteratorWithStartAndPrefix", reflect.TypeOf((*MockMerkleDB)(nil).NewIteratorWithStartAndPrefix), arg0, arg1) +} + +// NewPreallocatedView mocks base method. +func (m *MockMerkleDB) NewPreallocatedView(arg0 int) (TrieView, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewPreallocatedView", arg0) + ret0, _ := ret[0].(TrieView) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NewPreallocatedView indicates an expected call of NewPreallocatedView. +func (mr *MockMerkleDBMockRecorder) NewPreallocatedView(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewPreallocatedView", reflect.TypeOf((*MockMerkleDB)(nil).NewPreallocatedView), arg0) +} + +// NewView mocks base method. +func (m *MockMerkleDB) NewView() (TrieView, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewView") + ret0, _ := ret[0].(TrieView) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NewView indicates an expected call of NewView. +func (mr *MockMerkleDBMockRecorder) NewView() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewView", reflect.TypeOf((*MockMerkleDB)(nil).NewView)) +} + +// Put mocks base method. +func (m *MockMerkleDB) Put(arg0, arg1 []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Put", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Put indicates an expected call of Put. +func (mr *MockMerkleDBMockRecorder) Put(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockMerkleDB)(nil).Put), arg0, arg1) +} + +// Remove mocks base method. +func (m *MockMerkleDB) Remove(arg0 context.Context, arg1 []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Remove", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Remove indicates an expected call of Remove. +func (mr *MockMerkleDBMockRecorder) Remove(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockMerkleDB)(nil).Remove), arg0, arg1) +} + +// VerifyChangeProof mocks base method. +func (m *MockMerkleDB) VerifyChangeProof(arg0 context.Context, arg1 *ChangeProof, arg2, arg3 []byte, arg4 ids.ID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyChangeProof", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(error) + return ret0 +} + +// VerifyChangeProof indicates an expected call of VerifyChangeProof. +func (mr *MockMerkleDBMockRecorder) VerifyChangeProof(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyChangeProof", reflect.TypeOf((*MockMerkleDB)(nil).VerifyChangeProof), arg0, arg1, arg2, arg3, arg4) +} + +// getEditableNode mocks base method. +func (m *MockMerkleDB) getEditableNode(arg0 path) (*node, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getEditableNode", arg0) + ret0, _ := ret[0].(*node) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// getEditableNode indicates an expected call of getEditableNode. +func (mr *MockMerkleDBMockRecorder) getEditableNode(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getEditableNode", reflect.TypeOf((*MockMerkleDB)(nil).getEditableNode), arg0) +} + +// getKeyValues mocks base method. +func (m *MockMerkleDB) getKeyValues(arg0, arg1 []byte, arg2 int, arg3 set.Set[string], arg4 bool) ([]KeyValue, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getKeyValues", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].([]KeyValue) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// getKeyValues indicates an expected call of getKeyValues. +func (mr *MockMerkleDBMockRecorder) getKeyValues(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getKeyValues", reflect.TypeOf((*MockMerkleDB)(nil).getKeyValues), arg0, arg1, arg2, arg3, arg4) +} + +// getValue mocks base method. +func (m *MockMerkleDB) getValue(arg0 path, arg1 bool) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getValue", arg0, arg1) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// getValue indicates an expected call of getValue. +func (mr *MockMerkleDBMockRecorder) getValue(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getValue", reflect.TypeOf((*MockMerkleDB)(nil).getValue), arg0, arg1) +} diff --git a/x/merkledb/proof.go b/x/merkledb/proof.go index 842ee482e2b..b81d38421f9 100644 --- a/x/merkledb/proof.go +++ b/x/merkledb/proof.go @@ -305,157 +305,12 @@ type ChangeProof struct { KeyChanges []KeyChange } -// Returns nil iff all of the following hold: -// - [start] <= [end]. -// - [proof] is non-empty iff [proof.HadRootsInHistory]. -// - All keys in [proof.KeyValues] and [proof.DeletedKeys] are in [start, end]. -// If [start] is empty, all keys are considered > [start]. -// If [end] is empty, all keys are considered < [end]. -// - [proof.KeyValues] and [proof.DeletedKeys] are sorted in order of increasing key. -// - [proof.StartProof] and [proof.EndProof] are well-formed. -// - When the keys in [proof.KeyValues] are added to [db] and the keys in [proof.DeletedKeys] -// are removed from [db], the root ID of [db] is [expectedEndRootID]. -// -// Assumes [db.lock] isn't held. -func (proof *ChangeProof) Verify( - ctx context.Context, - db *Database, - start []byte, - end []byte, - expectedEndRootID ids.ID, -) error { - if len(end) > 0 && bytes.Compare(start, end) > 0 { - return ErrStartAfterEnd - } - - if !proof.HadRootsInHistory { - // The node we requested the proof from didn't have sufficient - // history to fulfill this request. - if !proof.Empty() { - // cannot have any changes if the root was missing - return ErrDataInMissingRootProof - } - return nil - } - - switch { - case proof.Empty(): - return ErrNoMerkleProof - case len(end) > 0 && len(proof.EndProof) == 0: - // We requested an end proof but didn't get one. - return ErrNoEndProof - case len(start) > 0 && len(proof.StartProof) == 0 && len(proof.EndProof) == 0: - // We requested a start proof but didn't get one. - // Note that we also have to check that [proof.EndProof] is empty - // to handle the case that the start proof is empty because all - // its nodes are also in the end proof, and those nodes are omitted. - return ErrNoStartProof - } - - // Make sure the key-value pairs are sorted and in [start, end]. - if err := verifyKeyChanges(proof.KeyChanges, start, end); err != nil { - return err - } - - smallestPath := newPath(start) - - // Make sure the start proof, if given, is well-formed. - if err := verifyProofPath(proof.StartProof, smallestPath); err != nil { - return err - } - - // Find the greatest key in [proof.KeyChanges] - // Note that [proof.EndProof] is a proof for this key. - // [largestPath] is also used when we add children of proof nodes to [trie] below. - largestKey := end - if len(proof.KeyChanges) > 0 { - // If [proof] has key-value pairs, we should insert children - // greater than [end] to ancestors of the node containing [end] - // so that we get the expected root ID. - largestKey = proof.KeyChanges[len(proof.KeyChanges)-1].Key - } - largestPath := newPath(largestKey) - - // Make sure the end proof, if given, is well-formed. - if err := verifyProofPath(proof.EndProof, largestPath); err != nil { - return err - } - - keyValues := make(map[path]Maybe[[]byte], len(proof.KeyChanges)) - for _, keyValue := range proof.KeyChanges { - keyValues[newPath(keyValue.Key)] = keyValue.Value - } - - // want to prevent commit writes to DB, but not prevent db reads - db.commitLock.RLock() - defer db.commitLock.RUnlock() - - if err := verifyAllChangeProofKeyValuesPresent( - ctx, - db, - proof.StartProof, - smallestPath, - largestPath, - keyValues, - ); err != nil { - return err - } - - if err := verifyAllChangeProofKeyValuesPresent( - ctx, - db, - proof.EndProof, - smallestPath, - largestPath, - keyValues, - ); err != nil { - return err - } - - // Don't need to lock [view] because nobody else has a reference to it. - view, err := db.newUntrackedView(len(proof.KeyChanges)) - if err != nil { - return err - } - - // Insert the key-value pairs into the trie. - for _, kv := range proof.KeyChanges { - if kv.Value.IsNothing() { - if err := view.removeFromTrie(newPath(kv.Key)); err != nil { - return err - } - } else if _, err := view.insertIntoTrie(newPath(kv.Key), kv.Value); err != nil { - return err - } - } - - // For all the nodes along the edges of the proof, insert children < [start] and > [largestKey] - // into the trie so that we get the expected root ID (if this proof is valid). - if err := addPathInfo(view, proof.StartProof, smallestPath, largestPath); err != nil { - return err - } - if err := addPathInfo(view, proof.EndProof, smallestPath, largestPath); err != nil { - return err - } - - // Make sure we get the expected root. - calculatedRoot, err := view.getMerkleRoot(ctx) - if err != nil { - return err - } - if expectedEndRootID != calculatedRoot { - return fmt.Errorf("%w:[%s], expected:[%s]", ErrInvalidProof, calculatedRoot, expectedEndRootID) - } - - return nil -} - // Verifies that all values present in the [proof]: // - Are nothing when deleted, not in the db, or the node has an odd path length. // - if the node's path is within the key range, that has a value that matches the value passed in the change list or in the db func verifyAllChangeProofKeyValuesPresent( ctx context.Context, - db *Database, + db MerkleDB, proof []ProofNode, start path, end path, diff --git a/x/merkledb/proof_test.go b/x/merkledb/proof_test.go index 87014af854a..6b602561809 100644 --- a/x/merkledb/proof_test.go +++ b/x/merkledb/proof_test.go @@ -16,7 +16,7 @@ import ( "github.com/ava-labs/avalanchego/utils/hashing" ) -func getBasicDB() (*Database, error) { +func getBasicDB() (*merkleDB, error) { return newDatabase( context.Background(), memdb.New(), @@ -29,7 +29,7 @@ func getBasicDB() (*Database, error) { ) } -func writeBasicBatch(t *testing.T, db *Database) { +func writeBasicBatch(t *testing.T, db *merkleDB) { batch := db.NewBatch() require.NoError(t, batch.Put([]byte{0}, []byte{0})) require.NoError(t, batch.Put([]byte{1}, []byte{1})) @@ -905,7 +905,7 @@ func Test_ChangeProof_Missing_History_For_EndRoot(t *testing.T) { require.NotNil(t, proof) require.False(t, proof.HadRootsInHistory) - require.NoError(t, proof.Verify(context.Background(), db, nil, nil, db.getMerkleRoot())) + require.NoError(t, db.VerifyChangeProof(context.Background(), proof, nil, nil, db.getMerkleRoot())) } func Test_ChangeProof_BadBounds(t *testing.T) { @@ -1005,7 +1005,7 @@ func Test_ChangeProof_Verify(t *testing.T) { require.NoError(t, err) require.NotNil(t, proof) - err = proof.Verify(context.Background(), dbClone, []byte("key21"), []byte("key30"), db.getMerkleRoot()) + err = dbClone.VerifyChangeProof(context.Background(), proof, []byte("key21"), []byte("key30"), db.getMerkleRoot()) require.NoError(t, err) // low maxLength @@ -1013,7 +1013,7 @@ func Test_ChangeProof_Verify(t *testing.T) { require.NoError(t, err) require.NotNil(t, proof) - err = proof.Verify(context.Background(), dbClone, nil, nil, db.getMerkleRoot()) + err = dbClone.VerifyChangeProof(context.Background(), proof, nil, nil, db.getMerkleRoot()) require.NoError(t, err) // nil start/end @@ -1021,7 +1021,7 @@ func Test_ChangeProof_Verify(t *testing.T) { require.NoError(t, err) require.NotNil(t, proof) - err = proof.Verify(context.Background(), dbClone, nil, nil, endRoot) + err = dbClone.VerifyChangeProof(context.Background(), proof, nil, nil, endRoot) require.NoError(t, err) err = dbClone.CommitChangeProof(context.Background(), proof) @@ -1035,7 +1035,7 @@ func Test_ChangeProof_Verify(t *testing.T) { require.NoError(t, err) require.NotNil(t, proof) - err = proof.Verify(context.Background(), dbClone, []byte("key20"), []byte("key30"), db.getMerkleRoot()) + err = dbClone.VerifyChangeProof(context.Background(), proof, []byte("key20"), []byte("key30"), db.getMerkleRoot()) require.NoError(t, err) } @@ -1098,7 +1098,7 @@ func Test_ChangeProof_Verify_Bad_Data(t *testing.T) { tt.malform(proof) - err = proof.Verify(context.Background(), dbClone, []byte{2}, []byte{3, 0}, db.getMerkleRoot()) + err = dbClone.VerifyChangeProof(context.Background(), proof, []byte{2}, []byte{3, 0}, db.getMerkleRoot()) require.ErrorIs(t, err, tt.expectedErr) }) } @@ -1315,7 +1315,7 @@ func Test_ChangeProof_Syntactic_Verify(t *testing.T) { t.Run(tt.name, func(t *testing.T) { db, err := getBasicDB() require.NoError(t, err) - err = tt.proof.Verify(context.Background(), db, tt.start, tt.end, ids.Empty) + err = db.VerifyChangeProof(context.Background(), tt.proof, tt.start, tt.end, ids.Empty) require.ErrorIs(t, err, tt.expectedErr) }) } diff --git a/x/merkledb/trie_test.go b/x/merkledb/trie_test.go index 53f69f33d09..d47b23ecaa8 100644 --- a/x/merkledb/trie_test.go +++ b/x/merkledb/trie_test.go @@ -36,7 +36,7 @@ func getNodeValue(t ReadOnlyTrie, key string) ([]byte, error) { return closestNode.value.value, nil } - if asDatabases, ok := t.(*Database); ok { + if asDatabases, ok := t.(*merkleDB); ok { view, err := asDatabases.NewView() if err != nil { return nil, err diff --git a/x/merkledb/trieview.go b/x/merkledb/trieview.go index fec31a719b6..b8601ed8a38 100644 --- a/x/merkledb/trieview.go +++ b/x/merkledb/trieview.go @@ -95,7 +95,7 @@ type trieView struct { // A Nothing value indicates that the key has been removed. unappliedValueChanges map[path]Maybe[[]byte] - db *Database + db *merkleDB // The root of the trie represented by this view. root *node @@ -155,7 +155,7 @@ func (t *trieView) NewPreallocatedView( // Creates a new view with the given [parentTrie]. func newTrieView( - db *Database, + db *merkleDB, parentTrie TrieView, root *node, estimatedSize int, @@ -176,7 +176,7 @@ func newTrieView( // Creates a new view with the given [parentTrie]. func newTrieViewWithChanges( - db *Database, + db *merkleDB, parentTrie TrieView, changes *changeSummary, estimatedSize int, diff --git a/x/sync/client.go b/x/sync/client.go index 73744158e78..6502893e62f 100644 --- a/x/sync/client.go +++ b/x/sync/client.go @@ -45,7 +45,7 @@ type Client interface { // GetChangeProof synchronously sends the given request, returning a parsed ChangesResponse or error // [verificationDB] is the local db that has all key/values in it for the proof's startroot within the proof's key range // Note: this verifies the response including the change proof. - GetChangeProof(ctx context.Context, request *syncpb.ChangeProofRequest, verificationDB *merkledb.Database) (*merkledb.ChangeProof, error) + GetChangeProof(ctx context.Context, request *syncpb.ChangeProofRequest, verificationDB merkledb.MerkleDB) (*merkledb.ChangeProof, error) } type client struct { @@ -79,7 +79,7 @@ func NewClient(config *ClientConfig) Client { // GetChangeProof synchronously retrieves the change proof given by [req]. // Upon failure, retries until the context is expired. // The returned change proof is verified. -func (c *client) GetChangeProof(ctx context.Context, req *syncpb.ChangeProofRequest, db *merkledb.Database) (*merkledb.ChangeProof, error) { +func (c *client) GetChangeProof(ctx context.Context, req *syncpb.ChangeProofRequest, db merkledb.MerkleDB) (*merkledb.ChangeProof, error) { parseFn := func(ctx context.Context, responseBytes []byte) (*merkledb.ChangeProof, error) { if len(responseBytes) > int(req.BytesLimit) { return nil, fmt.Errorf("%w: (%d) > %d)", errTooManyBytes, len(responseBytes), req.BytesLimit) @@ -101,7 +101,7 @@ func (c *client) GetChangeProof(ctx context.Context, req *syncpb.ChangeProofRequ return nil, err } - if err := changeProof.Verify(ctx, db, req.Start, req.End, endRoot); err != nil { + if err := db.VerifyChangeProof(ctx, changeProof, req.Start, req.End, endRoot); err != nil { return nil, fmt.Errorf("%s due to %w", errInvalidRangeProof, err) } return changeProof, nil diff --git a/x/sync/client_test.go b/x/sync/client_test.go index c7a43c1b2ec..f68685432e7 100644 --- a/x/sync/client_test.go +++ b/x/sync/client_test.go @@ -27,7 +27,7 @@ import ( func sendRangeRequest( t *testing.T, - db *merkledb.Database, + db merkledb.MerkleDB, request *syncpb.RangeProofRequest, maxAttempts uint32, modifyResponse func(*merkledb.RangeProof), @@ -126,7 +126,7 @@ func TestGetRangeProof(t *testing.T) { require.NoError(t, err) tests := map[string]struct { - db *merkledb.Database + db merkledb.MerkleDB request *syncpb.RangeProofRequest modifyResponse func(*merkledb.RangeProof) expectedErr error @@ -282,8 +282,8 @@ func TestGetRangeProof(t *testing.T) { func sendChangeRequest( t *testing.T, - db *merkledb.Database, - verificationDB *merkledb.Database, + db merkledb.MerkleDB, + verificationDB merkledb.MerkleDB, request *syncpb.ChangeProofRequest, maxAttempts uint32, modifyResponse func(*merkledb.ChangeProof), @@ -431,7 +431,7 @@ func TestGetChangeProof(t *testing.T) { require.NoError(t, err) tests := map[string]struct { - db *merkledb.Database + db merkledb.MerkleDB request *syncpb.ChangeProofRequest modifyResponse func(*merkledb.ChangeProof) expectedErr error diff --git a/x/sync/mock_client.go b/x/sync/mock_client.go index 844a2ea01f5..812c6761aea 100644 --- a/x/sync/mock_client.go +++ b/x/sync/mock_client.go @@ -1,3 +1,6 @@ +// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/x/sync (interfaces: Client) @@ -37,7 +40,7 @@ func (m *MockClient) EXPECT() *MockClientMockRecorder { } // GetChangeProof mocks base method. -func (m *MockClient) GetChangeProof(arg0 context.Context, arg1 *sync.ChangeProofRequest, arg2 *merkledb.Database) (*merkledb.ChangeProof, error) { +func (m *MockClient) GetChangeProof(arg0 context.Context, arg1 *sync.ChangeProofRequest, arg2 merkledb.MerkleDB) (*merkledb.ChangeProof, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetChangeProof", arg0, arg1, arg2) ret0, _ := ret[0].(*merkledb.ChangeProof) diff --git a/x/sync/network_server.go b/x/sync/network_server.go index 5c26bac7e7d..0041c87fe1d 100644 --- a/x/sync/network_server.go +++ b/x/sync/network_server.go @@ -45,11 +45,11 @@ var ErrMinProofSizeIsTooLarge = errors.New("cannot generate any proof within the type NetworkServer struct { appSender common.AppSender // Used to respond to peer requests via AppResponse. - db *merkledb.Database + db merkledb.MerkleDB log logging.Logger } -func NewNetworkServer(appSender common.AppSender, db *merkledb.Database, log logging.Logger) *NetworkServer { +func NewNetworkServer(appSender common.AppSender, db merkledb.MerkleDB, log logging.Logger) *NetworkServer { return &NetworkServer{ appSender: appSender, db: db, diff --git a/x/sync/sync_test.go b/x/sync/sync_test.go index 74db943a4a8..b1138fb9694 100644 --- a/x/sync/sync_test.go +++ b/x/sync/sync_test.go @@ -34,10 +34,10 @@ func newNoopTracer() trace.Tracer { } type mockClient struct { - db *merkledb.Database + db merkledb.MerkleDB } -func (client *mockClient) GetChangeProof(ctx context.Context, request *syncpb.ChangeProofRequest, _ *merkledb.Database) (*merkledb.ChangeProof, error) { +func (client *mockClient) GetChangeProof(ctx context.Context, request *syncpb.ChangeProofRequest, _ merkledb.MerkleDB) (*merkledb.ChangeProof, error) { startRoot, err := ids.ToID(request.StartRoot) if err != nil { return nil, err @@ -892,7 +892,7 @@ func Test_Sync_Error_During_Sync(t *testing.T) { }, ).AnyTimes() client.EXPECT().GetChangeProof(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, request *syncpb.ChangeProofRequest, _ *merkledb.Database) (*merkledb.ChangeProof, error) { + func(ctx context.Context, request *syncpb.ChangeProofRequest, _ merkledb.MerkleDB) (*merkledb.ChangeProof, error) { startRoot, err := ids.ToID(request.StartRoot) require.NoError(err) endRoot, err := ids.ToID(request.EndRoot) @@ -985,7 +985,7 @@ func Test_Sync_Result_Correct_Root_Update_Root_During(t *testing.T) { }, ).AnyTimes() client.EXPECT().GetChangeProof(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, request *syncpb.ChangeProofRequest, _ *merkledb.Database) (*merkledb.ChangeProof, error) { + func(ctx context.Context, request *syncpb.ChangeProofRequest, _ merkledb.MerkleDB) (*merkledb.ChangeProof, error) { <-updatedRootChan startRoot, err := ids.ToID(request.StartRoot) require.NoError(err) @@ -1038,8 +1038,8 @@ func Test_Sync_UpdateSyncTarget(t *testing.T) { defer ctrl.Finish() m, err := NewStateSyncManager(StateSyncConfig{ - SyncDB: &merkledb.Database{}, // Not used - Client: NewMockClient(ctrl), // Not used + SyncDB: merkledb.NewMockMerkleDB(ctrl), // Not used + Client: NewMockClient(ctrl), // Not used TargetRoot: ids.Empty, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -1079,12 +1079,12 @@ func Test_Sync_UpdateSyncTarget(t *testing.T) { require.Equal(1, m.unprocessedWork.Len()) } -func generateTrie(t *testing.T, r *rand.Rand, count int) (*merkledb.Database, error) { +func generateTrie(t *testing.T, r *rand.Rand, count int) (merkledb.MerkleDB, error) { db, _, err := generateTrieWithMinKeyLen(t, r, count, 0) return db, err } -func generateTrieWithMinKeyLen(t *testing.T, r *rand.Rand, count int, minKeyLen int) (*merkledb.Database, [][]byte, error) { +func generateTrieWithMinKeyLen(t *testing.T, r *rand.Rand, count int, minKeyLen int) (merkledb.MerkleDB, [][]byte, error) { db, err := merkledb.New( context.Background(), memdb.New(), diff --git a/x/sync/syncmanager.go b/x/sync/syncmanager.go index d661a3e11b3..066cd8ef5a9 100644 --- a/x/sync/syncmanager.go +++ b/x/sync/syncmanager.go @@ -106,7 +106,7 @@ type StateSyncManager struct { } type StateSyncConfig struct { - SyncDB *merkledb.Database + SyncDB merkledb.MerkleDB Client Client SimultaneousWorkLimit int Log logging.Logger