Files
node/x/blockdb/readblock_test.go
zeekay 7cf87b230b node-unified is the node
The module already called itself github.com/luxfi/node while containing no node
in Go: cmd/luxd was twenty-one lines that printed the size of a few types, and
the Go column of the matrix measured a VM plugin. A repo that fetches the thing
it claims to be has not replaced it, so the Go sources move in — 1515 files,
the daemon and everything under it — and the build reaches outside this module
for nothing.

Rust and C++ sit beside it unchanged and are still measured against it by the
differential. That is the point of one repo: the reference implementation and
the two ports are the same checkout, so parity is a thing you can run rather
than a thing you assert.

Verified: builds, and the closure carries no lux-private — checked at build time
rather than believed. The daemon starts twelve chains on a local network
(P X C Q Z A B G K M F, plus D through its plugin) and reports healthy.
2026-09-07 16:15:25 -07:00

218 lines
4.7 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package blockdb
import (
"bytes"
"errors"
"math"
"sync"
"sync/atomic"
"testing"
"github.com/stretchr/testify/require"
)
func TestReadOperations(t *testing.T) {
tests := []struct {
name string
readHeight uint64
noBlock bool
config *DatabaseConfig
setup func(db *Database)
wantErr error
}{
{
name: "read first block",
readHeight: 0,
},
{
name: "read max height block",
readHeight: 50,
},
{
name: "read height with no block",
readHeight: 40,
noBlock: true,
},
{
name: "read block higher than max height",
readHeight: 100,
noBlock: true,
},
{
name: "read valid block with non-zero minimum height",
readHeight: 25,
config: &DatabaseConfig{
MinimumHeight: 20,
MaxDataFileSize: DefaultMaxDataFileSize,
CheckpointInterval: 1024,
MaxDataFiles: DefaultMaxDataFileSize,
},
},
{
name: "database closed",
readHeight: 1,
setup: func(db *Database) {
db.Close()
},
wantErr: ErrDatabaseClosed,
},
{
name: "height below minimum",
readHeight: 5,
config: &DatabaseConfig{
MinimumHeight: 10,
MaxDataFileSize: DefaultMaxDataFileSize,
CheckpointInterval: 1024,
MaxDataFiles: DefaultMaxDataFileSize,
},
wantErr: ErrInvalidBlockHeight,
},
{
name: "block is past max height",
readHeight: 51,
wantErr: ErrBlockNotFound,
},
{
name: "block height is max height",
readHeight: math.MaxUint64,
wantErr: ErrBlockNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
config := tt.config
if config == nil {
defaultConfig := DefaultConfig()
config = &defaultConfig
}
store, cleanup := newTestDatabase(t, *config)
defer cleanup()
// Seed database with blocks based on config (unless skipSeed is true)
seededBlocks := make(map[uint64][]byte)
minHeight := config.MinimumHeight
maxHeight := minHeight + 50 // Always write 51 blocks
gapHeight := minHeight + 40 // Gap at relative position 40
for i := minHeight; i <= maxHeight; i++ {
if i == gapHeight {
continue // Create gap
}
block := randomBlock(t)
require.NoError(t, store.WriteBlock(i, block))
seededBlocks[i] = block
}
if tt.setup != nil {
tt.setup(store)
}
if tt.wantErr != nil {
_, err := store.ReadBlock(tt.readHeight)
require.ErrorIs(t, err, tt.wantErr)
return
}
// Handle success cases
if tt.noBlock {
_, err := store.ReadBlock(tt.readHeight)
require.ErrorIs(t, err, ErrBlockNotFound)
} else {
readBlock, err := store.ReadBlock(tt.readHeight)
require.NoError(t, err)
require.NotNil(t, readBlock)
expectedBlock := seededBlocks[tt.readHeight]
require.Equal(t, expectedBlock, readBlock)
}
})
}
}
func TestReadOperations_Concurrency(t *testing.T) {
store, cleanup := newTestDatabase(t, DefaultConfig())
defer cleanup()
// Pre-generate blocks and write them
numBlocks := 50
blocks := make([][]byte, numBlocks)
gapHeights := map[uint64]bool{
10: true,
20: true,
}
for i := range numBlocks {
if gapHeights[uint64(i)] {
continue
}
blocks[i] = randomBlock(t)
require.NoError(t, store.WriteBlock(uint64(i), blocks[i]))
}
var wg sync.WaitGroup
var errorCount atomic.Int32
var blockErrors atomic.Int32
for i := range numBlocks + 10 {
wg.Add(3) // One for each read operation
go func(height int) {
defer wg.Done()
block, err := store.ReadBlock(uint64(height))
if gapHeights[uint64(height)] || height >= numBlocks {
if err == nil || !errors.Is(err, ErrBlockNotFound) {
errorCount.Add(1)
}
} else {
if err != nil {
errorCount.Add(1)
return
}
if !bytes.Equal(blocks[height], block) {
blockErrors.Add(1)
}
}
}(i)
go func(height int) {
defer wg.Done()
_, err := store.ReadBlock(uint64(height))
if gapHeights[uint64(height)] || height >= numBlocks {
if err == nil || !errors.Is(err, ErrBlockNotFound) {
errorCount.Add(1)
}
} else {
if err != nil {
errorCount.Add(1)
return
}
}
}(i)
go func(height int) {
defer wg.Done()
_, err := store.ReadBlock(uint64(height))
if gapHeights[uint64(height)] || height >= numBlocks {
if err == nil || !errors.Is(err, ErrBlockNotFound) {
errorCount.Add(1)
}
} else {
if err != nil {
errorCount.Add(1)
return
}
}
}(i)
}
wg.Wait()
require.Zero(t, errorCount.Load(), "concurrent read operations had errors")
require.Zero(t, blockErrors.Load(), "block data mismatches detected")
}