mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
314 lines
8.3 KiB
Go
314 lines
8.3 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package indexer
|
|
|
|
import (
|
|
"context"
|
|
"math/rand"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/luxfi/consensus/core/choices"
|
|
consensustest "github.com/luxfi/consensus/test/helpers"
|
|
"github.com/luxfi/database"
|
|
"github.com/luxfi/database/memdb"
|
|
"github.com/luxfi/database/versiondb"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/log"
|
|
"github.com/luxfi/node/vms/proposervm/block"
|
|
"github.com/luxfi/node/vms/proposervm/state"
|
|
chain "github.com/luxfi/vm/chain"
|
|
)
|
|
|
|
// testBlock is a simple implementation for testing
|
|
type testBlock struct {
|
|
consensustest.Decidable
|
|
HeightV uint64
|
|
AcceptF func() error
|
|
RejectF func() error
|
|
}
|
|
|
|
func (b *testBlock) ID() ids.ID { return b.IDV }
|
|
func (b *testBlock) Parent() ids.ID { return ids.Empty }
|
|
func (b *testBlock) ParentID() ids.ID { return ids.Empty }
|
|
func (b *testBlock) Height() uint64 { return b.HeightV }
|
|
func (b *testBlock) Timestamp() time.Time { return time.Now() }
|
|
func (b *testBlock) Bytes() []byte { return nil }
|
|
func (b *testBlock) Status() uint8 { return uint8(b.StatusV) }
|
|
func (b *testBlock) Accept(context.Context) error {
|
|
if b.AcceptF != nil {
|
|
return b.AcceptF()
|
|
}
|
|
b.StatusV = choices.Accepted
|
|
return nil
|
|
}
|
|
func (b *testBlock) Reject(context.Context) error {
|
|
if b.RejectF != nil {
|
|
return b.RejectF()
|
|
}
|
|
b.StatusV = choices.Rejected
|
|
return nil
|
|
}
|
|
func (b *testBlock) Verify(context.Context) error { return nil }
|
|
|
|
func TestHeightBlockIndexPostFork(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
db := memdb.New()
|
|
vdb := versiondb.New(db)
|
|
storedState := state.New(vdb)
|
|
|
|
// Build a chain of post fork blocks
|
|
var (
|
|
blkNumber = uint64(10)
|
|
lastBlkID = ids.Empty.Prefix(0) // initially set to a dummyGenesisID
|
|
proBlks = make(map[ids.ID]chain.Block)
|
|
)
|
|
|
|
for blkHeight := uint64(1); blkHeight <= blkNumber; blkHeight++ {
|
|
blockBytes := ids.Empty.Prefix(blkHeight + blkNumber + 1)
|
|
dummyTS := time.Time{}
|
|
dummyPCH := uint64(2022)
|
|
|
|
// store postForkStatelessBlk in State ...
|
|
postForkStatelessBlk, err := block.BuildUnsigned(
|
|
lastBlkID,
|
|
dummyTS,
|
|
dummyPCH,
|
|
block.Epoch{},
|
|
blockBytes[:],
|
|
)
|
|
require.NoError(err)
|
|
require.NoError(storedState.PutBlock(postForkStatelessBlk))
|
|
|
|
// ... and create a corresponding test block just for block server
|
|
postForkBlk := &testBlock{
|
|
Decidable: consensustest.Decidable{
|
|
IDV: postForkStatelessBlk.ID(),
|
|
StatusV: choices.Accepted,
|
|
},
|
|
HeightV: blkHeight,
|
|
}
|
|
proBlks[postForkStatelessBlk.ID()] = postForkBlk
|
|
|
|
lastBlkID = postForkStatelessBlk.ID()
|
|
}
|
|
|
|
blkSrv := &TestBlockServer{
|
|
CantGetFullPostForkBlock: true,
|
|
CantCommit: true,
|
|
|
|
GetFullPostForkBlockF: func(_ context.Context, blkID ids.ID) (chain.Block, error) {
|
|
blk, found := proBlks[blkID]
|
|
if !found {
|
|
return nil, database.ErrNotFound
|
|
}
|
|
return blk, nil
|
|
},
|
|
CommitF: func() error {
|
|
return nil
|
|
},
|
|
}
|
|
|
|
hIndex := newHeightIndexer(blkSrv,
|
|
log.NoLog{},
|
|
storedState,
|
|
)
|
|
hIndex.commitFrequency = 0 // commit each block
|
|
|
|
// checkpoint last accepted block and show the whole chain in reindexed
|
|
require.NoError(hIndex.state.SetCheckpoint(lastBlkID))
|
|
require.NoError(hIndex.RepairHeightIndex(context.Background()))
|
|
require.True(hIndex.IsRepaired())
|
|
|
|
// check that height index is fully built
|
|
loadedForkHeight, err := storedState.GetForkHeight()
|
|
require.NoError(err)
|
|
require.Equal(uint64(1), loadedForkHeight)
|
|
for height := uint64(1); height <= blkNumber; height++ {
|
|
_, err := storedState.GetBlockIDAtHeight(height)
|
|
require.NoError(err)
|
|
}
|
|
}
|
|
|
|
func TestHeightBlockIndexAcrossFork(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
db := memdb.New()
|
|
vdb := versiondb.New(db)
|
|
storedState := state.New(vdb)
|
|
|
|
// Build a chain of post fork blocks
|
|
var (
|
|
blkNumber = uint64(10)
|
|
forkHeight = blkNumber / 2
|
|
lastBlkID = ids.Empty.Prefix(0) // initially set to a last pre fork blk
|
|
proBlks = make(map[ids.ID]chain.Block)
|
|
)
|
|
|
|
for blkHeight := forkHeight; blkHeight <= blkNumber; blkHeight++ {
|
|
blockBytes := ids.Empty.Prefix(blkHeight + blkNumber + 1)
|
|
dummyTS := time.Time{}
|
|
dummyPCH := uint64(2022)
|
|
|
|
// store postForkStatelessBlk in State ...
|
|
postForkStatelessBlk, err := block.BuildUnsigned(
|
|
lastBlkID,
|
|
dummyTS,
|
|
dummyPCH,
|
|
block.Epoch{},
|
|
blockBytes[:],
|
|
)
|
|
require.NoError(err)
|
|
require.NoError(storedState.PutBlock(postForkStatelessBlk))
|
|
|
|
// ... and create a corresponding test block just for block server
|
|
postForkBlk := &testBlock{
|
|
Decidable: consensustest.Decidable{
|
|
IDV: postForkStatelessBlk.ID(),
|
|
StatusV: choices.Accepted,
|
|
},
|
|
HeightV: blkHeight,
|
|
}
|
|
proBlks[postForkStatelessBlk.ID()] = postForkBlk
|
|
|
|
lastBlkID = postForkStatelessBlk.ID()
|
|
}
|
|
|
|
blkSrv := &TestBlockServer{
|
|
CantGetFullPostForkBlock: true,
|
|
CantCommit: true,
|
|
|
|
GetFullPostForkBlockF: func(_ context.Context, blkID ids.ID) (chain.Block, error) {
|
|
blk, found := proBlks[blkID]
|
|
if !found {
|
|
return nil, database.ErrNotFound
|
|
}
|
|
return blk, nil
|
|
},
|
|
CommitF: func() error {
|
|
return nil
|
|
},
|
|
}
|
|
|
|
hIndex := newHeightIndexer(blkSrv,
|
|
log.NoLog{},
|
|
storedState,
|
|
)
|
|
hIndex.commitFrequency = 0 // commit each block
|
|
|
|
// checkpoint last accepted block and show the whole chain in reindexed
|
|
require.NoError(hIndex.state.SetCheckpoint(lastBlkID))
|
|
require.NoError(hIndex.RepairHeightIndex(context.Background()))
|
|
require.True(hIndex.IsRepaired())
|
|
|
|
// check that height index is fully built
|
|
loadedForkHeight, err := storedState.GetForkHeight()
|
|
require.NoError(err)
|
|
require.Equal(forkHeight, loadedForkHeight)
|
|
for height := uint64(0); height < forkHeight; height++ {
|
|
_, err := storedState.GetBlockIDAtHeight(height)
|
|
require.ErrorIs(err, database.ErrNotFound)
|
|
}
|
|
for height := forkHeight; height <= blkNumber; height++ {
|
|
_, err := storedState.GetBlockIDAtHeight(height)
|
|
require.NoError(err)
|
|
}
|
|
}
|
|
|
|
func TestHeightBlockIndexResumeFromCheckPoint(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
db := memdb.New()
|
|
vdb := versiondb.New(db)
|
|
storedState := state.New(vdb)
|
|
|
|
// Build a chain of post fork blocks
|
|
var (
|
|
blkNumber = uint64(10)
|
|
forkHeight = blkNumber / 2
|
|
lastBlkID = ids.Empty.Prefix(0) // initially set to a last pre fork blk
|
|
proBlks = make(map[ids.ID]chain.Block)
|
|
)
|
|
|
|
for blkHeight := forkHeight; blkHeight <= blkNumber; blkHeight++ {
|
|
blockBytes := ids.Empty.Prefix(blkHeight + blkNumber + 1)
|
|
dummyTS := time.Time{}
|
|
dummyPCH := uint64(2022)
|
|
|
|
// store postForkStatelessBlk in State ...
|
|
postForkStatelessBlk, err := block.BuildUnsigned(
|
|
lastBlkID,
|
|
dummyTS,
|
|
dummyPCH,
|
|
block.Epoch{},
|
|
blockBytes[:],
|
|
)
|
|
require.NoError(err)
|
|
require.NoError(storedState.PutBlock(postForkStatelessBlk))
|
|
|
|
// ... and create a corresponding test block just for block server
|
|
postForkBlk := &testBlock{
|
|
Decidable: consensustest.Decidable{
|
|
IDV: postForkStatelessBlk.ID(),
|
|
StatusV: choices.Accepted,
|
|
},
|
|
HeightV: blkHeight,
|
|
}
|
|
proBlks[postForkStatelessBlk.ID()] = postForkBlk
|
|
|
|
lastBlkID = postForkStatelessBlk.ID()
|
|
}
|
|
|
|
blkSrv := &TestBlockServer{
|
|
CantGetFullPostForkBlock: true,
|
|
CantCommit: true,
|
|
|
|
GetFullPostForkBlockF: func(_ context.Context, blkID ids.ID) (chain.Block, error) {
|
|
blk, found := proBlks[blkID]
|
|
if !found {
|
|
return nil, database.ErrNotFound
|
|
}
|
|
return blk, nil
|
|
},
|
|
CommitF: func() error {
|
|
return nil
|
|
},
|
|
}
|
|
|
|
hIndex := newHeightIndexer(blkSrv,
|
|
log.NoLog{},
|
|
storedState,
|
|
)
|
|
hIndex.commitFrequency = 0 // commit each block
|
|
|
|
// pick a random block in the chain and checkpoint it;...
|
|
rndPostForkHeight := rand.Intn(int(blkNumber-forkHeight)) + int(forkHeight) // #nosec G404
|
|
var checkpointBlk chain.Block
|
|
for _, blk := range proBlks {
|
|
if blk.Height() != uint64(rndPostForkHeight) {
|
|
continue // not the blk we are looking for
|
|
}
|
|
|
|
checkpointBlk = blk
|
|
require.NoError(hIndex.state.SetCheckpoint(checkpointBlk.ID()))
|
|
break
|
|
}
|
|
|
|
// perform repair and show index is built
|
|
require.NoError(hIndex.RepairHeightIndex(context.Background()))
|
|
require.True(hIndex.IsRepaired())
|
|
|
|
// check that height index is fully built
|
|
loadedForkHeight, err := storedState.GetForkHeight()
|
|
require.NoError(err)
|
|
require.Equal(forkHeight, loadedForkHeight)
|
|
for height := forkHeight; height <= checkpointBlk.Height(); height++ {
|
|
_, err := storedState.GetBlockIDAtHeight(height)
|
|
require.NoError(err)
|
|
}
|
|
}
|