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

Implement interval tree to replace bootstrapping jobs queue #2756

Merged
merged 36 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c5b7aa9
Implement interval tree for syncing
StephenButtolph Feb 20, 2024
63efd3b
Persist intervals
StephenButtolph Feb 20, 2024
2454c46
rename
StephenButtolph Feb 20, 2024
0efa5eb
Add block execution
StephenButtolph Feb 21, 2024
6a65a2a
Add length + TODOs
StephenButtolph Feb 21, 2024
f2234bd
nit
StephenButtolph Feb 21, 2024
1f63238
error rather than panic
StephenButtolph Feb 21, 2024
6947876
Refactor database passing
StephenButtolph Feb 21, 2024
be64296
Add interval tests
StephenButtolph Mar 19, 2024
884f39c
Fix too frequent iterator releases and batch writes
StephenButtolph Mar 19, 2024
e4e92cc
nit + support cancellation
StephenButtolph Mar 19, 2024
72314d1
Add invariant comment + tests
StephenButtolph Mar 19, 2024
8851470
Merge branch 'master' into interval-tree-syncing
StephenButtolph Mar 20, 2024
03f60c6
lint
StephenButtolph Mar 20, 2024
f01b6a3
allow specifying the log level
StephenButtolph Mar 20, 2024
d5da1e9
upstream
StephenButtolph Mar 20, 2024
25e2801
cleanup
StephenButtolph Mar 20, 2024
f0c5c10
Merge branch 'master' into interval-tree-syncing
StephenButtolph Mar 20, 2024
b6d6ba9
Merge branch 'master' into interval-tree-syncing
StephenButtolph Mar 21, 2024
06fd656
Merge branch 'master' into interval-tree-syncing
StephenButtolph Mar 21, 2024
c89d24d
Remove redundent interface
StephenButtolph Mar 21, 2024
bc3fedf
nit
StephenButtolph Mar 25, 2024
8134e16
nit
StephenButtolph Mar 25, 2024
9991e39
Move GetMissingBlockIDs and Execute out of the interval package
StephenButtolph Mar 25, 2024
2877502
Merge branch 'master' into interval-tree-syncing
StephenButtolph Mar 25, 2024
f1dd0a6
nit
StephenButtolph Mar 26, 2024
bf29d86
remove storage
StephenButtolph Mar 26, 2024
5a8fadc
nit
StephenButtolph Mar 26, 2024
69ed897
nit
StephenButtolph Mar 26, 2024
9815047
nit
StephenButtolph Mar 26, 2024
5bf4899
nit
StephenButtolph Mar 26, 2024
9da3bc1
nit
StephenButtolph Mar 26, 2024
ccc8a29
remove randomness
StephenButtolph Mar 26, 2024
7ab2cb4
nit
StephenButtolph Mar 26, 2024
e19016f
remove dead code
StephenButtolph Mar 26, 2024
d7f4861
nit
joshua-kim Mar 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 44 additions & 0 deletions snow/engine/snowman/bootstrap/interval/blocks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package interval

import "github.com/ava-labs/avalanchego/database"

// Add the block to the tree and return if the parent block should be fetched,
// but wasn't desired before.
func Add(
db database.KeyValueWriterDeleter,
tree *Tree,
lastAcceptedHeight uint64,
height uint64,
blkBytes []byte,
) (bool, error) {
if height <= lastAcceptedHeight || tree.Contains(height) {
return false, nil
}

if err := PutBlock(db, height, blkBytes); err != nil {
return false, err
}
if err := tree.Add(db, height); err != nil {
return false, err
}

// We know that height is greater than lastAcceptedHeight here, so height-1
// is guaranteed not to underflow.
nextHeight := height - 1
return nextHeight != lastAcceptedHeight && !tree.Contains(nextHeight), nil
}

// Remove the block from the tree.
func Remove(
db database.KeyValueWriterDeleter,
tree *Tree,
height uint64,
) error {
if err := DeleteBlock(db, height); err != nil {
return err
}
return tree.Remove(db, height)
}
160 changes: 160 additions & 0 deletions snow/engine/snowman/bootstrap/interval/blocks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package interval

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/ava-labs/avalanchego/database"
"github.com/ava-labs/avalanchego/database/memdb"
)

