Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vms/platformvm: Add TestBuildBlockForceAdvanceTime test #2472

Merged
merged 9 commits into from
Dec 12, 2023
329 changes: 90 additions & 239 deletions vms/platformvm/block/builder/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"github.com/ava-labs/avalanchego/vms/platformvm/reward"
"github.com/ava-labs/avalanchego/vms/platformvm/state"
"github.com/ava-labs/avalanchego/vms/platformvm/txs"
"github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool"

blockexecutor "github.com/ava-labs/avalanchego/vms/platformvm/block/executor"
txexecutor "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor"
Expand Down Expand Up @@ -199,6 +198,96 @@ func TestBuildBlockShouldReward(t *testing.T) {
require.NotEmpty(rewardUTXOs)
}

func TestBuildBlockAdvanceTime(t *testing.T) {
require := require.New(t)

env := newEnvironment(t)
env.ctx.Lock.Lock()
defer func() {
require.NoError(shutdownEnvironment(env))
env.ctx.Lock.Unlock()
}()

var (
now = env.backend.Clk.Time()
nextTime = now.Add(2 * txexecutor.SyncBound)
)

// Add a staker to [env.state]
env.state.PutCurrentValidator(&state.Staker{
NextTime: nextTime,
Priority: txs.PrimaryNetworkValidatorCurrentPriority,
})

// Advance wall clock to [nextTime]
env.backend.Clk.Set(nextTime)

// [BuildBlock] should build a block advancing the time to [NextTime]
blkIntf, err := env.Builder.BuildBlock(context.Background())
require.NoError(err)

require.IsType(&blockexecutor.Block{}, blkIntf)
blk := blkIntf.(*blockexecutor.Block)
require.Empty(blk.Txs())
require.IsType(&block.BanffStandardBlock{}, blk.Block)
standardBlk := blk.Block.(*block.BanffStandardBlock)
require.Equal(nextTime.Unix(), standardBlk.Timestamp().Unix())
}

func TestBuildBlockForceAdvanceTime(t *testing.T) {
require := require.New(t)

env := newEnvironment(t)
env.ctx.Lock.Lock()
defer func() {
require.NoError(shutdownEnvironment(env))
env.ctx.Lock.Unlock()
}()

// Create a valid transaction
tx, err := env.txBuilder.NewCreateChainTx(
testSubnet1.ID(),
nil,
constants.AVMID,
nil,
"chain name",
[]*secp256k1.PrivateKey{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
ids.ShortEmpty,
)
require.NoError(err)
txID := tx.ID()

// Issue the transaction
require.NoError(env.network.IssueTx(context.Background(), tx))
require.True(env.mempool.Has(txID))

var (
now = env.backend.Clk.Time()
nextTime = now.Add(2 * txexecutor.SyncBound)
)

// Add a staker to [env.state]
env.state.PutCurrentValidator(&state.Staker{
NextTime: nextTime,
Priority: txs.PrimaryNetworkValidatorCurrentPriority,
})

// Advance wall clock to [nextTime] + [txexecutor.SyncBound]
env.backend.Clk.Set(nextTime.Add(txexecutor.SyncBound))

// [BuildBlock] should build a block advancing the time to [nextTime],
// not the current wall clock.
blkIntf, err := env.Builder.BuildBlock(context.Background())
require.NoError(err)

require.IsType(&blockexecutor.Block{}, blkIntf)
blk := blkIntf.(*blockexecutor.Block)
require.Equal([]*txs.Tx{tx}, blk.Txs())
require.IsType(&block.BanffStandardBlock{}, blk.Block)
standardBlk := blk.Block.(*block.BanffStandardBlock)
require.Equal(nextTime.Unix(), standardBlk.Timestamp().Unix())
}

func TestPreviouslyDroppedTxsCanBeReAddedToMempool(t *testing.T) {
require := require.New(t)

Expand Down Expand Up @@ -446,241 +535,3 @@ func TestGetNextStakerToReward(t *testing.T) {
})
}
}

