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 }