Files
consensus/engine/bft/block_builder_test.go
T
zeekay 4aac78700d Refactor: remove dead code, add thread safety, improve test coverage
- Remove unused packages: engine/dag/{getter,bootstrap,vertex/vertexmock},
  engine/pq/{bootstrap,block}, core/{tracker,verify}
- Add sync.RWMutex to protocol/wave for thread-safe map access
- Increase engine/dag test coverage from 11.7% to 98.3%
- Add comprehensive tests for DAG consensus, vertex, and engine
- All 27 packages pass tests, 16 at 100% coverage
2025-12-13 07:42:01 +00:00

220 lines
5.1 KiB
Go

//go:build ignore
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bft
import (
"context"
"errors"
"testing"
"time"
simplex "github.com/luxfi/bft"
"github.com/stretchr/testify/require"
"github.com/luxfi/consensus/engine/chain/block"
"github.com/luxfi/consensus/core"
"github.com/luxfi/log"
)
func TestBlockBuilder(t *testing.T) {
ctx := context.Background()
genesis := newTestBlock(t, newBlockConfig{})
child := newTestBlock(t, newBlockConfig{
prev: genesis,
})
tests := []struct {
name string
block block.Block
shouldBuild bool
expectedBlock simplex.VerifiedBlock
vmBlockBuildF func(ctx context.Context) (block.Block, error)
}{
{
name: "build block successfully",
block: child.vmBlock,
shouldBuild: true,
expectedBlock: child,
vmBlockBuildF: func(_ context.Context) (block.Block, error) {
return child.vmBlock, nil
},
},
{
name: "fail to build block",
block: nil,
shouldBuild: false,
vmBlockBuildF: func(_ context.Context) (block.Block, error) {
return nil, errors.New("failed to build block")
},
},
{
name: "fail to verify block",
block: nil,
vmBlockBuildF: func(_ context.Context) (block.Block, error) {
b := newTestBlock(t, newBlockConfig{
prev: genesis,
})
b.vmBlock.(*wrappedBlock).VerifyV = errors.New("verification failed")
return b.vmBlock, nil
},
shouldBuild: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
count := 0
vm := newTestVM()
vm.WaitForEventF = func(_ context.Context) (interface{}, error) {
count++
return core.PendingTxs, nil
}
vm.BuildBlockF = tt.vmBlockBuildF
bb := &BlockBuilder{
log: log.NoLog{},
vm: vm,
blockTracker: genesis.blockTracker,
}
timeoutCtx, cancelCtx := context.WithTimeout(ctx, 100*time.Millisecond)
defer cancelCtx()
block, built := bb.BuildBlock(timeoutCtx, child.BlockHeader().ProtocolMetadata)
require.Equal(t, tt.shouldBuild, built)
require.Equal(t, tt.expectedBlock, block)
if tt.expectedBlock == nil {
require.Greater(t, count, 1)
}
})
}
}
func TestBlockBuilderCancelContext(t *testing.T) {
ctx := context.Background()
vm := newTestVM()
genesis := newTestBlock(t, newBlockConfig{})
child := newTestBlock(t, newBlockConfig{
prev: genesis,
})
vm.WaitForEventF = func(ctx context.Context) (interface{}, error) {
<-ctx.Done()
return 0, ctx.Err()
}
bb := &BlockBuilder{
log: log.NoLog{},
vm: vm,
blockTracker: genesis.blockTracker,
}
timeoutCtx, cancelCtx := context.WithTimeout(ctx, 100*time.Millisecond)
defer cancelCtx()
_, built := bb.BuildBlock(timeoutCtx, child.BlockHeader().ProtocolMetadata)
require.False(t, built, "Block should not be built when context is cancelled")
}
func TestWaitForPendingBlock(t *testing.T) {
ctx := context.Background()
vm := newTestVM()
genesis := newTestBlock(t, newBlockConfig{})
count := 0
vm.WaitForEventF = func(_ context.Context) (interface{}, error) {
if count == 0 {
count++
return "StateSyncDone", nil
}
return common.PendingTxs, nil
}
bb := &BlockBuilder{
log: log.NoLog{},
vm: vm,
blockTracker: genesis.blockTracker,
}
bb.WaitForPendingBlock(ctx)
require.Equal(t, 1, count)
}
func TestBlockBuildingExponentialBackoff(t *testing.T) {
ctx := context.Background()
vm := newTestVM()
genesis := newTestBlock(t, newBlockConfig{})
child := newTestBlock(t, newBlockConfig{
prev: genesis,
})
const (
failedAttempts = 7
minimumExpectedDelay = initBackoff * (1<<failedAttempts - 1)
)
vm.WaitForEventF = func(_ context.Context) (interface{}, error) {
return common.PendingTxs, nil
}
count := 0
vm.BuildBlockF = func(_ context.Context) (block.Block, error) {
if count < failedAttempts {
count++
return nil, errors.New("failed to build block")
}
// on the 8th try, return the block successfully
return child.vmBlock, nil
}
bb := &BlockBuilder{
log: log.NoLog{},
vm: vm,
blockTracker: genesis.blockTracker,
}
start := time.Now()
block, built := bb.BuildBlock(ctx, child.BlockHeader().ProtocolMetadata)
endTime := time.Since(start)
require.True(t, built)
require.Equal(t, child.BlockHeader(), block.BlockHeader())
// 10, 20, 40, 80, 160, 320, 640 = 1270ms
require.GreaterOrEqual(t, endTime, minimumExpectedDelay)
}
func TestWaitForPendingBlockBackoff(t *testing.T) {
ctx := context.Background()
vm := newTestVM()
const (
failedAttempts = 7
minimumExpectedDelay = initBackoff * (1<<failedAttempts - 1)
)
count := 0
vm.WaitForEventF = func(_ context.Context) (interface{}, error) {
if count < failedAttempts {
count++
return "StateSyncDone", nil
}
return common.PendingTxs, nil
}
bb := &BlockBuilder{
log: log.NoLog{},
vm: vm,
blockTracker: nil,
}
start := time.Now()
bb.WaitForPendingBlock(ctx)
endTime := time.Since(start)
// 10, 20, 40, 80, 160, 320, 640 = 1270ms
require.GreaterOrEqual(t, endTime, minimumExpectedDelay)
}