Skip to content

Commit

Permalink
merkledb / sync -- Disambiguate no end root from no start root (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Dan Laine authored Dec 7, 2023
1 parent 7c33fa3 commit a754118
Show file tree
Hide file tree
Showing 7 changed files with 31 additions and 14 deletions.
2 changes: 2 additions & 0 deletions x/merkledb/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ type ChangeProofer interface {
// Returns at most [maxLength] key/value pairs.
// Returns [ErrInsufficientHistory] if this node has insufficient history
// to generate the proof.
// Returns [ErrNoEndRoot], which wraps [ErrInsufficientHistory], if the
// history doesn't contain the [endRootID].
GetChangeProof(
ctx context.Context,
startRootID ids.ID,
Expand Down
9 changes: 7 additions & 2 deletions x/merkledb/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import (
"github.com/ava-labs/avalanchego/utils/set"
)

var ErrInsufficientHistory = errors.New("insufficient history to generate proof")
var (
ErrInsufficientHistory = errors.New("insufficient history to generate proof")
ErrNoEndRoot = fmt.Errorf("%w: end root not found", ErrInsufficientHistory)
)

// stores previous trie states
type trieHistory struct {
Expand Down Expand Up @@ -77,6 +80,8 @@ func newTrieHistory(maxHistoryLookback int) *trieHistory {
// If [end] is Nothing, there's no upper bound on the range.
// Returns [ErrInsufficientHistory] if the history is insufficient
// to generate the proof.
// Returns [ErrNoEndRoot], which wraps [ErrInsufficientHistory], if
// the [endRoot] isn't in the history.
func (th *trieHistory) getValueChanges(
startRoot ids.ID,
endRoot ids.ID,
Expand All @@ -99,7 +104,7 @@ func (th *trieHistory) getValueChanges(
// lack the necessary history.
endRootChanges, ok := th.lastChanges[endRoot]
if !ok {
return nil, fmt.Errorf("%w: end root %s not found", ErrInsufficientHistory, endRoot)
return nil, fmt.Errorf("%w: %s", ErrNoEndRoot, endRoot)
}

// Confirm there's a change resulting in [startRoot] before
Expand Down
21 changes: 10 additions & 11 deletions x/merkledb/history_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,13 @@ func Test_History_Bad_GetValueChanges_Input(t *testing.T) {
require.NoError(batch.Put([]byte("key"), []byte("value")))
require.NoError(batch.Write())

toBeDeletedRoot := db.getMerkleRoot()
root1 := db.getMerkleRoot()

batch = db.NewBatch()
require.NoError(batch.Put([]byte("key"), []byte("value0")))
require.NoError(batch.Write())

startRoot := db.getMerkleRoot()
root2 := db.getMerkleRoot()

batch = db.NewBatch()
require.NoError(batch.Put([]byte("key1"), []byte("value0")))
Expand All @@ -185,31 +185,30 @@ func Test_History_Bad_GetValueChanges_Input(t *testing.T) {
require.NoError(batch.Put([]byte("key2"), []byte("value3")))
require.NoError(batch.Write())

endRoot := db.getMerkleRoot()
root3 := db.getMerkleRoot()

// ensure these start as valid calls
_, err = db.history.getValueChanges(toBeDeletedRoot, endRoot, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 1)
_, err = db.history.getValueChanges(root1, root3, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 1)
require.NoError(err)
_, err = db.history.getValueChanges(startRoot, endRoot, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 1)
_, err = db.history.getValueChanges(root2, root3, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 1)
require.NoError(err)

_, err = db.history.getValueChanges(startRoot, endRoot, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), -1)
_, err = db.history.getValueChanges(root2, root3, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), -1)
require.ErrorIs(err, ErrInvalidMaxLength)

_, err = db.history.getValueChanges(endRoot, startRoot, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 1)
_, err = db.history.getValueChanges(root3, root2, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 1)
require.ErrorIs(err, ErrInsufficientHistory)

// trigger the first root to be deleted by exiting the lookback window
// Cause root1 to be removed from the history
batch = db.NewBatch()
require.NoError(batch.Put([]byte("key2"), []byte("value4")))
require.NoError(batch.Write())

// now this root should no longer be present
_, err = db.history.getValueChanges(toBeDeletedRoot, endRoot, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 1)
_, err = db.history.getValueChanges(root1, root3, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 1)
require.ErrorIs(err, ErrInsufficientHistory)

// same start/end roots should yield an empty changelist
changes, err := db.history.getValueChanges(endRoot, endRoot, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 10)
changes, err := db.history.getValueChanges(root3, root3, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 10)
require.NoError(err)
require.Empty(changes.values)
}
Expand Down
1 change: 1 addition & 0 deletions x/merkledb/proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,7 @@ func Test_ChangeProof_Missing_History_For_EndRoot(t *testing.T) {
require.NoError(err)

_, err = db.GetChangeProof(context.Background(), startRoot, ids.Empty, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 50)
require.ErrorIs(err, ErrNoEndRoot)
require.ErrorIs(err, ErrInsufficientHistory)
}

Expand Down
3 changes: 3 additions & 0 deletions x/sync/g_db/db_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ func (c *DBClient) GetChangeProof(
}

// TODO handle merkledb.ErrInvalidMaxLength
// TODO disambiguate between the root not being present due to
// the end root not being present and the start root not being
// present before the end root. i.e. ErrNoEndRoot vs ErrInsufficientHistory.
if resp.GetRootNotPresent() {
return nil, merkledb.ErrInsufficientHistory
}
Expand Down
7 changes: 7 additions & 0 deletions x/sync/network_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,15 @@ func (s *NetworkServer) HandleChangeProofRequest(
changeProof, err := s.db.GetChangeProof(ctx, startRoot, endRoot, start, end, int(keyLimit))
if err != nil {
if !errors.Is(err, merkledb.ErrInsufficientHistory) {
// We should only fail to get a change proof if we have insufficient history.
// Other errors are unexpected.
return err
}
if errors.Is(err, merkledb.ErrNoEndRoot) {
// [s.db] doesn't have [endRoot] in its history.
// We can't generate a change/range proof. Drop this request.
return nil
}

// [s.db] doesn't have sufficient history to generate change proof.
// Generate a range proof for the end root ID instead.
Expand Down
2 changes: 1 addition & 1 deletion x/sync/network_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ func Test_Server_GetChangeProof(t *testing.T) {
"insufficient history for change proof or range proof": {
request: &pb.SyncGetChangeProofRequest{
// These roots don't exist so server has insufficient history
// to serve a change proof
// to serve a change proof or range proof
StartRootHash: ids.Empty[:],
EndRootHash: fakeRootID[:],
KeyLimit: defaultRequestKeyLimit,
Expand Down

0 comments on commit a754118

Please sign in to comment.