func TestBuildBlock(t *testing.T) {
env := newEnvironment(t)
env.ctx.Lock.Lock()
defer func() {
require.NoError(t, shutdownEnvironment(env))
env.ctx.Lock.Unlock()
}()

var (
now = env.backend.Clk.Time()
parentID = ids.GenerateTestID()
height = uint64(1337)
parentTimestamp = now.Add(-2 * time.Second)

defaultValidatorStake = 100 * units.MilliAvax
validatorStartTime = now.Add(2 * txexecutor.SyncBound)
validatorEndTime = validatorStartTime.Add(360 * 24 * time.Hour)
)

tx, err := env.txBuilder.NewAddValidatorTx(
defaultValidatorStake,
uint64(validatorStartTime.Unix()),
uint64(validatorEndTime.Unix()),
ids.GenerateTestNodeID(),
preFundedKeys[0].PublicKey().Address(),
reward.PercentDenominator,
[]*secp256k1.PrivateKey{preFundedKeys[0]},
preFundedKeys[0].PublicKey().Address(),
)
require.NoError(t, err)

type test struct {
name string
builderF func(*gomock.Controller) *builder
timestamp time.Time
forceAdvanceTime bool
parentStateF func(*gomock.Controller) state.Chain
expectedBlkF func(*require.Assertions) block.Block
expectedErr error
}

tests := []test{
{
name: "should advance time",
builderF: func(ctrl *gomock.Controller) *builder {
mempool := mempool.NewMockMempool(ctrl)

// There are no txs.
mempool.EXPECT().DropExpiredStakerTxs(gomock.Any()).Return([]ids.ID{})
mempool.EXPECT().Peek().Return(nil, false)

clk := &mockable.Clock{}
clk.Set(now)
return &builder{
Mempool: mempool,
txExecutorBackend: &txexecutor.Backend{
Clk: clk,
},
}
},
timestamp: now.Add(-1 * time.Second),
forceAdvanceTime: true,
parentStateF: func(ctrl *gomock.Controller) state.Chain {
s := state.NewMockChain(ctrl)

// add current validator that ends at [now] - 1 second.
// That is, it ends in the past but after the current chain time.
// Handle calls in [getNextStakerToReward]
// and [GetNextStakerChangeTime]
// when determining whether to issue a reward tx.
currentStakerIter := state.NewMockStakerIterator(ctrl)
gomock.InOrder(
// expect calls from [getNextStakerToReward]
currentStakerIter.EXPECT().Next().Return(true),
currentStakerIter.EXPECT().Value().Return(&state.Staker{
NextTime: now.Add(-1 * time.Second),
Priority: txs.PrimaryNetworkDelegatorCurrentPriority,
}),
currentStakerIter.EXPECT().Release(),
)

s.EXPECT().GetCurrentStakerIterator().Return(currentStakerIter, nil).Times(1)
return s
},
expectedBlkF: func(require *require.Assertions) block.Block {
expectedBlk, err := block.NewBanffStandardBlock(
now.Add(-1*time.Second), // note the advanced time
parentID,
height,
nil, // empty block to advance time
)
require.NoError(err)
return expectedBlk
},
expectedErr: nil,
},
{
name: "has a staker tx no force",
builderF: func(ctrl *gomock.Controller) *builder {
mempool := mempool.NewMockMempool(ctrl)

// There is a tx.
mempool.EXPECT().DropExpiredStakerTxs(gomock.Any()).Return([]ids.ID{})

gomock.InOrder(
mempool.EXPECT().Peek().Return(tx, true),
mempool.EXPECT().Remove([]*txs.Tx{tx}),
// Second loop iteration
mempool.EXPECT().Peek().Return(nil, false),
)

clk := &mockable.Clock{}
clk.Set(now)
return &builder{
Mempool: mempool,
txExecutorBackend: &txexecutor.Backend{
Clk: clk,
},
}
},
timestamp: parentTimestamp,
forceAdvanceTime: false,
parentStateF: func(ctrl *gomock.Controller) state.Chain {
s := state.NewMockChain(ctrl)

// Handle calls in [getNextStakerToReward]
// and [GetNextStakerChangeTime].
// Next validator change time is in the future.
currentStakerIter := state.NewMockStakerIterator(ctrl)
gomock.InOrder(
// expect calls from [getNextStakerToReward]
currentStakerIter.EXPECT().Next().Return(true),
currentStakerIter.EXPECT().Value().Return(&state.Staker{
NextTime: now.Add(time.Second),
Priority: txs.PrimaryNetworkDelegatorCurrentPriority,
}),
currentStakerIter.EXPECT().Release(),
)

s.EXPECT().GetCurrentStakerIterator().Return(currentStakerIter, nil).Times(1)
return s
},
expectedBlkF: func(require *require.Assertions) block.Block {
expectedBlk, err := block.NewBanffStandardBlock(
parentTimestamp,
parentID,
height,
[]*txs.Tx{tx},
)
require.NoError(err)
return expectedBlk
},
expectedErr: nil,
},
{
name: "has a staker tx with force",
builderF: func(ctrl *gomock.Controller) *builder {
mempool := mempool.NewMockMempool(ctrl)

// There are no decision txs
// There is a staker tx.
mempool.EXPECT().DropExpiredStakerTxs(gomock.Any()).Return([]ids.ID{})

gomock.InOrder(
mempool.EXPECT().Peek().Return(tx, true),
mempool.EXPECT().Remove([]*txs.Tx{tx}),
// Second loop iteration
mempool.EXPECT().Peek().Return(nil, false),
)

clk := &mockable.Clock{}
clk.Set(now)
return &builder{
Mempool: mempool,
txExecutorBackend: &txexecutor.Backend{
Clk: clk,
},
}
},
timestamp: parentTimestamp,
forceAdvanceTime: true,
parentStateF: func(ctrl *gomock.Controller) state.Chain {
s := state.NewMockChain(ctrl)

// Handle calls in [getNextStakerToReward]
// and [GetNextStakerChangeTime].
// Next validator change time is in the future.
currentStakerIter := state.NewMockStakerIterator(ctrl)
gomock.InOrder(
// expect calls from [getNextStakerToReward]
currentStakerIter.EXPECT().Next().Return(true),
currentStakerIter.EXPECT().Value().Return(&state.Staker{
NextTime: now.Add(time.Second),
Priority: txs.PrimaryNetworkDelegatorCurrentPriority,
}),
currentStakerIter.EXPECT().Release(),
)

s.EXPECT().GetCurrentStakerIterator().Return(currentStakerIter, nil).Times(1)
return s
},
expectedBlkF: func(require *require.Assertions) block.Block {
expectedBlk, err := block.NewBanffStandardBlock(
parentTimestamp,
parentID,
height,
[]*txs.Tx{tx},
)
require.NoError(err)
return expectedBlk
},
expectedErr: nil,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)

gotBlk, err := buildBlock(
tt.builderF(ctrl),
parentID,
height,
tt.timestamp,
tt.forceAdvanceTime,
tt.parentStateF(ctrl),
)
if tt.expectedErr != nil {
require.ErrorIs(err, tt.expectedErr)
return
}
require.NoError(err)
require.Equal(tt.expectedBlkF(require), gotBlk)
})
}
}
Loading