func TestAdd(t *testing.T) {
StephenButtolph marked this conversation as resolved.
Show resolved Hide resolved
tests := []struct {
name string
tree *Tree
lastAcceptedHeight uint64
height uint64
blkBytes []byte
expectedToPersist bool
expectedToWantParent bool
}{
{
name: "height already accepted",
tree: func() *Tree {
tree, err := NewTree(memdb.New())
require.NoError(t, err)
return tree
}(),
lastAcceptedHeight: 1,
height: 1,
blkBytes: []byte{1},
expectedToPersist: false,
expectedToWantParent: false,
},
{
name: "height already added",
tree: func() *Tree {
db := memdb.New()
tree, err := NewTree(db)
require.NoError(t, err)

require.NoError(t, tree.Add(db, 1))
return tree
}(),
lastAcceptedHeight: 0,
height: 1,
blkBytes: []byte{1},
expectedToPersist: false,
expectedToWantParent: false,
},
{
name: "next block is desired",
tree: func() *Tree {
tree, err := NewTree(memdb.New())
require.NoError(t, err)
return tree
}(),
lastAcceptedHeight: 0,
height: 2,
blkBytes: []byte{2},
expectedToPersist: true,
expectedToWantParent: true,
},
{
name: "next block is accepted",
tree: func() *Tree {
tree, err := NewTree(memdb.New())
require.NoError(t, err)
return tree
}(),
lastAcceptedHeight: 0,
height: 1,
blkBytes: []byte{1},
expectedToPersist: true,
expectedToWantParent: false,
},
{
name: "next block already added",
tree: func() *Tree {
db := memdb.New()
tree, err := NewTree(db)
require.NoError(t, err)

require.NoError(t, tree.Add(db, 1))
return tree
}(),
lastAcceptedHeight: 0,
height: 2,
blkBytes: []byte{2},
expectedToPersist: true,
expectedToWantParent: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)

db := memdb.New()
wantsParent, err := Add(
db,
test.tree,
test.lastAcceptedHeight,
test.height,
test.blkBytes,
)
require.NoError(err)
require.Equal(test.expectedToWantParent, wantsParent)

blkBytes, err := GetBlock(db, test.height)
if test.expectedToPersist {
require.NoError(err)
require.Equal(test.blkBytes, blkBytes)
require.True(test.tree.Contains(test.height))
} else {
require.ErrorIs(err, database.ErrNotFound)
}
})
}
}

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

db := memdb.New()
tree, err := NewTree(db)
require.NoError(err)
lastAcceptedHeight := uint64(1)
height := uint64(5)
blkBytes := []byte{5}

_, err = Add(
db,
tree,
lastAcceptedHeight,
height,
blkBytes,
)
require.NoError(err)

// Verify that the database has the block.
storedBlkBytes, err := GetBlock(db, height)
require.NoError(err)
require.Equal(blkBytes, storedBlkBytes)
require.Equal(uint64(1), tree.Len())

require.NoError(Remove(
db,
tree,
height,
))
require.Zero(tree.Len())

// Verify that the database no longer contains the block.
_, err = GetBlock(db, height)
require.ErrorIs(err, database.ErrNotFound)
require.Zero(tree.Len())
}
35 changes: 35 additions & 0 deletions snow/engine/snowman/bootstrap/interval/interval.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package interval

import "math"

type Interval struct {
LowerBound uint64
UpperBound uint64
}

func (i *Interval) Less(other *Interval) bool {
return i.UpperBound < other.UpperBound
}

func (i *Interval) Contains(height uint64) bool {
return i != nil &&
i.LowerBound <= height &&
height <= i.UpperBound
}

// AdjacentToLowerBound returns true if height is 1 less than lowerBound.
func (i *Interval) AdjacentToLowerBound(height uint64) bool {
return i != nil &&
height < math.MaxUint64 &&
height+1 == i.LowerBound
}

// AdjacentToUpperBound returns true if height is 1 greater than upperBound.
func (i *Interval) AdjacentToUpperBound(height uint64) bool {
return i != nil &&
i.UpperBound < math.MaxUint64 &&
i.UpperBound+1 == height
}