Files
zeekayandHanzo Dev cc9c510273 Depend on the renamed utility package
github.com/luxfi/utils is now github.com/luxfi/util. Import paths and the
module requirement follow the rename, and the luxUtils alias becomes luxUtil.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-06 04:29:57 -07:00

342 lines
9.2 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package state
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"github.com/luxfi/database"
"github.com/luxfi/database/memdb"
"github.com/luxfi/evmgpu/plugin/evm/validators/state/interfaces"
"github.com/luxfi/ids"
"github.com/luxfi/util/wrappers"
)
func TestState(t *testing.T) {
require := require.New(t)
db := memdb.New()
state, err := NewState(db)
require.NoError(err)
// get non-existent uptime
nodeID := ids.GenerateTestNodeID()
vID := ids.GenerateTestID()
chainID := ids.GenerateTestID() // Test chain ID
_, _, err = state.GetUptime(nodeID, chainID)
require.ErrorIs(err, database.ErrNotFound)
// set non-existent uptime
startTime := time.Now()
err = state.SetUptime(nodeID, chainID, 1, startTime)
require.ErrorIs(err, database.ErrNotFound)
// add new validator
vdr := interfaces.Validator{
ValidationID: vID,
NodeID: nodeID,
Weight: 1,
StartTimestamp: uint64(startTime.Unix()),
IsActive: true,
IsL1Validator: true,
}
err = state.AddValidator(vdr)
require.NoError(err)
// adding the same validator should fail
err = state.AddValidator(vdr)
require.ErrorIs(err, ErrAlreadyExists)
// adding the same nodeID should fail
newVdr := vdr
newVdr.ValidationID = ids.GenerateTestID()
err = state.AddValidator(newVdr)
require.ErrorIs(err, ErrAlreadyExists)
// get uptime
uptime, timeSinceLastUpdate, err := state.GetUptime(nodeID, chainID)
require.NoError(err)
require.Equal(time.Duration(0), uptime)
// timeSinceLastUpdate should be very small since we just added the validator
require.Less(timeSinceLastUpdate, time.Minute)
// set uptime
newUptime := 2 * time.Minute
newLastUpdated := time.Now()
require.NoError(state.SetUptime(nodeID, chainID, newUptime, newLastUpdated))
// get new uptime
uptime, timeSinceLastUpdate, err = state.GetUptime(nodeID, chainID)
require.NoError(err)
require.Equal(newUptime, uptime)
// timeSinceLastUpdate should be very small since we just set it
require.Less(timeSinceLastUpdate, time.Second)
// set status
vdr.IsActive = false
require.NoError(state.UpdateValidator(vdr))
// get status
data, err := state.GetValidator(vID)
require.NoError(err)
require.False(data.IsActive)
// set weight
newWeight := uint64(2)
vdr.Weight = newWeight
require.NoError(state.UpdateValidator(vdr))
// get weight
data, err = state.GetValidator(vID)
require.NoError(err)
require.Equal(newWeight, data.Weight)
// set a different node ID should fail
newNodeID := ids.GenerateTestNodeID()
vdr.NodeID = newNodeID
err = state.UpdateValidator(vdr)
require.ErrorIs(err, ErrImmutableField)
// set a different start time should fail
vdr.StartTimestamp = vdr.StartTimestamp + 100
err = state.UpdateValidator(vdr)
require.ErrorIs(err, ErrImmutableField)
// set IsL1Validator should fail
vdr.IsL1Validator = false
err = state.UpdateValidator(vdr)
require.ErrorIs(err, ErrImmutableField)
// set validation ID should result in not found
vdr.ValidationID = ids.GenerateTestID()
err = state.UpdateValidator(vdr)
require.ErrorIs(err, database.ErrNotFound)
// delete uptime
require.NoError(state.DeleteValidator(vID))
// get deleted uptime
_, _, err = state.GetUptime(nodeID, chainID)
require.ErrorIs(err, database.ErrNotFound)
}
func TestWriteValidator(t *testing.T) {
require := require.New(t)
db := memdb.New()
state, err := NewState(db)
require.NoError(err)
// write empty uptimes
require.NoError(state.WriteState())
// load uptime
nodeID := ids.GenerateTestNodeID()
vID := ids.GenerateTestID()
chainID := ids.GenerateTestID() // Test chain ID
startTime := time.Now()
require.NoError(state.AddValidator(interfaces.Validator{
ValidationID: vID,
NodeID: nodeID,
Weight: 1,
StartTimestamp: uint64(startTime.Unix()),
IsActive: true,
IsL1Validator: true,
}))
// write state, should reflect to DB
require.NoError(state.WriteState())
require.True(db.Has(vID[:]))
// set uptime
newUptime := 2 * time.Minute
newLastUpdated := startTime.Add(time.Hour)
require.NoError(state.SetUptime(nodeID, chainID, newUptime, newLastUpdated))
require.NoError(state.WriteState())
// refresh state, should load from DB
state, err = NewState(db)
require.NoError(err)
// get uptime
uptime, timeSinceLastUpdate, err := state.GetUptime(nodeID, chainID)
require.NoError(err)
require.Equal(newUptime, uptime)
// timeSinceLastUpdate should be small but accounting for db reload
require.Less(timeSinceLastUpdate, time.Minute)
// delete
require.NoError(state.DeleteValidator(vID))
// write state, should reflect to DB
require.NoError(state.WriteState())
require.False(db.Has(vID[:]))
}
func TestParseValidator(t *testing.T) {
testNodeID, err := ids.NodeIDFromString("NodeID-CaBYJ9kzHvrQFiYWowMkJGAQKGMJqZoat")
require.NoError(t, err)
type test struct {
name string
bytes []byte
expected *validatorData
expectedErr error
}
tests := []test{
{
name: "nil",
bytes: nil,
expected: &validatorData{
LastUpdated: 0,
StartTime: 0,
validationID: ids.Empty,
NodeID: ids.EmptyNodeID,
UpDuration: 0,
Weight: 0,
IsActive: false,
IsL1Validator: false,
},
expectedErr: nil,
},
{
name: "empty",
bytes: []byte{},
expected: &validatorData{
LastUpdated: 0,
StartTime: 0,
validationID: ids.Empty,
NodeID: ids.EmptyNodeID,
UpDuration: 0,
Weight: 0,
IsActive: false,
IsL1Validator: false,
},
expectedErr: nil,
},
{
name: "valid",
bytes: []byte{
// codec version
0x00, 0x00,
// up duration (little-endian u64: 6000000)
0x80, 0x8D, 0x5B, 0x00, 0x00, 0x00, 0x00, 0x00,
// last updated (little-endian u64: 900000)
0xA0, 0xBB, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00,
// node ID
0x7e, 0xef, 0xe8, 0x8a, 0x45, 0xfb, 0x7a, 0xc4,
0xb0, 0x59, 0xc9, 0x33, 0x71, 0x0a, 0x57, 0x33,
0xff, 0x9f, 0x4b, 0xab,
// weight (little-endian u64: 1)
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// start time (little-endian u64: 6000000)
0x80, 0x8D, 0x5B, 0x00, 0x00, 0x00, 0x00, 0x00,
// status
0x01,
// IsL1Validator
0x01,
},
expected: &validatorData{
UpDuration: time.Duration(6000000),
LastUpdated: 900000,
NodeID: testNodeID,
StartTime: 6000000,
IsActive: true,
Weight: 1,
IsL1Validator: true,
},
},
{
name: "invalid codec version",
bytes: []byte{
// codec version (little-endian u16: 2, unknown)
0x02, 0x00,
// up duration (little-endian u64: 6000000)
0x80, 0x8D, 0x5B, 0x00, 0x00, 0x00, 0x00, 0x00,
// last updated (little-endian u64: 900000)
0xA0, 0xBB, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00,
},
expected: nil,
expectedErr: ErrUnknownVersion,
},
{
name: "short byte len",
bytes: []byte{
// codec version
0x00, 0x00,
// up duration (little-endian u64: 6000000)
0x80, 0x8D, 0x5B, 0x00, 0x00, 0x00, 0x00, 0x00,
// last updated (little-endian u64: 900000)
0xA0, 0xBB, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00,
},
expected: nil,
expectedErr: wrappers.ErrInsufficientLength,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require := require.New(t)
var data validatorData
err := parseValidatorData(tt.bytes, &data)
require.ErrorIs(err, tt.expectedErr)
if tt.expectedErr != nil {
return
}
require.Equal(tt.expected, &data)
})
}
}
func TestStateListener(t *testing.T) {
require := require.New(t)
db := memdb.New()
state, err := NewState(db)
require.NoError(err)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
expectedvID := ids.GenerateTestID()
expectedNodeID := ids.GenerateTestNodeID()
expectedStartTime := time.Now()
mockListener := interfaces.NewMockStateCallbackListener(ctrl)
// add initial validator to test RegisterListener
initialvID := ids.GenerateTestID()
initialNodeID := ids.GenerateTestNodeID()
initialStartTime := time.Now()
// add initial validator
require.NoError(state.AddValidator(interfaces.Validator{
ValidationID: initialvID,
NodeID: initialNodeID,
Weight: 1,
StartTimestamp: uint64(initialStartTime.Unix()),
IsActive: true,
IsL1Validator: true,
}))
// register listener
mockListener.EXPECT().OnValidatorAdded(initialvID, initialNodeID, uint64(initialStartTime.Unix()), true)
state.RegisterListener(mockListener)
// add new validator
mockListener.EXPECT().OnValidatorAdded(expectedvID, expectedNodeID, uint64(expectedStartTime.Unix()), true)
vdr := interfaces.Validator{
ValidationID: expectedvID,
NodeID: expectedNodeID,
Weight: 1,
StartTimestamp: uint64(expectedStartTime.Unix()),
IsActive: true,
IsL1Validator: true,
}
require.NoError(state.AddValidator(vdr))
// set status
mockListener.EXPECT().OnValidatorStatusUpdated(expectedvID, expectedNodeID, false)
vdr.IsActive = false
require.NoError(state.UpdateValidator(vdr))
// set status twice should not trigger listener
require.NoError(state.UpdateValidator(vdr))
// remove validator
mockListener.EXPECT().OnValidatorRemoved(expectedvID, expectedNodeID)
require.NoError(state.DeleteValidator(expectedvID))
}