github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/block/executor/acceptor_test.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package executor
     5  
     6  import (
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/require"
    11  	"go.uber.org/mock/gomock"
    12  
    13  	"github.com/ava-labs/avalanchego/chains/atomic"
    14  	"github.com/ava-labs/avalanchego/chains/atomic/atomicmock"
    15  	"github.com/ava-labs/avalanchego/database/databasemock"
    16  	"github.com/ava-labs/avalanchego/ids"
    17  	"github.com/ava-labs/avalanchego/snow"
    18  	"github.com/ava-labs/avalanchego/utils"
    19  	"github.com/ava-labs/avalanchego/utils/logging"
    20  	"github.com/ava-labs/avalanchego/utils/timer/mockable"
    21  	"github.com/ava-labs/avalanchego/vms/components/verify"
    22  	"github.com/ava-labs/avalanchego/vms/platformvm/block"
    23  	"github.com/ava-labs/avalanchego/vms/platformvm/metrics"
    24  	"github.com/ava-labs/avalanchego/vms/platformvm/state"
    25  	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
    26  	"github.com/ava-labs/avalanchego/vms/platformvm/validators"
    27  	"github.com/ava-labs/avalanchego/vms/secp256k1fx"
    28  )
    29  
    30  func TestAcceptorVisitProposalBlock(t *testing.T) {
    31  	require := require.New(t)
    32  	ctrl := gomock.NewController(t)
    33  
    34  	lastAcceptedID := ids.GenerateTestID()
    35  
    36  	blk, err := block.NewApricotProposalBlock(
    37  		lastAcceptedID,
    38  		1,
    39  		&txs.Tx{
    40  			Unsigned: &txs.AddDelegatorTx{
    41  				// Without the line below, this function will error.
    42  				DelegationRewardsOwner: &secp256k1fx.OutputOwners{},
    43  			},
    44  			Creds: []verify.Verifiable{},
    45  		},
    46  	)
    47  	require.NoError(err)
    48  
    49  	blkID := blk.ID()
    50  
    51  	s := state.NewMockState(ctrl)
    52  	s.EXPECT().Checksum().Return(ids.Empty).Times(1)
    53  
    54  	acceptor := &acceptor{
    55  		backend: &backend{
    56  			ctx: &snow.Context{
    57  				Log: logging.NoLog{},
    58  			},
    59  			blkIDToState: map[ids.ID]*blockState{
    60  				blkID: {},
    61  			},
    62  			state: s,
    63  		},
    64  		metrics:    metrics.Noop,
    65  		validators: validators.TestManager,
    66  	}
    67  
    68  	require.NoError(acceptor.ApricotProposalBlock(blk))
    69  
    70  	require.Equal(blkID, acceptor.backend.lastAccepted)
    71  
    72  	_, exists := acceptor.GetState(blkID)
    73  	require.False(exists)
    74  
    75  	s.EXPECT().GetLastAccepted().Return(lastAcceptedID).Times(1)
    76  
    77  	_, exists = acceptor.GetState(lastAcceptedID)
    78  	require.True(exists)
    79  }
    80  
    81  func TestAcceptorVisitAtomicBlock(t *testing.T) {
    82  	require := require.New(t)
    83  	ctrl := gomock.NewController(t)
    84  
    85  	s := state.NewMockState(ctrl)
    86  	sharedMemory := atomicmock.NewSharedMemory(ctrl)
    87  
    88  	parentID := ids.GenerateTestID()
    89  	acceptor := &acceptor{
    90  		backend: &backend{
    91  			lastAccepted: parentID,
    92  			blkIDToState: make(map[ids.ID]*blockState),
    93  			state:        s,
    94  			ctx: &snow.Context{
    95  				Log:          logging.NoLog{},
    96  				SharedMemory: sharedMemory,
    97  			},
    98  		},
    99  		metrics:    metrics.Noop,
   100  		validators: validators.TestManager,
   101  	}
   102  
   103  	blk, err := block.NewApricotAtomicBlock(
   104  		parentID,
   105  		1,
   106  		&txs.Tx{
   107  			Unsigned: &txs.AddDelegatorTx{
   108  				// Without the line below, this function will error.
   109  				DelegationRewardsOwner: &secp256k1fx.OutputOwners{},
   110  			},
   111  			Creds: []verify.Verifiable{},
   112  		},
   113  	)
   114  	require.NoError(err)
   115  
   116  	// Set expected calls on the state.
   117  	// We should error after [commonAccept] is called.
   118  	s.EXPECT().SetLastAccepted(blk.ID()).Times(1)
   119  	s.EXPECT().SetHeight(blk.Height()).Times(1)
   120  	s.EXPECT().AddStatelessBlock(blk).Times(1)
   121  
   122  	err = acceptor.ApricotAtomicBlock(blk)
   123  	require.ErrorIs(err, errMissingBlockState)
   124  
   125  	// Set [blk]'s state in the map as though it had been verified.
   126  	onAcceptState := state.NewMockDiff(ctrl)
   127  	childID := ids.GenerateTestID()
   128  	atomicRequests := make(map[ids.ID]*atomic.Requests)
   129  	acceptor.backend.blkIDToState[blk.ID()] = &blockState{
   130  		onAcceptState:  onAcceptState,
   131  		atomicRequests: atomicRequests,
   132  	}
   133  	// Give [blk] a child.
   134  	childOnAcceptState := state.NewMockDiff(ctrl)
   135  	childOnAbortState := state.NewMockDiff(ctrl)
   136  	childOnCommitState := state.NewMockDiff(ctrl)
   137  	childState := &blockState{
   138  		onAcceptState: childOnAcceptState,
   139  		proposalBlockState: proposalBlockState{
   140  			onAbortState:  childOnAbortState,
   141  			onCommitState: childOnCommitState,
   142  		},
   143  	}
   144  	acceptor.backend.blkIDToState[childID] = childState
   145  
   146  	// Set expected calls on dependencies.
   147  	s.EXPECT().SetLastAccepted(blk.ID()).Times(1)
   148  	s.EXPECT().SetHeight(blk.Height()).Times(1)
   149  	s.EXPECT().AddStatelessBlock(blk).Times(1)
   150  	batch := databasemock.NewBatch(ctrl)
   151  	s.EXPECT().CommitBatch().Return(batch, nil).Times(1)
   152  	s.EXPECT().Abort().Times(1)
   153  	onAcceptState.EXPECT().Apply(s).Times(1)
   154  	sharedMemory.EXPECT().Apply(atomicRequests, batch).Return(nil).Times(1)
   155  	s.EXPECT().Checksum().Return(ids.Empty).Times(1)
   156  
   157  	require.NoError(acceptor.ApricotAtomicBlock(blk))
   158  }
   159  
   160  func TestAcceptorVisitStandardBlock(t *testing.T) {
   161  	require := require.New(t)
   162  	ctrl := gomock.NewController(t)
   163  
   164  	s := state.NewMockState(ctrl)
   165  	sharedMemory := atomicmock.NewSharedMemory(ctrl)
   166  
   167  	parentID := ids.GenerateTestID()
   168  	clk := &mockable.Clock{}
   169  	acceptor := &acceptor{
   170  		backend: &backend{
   171  			lastAccepted: parentID,
   172  			blkIDToState: make(map[ids.ID]*blockState),
   173  			state:        s,
   174  			ctx: &snow.Context{
   175  				Log:          logging.NoLog{},
   176  				SharedMemory: sharedMemory,
   177  			},
   178  		},
   179  		metrics:    metrics.Noop,
   180  		validators: validators.TestManager,
   181  	}
   182  
   183  	blk, err := block.NewBanffStandardBlock(
   184  		clk.Time(),
   185  		parentID,
   186  		1,
   187  		[]*txs.Tx{
   188  			{
   189  				Unsigned: &txs.AddDelegatorTx{
   190  					// Without the line below, this function will error.
   191  					DelegationRewardsOwner: &secp256k1fx.OutputOwners{},
   192  				},
   193  				Creds: []verify.Verifiable{},
   194  			},
   195  		},
   196  	)
   197  	require.NoError(err)
   198  
   199  	// Set expected calls on the state.
   200  	// We should error after [commonAccept] is called.
   201  	s.EXPECT().SetLastAccepted(blk.ID()).Times(1)
   202  	s.EXPECT().SetHeight(blk.Height()).Times(1)
   203  	s.EXPECT().AddStatelessBlock(blk).Times(1)
   204  
   205  	err = acceptor.BanffStandardBlock(blk)
   206  	require.ErrorIs(err, errMissingBlockState)
   207  
   208  	// Set [blk]'s state in the map as though it had been verified.
   209  	onAcceptState := state.NewMockDiff(ctrl)
   210  	childID := ids.GenerateTestID()
   211  	atomicRequests := make(map[ids.ID]*atomic.Requests)
   212  	calledOnAcceptFunc := false
   213  	acceptor.backend.blkIDToState[blk.ID()] = &blockState{
   214  		onAcceptState: onAcceptState,
   215  		onAcceptFunc: func() {
   216  			calledOnAcceptFunc = true
   217  		},
   218  
   219  		atomicRequests: atomicRequests,
   220  	}
   221  	// Give [blk] a child.
   222  	childOnAcceptState := state.NewMockDiff(ctrl)
   223  	childOnAbortState := state.NewMockDiff(ctrl)
   224  	childOnCommitState := state.NewMockDiff(ctrl)
   225  	childState := &blockState{
   226  		onAcceptState: childOnAcceptState,
   227  		proposalBlockState: proposalBlockState{
   228  			onAbortState:  childOnAbortState,
   229  			onCommitState: childOnCommitState,
   230  		},
   231  	}
   232  	acceptor.backend.blkIDToState[childID] = childState
   233  
   234  	// Set expected calls on dependencies.
   235  	s.EXPECT().SetLastAccepted(blk.ID()).Times(1)
   236  	s.EXPECT().SetHeight(blk.Height()).Times(1)
   237  	s.EXPECT().AddStatelessBlock(blk).Times(1)
   238  	batch := databasemock.NewBatch(ctrl)
   239  	s.EXPECT().CommitBatch().Return(batch, nil).Times(1)
   240  	s.EXPECT().Abort().Times(1)
   241  	onAcceptState.EXPECT().Apply(s).Times(1)
   242  	sharedMemory.EXPECT().Apply(atomicRequests, batch).Return(nil).Times(1)
   243  	s.EXPECT().Checksum().Return(ids.Empty).Times(1)
   244  
   245  	require.NoError(acceptor.BanffStandardBlock(blk))
   246  	require.True(calledOnAcceptFunc)
   247  	require.Equal(blk.ID(), acceptor.backend.lastAccepted)
   248  }
   249  
   250  func TestAcceptorVisitCommitBlock(t *testing.T) {
   251  	require := require.New(t)
   252  	ctrl := gomock.NewController(t)
   253  
   254  	s := state.NewMockState(ctrl)
   255  	sharedMemory := atomicmock.NewSharedMemory(ctrl)
   256  
   257  	parentID := ids.GenerateTestID()
   258  	acceptor := &acceptor{
   259  		backend: &backend{
   260  			lastAccepted: parentID,
   261  			blkIDToState: make(map[ids.ID]*blockState),
   262  			state:        s,
   263  			ctx: &snow.Context{
   264  				Log:          logging.NoLog{},
   265  				SharedMemory: sharedMemory,
   266  			},
   267  		},
   268  		metrics:      metrics.Noop,
   269  		validators:   validators.TestManager,
   270  		bootstrapped: &utils.Atomic[bool]{},
   271  	}
   272  
   273  	blk, err := block.NewApricotCommitBlock(parentID, 1 /*height*/)
   274  	require.NoError(err)
   275  
   276  	err = acceptor.ApricotCommitBlock(blk)
   277  	require.ErrorIs(err, state.ErrMissingParentState)
   278  
   279  	// Set [blk]'s parent in the state map.
   280  	parentOnAcceptState := state.NewMockDiff(ctrl)
   281  	parentOnAbortState := state.NewMockDiff(ctrl)
   282  	parentOnCommitState := state.NewMockDiff(ctrl)
   283  	parentStatelessBlk := block.NewMockBlock(ctrl)
   284  	calledOnAcceptFunc := false
   285  	atomicRequests := make(map[ids.ID]*atomic.Requests)
   286  	parentState := &blockState{
   287  		proposalBlockState: proposalBlockState{
   288  			onAbortState:  parentOnAbortState,
   289  			onCommitState: parentOnCommitState,
   290  		},
   291  		statelessBlock: parentStatelessBlk,
   292  
   293  		onAcceptState: parentOnAcceptState,
   294  		onAcceptFunc: func() {
   295  			calledOnAcceptFunc = true
   296  		},
   297  
   298  		atomicRequests: atomicRequests,
   299  	}
   300  	acceptor.backend.blkIDToState[parentID] = parentState
   301  
   302  	blkID := blk.ID()
   303  	// Set expected calls on dependencies.
   304  	// Make sure the parent is accepted first.
   305  	gomock.InOrder(
   306  		parentStatelessBlk.EXPECT().ID().Return(parentID).Times(1),
   307  		s.EXPECT().SetLastAccepted(parentID).Times(1),
   308  		parentStatelessBlk.EXPECT().Height().Return(blk.Height()-1).Times(1),
   309  		s.EXPECT().SetHeight(blk.Height()-1).Times(1),
   310  		s.EXPECT().AddStatelessBlock(parentState.statelessBlock).Times(1),
   311  
   312  		s.EXPECT().SetLastAccepted(blkID).Times(1),
   313  		s.EXPECT().SetHeight(blk.Height()).Times(1),
   314  		s.EXPECT().AddStatelessBlock(blk).Times(1),
   315  	)
   316  
   317  	err = acceptor.ApricotCommitBlock(blk)
   318  	require.ErrorIs(err, errMissingBlockState)
   319  
   320  	parentOnCommitState.EXPECT().GetTimestamp().Return(time.Unix(0, 0))
   321  
   322  	// Set [blk]'s state in the map as though it had been verified.
   323  	acceptor.backend.blkIDToState[parentID] = parentState
   324  	acceptor.backend.blkIDToState[blkID] = &blockState{
   325  		onAcceptState: parentState.onCommitState,
   326  		onAcceptFunc:  parentState.onAcceptFunc,
   327  
   328  		inputs:         parentState.inputs,
   329  		timestamp:      parentOnCommitState.GetTimestamp(),
   330  		atomicRequests: parentState.atomicRequests,
   331  	}
   332  
   333  	batch := databasemock.NewBatch(ctrl)
   334  
   335  	// Set expected calls on dependencies.
   336  	// Make sure the parent is accepted first.
   337  	gomock.InOrder(
   338  		parentStatelessBlk.EXPECT().ID().Return(parentID).Times(1),
   339  		s.EXPECT().SetLastAccepted(parentID).Times(1),
   340  		parentStatelessBlk.EXPECT().Height().Return(blk.Height()-1).Times(1),
   341  		s.EXPECT().SetHeight(blk.Height()-1).Times(1),
   342  		s.EXPECT().AddStatelessBlock(parentState.statelessBlock).Times(1),
   343  
   344  		s.EXPECT().SetLastAccepted(blkID).Times(1),
   345  		s.EXPECT().SetHeight(blk.Height()).Times(1),
   346  		s.EXPECT().AddStatelessBlock(blk).Times(1),
   347  
   348  		parentOnCommitState.EXPECT().Apply(s).Times(1),
   349  		s.EXPECT().CommitBatch().Return(batch, nil).Times(1),
   350  		sharedMemory.EXPECT().Apply(atomicRequests, batch).Return(nil).Times(1),
   351  		s.EXPECT().Checksum().Return(ids.Empty).Times(1),
   352  		s.EXPECT().Abort().Times(1),
   353  	)
   354  
   355  	require.NoError(acceptor.ApricotCommitBlock(blk))
   356  	require.True(calledOnAcceptFunc)
   357  	require.Equal(blk.ID(), acceptor.backend.lastAccepted)
   358  }
   359  
   360  func TestAcceptorVisitAbortBlock(t *testing.T) {
   361  	require := require.New(t)
   362  	ctrl := gomock.NewController(t)
   363  
   364  	s := state.NewMockState(ctrl)
   365  	sharedMemory := atomicmock.NewSharedMemory(ctrl)
   366  
   367  	parentID := ids.GenerateTestID()
   368  	acceptor := &acceptor{
   369  		backend: &backend{
   370  			lastAccepted: parentID,
   371  			blkIDToState: make(map[ids.ID]*blockState),
   372  			state:        s,
   373  			ctx: &snow.Context{
   374  				Log:          logging.NoLog{},
   375  				SharedMemory: sharedMemory,
   376  			},
   377  		},
   378  		metrics:      metrics.Noop,
   379  		validators:   validators.TestManager,
   380  		bootstrapped: &utils.Atomic[bool]{},
   381  	}
   382  
   383  	blk, err := block.NewApricotAbortBlock(parentID, 1 /*height*/)
   384  	require.NoError(err)
   385  
   386  	err = acceptor.ApricotAbortBlock(blk)
   387  	require.ErrorIs(err, state.ErrMissingParentState)
   388  
   389  	// Set [blk]'s parent in the state map.
   390  	parentOnAcceptState := state.NewMockDiff(ctrl)
   391  	parentOnAbortState := state.NewMockDiff(ctrl)
   392  	parentOnCommitState := state.NewMockDiff(ctrl)
   393  	parentStatelessBlk := block.NewMockBlock(ctrl)
   394  	calledOnAcceptFunc := false
   395  	atomicRequests := make(map[ids.ID]*atomic.Requests)
   396  	parentState := &blockState{
   397  		proposalBlockState: proposalBlockState{
   398  			onAbortState:  parentOnAbortState,
   399  			onCommitState: parentOnCommitState,
   400  		},
   401  		statelessBlock: parentStatelessBlk,
   402  
   403  		onAcceptState: parentOnAcceptState,
   404  		onAcceptFunc: func() {
   405  			calledOnAcceptFunc = true
   406  		},
   407  
   408  		atomicRequests: atomicRequests,
   409  	}
   410  	acceptor.backend.blkIDToState[parentID] = parentState
   411  
   412  	blkID := blk.ID()
   413  	// Set expected calls on dependencies.
   414  	// Make sure the parent is accepted first.
   415  	gomock.InOrder(
   416  		parentStatelessBlk.EXPECT().ID().Return(parentID).Times(1),
   417  		s.EXPECT().SetLastAccepted(parentID).Times(1),
   418  		parentStatelessBlk.EXPECT().Height().Return(blk.Height()-1).Times(1),
   419  		s.EXPECT().SetHeight(blk.Height()-1).Times(1),
   420  		s.EXPECT().AddStatelessBlock(parentState.statelessBlock).Times(1),
   421  
   422  		s.EXPECT().SetLastAccepted(blkID).Times(1),
   423  		s.EXPECT().SetHeight(blk.Height()).Times(1),
   424  		s.EXPECT().AddStatelessBlock(blk).Times(1),
   425  	)
   426  
   427  	err = acceptor.ApricotAbortBlock(blk)
   428  	require.ErrorIs(err, errMissingBlockState)
   429  
   430  	parentOnAbortState.EXPECT().GetTimestamp().Return(time.Unix(0, 0))
   431  
   432  	// Set [blk]'s state in the map as though it had been verified.
   433  	acceptor.backend.blkIDToState[parentID] = parentState
   434  	acceptor.backend.blkIDToState[blkID] = &blockState{
   435  		onAcceptState: parentState.onAbortState,
   436  		onAcceptFunc:  parentState.onAcceptFunc,
   437  
   438  		inputs:         parentState.inputs,
   439  		timestamp:      parentOnAbortState.GetTimestamp(),
   440  		atomicRequests: parentState.atomicRequests,
   441  	}
   442  
   443  	batch := databasemock.NewBatch(ctrl)
   444  
   445  	// Set expected calls on dependencies.
   446  	// Make sure the parent is accepted first.
   447  	gomock.InOrder(
   448  		parentStatelessBlk.EXPECT().ID().Return(parentID).Times(1),
   449  		s.EXPECT().SetLastAccepted(parentID).Times(1),
   450  		parentStatelessBlk.EXPECT().Height().Return(blk.Height()-1).Times(1),
   451  		s.EXPECT().SetHeight(blk.Height()-1).Times(1),
   452  		s.EXPECT().AddStatelessBlock(parentState.statelessBlock).Times(1),
   453  
   454  		s.EXPECT().SetLastAccepted(blkID).Times(1),
   455  		s.EXPECT().SetHeight(blk.Height()).Times(1),
   456  		s.EXPECT().AddStatelessBlock(blk).Times(1),
   457  
   458  		parentOnAbortState.EXPECT().Apply(s).Times(1),
   459  		s.EXPECT().CommitBatch().Return(batch, nil).Times(1),
   460  		sharedMemory.EXPECT().Apply(atomicRequests, batch).Return(nil).Times(1),
   461  		s.EXPECT().Checksum().Return(ids.Empty).Times(1),
   462  		s.EXPECT().Abort().Times(1),
   463  	)
   464  
   465  	require.NoError(acceptor.ApricotAbortBlock(blk))
   466  	require.True(calledOnAcceptFunc)
   467  	require.Equal(blk.ID(), acceptor.backend.lastAccepted)
   468  }