Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merkle db fix range proof commit bug #2019

Merged
merged 13 commits into from
Sep 15, 2023
263 changes: 137 additions & 126 deletions proto/pb/sync/sync.pb.go

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion proto/sync/sync.proto
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ message GetRangeProofResponse {

message CommitRangeProofRequest {
MaybeBytes start_key = 1;
RangeProof range_proof = 2;
MaybeBytes end_key = 2;
RangeProof range_proof = 3;
}

message ChangeProof {
Expand Down
16 changes: 8 additions & 8 deletions x/merkledb/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ type RangeProofer interface {

// 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 maybe.Maybe[[]byte], proof *RangeProof) error
CommitRangeProof(ctx context.Context, start, end maybe.Maybe[[]byte], proof *RangeProof) error
dboehm-avalabs marked this conversation as resolved.
Show resolved Hide resolved
}

type MerkleDB interface {
Expand Down Expand Up @@ -334,7 +334,7 @@ func (db *merkleDB) CommitChangeProof(ctx context.Context, proof *ChangeProof) e
return view.commitToDB(ctx)
}

func (db *merkleDB) CommitRangeProof(ctx context.Context, start maybe.Maybe[[]byte], proof *RangeProof) error {
func (db *merkleDB) CommitRangeProof(ctx context.Context, start, end maybe.Maybe[[]byte], proof *RangeProof) error {
db.commitLock.Lock()
defer db.commitLock.Unlock()

Expand All @@ -352,11 +352,11 @@ func (db *merkleDB) CommitRangeProof(ctx context.Context, start maybe.Maybe[[]by
}
}

var largestKey []byte
largestKey := end
if len(proof.KeyValues) > 0 {
largestKey = proof.KeyValues[len(proof.KeyValues)-1].Key
largestKey = maybe.Some(proof.KeyValues[len(proof.KeyValues)-1].Key)
}
keysToDelete, err := db.getKeysNotInSet(start.Value(), largestKey, keys)
keysToDelete, err := db.getKeysNotInSet(start, largestKey, keys)
if err != nil {
return err
}
Expand Down Expand Up @@ -1130,17 +1130,17 @@ func (db *merkleDB) getHistoricalViewForRange(
// Returns all 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.
dboehm-avalabs marked this conversation as resolved.
Show resolved Hide resolved
func (db *merkleDB) getKeysNotInSet(start, end []byte, keySet set.Set[string]) ([][]byte, error) {
func (db *merkleDB) getKeysNotInSet(start, end maybe.Maybe[[]byte], keySet set.Set[string]) ([][]byte, error) {
db.lock.RLock()
defer db.lock.RUnlock()

it := db.NewIteratorWithStart(start)
it := db.NewIteratorWithStart(start.Value())
defer it.Release()

keysNotInSet := make([][]byte, 0, keySet.Len())
for it.Next() {
key := it.Key()
if len(end) != 0 && bytes.Compare(key, end) > 0 {
if end.HasValue() && bytes.Compare(key, end.Value()) > 0 {
break
}
if !keySet.Contains(string(key)) {
Expand Down
47 changes: 44 additions & 3 deletions x/merkledb/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,47 @@ func Test_MerkleDB_Invalidate_Siblings_On_Commit(t *testing.T) {
require.False(viewToCommit.(*trieView).isInvalid())
}

func Test_MerkleDB_Commit_Proof_To_Empty_Trie(t *testing.T) {
func Test_MerkleDB_CommitRangeProof_DeletesValuesInRange(t *testing.T) {
require := require.New(t)

db, err := getBasicDB()
require.NoError(err)

// value that shouldn't be deleted
require.NoError(db.Put([]byte("key6"), []byte("3")))

startRoot, err := db.GetMerkleRoot(context.Background())
require.NoError(err)

// Get an empty proof
proof, err := db.GetRangeProof(
context.Background(),
maybe.Nothing[[]byte](),
maybe.Some([]byte("key3")),
10,
)
require.NoError(err)

// confirm there are no key.values in the proof
require.Empty(proof.KeyValues)

// add values to be deleted by proof commit
batch := db.NewBatch()
require.NoError(batch.Put([]byte("key1"), []byte("1")))
require.NoError(batch.Put([]byte("key2"), []byte("2")))
require.NoError(batch.Put([]byte("key3"), []byte("3")))
require.NoError(batch.Write())

// despite having no key/values in it, committing this proof should delete key1-key3.
require.NoError(db.CommitRangeProof(context.Background(), maybe.Nothing[[]byte](), maybe.Some([]byte("key3")), proof))

afterCommitRoot, err := db.GetMerkleRoot(context.Background())
require.NoError(err)

require.Equal(startRoot, afterCommitRoot)
}

func Test_MerkleDB_CommitRangeProof_EmptyTrie(t *testing.T) {
require := require.New(t)

// Populate [db1] with 3 key-value pairs.
Expand All @@ -326,7 +366,7 @@ func Test_MerkleDB_Commit_Proof_To_Empty_Trie(t *testing.T) {
db2, err := getBasicDB()
require.NoError(err)

require.NoError(db2.CommitRangeProof(context.Background(), maybe.Some([]byte("key1")), proof))
require.NoError(db2.CommitRangeProof(context.Background(), maybe.Some([]byte("key1")), maybe.Some([]byte("key3")), proof))

// [db2] should have the same key-value pairs as [db1].
db2Root, err := db2.GetMerkleRoot(context.Background())
Expand All @@ -338,7 +378,7 @@ func Test_MerkleDB_Commit_Proof_To_Empty_Trie(t *testing.T) {
require.Equal(db1Root, db2Root)
}

func Test_MerkleDB_Commit_Proof_To_Filled_Trie(t *testing.T) {
func Test_MerkleDB_CommitRangeProof_TrieWithInitialValues(t *testing.T) {
require := require.New(t)

// Populate [db1] with 3 key-value pairs.
Expand Down Expand Up @@ -374,6 +414,7 @@ func Test_MerkleDB_Commit_Proof_To_Filled_Trie(t *testing.T) {
require.NoError(db2.CommitRangeProof(
context.Background(),
maybe.Some([]byte("key1")),
maybe.Some([]byte("key3")),
proof,
))

Expand Down
8 changes: 4 additions & 4 deletions x/merkledb/mock_db.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions x/sync/g_db/db_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,18 @@ func (c *DBClient) GetRangeProofAtRoot(
func (c *DBClient) CommitRangeProof(
ctx context.Context,
startKey maybe.Maybe[[]byte],
endKey maybe.Maybe[[]byte],
proof *merkledb.RangeProof,
) error {
_, err := c.client.CommitRangeProof(ctx, &pb.CommitRangeProofRequest{
StartKey: &pb.MaybeBytes{
IsNothing: startKey.IsNothing(),
Value: startKey.Value(),
},
EndKey: &pb.MaybeBytes{
IsNothing: endKey.IsNothing(),
Value: endKey.Value(),
},
RangeProof: proof.ToProto(),
})
return err
Expand Down
7 changes: 6 additions & 1 deletion x/sync/g_db/db_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ func (s *DBServer) CommitRangeProof(
start = maybe.Some(req.StartKey.Value)
}

err := s.db.CommitRangeProof(ctx, start, &proof)
end := maybe.Nothing[[]byte]()
if req.EndKey != nil && !req.EndKey.IsNothing {
end = maybe.Some(req.EndKey.Value)
}

err := s.db.CommitRangeProof(ctx, start, end, &proof)
return &emptypb.Empty{}, err
}
15 changes: 8 additions & 7 deletions x/sync/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ func (m *Manager) getAndApplyChangeProof(ctx context.Context, work *workItem) {
largestHandledKey := work.end
if len(rangeProof.KeyValues) > 0 {
// Add all the key-value pairs we got to the database.
if err := m.config.DB.CommitRangeProof(ctx, work.start, rangeProof); err != nil {
if err := m.config.DB.CommitRangeProof(ctx, work.start, work.end, rangeProof); err != nil {
m.setError(err)
return
}
Expand Down Expand Up @@ -351,13 +351,14 @@ func (m *Manager) getAndApplyRangeProof(ctx context.Context, work *workItem) {
}

largestHandledKey := work.end
if len(proof.KeyValues) > 0 {
// Add all the key-value pairs we got to the database.
if err := m.config.DB.CommitRangeProof(ctx, work.start, proof); err != nil {
m.setError(err)
return
}

// Replace all the key-value pairs in the DB from start to end with values from the response.
if err := m.config.DB.CommitRangeProof(ctx, work.start, work.end, proof); err != nil {
m.setError(err)
return
}

if len(proof.KeyValues) > 0 {
largestHandledKey = maybe.Some(proof.KeyValues[len(proof.KeyValues)-1].Key)
}

Expand Down
1 change: 1 addition & 0 deletions x/sync/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,7 @@ func TestFindNextKeyRandom(t *testing.T) {
require.NoError(localDB.CommitRangeProof(
context.Background(),
startKey,
endKey,
remoteProof,
))

Expand Down
Loading