Files

285 lines
7.3 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package executor
import (
"errors"
"testing"
"time"
"github.com/luxfi/mock/gomock"
"github.com/stretchr/testify/require"
"github.com/luxfi/ids"
"github.com/luxfi/math/set"
"github.com/luxfi/proto/x/block"
"github.com/luxfi/proto/x/state/statemock"
"github.com/luxfi/proto/x/txs"
"github.com/luxfi/proto/x/txs/txsmock"
)
var (
errTest = errors.New("test error")
errTestSyntacticVerifyFail = errors.New("test error")
errTestSemanticVerifyFail = errors.New("test error")
errTestExecutionFail = errors.New("test error")
)
func TestManagerGetStatelessBlock(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
state := statemock.NewState(ctrl)
m := &manager{
state: state,
blkIDToState: map[ids.ID]*blockState{},
}
// Case: block is in memory
{
statelessBlk := block.NewMockBlock(ctrl)
blkID := ids.GenerateTestID()
blk := &blockState{
statelessBlock: statelessBlk,
}
m.blkIDToState[blkID] = blk
gotBlk, err := m.GetStatelessBlock(blkID)
require.NoError(err)
require.Equal(statelessBlk, gotBlk)
}
// Case: block isn't in memory
{
blkID := ids.GenerateTestID()
blk := block.NewMockBlock(ctrl)
state.EXPECT().GetBlock(blkID).Return(blk, nil)
gotBlk, err := m.GetStatelessBlock(blkID)
require.NoError(err)
require.Equal(blk, gotBlk)
}
// Case: error while getting block from state
{
blkID := ids.GenerateTestID()
state.EXPECT().GetBlock(blkID).Return(nil, errTest)
_, err := m.GetStatelessBlock(blkID)
require.ErrorIs(err, errTest)
}
}
func TestManagerGetState(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
s := statemock.NewState(ctrl)
m := &manager{
state: s,
blkIDToState: map[ids.ID]*blockState{},
lastAccepted: ids.GenerateTestID(),
}
// Case: Block is in memory
{
diff := statemock.NewDiff(ctrl)
blkID := ids.GenerateTestID()
m.blkIDToState[blkID] = &blockState{
onAcceptState: diff,
}
gotState, ok := m.GetState(blkID)
require.True(ok)
require.Equal(diff, gotState)
}
// Case: Block isn't in memory; block isn't last accepted
{
blkID := ids.GenerateTestID()
gotState, ok := m.GetState(blkID)
require.False(ok)
require.Equal(s, gotState)
}
// Case: Block isn't in memory; block is last accepted
{
gotState, ok := m.GetState(m.lastAccepted)
require.True(ok)
require.Equal(s, gotState)
}
}
func TestManagerVerifyTx(t *testing.T) {
type test struct {
name string
txF func(*gomock.Controller) *txs.Tx
managerF func(*gomock.Controller) *manager
expectedErr error
}
tests := []test{
{
name: "not bootstrapped",
txF: func(*gomock.Controller) *txs.Tx {
return &txs.Tx{}
},
managerF: func(*gomock.Controller) *manager {
return &manager{
backend: defaultTestBackend(false, nil),
}
},
expectedErr: ErrChainNotSynced,
},
{
name: "fails syntactic verification",
txF: func(ctrl *gomock.Controller) *txs.Tx {
unsigned := txsmock.NewUnsignedTx(ctrl)
unsigned.EXPECT().Visit(gomock.Any()).Return(errTestSyntacticVerifyFail)
return &txs.Tx{
Unsigned: unsigned,
}
},
managerF: func(*gomock.Controller) *manager {
return &manager{
backend: defaultTestBackend(true, nil),
}
},
expectedErr: errTestSyntacticVerifyFail,
},
{
name: "fails semantic verification",
txF: func(ctrl *gomock.Controller) *txs.Tx {
unsigned := txsmock.NewUnsignedTx(ctrl)
// Syntactic verification passes
unsigned.EXPECT().Visit(gomock.Any()).Return(nil)
// Semantic verification fails
unsigned.EXPECT().Visit(gomock.Any()).Return(errTestSemanticVerifyFail)
return &txs.Tx{
Unsigned: unsigned,
}
},
managerF: func(ctrl *gomock.Controller) *manager {
lastAcceptedID := ids.GenerateTestID()
// These values don't matter for this test
state := statemock.NewState(ctrl)
state.EXPECT().GetLastAccepted().Return(lastAcceptedID)
state.EXPECT().GetTimestamp().Return(time.Time{})
return &manager{
backend: defaultTestBackend(true, nil),
state: state,
lastAccepted: lastAcceptedID,
}
},
expectedErr: errTestSemanticVerifyFail,
},
{
name: "fails execution",
txF: func(ctrl *gomock.Controller) *txs.Tx {
unsigned := txsmock.NewUnsignedTx(ctrl)
// Syntactic verification passes
unsigned.EXPECT().Visit(gomock.Any()).Return(nil)
// Semantic verification passes
unsigned.EXPECT().Visit(gomock.Any()).Return(nil)
// Execution fails
unsigned.EXPECT().Visit(gomock.Any()).Return(errTestExecutionFail)
return &txs.Tx{
Unsigned: unsigned,
}
},
managerF: func(ctrl *gomock.Controller) *manager {
lastAcceptedID := ids.GenerateTestID()
// These values don't matter for this test
state := statemock.NewState(ctrl)
state.EXPECT().GetLastAccepted().Return(lastAcceptedID)
state.EXPECT().GetTimestamp().Return(time.Time{})
return &manager{
backend: defaultTestBackend(true, nil),
state: state,
lastAccepted: lastAcceptedID,
}
},
expectedErr: errTestExecutionFail,
},
{
name: "happy path",
txF: func(ctrl *gomock.Controller) *txs.Tx {
unsigned := txsmock.NewUnsignedTx(ctrl)
// Syntactic verification passes
unsigned.EXPECT().Visit(gomock.Any()).Return(nil)
// Semantic verification passes
unsigned.EXPECT().Visit(gomock.Any()).Return(nil)
// Execution passes
unsigned.EXPECT().Visit(gomock.Any()).Return(nil)
return &txs.Tx{
Unsigned: unsigned,
}
},
managerF: func(ctrl *gomock.Controller) *manager {
lastAcceptedID := ids.GenerateTestID()
// These values don't matter for this test
state := statemock.NewState(ctrl)
state.EXPECT().GetLastAccepted().Return(lastAcceptedID)
state.EXPECT().GetTimestamp().Return(time.Time{})
return &manager{
backend: defaultTestBackend(true, nil),
state: state,
lastAccepted: lastAcceptedID,
}
},
expectedErr: nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
m := test.managerF(ctrl)
tx := test.txF(ctrl)
err := m.VerifyTx(tx)
require.ErrorIs(err, test.expectedErr)
})
}
}
func TestVerifyUniqueInputs(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
// Case: No inputs
{
m := &manager{}
require.NoError(m.VerifyUniqueInputs(ids.GenerateTestID(), make(set.Set[ids.ID])))
}
// blk0 is blk1's parent
blk0ID, blk1ID := ids.GenerateTestID(), ids.GenerateTestID()
blk0, blk1 := block.NewMockBlock(ctrl), block.NewMockBlock(ctrl)
blk1.EXPECT().Parent().Return(blk0ID).AnyTimes()
blk0.EXPECT().Parent().Return(ids.Empty).AnyTimes() // blk0's parent is accepted
inputID := ids.GenerateTestID()
m := &manager{
blkIDToState: map[ids.ID]*blockState{
blk0ID: {
statelessBlock: blk0,
importedInputs: set.Of(inputID),
},
blk1ID: {
statelessBlock: blk1,
importedInputs: set.Of(ids.GenerateTestID()),
},
},
}
// [blk1]'s parent, [blk0], has [inputID] as an input
err := m.VerifyUniqueInputs(blk1ID, set.Of(inputID))
require.ErrorIs(err, ErrConflictingParentTxs)
require.NoError(m.VerifyUniqueInputs(blk1ID, set.Of(ids.GenerateTestID())))
}