github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/block/executor/block_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  	"context"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/require"
    12  	"go.uber.org/mock/gomock"
    13  
    14  	"github.com/MetalBlockchain/metalgo/database"
    15  	"github.com/MetalBlockchain/metalgo/ids"
    16  	"github.com/MetalBlockchain/metalgo/snow/choices"
    17  	"github.com/MetalBlockchain/metalgo/snow/snowtest"
    18  	"github.com/MetalBlockchain/metalgo/snow/uptime"
    19  	"github.com/MetalBlockchain/metalgo/utils/constants"
    20  	"github.com/MetalBlockchain/metalgo/vms/platformvm/block"
    21  	"github.com/MetalBlockchain/metalgo/vms/platformvm/config"
    22  	"github.com/MetalBlockchain/metalgo/vms/platformvm/reward"
    23  	"github.com/MetalBlockchain/metalgo/vms/platformvm/state"
    24  	"github.com/MetalBlockchain/metalgo/vms/platformvm/status"
    25  	"github.com/MetalBlockchain/metalgo/vms/platformvm/txs"
    26  	"github.com/MetalBlockchain/metalgo/vms/platformvm/txs/executor"
    27  )
    28  
    29  func TestStatus(t *testing.T) {
    30  	type test struct {
    31  		name           string
    32  		blockF         func(*gomock.Controller) *Block
    33  		expectedStatus choices.Status
    34  	}
    35  
    36  	tests := []test{
    37  		{
    38  			name: "last accepted",
    39  			blockF: func(ctrl *gomock.Controller) *Block {
    40  				blkID := ids.GenerateTestID()
    41  				statelessBlk := block.NewMockBlock(ctrl)
    42  				statelessBlk.EXPECT().ID().Return(blkID)
    43  
    44  				manager := &manager{
    45  					backend: &backend{
    46  						lastAccepted: blkID,
    47  					},
    48  				}
    49  
    50  				return &Block{
    51  					Block:   statelessBlk,
    52  					manager: manager,
    53  				}
    54  			},
    55  			expectedStatus: choices.Accepted,
    56  		},
    57  		{
    58  			name: "processing",
    59  			blockF: func(ctrl *gomock.Controller) *Block {
    60  				blkID := ids.GenerateTestID()
    61  				statelessBlk := block.NewMockBlock(ctrl)
    62  				statelessBlk.EXPECT().ID().Return(blkID)
    63  
    64  				manager := &manager{
    65  					backend: &backend{
    66  						blkIDToState: map[ids.ID]*blockState{
    67  							blkID: {},
    68  						},
    69  					},
    70  				}
    71  				return &Block{
    72  					Block:   statelessBlk,
    73  					manager: manager,
    74  				}
    75  			},
    76  			expectedStatus: choices.Processing,
    77  		},
    78  		{
    79  			name: "in database",
    80  			blockF: func(ctrl *gomock.Controller) *Block {
    81  				blkID := ids.GenerateTestID()
    82  				statelessBlk := block.NewMockBlock(ctrl)
    83  				statelessBlk.EXPECT().ID().Return(blkID)
    84  
    85  				state := state.NewMockState(ctrl)
    86  				state.EXPECT().GetStatelessBlock(blkID).Return(statelessBlk, nil)
    87  
    88  				manager := &manager{
    89  					backend: &backend{
    90  						state: state,
    91  					},
    92  				}
    93  				return &Block{
    94  					Block:   statelessBlk,
    95  					manager: manager,
    96  				}
    97  			},
    98  			expectedStatus: choices.Accepted,
    99  		},
   100  		{
   101  			name: "not in map or database",
   102  			blockF: func(ctrl *gomock.Controller) *Block {
   103  				blkID := ids.GenerateTestID()
   104  				statelessBlk := block.NewMockBlock(ctrl)
   105  				statelessBlk.EXPECT().ID().Return(blkID)
   106  
   107  				state := state.NewMockState(ctrl)
   108  				state.EXPECT().GetStatelessBlock(blkID).Return(nil, database.ErrNotFound)
   109  
   110  				manager := &manager{
   111  					backend: &backend{
   112  						state: state,
   113  					},
   114  				}
   115  				return &Block{
   116  					Block:   statelessBlk,
   117  					manager: manager,
   118  				}
   119  			},
   120  			expectedStatus: choices.Processing,
   121  		},
   122  	}
   123  
   124  	for _, tt := range tests {
   125  		t.Run(tt.name, func(t *testing.T) {
   126  			ctrl := gomock.NewController(t)
   127  
   128  			blk := tt.blockF(ctrl)
   129  			require.Equal(t, tt.expectedStatus, blk.Status())
   130  		})
   131  	}
   132  }
   133  
   134  func TestBlockOptions(t *testing.T) {
   135  	type test struct {
   136  		name                   string
   137  		blkF                   func(*gomock.Controller) *Block
   138  		expectedPreferenceType block.Block
   139  	}
   140  
   141  	tests := []test{
   142  		{
   143  			name: "apricot proposal block; commit preferred",
   144  			blkF: func(ctrl *gomock.Controller) *Block {
   145  				state := state.NewMockState(ctrl)
   146  
   147  				uptimes := uptime.NewMockCalculator(ctrl)
   148  
   149  				manager := &manager{
   150  					backend: &backend{
   151  						state: state,
   152  						ctx:   snowtest.Context(t, snowtest.PChainID),
   153  					},
   154  					txExecutorBackend: &executor.Backend{
   155  						Config: &config.Config{
   156  							UptimePercentage: 0,
   157  						},
   158  						Uptimes: uptimes,
   159  					},
   160  				}
   161  
   162  				return &Block{
   163  					Block:   &block.ApricotProposalBlock{},
   164  					manager: manager,
   165  				}
   166  			},
   167  			expectedPreferenceType: &block.ApricotCommitBlock{},
   168  		},
   169  		{
   170  			name: "banff proposal block; invalid proposal tx",
   171  			blkF: func(ctrl *gomock.Controller) *Block {
   172  				state := state.NewMockState(ctrl)
   173  
   174  				uptimes := uptime.NewMockCalculator(ctrl)
   175  
   176  				manager := &manager{
   177  					backend: &backend{
   178  						state: state,
   179  						ctx:   snowtest.Context(t, snowtest.PChainID),
   180  					},
   181  					txExecutorBackend: &executor.Backend{
   182  						Config: &config.Config{
   183  							UptimePercentage: 0,
   184  						},
   185  						Uptimes: uptimes,
   186  					},
   187  				}
   188  
   189  				return &Block{
   190  					Block: &block.BanffProposalBlock{
   191  						ApricotProposalBlock: block.ApricotProposalBlock{
   192  							Tx: &txs.Tx{
   193  								Unsigned: &txs.CreateChainTx{},
   194  							},
   195  						},
   196  					},
   197  					manager: manager,
   198  				}
   199  			},
   200  			expectedPreferenceType: &block.BanffCommitBlock{},
   201  		},
   202  		{
   203  			name: "banff proposal block; missing tx",
   204  			blkF: func(ctrl *gomock.Controller) *Block {
   205  				stakerTxID := ids.GenerateTestID()
   206  
   207  				state := state.NewMockState(ctrl)
   208  				state.EXPECT().GetTx(stakerTxID).Return(nil, status.Unknown, database.ErrNotFound)
   209  
   210  				uptimes := uptime.NewMockCalculator(ctrl)
   211  
   212  				manager := &manager{
   213  					backend: &backend{
   214  						state: state,
   215  						ctx:   snowtest.Context(t, snowtest.PChainID),
   216  					},
   217  					txExecutorBackend: &executor.Backend{
   218  						Config: &config.Config{
   219  							UptimePercentage: 0,
   220  						},
   221  						Uptimes: uptimes,
   222  					},
   223  				}
   224  
   225  				return &Block{
   226  					Block: &block.BanffProposalBlock{
   227  						ApricotProposalBlock: block.ApricotProposalBlock{
   228  							Tx: &txs.Tx{
   229  								Unsigned: &txs.RewardValidatorTx{
   230  									TxID: stakerTxID,
   231  								},
   232  							},
   233  						},
   234  					},
   235  					manager: manager,
   236  				}
   237  			},
   238  			expectedPreferenceType: &block.BanffCommitBlock{},
   239  		},
   240  		{
   241  			name: "banff proposal block; error fetching staker tx",
   242  			blkF: func(ctrl *gomock.Controller) *Block {
   243  				stakerTxID := ids.GenerateTestID()
   244  
   245  				state := state.NewMockState(ctrl)
   246  				state.EXPECT().GetTx(stakerTxID).Return(nil, status.Unknown, database.ErrClosed)
   247  
   248  				uptimes := uptime.NewMockCalculator(ctrl)
   249  
   250  				manager := &manager{
   251  					backend: &backend{
   252  						state: state,
   253  						ctx:   snowtest.Context(t, snowtest.PChainID),
   254  					},
   255  					txExecutorBackend: &executor.Backend{
   256  						Config: &config.Config{
   257  							UptimePercentage: 0,
   258  						},
   259  						Uptimes: uptimes,
   260  					},
   261  				}
   262  
   263  				return &Block{
   264  					Block: &block.BanffProposalBlock{
   265  						ApricotProposalBlock: block.ApricotProposalBlock{
   266  							Tx: &txs.Tx{
   267  								Unsigned: &txs.RewardValidatorTx{
   268  									TxID: stakerTxID,
   269  								},
   270  							},
   271  						},
   272  					},
   273  					manager: manager,
   274  				}
   275  			},
   276  			expectedPreferenceType: &block.BanffCommitBlock{},
   277  		},
   278  		{
   279  			name: "banff proposal block; unexpected staker tx type",
   280  			blkF: func(ctrl *gomock.Controller) *Block {
   281  				stakerTxID := ids.GenerateTestID()
   282  				stakerTx := &txs.Tx{
   283  					Unsigned: &txs.CreateChainTx{},
   284  				}
   285  
   286  				state := state.NewMockState(ctrl)
   287  				state.EXPECT().GetTx(stakerTxID).Return(stakerTx, status.Committed, nil)
   288  
   289  				uptimes := uptime.NewMockCalculator(ctrl)
   290  
   291  				manager := &manager{
   292  					backend: &backend{
   293  						state: state,
   294  						ctx:   snowtest.Context(t, snowtest.PChainID),
   295  					},
   296  					txExecutorBackend: &executor.Backend{
   297  						Config: &config.Config{
   298  							UptimePercentage: 0,
   299  						},
   300  						Uptimes: uptimes,
   301  					},
   302  				}
   303  
   304  				return &Block{
   305  					Block: &block.BanffProposalBlock{
   306  						ApricotProposalBlock: block.ApricotProposalBlock{
   307  							Tx: &txs.Tx{
   308  								Unsigned: &txs.RewardValidatorTx{
   309  									TxID: stakerTxID,
   310  								},
   311  							},
   312  						},
   313  					},
   314  					manager: manager,
   315  				}
   316  			},
   317  			expectedPreferenceType: &block.BanffCommitBlock{},
   318  		},
   319  		{
   320  			name: "banff proposal block; missing primary network validator",
   321  			blkF: func(ctrl *gomock.Controller) *Block {
   322  				var (
   323  					stakerTxID = ids.GenerateTestID()
   324  					nodeID     = ids.GenerateTestNodeID()
   325  					subnetID   = ids.GenerateTestID()
   326  					stakerTx   = &txs.Tx{
   327  						Unsigned: &txs.AddPermissionlessValidatorTx{
   328  							Validator: txs.Validator{
   329  								NodeID: nodeID,
   330  							},
   331  							Subnet: subnetID,
   332  						},
   333  					}
   334  				)
   335  
   336  				state := state.NewMockState(ctrl)
   337  				state.EXPECT().GetTx(stakerTxID).Return(stakerTx, status.Committed, nil)
   338  				state.EXPECT().GetCurrentValidator(constants.PrimaryNetworkID, nodeID).Return(nil, database.ErrNotFound)
   339  
   340  				uptimes := uptime.NewMockCalculator(ctrl)
   341  
   342  				manager := &manager{
   343  					backend: &backend{
   344  						state: state,
   345  						ctx:   snowtest.Context(t, snowtest.PChainID),
   346  					},
   347  					txExecutorBackend: &executor.Backend{
   348  						Config: &config.Config{
   349  							UptimePercentage: 0,
   350  						},
   351  						Uptimes: uptimes,
   352  					},
   353  				}
   354  
   355  				return &Block{
   356  					Block: &block.BanffProposalBlock{
   357  						ApricotProposalBlock: block.ApricotProposalBlock{
   358  							Tx: &txs.Tx{
   359  								Unsigned: &txs.RewardValidatorTx{
   360  									TxID: stakerTxID,
   361  								},
   362  							},
   363  						},
   364  					},
   365  					manager: manager,
   366  				}
   367  			},
   368  			expectedPreferenceType: &block.BanffCommitBlock{},
   369  		},
   370  		{
   371  			name: "banff proposal block; failed calculating primary network uptime",
   372  			blkF: func(ctrl *gomock.Controller) *Block {
   373  				var (
   374  					stakerTxID = ids.GenerateTestID()
   375  					nodeID     = ids.GenerateTestNodeID()
   376  					subnetID   = constants.PrimaryNetworkID
   377  					stakerTx   = &txs.Tx{
   378  						Unsigned: &txs.AddPermissionlessValidatorTx{
   379  							Validator: txs.Validator{
   380  								NodeID: nodeID,
   381  							},
   382  							Subnet: subnetID,
   383  						},
   384  					}
   385  					primaryNetworkValidatorStartTime = time.Now()
   386  					staker                           = &state.Staker{
   387  						StartTime: primaryNetworkValidatorStartTime,
   388  					}
   389  				)
   390  
   391  				state := state.NewMockState(ctrl)
   392  				state.EXPECT().GetTx(stakerTxID).Return(stakerTx, status.Committed, nil)
   393  				state.EXPECT().GetCurrentValidator(constants.PrimaryNetworkID, nodeID).Return(staker, nil)
   394  
   395  				uptimes := uptime.NewMockCalculator(ctrl)
   396  				uptimes.EXPECT().CalculateUptimePercentFrom(nodeID, constants.PrimaryNetworkID, primaryNetworkValidatorStartTime).Return(0.0, database.ErrNotFound)
   397  
   398  				manager := &manager{
   399  					backend: &backend{
   400  						state: state,
   401  						ctx:   snowtest.Context(t, snowtest.PChainID),
   402  					},
   403  					txExecutorBackend: &executor.Backend{
   404  						Config: &config.Config{
   405  							UptimePercentage: 0,
   406  						},
   407  						Uptimes: uptimes,
   408  					},
   409  				}
   410  
   411  				return &Block{
   412  					Block: &block.BanffProposalBlock{
   413  						ApricotProposalBlock: block.ApricotProposalBlock{
   414  							Tx: &txs.Tx{
   415  								Unsigned: &txs.RewardValidatorTx{
   416  									TxID: stakerTxID,
   417  								},
   418  							},
   419  						},
   420  					},
   421  					manager: manager,
   422  				}
   423  			},
   424  			expectedPreferenceType: &block.BanffCommitBlock{},
   425  		},
   426  		{
   427  			name: "banff proposal block; failed fetching subnet transformation",
   428  			blkF: func(ctrl *gomock.Controller) *Block {
   429  				var (
   430  					stakerTxID = ids.GenerateTestID()
   431  					nodeID     = ids.GenerateTestNodeID()
   432  					subnetID   = ids.GenerateTestID()
   433  					stakerTx   = &txs.Tx{
   434  						Unsigned: &txs.AddPermissionlessValidatorTx{
   435  							Validator: txs.Validator{
   436  								NodeID: nodeID,
   437  							},
   438  							Subnet: subnetID,
   439  						},
   440  					}
   441  					primaryNetworkValidatorStartTime = time.Now()
   442  					staker                           = &state.Staker{
   443  						StartTime: primaryNetworkValidatorStartTime,
   444  					}
   445  				)
   446  
   447  				state := state.NewMockState(ctrl)
   448  				state.EXPECT().GetTx(stakerTxID).Return(stakerTx, status.Committed, nil)
   449  				state.EXPECT().GetCurrentValidator(constants.PrimaryNetworkID, nodeID).Return(staker, nil)
   450  				state.EXPECT().GetSubnetTransformation(subnetID).Return(nil, database.ErrNotFound)
   451  
   452  				uptimes := uptime.NewMockCalculator(ctrl)
   453  
   454  				manager := &manager{
   455  					backend: &backend{
   456  						state: state,
   457  						ctx:   snowtest.Context(t, snowtest.PChainID),
   458  					},
   459  					txExecutorBackend: &executor.Backend{
   460  						Config: &config.Config{
   461  							UptimePercentage: 0,
   462  						},
   463  						Uptimes: uptimes,
   464  					},
   465  				}
   466  
   467  				return &Block{
   468  					Block: &block.BanffProposalBlock{
   469  						ApricotProposalBlock: block.ApricotProposalBlock{
   470  							Tx: &txs.Tx{
   471  								Unsigned: &txs.RewardValidatorTx{
   472  									TxID: stakerTxID,
   473  								},
   474  							},
   475  						},
   476  					},
   477  					manager: manager,
   478  				}
   479  			},
   480  			expectedPreferenceType: &block.BanffCommitBlock{},
   481  		},
   482  		{
   483  			name: "banff proposal block; prefers commit",
   484  			blkF: func(ctrl *gomock.Controller) *Block {
   485  				var (
   486  					stakerTxID = ids.GenerateTestID()
   487  					nodeID     = ids.GenerateTestNodeID()
   488  					subnetID   = ids.GenerateTestID()
   489  					stakerTx   = &txs.Tx{
   490  						Unsigned: &txs.AddPermissionlessValidatorTx{
   491  							Validator: txs.Validator{
   492  								NodeID: nodeID,
   493  							},
   494  							Subnet: subnetID,
   495  						},
   496  					}
   497  					primaryNetworkValidatorStartTime = time.Now()
   498  					staker                           = &state.Staker{
   499  						StartTime: primaryNetworkValidatorStartTime,
   500  					}
   501  					transformSubnetTx = &txs.Tx{
   502  						Unsigned: &txs.TransformSubnetTx{
   503  							UptimeRequirement: .2 * reward.PercentDenominator,
   504  						},
   505  					}
   506  				)
   507  
   508  				state := state.NewMockState(ctrl)
   509  				state.EXPECT().GetTx(stakerTxID).Return(stakerTx, status.Committed, nil)
   510  				state.EXPECT().GetCurrentValidator(constants.PrimaryNetworkID, nodeID).Return(staker, nil)
   511  				state.EXPECT().GetSubnetTransformation(subnetID).Return(transformSubnetTx, nil)
   512  
   513  				uptimes := uptime.NewMockCalculator(ctrl)
   514  				uptimes.EXPECT().CalculateUptimePercentFrom(nodeID, constants.PrimaryNetworkID, primaryNetworkValidatorStartTime).Return(.5, nil)
   515  
   516  				manager := &manager{
   517  					backend: &backend{
   518  						state: state,
   519  						ctx:   snowtest.Context(t, snowtest.PChainID),
   520  					},
   521  					txExecutorBackend: &executor.Backend{
   522  						Config: &config.Config{
   523  							UptimePercentage: .8,
   524  						},
   525  						Uptimes: uptimes,
   526  					},
   527  				}
   528  
   529  				return &Block{
   530  					Block: &block.BanffProposalBlock{
   531  						ApricotProposalBlock: block.ApricotProposalBlock{
   532  							Tx: &txs.Tx{
   533  								Unsigned: &txs.RewardValidatorTx{
   534  									TxID: stakerTxID,
   535  								},
   536  							},
   537  						},
   538  					},
   539  					manager: manager,
   540  				}
   541  			},
   542  			expectedPreferenceType: &block.BanffCommitBlock{},
   543  		},
   544  		{
   545  			name: "banff proposal block; prefers abort",
   546  			blkF: func(ctrl *gomock.Controller) *Block {
   547  				var (
   548  					stakerTxID = ids.GenerateTestID()
   549  					nodeID     = ids.GenerateTestNodeID()
   550  					subnetID   = ids.GenerateTestID()
   551  					stakerTx   = &txs.Tx{
   552  						Unsigned: &txs.AddPermissionlessValidatorTx{
   553  							Validator: txs.Validator{
   554  								NodeID: nodeID,
   555  							},
   556  							Subnet: subnetID,
   557  						},
   558  					}
   559  					primaryNetworkValidatorStartTime = time.Now()
   560  					staker                           = &state.Staker{
   561  						StartTime: primaryNetworkValidatorStartTime,
   562  					}
   563  					transformSubnetTx = &txs.Tx{
   564  						Unsigned: &txs.TransformSubnetTx{
   565  							UptimeRequirement: .6 * reward.PercentDenominator,
   566  						},
   567  					}
   568  				)
   569  
   570  				state := state.NewMockState(ctrl)
   571  				state.EXPECT().GetTx(stakerTxID).Return(stakerTx, status.Committed, nil)
   572  				state.EXPECT().GetCurrentValidator(constants.PrimaryNetworkID, nodeID).Return(staker, nil)
   573  				state.EXPECT().GetSubnetTransformation(subnetID).Return(transformSubnetTx, nil)
   574  
   575  				uptimes := uptime.NewMockCalculator(ctrl)
   576  				uptimes.EXPECT().CalculateUptimePercentFrom(nodeID, constants.PrimaryNetworkID, primaryNetworkValidatorStartTime).Return(.5, nil)
   577  
   578  				manager := &manager{
   579  					backend: &backend{
   580  						state: state,
   581  						ctx:   snowtest.Context(t, snowtest.PChainID),
   582  					},
   583  					txExecutorBackend: &executor.Backend{
   584  						Config: &config.Config{
   585  							UptimePercentage: .8,
   586  						},
   587  						Uptimes: uptimes,
   588  					},
   589  				}
   590  
   591  				return &Block{
   592  					Block: &block.BanffProposalBlock{
   593  						ApricotProposalBlock: block.ApricotProposalBlock{
   594  							Tx: &txs.Tx{
   595  								Unsigned: &txs.RewardValidatorTx{
   596  									TxID: stakerTxID,
   597  								},
   598  							},
   599  						},
   600  					},
   601  					manager: manager,
   602  				}
   603  			},
   604  			expectedPreferenceType: &block.BanffAbortBlock{},
   605  		},
   606  	}
   607  
   608  	for _, tt := range tests {
   609  		t.Run(tt.name, func(t *testing.T) {
   610  			ctrl := gomock.NewController(t)
   611  			require := require.New(t)
   612  
   613  			blk := tt.blkF(ctrl)
   614  			options, err := blk.Options(context.Background())
   615  			require.NoError(err)
   616  			require.IsType(tt.expectedPreferenceType, options[0].(*Block).Block)
   617  		})
   618  	}
   619  }