mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
25afc8ad90
In this commit, a new concept called a RangeIndex is introduced. It provides an efficient way to keep track of numbers added to a set by keeping track of various ranges instead of individual numbers. Notably, it also provides a way to map the contents & diffs applied to the in memory RangeIndex (which uses a sorted array structure) to a persisted KV structure.
350 lines
7.5 KiB
Go
350 lines
7.5 KiB
Go
package wtdb
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestRangeIndex tests that the RangeIndex works as expected.
|
|
func TestRangeIndex(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
assertRanges := func(index *RangeIndex, kvStore *mockKVStore,
|
|
v map[uint64]uint64) {
|
|
|
|
require.EqualValues(t, v, index.GetAllRanges())
|
|
require.EqualValues(t, v, kvStore.kv)
|
|
}
|
|
|
|
t.Run("test zero value height", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
kvStore := newMockKVStore(nil)
|
|
index, err := NewRangeIndex(nil)
|
|
require.NoError(t, err)
|
|
|
|
// Since zero values are tricky, assert that the empty index
|
|
// does not include zero.
|
|
require.False(t, index.IsInIndex(0))
|
|
|
|
// Now add zero to the index.
|
|
_ = index.Add(0, kvStore)
|
|
assertRanges(index, kvStore, map[uint64]uint64{0: 0})
|
|
require.True(t, index.IsInIndex(0))
|
|
})
|
|
|
|
t.Run("add duplicates", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
kvStore := newMockKVStore(nil)
|
|
index, err := NewRangeIndex(nil)
|
|
require.NoError(t, err)
|
|
|
|
require.False(t, index.IsInIndex(1))
|
|
|
|
// Add 1 to the range.
|
|
err = index.Add(1, kvStore)
|
|
require.NoError(t, err)
|
|
|
|
assertRanges(index, kvStore, map[uint64]uint64{1: 1})
|
|
require.EqualValues(t, 1, index.MaxHeight())
|
|
require.True(t, index.IsInIndex(1))
|
|
|
|
// Add 1 again and assert that nothing has changed.
|
|
err = index.Add(1, kvStore)
|
|
require.NoError(t, err)
|
|
|
|
assertRanges(index, kvStore, map[uint64]uint64{1: 1})
|
|
require.EqualValues(t, 1, index.MaxHeight())
|
|
require.True(t, index.IsInIndex(1))
|
|
})
|
|
|
|
t.Run("extend an existing range", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
kvStore := newMockKVStore(nil)
|
|
index, err := NewRangeIndex(nil)
|
|
require.NoError(t, err)
|
|
|
|
assertRanges(index, kvStore, map[uint64]uint64{})
|
|
|
|
// Add 2.
|
|
_ = index.Add(2, kvStore)
|
|
assertRanges(index, kvStore, map[uint64]uint64{
|
|
2: 2,
|
|
})
|
|
|
|
// Add 3, 4 and 5 and assert that these just extend the existing
|
|
// range by incrementing its end value.
|
|
_ = index.Add(3, kvStore)
|
|
_ = index.Add(4, kvStore)
|
|
_ = index.Add(5, kvStore)
|
|
assertRanges(index, kvStore, map[uint64]uint64{
|
|
2: 5,
|
|
})
|
|
|
|
// Now add 1 and 0 and assert that these just extend the
|
|
// existing range by decrementing its start value.
|
|
_ = index.Add(1, kvStore)
|
|
_ = index.Add(0, kvStore)
|
|
assertRanges(index, kvStore, map[uint64]uint64{
|
|
0: 5,
|
|
})
|
|
|
|
// Assert various other properties of the current range.
|
|
require.True(t, index.IsInIndex(3))
|
|
require.EqualValues(t, 5, index.MaxHeight())
|
|
})
|
|
|
|
t.Run("add new ranges above and below", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Initialise the index with an initial range.
|
|
initialState := map[uint64]uint64{
|
|
4: 10,
|
|
}
|
|
|
|
kvStore := newMockKVStore(initialState)
|
|
index, err := NewRangeIndex(initialState)
|
|
require.NoError(t, err)
|
|
|
|
// Add 2 and 12. This should create two new ranges in the index.
|
|
_ = index.Add(12, kvStore)
|
|
_ = index.Add(2, kvStore)
|
|
assertRanges(index, kvStore, map[uint64]uint64{
|
|
2: 2,
|
|
4: 10,
|
|
12: 12,
|
|
})
|
|
|
|
// Assert various other properties of the current range.
|
|
require.EqualValues(t, 12, index.MaxHeight())
|
|
require.False(t, index.IsInIndex(3))
|
|
require.False(t, index.IsInIndex(11))
|
|
require.True(t, index.IsInIndex(2))
|
|
require.True(t, index.IsInIndex(5))
|
|
require.True(t, index.IsInIndex(12))
|
|
})
|
|
|
|
t.Run("merging two ranges", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Initialise the index with an initial set of ranges.
|
|
initialState := map[uint64]uint64{
|
|
2: 2,
|
|
4: 10,
|
|
12: 12,
|
|
}
|
|
|
|
kvStore := newMockKVStore(initialState)
|
|
index, err := NewRangeIndex(initialState)
|
|
require.NoError(t, err)
|
|
|
|
// Adding 3 should merge the first and second ranges.
|
|
_ = index.Add(3, kvStore)
|
|
assertRanges(index, kvStore, map[uint64]uint64{
|
|
2: 10,
|
|
12: 12,
|
|
})
|
|
|
|
// Adding 11 should merge the first and second ranges.
|
|
_ = index.Add(11, kvStore)
|
|
assertRanges(index, kvStore, map[uint64]uint64{
|
|
2: 12,
|
|
})
|
|
|
|
// Assert various other properties of the current range.
|
|
require.EqualValues(t, 12, index.MaxHeight())
|
|
require.True(t, index.IsInIndex(2))
|
|
require.True(t, index.IsInIndex(5))
|
|
require.True(t, index.IsInIndex(12))
|
|
require.False(t, index.IsInIndex(1))
|
|
})
|
|
|
|
t.Run("failure applying KV store updates", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
kvStore := newMockKVStore(nil)
|
|
index, err := NewRangeIndex(nil)
|
|
require.NoError(t, err)
|
|
|
|
assertRanges(index, kvStore, map[uint64]uint64{})
|
|
|
|
// Ensure that the kv store will return an error when its
|
|
// methods are called.
|
|
kvStore.setError(fmt.Errorf("db error"))
|
|
|
|
// Now attempt to add a new item to the range.
|
|
err = index.Add(20, kvStore)
|
|
require.Error(t, err)
|
|
|
|
// Assert that the update failed for both the kv store and the
|
|
// array store.
|
|
assertRanges(index, kvStore, map[uint64]uint64{})
|
|
|
|
// Now let the kv store again not return an error.
|
|
kvStore.setError(nil)
|
|
|
|
// Again attempt to add a new item to the range.
|
|
err = index.Add(20, kvStore)
|
|
require.NoError(t, err)
|
|
|
|
// It should now succeed.
|
|
assertRanges(index, kvStore, map[uint64]uint64{
|
|
20: 20,
|
|
})
|
|
})
|
|
|
|
t.Run("initialising with different ranges", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
input map[uint64]uint64
|
|
expectErr bool
|
|
expectedIndex map[uint64]uint64
|
|
}{
|
|
{
|
|
name: "invalid ranges",
|
|
input: map[uint64]uint64{
|
|
1: 5,
|
|
6: 4,
|
|
},
|
|
expectErr: true,
|
|
},
|
|
{
|
|
name: "non-overlapping ranges",
|
|
input: map[uint64]uint64{
|
|
1: 2,
|
|
4: 6,
|
|
8: 20,
|
|
},
|
|
expectedIndex: map[uint64]uint64{
|
|
1: 2,
|
|
4: 6,
|
|
8: 20,
|
|
},
|
|
},
|
|
{
|
|
name: "merge-able ranges",
|
|
input: map[uint64]uint64{
|
|
1: 2,
|
|
3: 6,
|
|
7: 20,
|
|
},
|
|
expectedIndex: map[uint64]uint64{
|
|
1: 20,
|
|
},
|
|
},
|
|
{
|
|
name: "overlapping ranges",
|
|
input: map[uint64]uint64{
|
|
1: 4,
|
|
3: 7,
|
|
6: 20,
|
|
},
|
|
expectedIndex: map[uint64]uint64{
|
|
1: 20,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
index, err := NewRangeIndex(test.input)
|
|
if test.expectErr {
|
|
require.Error(t, err)
|
|
continue
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
require.EqualValues(
|
|
t, test.expectedIndex, index.GetAllRanges(),
|
|
)
|
|
}
|
|
})
|
|
|
|
t.Run("test large number of random inserts", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
size := 10000
|
|
|
|
// Construct an array with values from 0 to size.
|
|
arr := make([]uint64, size)
|
|
for i := 0; i < size; i++ {
|
|
arr[i] = uint64(i)
|
|
}
|
|
|
|
// Shuffle the array so that the values are not added in order.
|
|
rand.Seed(time.Now().UnixNano())
|
|
rand.Shuffle(len(arr), func(i, j int) {
|
|
arr[i], arr[j] = arr[j], arr[i]
|
|
})
|
|
|
|
kvStore := newMockKVStore(nil)
|
|
index, err := NewRangeIndex(nil)
|
|
require.NoError(t, err)
|
|
|
|
// Now add each item in the array to the index.
|
|
for _, n := range arr {
|
|
_ = index.Add(n, kvStore)
|
|
}
|
|
|
|
// Assert that in the end, there is only a single range.
|
|
assertRanges(index, kvStore, map[uint64]uint64{
|
|
0: uint64(size - 1),
|
|
})
|
|
|
|
require.EqualValues(t, uint64(size-1), index.MaxHeight())
|
|
})
|
|
}
|
|
|
|
type mockKVStore struct {
|
|
kv map[uint64]uint64
|
|
|
|
err error
|
|
}
|
|
|
|
func newMockKVStore(initialRanges map[uint64]uint64) *mockKVStore {
|
|
if initialRanges != nil {
|
|
return &mockKVStore{
|
|
kv: initialRanges,
|
|
}
|
|
}
|
|
|
|
return &mockKVStore{
|
|
kv: make(map[uint64]uint64),
|
|
}
|
|
}
|
|
|
|
func (m *mockKVStore) setError(err error) {
|
|
m.err = err
|
|
}
|
|
|
|
func (m *mockKVStore) Put(key, value []byte) error {
|
|
if m.err != nil {
|
|
return m.err
|
|
}
|
|
|
|
k := byteOrder.Uint64(key)
|
|
v := byteOrder.Uint64(value)
|
|
|
|
m.kv[k] = v
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *mockKVStore) Delete(key []byte) error {
|
|
if m.err != nil {
|
|
return m.err
|
|
}
|
|
|
|
k := byteOrder.Uint64(key)
|
|
delete(m.kv, k)
|
|
|
|
return nil
|
|
}
|