Files
node/vms/platformvm/block/executor/proposal_block_test.go
T
Hanzo AI 3774075c95 feat: clean up dead code, update deps, add xvm genesis/fx and genesis builder
Remove unused bloom filter, test helpers, metrics scaffolding, and
linearizable VM wrapper. Trim stale go.sum entries. Add genesis builder
helpers and xvm FX/genesis initialization.
2026-04-19 17:05:59 -07:00

1521 lines
46 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package executor
import (
"context"
"fmt"
"testing"
"time"
"github.com/luxfi/mock/gomock"
"github.com/stretchr/testify/require"
"github.com/luxfi/runtime"
"github.com/luxfi/database"
"github.com/luxfi/ids"
"github.com/luxfi/constants"
"github.com/luxfi/crypto/bls/signer/localsigner"
"github.com/luxfi/crypto/secp256k1"
"github.com/luxfi/node/upgrade/upgradetest"
"github.com/luxfi/node/vms/components/gas"
lux "github.com/luxfi/utxo"
"github.com/luxfi/node/vms/platformvm/block"
"github.com/luxfi/node/vms/platformvm/genesis/genesistest"
"github.com/luxfi/node/vms/platformvm/reward"
"github.com/luxfi/node/vms/platformvm/state"
"github.com/luxfi/node/vms/platformvm/status"
"github.com/luxfi/node/vms/platformvm/txs"
"github.com/luxfi/node/vms/platformvm/txs/executor"
"github.com/luxfi/timer/mockable"
"github.com/luxfi/container/iterator"
"github.com/luxfi/vm/chain"
"github.com/luxfi/node/vms/platformvm/signer"
"github.com/luxfi/utxo/secp256k1fx"
walletcommon "github.com/luxfi/node/wallet/network/primary/common"
)
func TestApricotProposalBlockTimeVerification(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
env := newEnvironment(t, ctrl, upgradetest.ApricotPhase5)
// create apricotParentBlk. It's a standard one for simplicity
parentHeight := uint64(2022)
apricotParentBlk, err := block.NewApricotStandardBlock(
ids.Empty, // does not matter
parentHeight,
nil, // txs do not matter in this test
)
require.NoError(err)
parentID := apricotParentBlk.ID()
// store parent block, with relevant quantities
onParentAccept := state.NewMockDiff(ctrl)
env.blkManager.(*manager).blkIDToState[parentID] = &blockState{
statelessBlock: apricotParentBlk,
onAcceptState: onParentAccept,
}
env.blkManager.(*manager).lastAccepted = parentID
chainTime := env.clk.Time().Truncate(time.Second)
// create a proposal transaction to be included into proposal block
utx := &txs.AddValidatorTx{
BaseTx: txs.BaseTx{},
Validator: txs.Validator{End: uint64(chainTime.Unix())},
StakeOuts: []*lux.TransferableOutput{
{
Asset: lux.Asset{
ID: env.rt.XAssetID,
},
Out: &secp256k1fx.TransferOutput{
Amt: 1,
},
},
},
RewardsOwner: &secp256k1fx.OutputOwners{},
DelegationShares: uint32(defaultTxFee),
}
addValTx := &txs.Tx{Unsigned: utx}
require.NoError(addValTx.Initialize(txs.Codec))
blkTx := &txs.Tx{
Unsigned: &txs.RewardValidatorTx{
TxID: addValTx.ID(),
},
}
// setup state to validate proposal block transaction
onParentAccept.EXPECT().GetTimestamp().Return(chainTime).AnyTimes()
onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes()
onParentAccept.EXPECT().GetL1ValidatorExcess().Return(gas.Gas(0)).AnyTimes()
onParentAccept.EXPECT().GetAccruedFees().Return(uint64(0)).AnyTimes()
onParentAccept.EXPECT().NumActiveL1Validators().Return(0).AnyTimes()
onParentAccept.EXPECT().GetCurrentStakerIterator().Return(
iterator.FromSlice(&state.Staker{
TxID: addValTx.ID(),
NodeID: utx.NodeID(),
ChainID: utx.ChainID(),
StartTime: utx.StartTime(),
NextTime: chainTime,
EndTime: chainTime,
}),
nil,
)
onParentAccept.EXPECT().GetTx(addValTx.ID()).Return(addValTx, status.Committed, nil)
onParentAccept.EXPECT().GetCurrentSupply(constants.PrimaryNetworkID).Return(uint64(1000), nil).AnyTimes()
onParentAccept.EXPECT().GetDelegateeReward(constants.PrimaryNetworkID, utx.NodeID()).Return(uint64(0), nil).AnyTimes()
env.mockedState.EXPECT().GetUptime(gomock.Any(), gomock.Any()).Return(
time.Microsecond, /*upDuration*/
time.Microsecond, /*lastUpdated - should be Duration, not Time*/
nil, /*err*/
).AnyTimes()
// wrong height
statelessProposalBlock, err := block.NewApricotProposalBlock(
parentID,
parentHeight,
blkTx,
)
require.NoError(err)
proposalBlock := env.blkManager.NewBlock(statelessProposalBlock)
err = proposalBlock.Verify(context.Background())
require.ErrorIs(err, errIncorrectBlockHeight)
// valid
statelessProposalBlock, err = block.NewApricotProposalBlock(
parentID,
parentHeight+1,
blkTx,
)
require.NoError(err)
proposalBlock = env.blkManager.NewBlock(statelessProposalBlock)
require.NoError(proposalBlock.Verify(context.Background()))
}
func TestBanffProposalBlockTimeVerification(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
env := newEnvironment(t, ctrl, upgradetest.Banff)
// create parentBlock. It's a standard one for simplicity
parentTime := genesistest.DefaultValidatorStartTime
parentHeight := uint64(2022)
banffParentBlk, err := block.NewApricotStandardBlock(
ids.GenerateTestID(), // does not matter
parentHeight,
nil, // txs do not matter in this test
)
require.NoError(err)
parentID := banffParentBlk.ID()
// store parent block, with relevant quantities
chainTime := parentTime
onParentAccept := state.NewMockDiff(ctrl)
onParentAccept.EXPECT().GetTimestamp().Return(parentTime).AnyTimes()
onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes()
onParentAccept.EXPECT().GetL1ValidatorExcess().Return(gas.Gas(0)).AnyTimes()
onParentAccept.EXPECT().GetAccruedFees().Return(uint64(0)).AnyTimes()
onParentAccept.EXPECT().NumActiveL1Validators().Return(0).AnyTimes()
onParentAccept.EXPECT().GetCurrentSupply(constants.PrimaryNetworkID).Return(uint64(1000), nil).AnyTimes()
env.blkManager.(*manager).blkIDToState[parentID] = &blockState{
statelessBlock: banffParentBlk,
onAcceptState: onParentAccept,
timestamp: parentTime,
}
env.blkManager.(*manager).lastAccepted = parentID
env.mockedState.EXPECT().GetLastAccepted().Return(parentID).AnyTimes()
env.mockedState.EXPECT().GetStatelessBlock(gomock.Any()).DoAndReturn(
func(blockID ids.ID) (block.Block, error) {
if blockID == parentID {
return banffParentBlk, nil
}
return nil, database.ErrNotFound
}).AnyTimes()
// setup state to validate proposal block transaction
nextStakerTime := chainTime.Add(executor.SyncBound).Add(-1 * time.Second)
unsignedNextStakerTx := &txs.AddValidatorTx{
BaseTx: txs.BaseTx{},
Validator: txs.Validator{End: uint64(nextStakerTime.Unix())},
StakeOuts: []*lux.TransferableOutput{
{
Asset: lux.Asset{
ID: env.rt.XAssetID,
},
Out: &secp256k1fx.TransferOutput{
Amt: 1,
},
},
},
RewardsOwner: &secp256k1fx.OutputOwners{},
DelegationShares: uint32(defaultTxFee),
}
nextStakerTx := &txs.Tx{Unsigned: unsignedNextStakerTx}
require.NoError(nextStakerTx.Initialize(txs.Codec))
nextStakerTxID := nextStakerTx.ID()
onParentAccept.EXPECT().GetTx(nextStakerTxID).Return(nextStakerTx, status.Processing, nil)
onParentAccept.EXPECT().GetCurrentStakerIterator().DoAndReturn(func() (iterator.Iterator[*state.Staker], error) {
return iterator.FromSlice(
&state.Staker{
TxID: nextStakerTxID,
EndTime: nextStakerTime,
NextTime: nextStakerTime,
Priority: txs.PrimaryNetworkValidatorCurrentPriority,
},
), nil
}).AnyTimes()
onParentAccept.EXPECT().GetPendingStakerIterator().Return(iterator.Empty[*state.Staker]{}, nil).AnyTimes()
onParentAccept.EXPECT().GetActiveL1ValidatorsIterator().Return(iterator.Empty[state.L1Validator]{}, nil).AnyTimes()
onParentAccept.EXPECT().GetExpiryIterator().Return(iterator.Empty[state.ExpiryEntry]{}, nil).AnyTimes()
onParentAccept.EXPECT().GetDelegateeReward(constants.PrimaryNetworkID, unsignedNextStakerTx.NodeID()).Return(uint64(0), nil).AnyTimes()
env.mockedState.EXPECT().GetUptime(gomock.Any(), gomock.Any()).Return(
time.Microsecond, /*upDuration*/
time.Microsecond, /*lastUpdated - should be Duration, not Time*/
nil, /*err*/
).AnyTimes()
// create proposal tx to be included in the proposal block
blkTx := &txs.Tx{
Unsigned: &txs.RewardValidatorTx{
TxID: nextStakerTxID,
},
}
require.NoError(blkTx.Initialize(txs.Codec))
{
// wrong height
statelessProposalBlock, err := block.NewBanffProposalBlock(
parentTime.Add(time.Second),
parentID,
banffParentBlk.Height(),
blkTx,
[]*txs.Tx{},
)
require.NoError(err)
block := env.blkManager.NewBlock(statelessProposalBlock)
err = block.Verify(context.Background())
require.ErrorIs(err, errIncorrectBlockHeight)
}
{
// wrong block version
statelessProposalBlock, err := block.NewApricotProposalBlock(
parentID,
banffParentBlk.Height()+1,
blkTx,
)
require.NoError(err)
block := env.blkManager.NewBlock(statelessProposalBlock)
err = block.Verify(context.Background())
require.ErrorIs(err, errApricotBlockIssuedAfterFork)
}
{
// wrong timestamp, earlier than parent
statelessProposalBlock, err := block.NewBanffProposalBlock(
parentTime.Add(-1*time.Second),
parentID,
banffParentBlk.Height()+1,
blkTx,
[]*txs.Tx{},
)
require.NoError(err)
block := env.blkManager.NewBlock(statelessProposalBlock)
err = block.Verify(context.Background())
require.ErrorIs(err, executor.ErrChildBlockEarlierThanParent)
}
{
// wrong timestamp, violated synchrony bound
initClkTime := env.clk.Time()
env.clk.Set(parentTime.Add(-executor.SyncBound))
statelessProposalBlock, err := block.NewBanffProposalBlock(
parentTime.Add(time.Second),
parentID,
banffParentBlk.Height()+1,
blkTx,
[]*txs.Tx{},
)
require.NoError(err)
block := env.blkManager.NewBlock(statelessProposalBlock)
err = block.Verify(context.Background())
require.ErrorIs(err, executor.ErrChildBlockBeyondSyncBound)
env.clk.Set(initClkTime)
}
{
// wrong timestamp, skipped staker set change event
skippedStakerEventTimeStamp := nextStakerTime.Add(time.Second)
statelessProposalBlock, err := block.NewBanffProposalBlock(
skippedStakerEventTimeStamp,
parentID,
banffParentBlk.Height()+1,
blkTx,
[]*txs.Tx{},
)
require.NoError(err)
block := env.blkManager.NewBlock(statelessProposalBlock)
err = block.Verify(context.Background())
require.ErrorIs(err, executor.ErrChildBlockAfterStakerChangeTime)
}
{
// wrong tx content (no advance time txs)
invalidTx := &txs.Tx{
Unsigned: &txs.AdvanceTimeTx{
Time: uint64(nextStakerTime.Unix()),
},
}
require.NoError(invalidTx.Initialize(txs.Codec))
statelessProposalBlock, err := block.NewBanffProposalBlock(
parentTime.Add(time.Second),
parentID,
banffParentBlk.Height()+1,
invalidTx,
[]*txs.Tx{},
)
require.NoError(err)
block := env.blkManager.NewBlock(statelessProposalBlock)
err = block.Verify(context.Background())
require.ErrorIs(err, executor.ErrAdvanceTimeTxIssuedAfterBanff)
}
{
// valid
statelessProposalBlock, err := block.NewBanffProposalBlock(
nextStakerTime,
parentID,
banffParentBlk.Height()+1,
blkTx,
[]*txs.Tx{},
)
require.NoError(err)
block := env.blkManager.NewBlock(statelessProposalBlock)
require.NoError(block.Verify(context.Background()))
}
}
func TestBanffProposalBlockUpdateStakers(t *testing.T) {
// Chronological order (not in scale):
// Staker0: |--- ??? // Staker0 end time depends on the test
// Staker1: |------------------------------------------------------|
// Staker2: |------------------------|
// Staker3: |------------------------|
// Staker3sub: |----------------|
// Staker4: |------------------------|
// Staker5: |--------------------|
// Staker0 it's here just to allow to issue a proposal block with the chosen endTime.
// In this test multiple stakers may join and leave the staker set at the same time.
// The order in which they do it is asserted; the order may depend on the staker.TxID,
// which in turns depend on every feature of the transaction creating the staker.
// So in this test we avoid ids.GenerateTestNodeID, in favour of ids.BuildTestNodeID
// so that TxID does not depend on the order we run tests. We also explicitly declare
// the change address, to avoid picking a random one in case multiple funding keys are set.
staker0 := staker{
nodeID: ids.BuildTestNodeID([]byte{0xf0}),
rewardAddress: ids.ShortID{0xf0},
startTime: genesistest.DefaultValidatorStartTime,
endTime: time.Time{}, // actual endTime depends on specific test
}
staker1 := staker{
nodeID: ids.BuildTestNodeID([]byte{0xf1}),
rewardAddress: ids.ShortID{0xf1},
startTime: genesistest.DefaultValidatorStartTime.Add(1 * time.Minute),
endTime: genesistest.DefaultValidatorStartTime.Add(10 * defaultMinStakingDuration).Add(1 * time.Minute),
}
staker2 := staker{
nodeID: ids.BuildTestNodeID([]byte{0xf2}),
rewardAddress: ids.ShortID{0xf2},
startTime: staker1.startTime.Add(1 * time.Minute),
endTime: staker1.startTime.Add(1 * time.Minute).Add(defaultMinStakingDuration),
}
staker3 := staker{
nodeID: ids.BuildTestNodeID([]byte{0xf3}),
rewardAddress: ids.ShortID{0xf3},
startTime: staker2.startTime.Add(1 * time.Minute),
endTime: staker2.endTime.Add(1 * time.Minute),
}
staker3Sub := staker{
nodeID: ids.BuildTestNodeID([]byte{0xf3}),
rewardAddress: ids.ShortID{0xff},
startTime: staker3.startTime.Add(1 * time.Minute),
endTime: staker3.endTime.Add(-1 * time.Minute),
}
staker4 := staker{
nodeID: ids.BuildTestNodeID([]byte{0xf4}),
rewardAddress: ids.ShortID{0xf4},
startTime: staker3.startTime,
endTime: staker3.endTime,
}
staker5 := staker{
nodeID: ids.BuildTestNodeID([]byte{0xf5}),
rewardAddress: ids.ShortID{0xf5},
startTime: staker2.endTime,
endTime: staker2.endTime.Add(defaultMinStakingDuration),
}
tests := []test{
{
description: "advance time to before staker1 start with chain",
stakers: []staker{staker1, staker2, staker3, staker4, staker5},
chainStakers: []staker{staker1, staker2, staker3, staker4, staker5},
advanceTimeTo: []time.Time{staker1.startTime.Add(-1 * time.Second)},
expectedStakers: map[ids.NodeID]stakerStatus{
staker1.nodeID: pending,
staker2.nodeID: pending,
staker3.nodeID: pending,
staker4.nodeID: pending,
staker5.nodeID: pending,
},
expectedNetStakers: map[ids.NodeID]stakerStatus{
staker1.nodeID: pending,
staker2.nodeID: pending,
staker3.nodeID: pending,
staker4.nodeID: pending,
staker5.nodeID: pending,
},
},
{
description: "advance time to staker 1 start with chain",
stakers: []staker{staker1, staker2, staker3, staker4, staker5},
chainStakers: []staker{staker1},
advanceTimeTo: []time.Time{staker1.startTime},
expectedStakers: map[ids.NodeID]stakerStatus{
staker1.nodeID: current,
staker2.nodeID: pending,
staker3.nodeID: pending,
staker4.nodeID: pending,
staker5.nodeID: pending,
},
expectedNetStakers: map[ids.NodeID]stakerStatus{
staker1.nodeID: current,
staker2.nodeID: pending,
staker3.nodeID: pending,
staker4.nodeID: pending,
staker5.nodeID: pending,
},
},
{
description: "advance time to the staker2 start",
stakers: []staker{staker1, staker2, staker3, staker4, staker5},
advanceTimeTo: []time.Time{staker1.startTime, staker2.startTime},
expectedStakers: map[ids.NodeID]stakerStatus{
staker1.nodeID: current,
staker2.nodeID: current,
staker3.nodeID: pending,
staker4.nodeID: pending,
staker5.nodeID: pending,
},
},
{
description: "staker3 should validate only primary network",
stakers: []staker{staker1, staker2, staker3, staker4, staker5},
chainStakers: []staker{staker1, staker2, staker3Sub, staker4, staker5},
advanceTimeTo: []time.Time{staker1.startTime, staker2.startTime, staker3.startTime},
expectedStakers: map[ids.NodeID]stakerStatus{
staker1.nodeID: current,
staker2.nodeID: current,
staker3.nodeID: current,
staker4.nodeID: current,
staker5.nodeID: pending,
},
expectedNetStakers: map[ids.NodeID]stakerStatus{
staker1.nodeID: current,
staker2.nodeID: current,
staker3Sub.nodeID: pending,
staker4.nodeID: current,
staker5.nodeID: pending,
},
},
{
description: "advance time to staker3 start with chain",
stakers: []staker{staker1, staker2, staker3, staker4, staker5},
chainStakers: []staker{staker1, staker2, staker3Sub, staker4, staker5},
advanceTimeTo: []time.Time{staker1.startTime, staker2.startTime, staker3.startTime, staker3Sub.startTime},
expectedStakers: map[ids.NodeID]stakerStatus{
staker1.nodeID: current,
staker2.nodeID: current,
staker3.nodeID: current,
staker4.nodeID: current,
staker5.nodeID: pending,
},
expectedNetStakers: map[ids.NodeID]stakerStatus{
staker1.nodeID: current,
staker2.nodeID: current,
staker3.nodeID: current,
staker4.nodeID: current,
staker5.nodeID: pending,
},
},
}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
require := require.New(t)
env := newEnvironment(t, nil, upgradetest.Banff)
netID := testNet1.ID()
env.config.TrackedChains.Add(netID)
for _, staker := range test.stakers {
wallet := newWallet(t, env, walletConfig{})
tx, err := wallet.IssueAddValidatorTx(
&txs.Validator{
NodeID: staker.nodeID,
Start: uint64(staker.startTime.Unix()),
End: uint64(staker.endTime.Unix()),
Wght: env.config.MinValidatorStake,
},
&secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{staker.rewardAddress},
},
reward.PercentDenominator,
)
require.NoError(err)
staker, err := state.NewPendingStaker(
tx.ID(),
tx.Unsigned.(*txs.AddValidatorTx),
)
require.NoError(err)
require.NoError(env.state.PutPendingValidator(staker))
env.state.AddTx(tx, status.Committed)
require.NoError(env.state.Commit())
}
for _, subStaker := range test.chainStakers {
wallet := newWallet(t, env, walletConfig{
chainIDs: []ids.ID{netID},
})
tx, err := wallet.IssueAddChainValidatorTx(
&txs.ChainValidator{
Validator: txs.Validator{
NodeID: subStaker.nodeID,
Start: uint64(subStaker.startTime.Unix()),
End: uint64(subStaker.endTime.Unix()),
Wght: 10,
},
Chain: netID,
},
)
require.NoError(err)
chainStaker, err := state.NewPendingStaker(
tx.ID(),
tx.Unsigned.(*txs.AddChainValidatorTx),
)
require.NoError(err)
require.NoError(env.state.PutPendingValidator(chainStaker))
env.state.AddTx(tx, status.Committed)
require.NoError(env.state.Commit())
}
for _, newTime := range test.advanceTimeTo {
env.clk.Set(newTime)
// add Staker0 (with the right end time) to state
// so to allow proposalBlk issuance
staker0.endTime = newTime
wallet := newWallet(t, env, walletConfig{})
addStaker0, err := wallet.IssueAddValidatorTx(
&txs.Validator{
NodeID: staker0.nodeID,
Start: uint64(staker0.startTime.Unix()),
End: uint64(staker0.endTime.Unix()),
Wght: 10,
},
&secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{staker0.rewardAddress},
},
reward.PercentDenominator,
)
require.NoError(err)
// store Staker0 to state
addValTx := addStaker0.Unsigned.(*txs.AddValidatorTx)
staker0, err := state.NewCurrentStaker(
addStaker0.ID(),
addValTx,
addValTx.StartTime(),
0,
)
require.NoError(err)
require.NoError(env.state.PutCurrentValidator(staker0))
env.state.AddTx(addStaker0, status.Committed)
require.NoError(env.state.Commit())
s0RewardTx := &txs.Tx{
Unsigned: &txs.RewardValidatorTx{
TxID: staker0.TxID,
},
}
require.NoError(s0RewardTx.Initialize(txs.Codec))
// build proposal block moving ahead chain time
// as well as rewarding staker0
preferredID := env.state.GetLastAccepted()
t.Logf("Getting parent block with ID: %s", preferredID)
parentBlk, err := env.state.GetStatelessBlock(preferredID)
require.NoError(err)
statelessProposalBlock, err := block.NewBanffProposalBlock(
newTime,
parentBlk.ID(),
parentBlk.Height()+1,
s0RewardTx,
[]*txs.Tx{},
)
require.NoError(err)
// verify and accept the block
block := env.blkManager.NewBlock(statelessProposalBlock)
require.NoError(block.Verify(context.Background()))
options, err := block.(chain.OracleBlock).Options(context.Background())
require.NoError(err)
require.NoError(options[0].Verify(context.Background()))
require.NoError(block.Accept(context.Background()))
t.Logf("Accepted proposal block: %s", block.ID())
require.NoError(options[0].Accept(context.Background()))
t.Logf("Accepted option block: %s (last accepted now: %s)", options[0].ID(), env.state.GetLastAccepted())
// Commit state after accepting blocks so they persist for next iteration
require.NoError(env.state.Commit())
t.Logf("Committed state")
// Also commit the versiondb to persist to underlying memdb
require.NoError(env.baseDB.Commit())
t.Logf("Committed baseDB")
// Verify we can immediately retrieve the block
retrievedBlk, err := env.state.GetStatelessBlock(options[0].ID())
if err != nil {
t.Logf("ERROR: Cannot retrieve just-committed block %s: %v", options[0].ID(), err)
// Try direct baseDB access to see if block is there
blockKey := options[0].ID()
rawBytes, dbErr := env.baseDB.Get(append([]byte("block/"), blockKey[:]...))
if dbErr != nil {
t.Logf("BaseDB also doesn't have block: %v", dbErr)
} else {
t.Logf("BaseDB HAS the block! (%d bytes)", len(rawBytes))
}
} else {
t.Logf("SUCCESS: Retrieved committed block %s", retrievedBlk.ID())
}
}
// No need for extra commit here - already committed in loop
for stakerNodeID, status := range test.expectedStakers {
switch status {
case pending:
_, err := env.state.GetPendingValidator(constants.PrimaryNetworkID, stakerNodeID)
require.NoError(err)
_, ok := env.config.Validators.GetValidator(constants.PrimaryNetworkID, stakerNodeID)
require.False(ok)
case current:
_, err := env.state.GetCurrentValidator(constants.PrimaryNetworkID, stakerNodeID)
require.NoError(err)
_, ok := env.config.Validators.GetValidator(constants.PrimaryNetworkID, stakerNodeID)
require.True(ok)
}
}
for stakerNodeID, status := range test.expectedNetStakers {
switch status {
case pending:
_, ok := env.config.Validators.GetValidator(netID, stakerNodeID)
require.False(ok)
case current:
_, ok := env.config.Validators.GetValidator(netID, stakerNodeID)
require.True(ok)
}
}
})
}
}
func TestBanffProposalBlockRemoveNetValidator(t *testing.T) {
require := require.New(t)
env := newEnvironment(t, nil, upgradetest.Banff)
netID := testNet1.ID()
wallet := newWallet(t, env, walletConfig{
chainIDs: []ids.ID{netID},
})
env.config.TrackedChains.Add(netID)
// Add a chain validator to the staker set
chainValidatorNodeID := genesistest.DefaultNodeIDs[0]
chainVdr1EndTime := genesistest.DefaultValidatorStartTime.Add(defaultMinStakingDuration)
tx, err := wallet.IssueAddChainValidatorTx(
&txs.ChainValidator{
Validator: txs.Validator{
NodeID: chainValidatorNodeID,
Start: genesistest.DefaultValidatorStartTimeUnix,
End: uint64(chainVdr1EndTime.Unix()),
Wght: 1,
},
Chain: netID,
},
)
require.NoError(err)
addNetValTx := tx.Unsigned.(*txs.AddChainValidatorTx)
staker, err := state.NewCurrentStaker(
tx.ID(),
addNetValTx,
addNetValTx.StartTime(),
0,
)
require.NoError(err)
require.NoError(env.state.PutCurrentValidator(staker))
env.state.AddTx(tx, status.Committed)
require.NoError(env.state.Commit())
// The above validator is now part of the staking set
// Queue a staker that joins the staker set after the above validator leaves
chainVdr2NodeID := genesistest.DefaultNodeIDs[1]
tx, err = wallet.IssueAddChainValidatorTx(
&txs.ChainValidator{
Validator: txs.Validator{
NodeID: chainVdr2NodeID,
Start: uint64(chainVdr1EndTime.Add(time.Second).Unix()),
End: uint64(chainVdr1EndTime.Add(time.Second).Add(defaultMinStakingDuration).Unix()),
Wght: 1,
},
Chain: netID,
},
)
require.NoError(err)
staker, err = state.NewPendingStaker(
tx.ID(),
tx.Unsigned.(*txs.AddChainValidatorTx),
)
require.NoError(err)
require.NoError(env.state.PutPendingValidator(staker))
env.state.AddTx(tx, status.Committed)
require.NoError(env.state.Commit())
// The above validator is now in the pending staker set
// Advance time to the first staker's end time.
env.clk.Set(chainVdr1EndTime)
// add Staker0 (with the right end time) to state
// so to allow proposalBlk issuance
staker0EndTime := chainVdr1EndTime
addStaker0, err := wallet.IssueAddValidatorTx(
&txs.Validator{
NodeID: ids.GenerateTestNodeID(),
Start: genesistest.DefaultValidatorStartTimeUnix,
End: uint64(staker0EndTime.Unix()),
Wght: 10,
},
&secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{ids.GenerateTestShortID()},
},
reward.PercentDenominator,
walletcommon.WithChangeOwner(&secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{ids.ShortEmpty},
}),
)
require.NoError(err)
// store Staker0 to state
addValTx := addStaker0.Unsigned.(*txs.AddValidatorTx)
staker, err = state.NewCurrentStaker(
addStaker0.ID(),
addValTx,
addValTx.StartTime(),
0,
)
require.NoError(err)
require.NoError(env.state.PutCurrentValidator(staker))
env.state.AddTx(addStaker0, status.Committed)
require.NoError(env.state.Commit())
// create rewardTx for staker0
s0RewardTx := &txs.Tx{
Unsigned: &txs.RewardValidatorTx{
TxID: addStaker0.ID(),
},
}
require.NoError(s0RewardTx.Initialize(txs.Codec))
// build proposal block moving ahead chain time
preferredID := env.state.GetLastAccepted()
parentBlk, err := env.state.GetStatelessBlock(preferredID)
require.NoError(err)
statelessProposalBlock, err := block.NewBanffProposalBlock(
chainVdr1EndTime,
parentBlk.ID(),
parentBlk.Height()+1,
s0RewardTx,
[]*txs.Tx{},
)
require.NoError(err)
propBlk := env.blkManager.NewBlock(statelessProposalBlock)
require.NoError(propBlk.Verify(context.Background())) // verify and update staker set
options, err := propBlk.(chain.OracleBlock).Options(context.Background())
require.NoError(err)
commitBlk := options[0]
require.NoError(commitBlk.Verify(context.Background()))
blkStateMap := env.blkManager.(*manager).blkIDToState
updatedState := blkStateMap[commitBlk.ID()].onAcceptState
_, err = updatedState.GetCurrentValidator(netID, chainValidatorNodeID)
require.ErrorIs(err, database.ErrNotFound)
// Check VM Validators are removed successfully
require.NoError(propBlk.Accept(context.Background()))
require.NoError(commitBlk.Accept(context.Background()))
_, ok := env.config.Validators.GetValidator(netID, chainVdr2NodeID)
require.False(ok)
_, ok = env.config.Validators.GetValidator(netID, chainValidatorNodeID)
require.False(ok)
}
func TestBanffProposalBlockTrackedNet(t *testing.T) {
for _, tracked := range []bool{true, false} {
t.Run(fmt.Sprintf("tracked %t", tracked), func(t *testing.T) {
require := require.New(t)
env := newEnvironment(t, nil, upgradetest.Banff)
netID := testNet1.ID()
if tracked {
env.config.TrackedChains.Add(netID)
}
wallet := newWallet(t, env, walletConfig{
chainIDs: []ids.ID{netID},
})
// Add a chain validator to the staker set
chainValidatorNodeID := genesistest.DefaultNodeIDs[0]
chainVdr1StartTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Minute)
chainVdr1EndTime := genesistest.DefaultValidatorStartTime.Add(10 * defaultMinStakingDuration).Add(1 * time.Minute)
tx, err := wallet.IssueAddChainValidatorTx(
&txs.ChainValidator{
Validator: txs.Validator{
NodeID: chainValidatorNodeID,
Start: uint64(chainVdr1StartTime.Unix()),
End: uint64(chainVdr1EndTime.Unix()),
Wght: 1,
},
Chain: netID,
},
)
require.NoError(err)
staker, err := state.NewPendingStaker(
tx.ID(),
tx.Unsigned.(*txs.AddChainValidatorTx),
)
require.NoError(err)
require.NoError(env.state.PutPendingValidator(staker))
env.state.AddTx(tx, status.Committed)
require.NoError(env.state.Commit())
// Advance time to the staker's start time.
env.clk.Set(chainVdr1StartTime)
// add Staker0 (with the right end time) to state
// so to allow proposalBlk issuance
staker0StartTime := genesistest.DefaultValidatorStartTime
staker0EndTime := chainVdr1StartTime
addStaker0, err := wallet.IssueAddValidatorTx(
&txs.Validator{
NodeID: ids.GenerateTestNodeID(),
Start: uint64(staker0StartTime.Unix()),
End: uint64(staker0EndTime.Unix()),
Wght: 10,
},
&secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{ids.GenerateTestShortID()},
},
reward.PercentDenominator,
)
require.NoError(err)
// store Staker0 to state
addValTx := addStaker0.Unsigned.(*txs.AddValidatorTx)
staker, err = state.NewCurrentStaker(
addStaker0.ID(),
addValTx,
addValTx.StartTime(),
0,
)
require.NoError(err)
require.NoError(env.state.PutCurrentValidator(staker))
env.state.AddTx(addStaker0, status.Committed)
require.NoError(env.state.Commit())
// create rewardTx for staker0
s0RewardTx := &txs.Tx{
Unsigned: &txs.RewardValidatorTx{
TxID: addStaker0.ID(),
},
}
require.NoError(s0RewardTx.Initialize(txs.Codec))
// build proposal block moving ahead chain time
preferredID := env.state.GetLastAccepted()
parentBlk, err := env.state.GetStatelessBlock(preferredID)
require.NoError(err)
statelessProposalBlock, err := block.NewBanffProposalBlock(
chainVdr1StartTime,
parentBlk.ID(),
parentBlk.Height()+1,
s0RewardTx,
[]*txs.Tx{},
)
require.NoError(err)
propBlk := env.blkManager.NewBlock(statelessProposalBlock)
require.NoError(propBlk.Verify(context.Background())) // verify update staker set
options, err := propBlk.(chain.OracleBlock).Options(context.Background())
require.NoError(err)
commitBlk := options[0]
require.NoError(commitBlk.Verify(context.Background()))
require.NoError(propBlk.Accept(context.Background()))
require.NoError(commitBlk.Accept(context.Background()))
_, ok := env.config.Validators.GetValidator(netID, chainValidatorNodeID)
require.True(ok)
})
}
}
func TestBanffProposalBlockDelegatorStakerWeight(t *testing.T) {
require := require.New(t)
env := newEnvironment(t, nil, upgradetest.Banff)
// Case: Timestamp is after next validator start time
// Add a pending validator
pendingValidatorStartTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Second)
pendingValidatorEndTime := pendingValidatorStartTime.Add(defaultMaxStakingDuration)
nodeID := ids.GenerateTestNodeID()
rewardAddress := ids.GenerateTestShortID()
addPendingValidator(
t,
env,
pendingValidatorStartTime,
pendingValidatorEndTime,
nodeID,
rewardAddress,
[]*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
)
wallet := newWallet(t, env, walletConfig{})
rewardsOwner := &secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{ids.GenerateTestShortID()},
}
// add Staker0 (with the right end time) to state
// just to allow proposalBlk issuance (with a reward Tx)
staker0StartTime := genesistest.DefaultValidatorStartTime
staker0EndTime := pendingValidatorStartTime
addStaker0, err := wallet.IssueAddValidatorTx(
&txs.Validator{
NodeID: ids.GenerateTestNodeID(),
Start: uint64(staker0StartTime.Unix()),
End: uint64(staker0EndTime.Unix()),
Wght: 10,
},
rewardsOwner,
reward.PercentDenominator,
)
require.NoError(err)
// store Staker0 to state
addValTx := addStaker0.Unsigned.(*txs.AddValidatorTx)
staker, err := state.NewCurrentStaker(
addStaker0.ID(),
addValTx,
addValTx.StartTime(),
0,
)
require.NoError(err)
require.NoError(env.state.PutCurrentValidator(staker))
env.state.AddTx(addStaker0, status.Committed)
require.NoError(env.state.Commit())
// create rewardTx for staker0
s0RewardTx := &txs.Tx{
Unsigned: &txs.RewardValidatorTx{
TxID: addStaker0.ID(),
},
}
require.NoError(s0RewardTx.Initialize(txs.Codec))
// build proposal block moving ahead chain time
preferredID := env.state.GetLastAccepted()
parentBlk, err := env.state.GetStatelessBlock(preferredID)
require.NoError(err)
statelessProposalBlock, err := block.NewBanffProposalBlock(
pendingValidatorStartTime,
parentBlk.ID(),
parentBlk.Height()+1,
s0RewardTx,
[]*txs.Tx{},
)
require.NoError(err)
propBlk := env.blkManager.NewBlock(statelessProposalBlock)
require.NoError(propBlk.Verify(context.Background()))
options, err := propBlk.(chain.OracleBlock).Options(context.Background())
require.NoError(err)
commitBlk := options[0]
require.NoError(commitBlk.Verify(context.Background()))
require.NoError(propBlk.Accept(context.Background()))
require.NoError(commitBlk.Accept(context.Background()))
// Test validator weight before delegation
vdrWeight := env.config.Validators.GetWeight(constants.PrimaryNetworkID, nodeID)
require.Equal(env.config.MinValidatorStake, vdrWeight)
// Add delegator
pendingDelegatorStartTime := pendingValidatorStartTime.Add(1 * time.Second)
pendingDelegatorEndTime := pendingDelegatorStartTime.Add(1 * time.Second)
addDelegatorTx, err := wallet.IssueAddDelegatorTx(
&txs.Validator{
NodeID: nodeID,
Start: uint64(pendingDelegatorStartTime.Unix()),
End: uint64(pendingDelegatorEndTime.Unix()),
Wght: env.config.MinDelegatorStake,
},
rewardsOwner,
)
require.NoError(err)
staker, err = state.NewPendingStaker(
addDelegatorTx.ID(),
addDelegatorTx.Unsigned.(*txs.AddDelegatorTx),
)
require.NoError(err)
env.state.PutPendingDelegator(staker)
env.state.AddTx(addDelegatorTx, status.Committed)
env.state.SetHeight( /*dummyHeight*/ uint64(1))
require.NoError(env.state.Commit())
// add Staker0 (with the right end time) to state
// so to allow proposalBlk issuance
staker0EndTime = pendingDelegatorStartTime
addStaker0, err = wallet.IssueAddValidatorTx(
&txs.Validator{
NodeID: ids.GenerateTestNodeID(),
Start: uint64(staker0StartTime.Unix()),
End: uint64(staker0EndTime.Unix()),
Wght: 10,
},
rewardsOwner,
reward.PercentDenominator,
)
require.NoError(err)
// store Staker0 to state
addValTx = addStaker0.Unsigned.(*txs.AddValidatorTx)
staker, err = state.NewCurrentStaker(
addStaker0.ID(),
addValTx,
addValTx.StartTime(),
0,
)
require.NoError(err)
require.NoError(env.state.PutCurrentValidator(staker))
env.state.AddTx(addStaker0, status.Committed)
require.NoError(env.state.Commit())
// create rewardTx for staker0
s0RewardTx = &txs.Tx{
Unsigned: &txs.RewardValidatorTx{
TxID: addStaker0.ID(),
},
}
require.NoError(s0RewardTx.Initialize(txs.Codec))
// Advance Time
preferredID = env.state.GetLastAccepted()
parentBlk, err = env.state.GetStatelessBlock(preferredID)
require.NoError(err)
statelessProposalBlock, err = block.NewBanffProposalBlock(
pendingDelegatorStartTime,
parentBlk.ID(),
parentBlk.Height()+1,
s0RewardTx,
[]*txs.Tx{},
)
require.NoError(err)
propBlk = env.blkManager.NewBlock(statelessProposalBlock)
require.NoError(propBlk.Verify(context.Background()))
options, err = propBlk.(chain.OracleBlock).Options(context.Background())
require.NoError(err)
commitBlk = options[0]
require.NoError(commitBlk.Verify(context.Background()))
require.NoError(propBlk.Accept(context.Background()))
require.NoError(commitBlk.Accept(context.Background()))
// Test validator weight after delegation
vdrWeight = env.config.Validators.GetWeight(constants.PrimaryNetworkID, nodeID)
require.Equal(env.config.MinDelegatorStake+env.config.MinValidatorStake, vdrWeight)
}
func TestBanffProposalBlockDelegatorStakers(t *testing.T) {
require := require.New(t)
env := newEnvironment(t, nil, upgradetest.Banff)
// Case: Timestamp is after next validator start time
// Add a pending validator
pendingValidatorStartTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Second)
pendingValidatorEndTime := pendingValidatorStartTime.Add(defaultMinStakingDuration)
nodeIDKey, _ := secp256k1.NewPrivateKey()
rewardAddress := nodeIDKey.Address()
nodeID := ids.BuildTestNodeID(rewardAddress[:])
addPendingValidator(
t,
env,
pendingValidatorStartTime,
pendingValidatorEndTime,
nodeID,
rewardAddress,
[]*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
)
wallet := newWallet(t, env, walletConfig{})
rewardsOwner := &secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{ids.GenerateTestShortID()},
}
// add Staker0 (with the right end time) to state
// so to allow proposalBlk issuance
staker0StartTime := genesistest.DefaultValidatorStartTime
staker0EndTime := pendingValidatorStartTime
addStaker0, err := wallet.IssueAddValidatorTx(
&txs.Validator{
NodeID: ids.GenerateTestNodeID(),
Start: uint64(staker0StartTime.Unix()),
End: uint64(staker0EndTime.Unix()),
Wght: 10,
},
rewardsOwner,
reward.PercentDenominator,
)
require.NoError(err)
// store Staker0 to state
addValTx := addStaker0.Unsigned.(*txs.AddValidatorTx)
staker, err := state.NewCurrentStaker(
addStaker0.ID(),
addValTx,
addValTx.StartTime(),
0,
)
require.NoError(err)
require.NoError(env.state.PutCurrentValidator(staker))
env.state.AddTx(addStaker0, status.Committed)
require.NoError(env.state.Commit())
// create rewardTx for staker0
s0RewardTx := &txs.Tx{
Unsigned: &txs.RewardValidatorTx{
TxID: addStaker0.ID(),
},
}
require.NoError(s0RewardTx.Initialize(txs.Codec))
// build proposal block moving ahead chain time
preferredID := env.state.GetLastAccepted()
parentBlk, err := env.state.GetStatelessBlock(preferredID)
require.NoError(err)
statelessProposalBlock, err := block.NewBanffProposalBlock(
pendingValidatorStartTime,
parentBlk.ID(),
parentBlk.Height()+1,
s0RewardTx,
[]*txs.Tx{},
)
require.NoError(err)
propBlk := env.blkManager.NewBlock(statelessProposalBlock)
require.NoError(propBlk.Verify(context.Background()))
options, err := propBlk.(chain.OracleBlock).Options(context.Background())
require.NoError(err)
commitBlk := options[0]
require.NoError(commitBlk.Verify(context.Background()))
require.NoError(propBlk.Accept(context.Background()))
require.NoError(commitBlk.Accept(context.Background()))
// Test validator weight before delegation
vdrWeight := env.config.Validators.GetWeight(constants.PrimaryNetworkID, nodeID)
require.Equal(env.config.MinValidatorStake, vdrWeight)
// Add delegator
pendingDelegatorStartTime := pendingValidatorStartTime.Add(1 * time.Second)
pendingDelegatorEndTime := pendingDelegatorStartTime.Add(defaultMinStakingDuration)
addDelegatorTx, err := wallet.IssueAddDelegatorTx(
&txs.Validator{
NodeID: nodeID,
Start: uint64(pendingDelegatorStartTime.Unix()),
End: uint64(pendingDelegatorEndTime.Unix()),
Wght: env.config.MinDelegatorStake,
},
rewardsOwner,
)
require.NoError(err)
staker, err = state.NewPendingStaker(
addDelegatorTx.ID(),
addDelegatorTx.Unsigned.(*txs.AddDelegatorTx),
)
require.NoError(err)
env.state.PutPendingDelegator(staker)
env.state.AddTx(addDelegatorTx, status.Committed)
env.state.SetHeight( /*dummyHeight*/ uint64(1))
require.NoError(env.state.Commit())
// add Staker0 (with the right end time) to state
// so to allow proposalBlk issuance
staker0EndTime = pendingDelegatorStartTime
addStaker0, err = wallet.IssueAddValidatorTx(
&txs.Validator{
NodeID: ids.GenerateTestNodeID(),
Start: uint64(staker0StartTime.Unix()),
End: uint64(staker0EndTime.Unix()),
Wght: 10,
},
rewardsOwner,
reward.PercentDenominator,
)
require.NoError(err)
// store Staker0 to state
addValTx = addStaker0.Unsigned.(*txs.AddValidatorTx)
staker, err = state.NewCurrentStaker(
addStaker0.ID(),
addValTx,
addValTx.StartTime(),
0,
)
require.NoError(err)
require.NoError(env.state.PutCurrentValidator(staker))
env.state.AddTx(addStaker0, status.Committed)
require.NoError(env.state.Commit())
// create rewardTx for staker0
s0RewardTx = &txs.Tx{
Unsigned: &txs.RewardValidatorTx{
TxID: addStaker0.ID(),
},
}
require.NoError(s0RewardTx.Initialize(txs.Codec))
// Advance Time
preferredID = env.state.GetLastAccepted()
parentBlk, err = env.state.GetStatelessBlock(preferredID)
require.NoError(err)
statelessProposalBlock, err = block.NewBanffProposalBlock(
pendingDelegatorStartTime,
parentBlk.ID(),
parentBlk.Height()+1,
s0RewardTx,
[]*txs.Tx{},
)
require.NoError(err)
propBlk = env.blkManager.NewBlock(statelessProposalBlock)
require.NoError(propBlk.Verify(context.Background()))
options, err = propBlk.(chain.OracleBlock).Options(context.Background())
require.NoError(err)
commitBlk = options[0]
require.NoError(commitBlk.Verify(context.Background()))
require.NoError(propBlk.Accept(context.Background()))
require.NoError(commitBlk.Accept(context.Background()))
// Test validator weight after delegation
vdrWeight = env.config.Validators.GetWeight(constants.PrimaryNetworkID, nodeID)
require.Equal(env.config.MinDelegatorStake+env.config.MinValidatorStake, vdrWeight)
}
func TestAddValidatorProposalBlock(t *testing.T) {
require := require.New(t)
env := newEnvironment(t, nil, upgradetest.Durango)
wallet := newWallet(t, env, walletConfig{})
now := env.clk.Time()
// Create validator tx
var (
validatorStartTime = now.Add(2 * executor.SyncBound)
validatorEndTime = validatorStartTime.Add(env.config.MinStakeDuration)
nodeID = ids.GenerateTestNodeID()
)
sk, err := localsigner.New()
require.NoError(err)
pop, err := signer.NewProofOfPossession(sk)
require.NoError(err)
rewardsOwner := &secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{ids.GenerateTestShortID()},
}
addValidatorTx, err := wallet.IssueAddPermissionlessValidatorTx(
&txs.ChainValidator{
Validator: txs.Validator{
NodeID: nodeID,
Start: uint64(validatorStartTime.Unix()),
End: uint64(validatorEndTime.Unix()),
Wght: env.config.MinValidatorStake,
},
Chain: constants.PrimaryNetworkID,
},
pop,
env.rt.XAssetID,
rewardsOwner,
rewardsOwner,
10000,
)
require.NoError(err)
// Add validator through a [StandardBlock]
preferredID := env.blkManager.Preferred()
preferred, err := env.blkManager.GetStatelessBlock(preferredID)
require.NoError(err)
statelessBlk, err := block.NewBanffStandardBlock(
now.Add(executor.SyncBound),
preferredID,
preferred.Height()+1,
[]*txs.Tx{addValidatorTx},
)
require.NoError(err)
blk := env.blkManager.NewBlock(statelessBlk)
require.NoError(blk.Verify(context.Background()))
require.NoError(blk.Accept(context.Background()))
env.blkManager.SetPreference(statelessBlk.ID())
// Should be current
staker, err := env.state.GetCurrentValidator(constants.PrimaryNetworkID, nodeID)
require.NoError(err)
require.NotNil(staker)
// Advance time until next staker change time is [validatorEndTime]
for {
nextStakerChangeTime, err := state.GetNextStakerChangeTime(
env.config.ValidatorFeeConfig,
env.state,
mockable.MaxTime,
)
require.NoError(err)
if nextStakerChangeTime.Equal(validatorEndTime) {
break
}
preferredID = env.blkManager.Preferred()
preferred, err = env.blkManager.GetStatelessBlock(preferredID)
require.NoError(err)
statelessBlk, err = block.NewBanffStandardBlock(
nextStakerChangeTime,
preferredID,
preferred.Height()+1,
nil,
)
require.NoError(err)
blk = env.blkManager.NewBlock(statelessBlk)
require.NoError(blk.Verify(context.Background()))
require.NoError(blk.Accept(context.Background()))
env.blkManager.SetPreference(statelessBlk.ID())
}
env.clk.Set(validatorEndTime)
now = env.clk.Time()
// Create another validator tx
validatorStartTime = now.Add(2 * executor.SyncBound)
validatorEndTime = validatorStartTime.Add(env.config.MinStakeDuration)
nodeID = ids.GenerateTestNodeID()
sk, err = localsigner.New()
require.NoError(err)
pop, err = signer.NewProofOfPossession(sk)
require.NoError(err)
addValidatorTx2, err := wallet.IssueAddPermissionlessValidatorTx(
&txs.ChainValidator{
Validator: txs.Validator{
NodeID: nodeID,
Start: uint64(validatorStartTime.Unix()),
End: uint64(validatorEndTime.Unix()),
Wght: env.config.MinValidatorStake,
},
Chain: constants.PrimaryNetworkID,
},
pop,
env.rt.XAssetID,
rewardsOwner,
rewardsOwner,
10000,
)
require.NoError(err)
// Add validator through a [ProposalBlock] and reward the last one
preferredID = env.blkManager.Preferred()
preferred, err = env.blkManager.GetStatelessBlock(preferredID)
require.NoError(err)
rewardValidatorTx, err := newRewardValidatorTx(t, addValidatorTx.ID())
require.NoError(err)
statelessProposalBlk, err := block.NewBanffProposalBlock(
now,
preferredID,
preferred.Height()+1,
rewardValidatorTx,
[]*txs.Tx{addValidatorTx2},
)
require.NoError(err)
blk = env.blkManager.NewBlock(statelessProposalBlk)
require.NoError(blk.Verify(context.Background()))
options, err := blk.(chain.OracleBlock).Options(context.Background())
require.NoError(err)
commitBlk := options[0]
require.NoError(commitBlk.Verify(context.Background()))
require.NoError(blk.Accept(context.Background()))
require.NoError(commitBlk.Accept(context.Background()))
// Should be current
staker, err = env.state.GetCurrentValidator(constants.PrimaryNetworkID, nodeID)
require.NoError(err)
require.NotNil(staker)
rewardUTXOs, err := env.state.GetRewardUTXOs(addValidatorTx.ID())
require.NoError(err)
require.NotEmpty(rewardUTXOs)
}
func newRewardValidatorTx(t testing.TB, txID ids.ID) (*txs.Tx, error) {
utx := &txs.RewardValidatorTx{TxID: txID}
tx, err := txs.NewSigned(utx, txs.Codec, nil)
if err != nil {
return nil, err
}
// Create a runtime with the proper IDs
rt := &runtime.Runtime{
NetworkID: constants.UnitTestID,
ChainID: constants.PlatformChainID,
}
return tx, tx.SyntacticVerify(rt)
}