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