mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
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.
1521 lines
46 KiB
Go
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)
|
|
}
|