Files
node/chains/block_handler_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

248 lines
6.8 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package chains
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/ids"
)
// TestAcceptRejectDerivation tests the accept/reject derivation logic from the Chits handler
// This tests the core logic: Accept=true only if block exists AND verifies
func TestAcceptRejectDerivation(t *testing.T) {
testCases := []struct {
name string
blockExists bool
verifyError error
expectedAccept bool
}{
{
name: "accept when block exists and verifies",
blockExists: true,
verifyError: nil,
expectedAccept: true,
},
{
name: "reject when block not found",
blockExists: false,
verifyError: nil,
expectedAccept: false,
},
{
name: "reject when block fails verification",
blockExists: true,
verifyError: errors.New("verification failed"),
expectedAccept: false,
},
{
name: "reject when block has invalid signature",
blockExists: true,
verifyError: errors.New("invalid signature"),
expectedAccept: false,
},
{
name: "reject when block has invalid parent",
blockExists: true,
verifyError: errors.New("unknown parent"),
expectedAccept: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
// Simulate the accept/reject logic from HandleInbound
// This matches the logic in manager.go lines 1912-1920
accept := deriveAcceptFromVerification(
context.Background(),
tc.blockExists,
tc.verifyError,
)
require.Equal(tc.expectedAccept, accept, "Accept value mismatch")
})
}
}
// deriveAcceptFromVerification simulates the logic in blockHandler.HandleInbound
// for the Chits case. This is the core accept/reject derivation logic.
func deriveAcceptFromVerification(ctx context.Context, blockExists bool, verifyError error) bool {
// This matches the logic in manager.go:
// accept := false
// if b.vm != nil {
// if blk, err := b.vm.GetBlock(ctx, preferredID); err == nil {
// if err := blk.Verify(ctx); err == nil {
// accept = true
// }
// }
// }
if !blockExists {
return false
}
if verifyError != nil {
return false
}
return true
}
// TestAcceptRejectWithNilVM tests that Accept=false when VM is nil
func TestAcceptRejectWithNilVM(t *testing.T) {
require := require.New(t)
// When VM is nil, we can't get the block, so accept should be false
// This is the outer check: if b.vm != nil { ... }
vmIsNil := true
accept := false
if !vmIsNil {
// Would call vm.GetBlock and Verify
accept = true
}
require.False(accept, "Accept should be false when VM is nil")
}
// TestChitsMessageLength tests that messages shorter than 32 bytes are handled
func TestChitsMessageLength(t *testing.T) {
testCases := []struct {
name string
messageLen int
shouldParse bool
}{
{
name: "empty message",
messageLen: 0,
shouldParse: false,
},
{
name: "message too short (16 bytes)",
messageLen: 16,
shouldParse: false,
},
{
name: "message too short (31 bytes)",
messageLen: 31,
shouldParse: false,
},
{
name: "message exactly 32 bytes",
messageLen: 32,
shouldParse: true,
},
{
name: "message longer than 32 bytes",
messageLen: 64,
shouldParse: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
// This matches the logic in manager.go:
// if len(msg.Message) >= 32 && b.engine != nil { ... }
msg := make([]byte, tc.messageLen)
canParse := len(msg) >= 32
require.Equal(tc.shouldParse, canParse, "Parse decision mismatch")
// If we can parse, verify we can extract a block ID
if canParse {
var preferredID ids.ID
copy(preferredID[:], msg[:32])
require.Len(preferredID[:], 32, "Block ID should be 32 bytes")
}
})
}
}
// TestVoteCreation tests that Vote struct is created correctly with derived Accept value
func TestVoteCreation(t *testing.T) {
require := require.New(t)
// Create a vote with Accept derived from verification
blockID := ids.GenerateTestID()
nodeID := ids.GenerateTestNodeID()
// Case 1: Block exists and verifies - accept should be true
accept1 := deriveAcceptFromVerification(context.Background(), true, nil)
require.True(accept1)
// Case 2: Block exists but fails verification - accept should be false
accept2 := deriveAcceptFromVerification(context.Background(), true, errors.New("bad block"))
require.False(accept2)
// Case 3: Block doesn't exist - accept should be false
accept3 := deriveAcceptFromVerification(context.Background(), false, nil)
require.False(accept3)
// Verify the block ID and node ID are preserved
require.NotEqual(ids.Empty, blockID)
require.NotEqual(ids.EmptyNodeID, nodeID)
}
// TestAcceptRejectOldVsNew tests that the new logic correctly rejects
// votes that would have been incorrectly accepted with hardcoded Accept=true
func TestAcceptRejectOldVsNew(t *testing.T) {
require := require.New(t)
// OLD LOGIC (before fix): Accept was always true
// oldAccept := true // WRONG - this was hardcoded
// NEW LOGIC: Accept is derived from verification
// This ensures we don't accept votes for blocks we can't verify
testCases := []struct {
name string
blockExists bool
verifyError error
oldAccept bool // What old logic would have done (always true)
newAccept bool // What new logic does (derived from verification)
}{
{
name: "valid block - old and new agree",
blockExists: true,
verifyError: nil,
oldAccept: true,
newAccept: true,
},
{
name: "missing block - old accepted wrongly, new rejects",
blockExists: false,
verifyError: nil,
oldAccept: true, // BUG: Old logic would accept
newAccept: false, // CORRECT: New logic rejects
},
{
name: "invalid block - old accepted wrongly, new rejects",
blockExists: true,
verifyError: errors.New("invalid"),
oldAccept: true, // BUG: Old logic would accept
newAccept: false, // CORRECT: New logic rejects
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
newAccept := deriveAcceptFromVerification(context.Background(), tc.blockExists, tc.verifyError)
require.Equal(tc.newAccept, newAccept, "New accept value mismatch")
// Verify the new logic is an improvement over the old logic
if tc.blockExists && tc.verifyError == nil {
// Valid block: both should accept
require.Equal(tc.oldAccept, newAccept)
} else {
// Invalid or missing block: new logic should reject, old logic was wrong
require.NotEqual(tc.oldAccept, newAccept, "New logic should reject what old logic wrongly accepted")
}
})
}
}