github.com/MetalBlockchain/metalgo@v1.11.9/vms/proposervm/post_fork_option_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 proposervm 5 6 import ( 7 "bytes" 8 "context" 9 "testing" 10 "time" 11 12 "github.com/prometheus/client_golang/prometheus" 13 "github.com/stretchr/testify/require" 14 15 "github.com/MetalBlockchain/metalgo/database" 16 "github.com/MetalBlockchain/metalgo/ids" 17 "github.com/MetalBlockchain/metalgo/snow" 18 "github.com/MetalBlockchain/metalgo/snow/choices" 19 "github.com/MetalBlockchain/metalgo/snow/consensus/snowman" 20 "github.com/MetalBlockchain/metalgo/snow/consensus/snowman/snowmantest" 21 "github.com/MetalBlockchain/metalgo/snow/engine/common" 22 "github.com/MetalBlockchain/metalgo/vms/proposervm/block" 23 ) 24 25 var _ snowman.OracleBlock = (*TestOptionsBlock)(nil) 26 27 type TestOptionsBlock struct { 28 snowmantest.Block 29 opts [2]snowman.Block 30 optsErr error 31 } 32 33 func (tob TestOptionsBlock) Options(context.Context) ([2]snowman.Block, error) { 34 return tob.opts, tob.optsErr 35 } 36 37 // ProposerBlock.Verify tests section 38 func TestBlockVerify_PostForkOption_ParentChecks(t *testing.T) { 39 require := require.New(t) 40 41 var ( 42 activationTime = time.Unix(0, 0) 43 durangoTime = activationTime 44 ) 45 coreVM, _, proVM, _ := initTestProposerVM(t, activationTime, durangoTime, 0) 46 defer func() { 47 require.NoError(proVM.Shutdown(context.Background())) 48 }() 49 50 // create post fork oracle block ... 51 coreTestBlk := snowmantest.BuildChild(snowmantest.Genesis) 52 preferredBlk := snowmantest.BuildChild(coreTestBlk) 53 oracleCoreBlk := &TestOptionsBlock{ 54 Block: *coreTestBlk, 55 opts: [2]snowman.Block{ 56 preferredBlk, 57 snowmantest.BuildChild(coreTestBlk), 58 }, 59 } 60 61 coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { 62 return oracleCoreBlk, nil 63 } 64 coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { 65 switch blkID { 66 case snowmantest.GenesisID: 67 return snowmantest.Genesis, nil 68 case oracleCoreBlk.ID(): 69 return oracleCoreBlk, nil 70 case oracleCoreBlk.opts[0].ID(): 71 return oracleCoreBlk.opts[0], nil 72 case oracleCoreBlk.opts[1].ID(): 73 return oracleCoreBlk.opts[1], nil 74 default: 75 return nil, database.ErrNotFound 76 } 77 } 78 coreVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { 79 switch { 80 case bytes.Equal(b, snowmantest.GenesisBytes): 81 return snowmantest.Genesis, nil 82 case bytes.Equal(b, oracleCoreBlk.Bytes()): 83 return oracleCoreBlk, nil 84 case bytes.Equal(b, oracleCoreBlk.opts[0].Bytes()): 85 return oracleCoreBlk.opts[0], nil 86 case bytes.Equal(b, oracleCoreBlk.opts[1].Bytes()): 87 return oracleCoreBlk.opts[1], nil 88 default: 89 return nil, errUnknownBlock 90 } 91 } 92 93 parentBlk, err := proVM.BuildBlock(context.Background()) 94 require.NoError(err) 95 96 require.NoError(parentBlk.Verify(context.Background())) 97 require.NoError(proVM.SetPreference(context.Background(), parentBlk.ID())) 98 99 // retrieve options ... 100 require.IsType(&postForkBlock{}, parentBlk) 101 postForkOracleBlk := parentBlk.(*postForkBlock) 102 opts, err := postForkOracleBlk.Options(context.Background()) 103 require.NoError(err) 104 require.IsType(&postForkOption{}, opts[0]) 105 106 // ... and verify them 107 require.NoError(opts[0].Verify(context.Background())) 108 require.NoError(opts[1].Verify(context.Background())) 109 110 // show we can build on options 111 require.NoError(proVM.SetPreference(context.Background(), opts[0].ID())) 112 113 childCoreBlk := snowmantest.BuildChild(preferredBlk) 114 coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { 115 return childCoreBlk, nil 116 } 117 require.NoError(waitForProposerWindow(proVM, opts[0], postForkOracleBlk.PChainHeight())) 118 119 proChild, err := proVM.BuildBlock(context.Background()) 120 require.NoError(err) 121 require.IsType(&postForkBlock{}, proChild) 122 require.NoError(proChild.Verify(context.Background())) 123 } 124 125 // ProposerBlock.Accept tests section 126 func TestBlockVerify_PostForkOption_CoreBlockVerifyIsCalledOnce(t *testing.T) { 127 require := require.New(t) 128 129 // Verify an option once; then show that another verify call would not call coreBlk.Verify() 130 var ( 131 activationTime = time.Unix(0, 0) 132 durangoTime = activationTime 133 ) 134 coreVM, _, proVM, _ := initTestProposerVM(t, activationTime, durangoTime, 0) 135 defer func() { 136 require.NoError(proVM.Shutdown(context.Background())) 137 }() 138 139 // create post fork oracle block ... 140 coreTestBlk := snowmantest.BuildChild(snowmantest.Genesis) 141 coreOpt0 := snowmantest.BuildChild(coreTestBlk) 142 coreOpt1 := snowmantest.BuildChild(coreTestBlk) 143 oracleCoreBlk := &TestOptionsBlock{ 144 Block: *coreTestBlk, 145 opts: [2]snowman.Block{ 146 coreOpt0, 147 coreOpt1, 148 }, 149 } 150 151 coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { 152 return oracleCoreBlk, nil 153 } 154 coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { 155 switch blkID { 156 case snowmantest.GenesisID: 157 return snowmantest.Genesis, nil 158 case oracleCoreBlk.ID(): 159 return oracleCoreBlk, nil 160 case oracleCoreBlk.opts[0].ID(): 161 return oracleCoreBlk.opts[0], nil 162 case oracleCoreBlk.opts[1].ID(): 163 return oracleCoreBlk.opts[1], nil 164 default: 165 return nil, database.ErrNotFound 166 } 167 } 168 coreVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { 169 switch { 170 case bytes.Equal(b, snowmantest.GenesisBytes): 171 return snowmantest.Genesis, nil 172 case bytes.Equal(b, oracleCoreBlk.Bytes()): 173 return oracleCoreBlk, nil 174 case bytes.Equal(b, oracleCoreBlk.opts[0].Bytes()): 175 return oracleCoreBlk.opts[0], nil 176 case bytes.Equal(b, oracleCoreBlk.opts[1].Bytes()): 177 return oracleCoreBlk.opts[1], nil 178 default: 179 return nil, errUnknownBlock 180 } 181 } 182 183 parentBlk, err := proVM.BuildBlock(context.Background()) 184 require.NoError(err) 185 186 require.NoError(parentBlk.Verify(context.Background())) 187 require.NoError(proVM.SetPreference(context.Background(), parentBlk.ID())) 188 189 // retrieve options ... 190 require.IsType(&postForkBlock{}, parentBlk) 191 postForkOracleBlk := parentBlk.(*postForkBlock) 192 opts, err := postForkOracleBlk.Options(context.Background()) 193 require.NoError(err) 194 require.IsType(&postForkOption{}, opts[0]) 195 196 // ... and verify them the first time 197 require.NoError(opts[0].Verify(context.Background())) 198 require.NoError(opts[1].Verify(context.Background())) 199 200 // set error on coreBlock.Verify and recall Verify() 201 coreOpt0.VerifyV = errDuplicateVerify 202 coreOpt1.VerifyV = errDuplicateVerify 203 204 // ... and verify them again. They verify without call to innerBlk 205 require.NoError(opts[0].Verify(context.Background())) 206 require.NoError(opts[1].Verify(context.Background())) 207 } 208 209 func TestBlockAccept_PostForkOption_SetsLastAcceptedBlock(t *testing.T) { 210 require := require.New(t) 211 212 var ( 213 activationTime = time.Unix(0, 0) 214 durangoTime = activationTime 215 ) 216 coreVM, _, proVM, _ := initTestProposerVM(t, activationTime, durangoTime, 0) 217 defer func() { 218 require.NoError(proVM.Shutdown(context.Background())) 219 }() 220 221 // create post fork oracle block ... 222 coreTestBlk := snowmantest.BuildChild(snowmantest.Genesis) 223 oracleCoreBlk := &TestOptionsBlock{ 224 Block: *coreTestBlk, 225 opts: [2]snowman.Block{ 226 snowmantest.BuildChild(coreTestBlk), 227 snowmantest.BuildChild(coreTestBlk), 228 }, 229 } 230 231 coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { 232 return oracleCoreBlk, nil 233 } 234 coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { 235 switch blkID { 236 case snowmantest.GenesisID: 237 return snowmantest.Genesis, nil 238 case oracleCoreBlk.ID(): 239 return oracleCoreBlk, nil 240 case oracleCoreBlk.opts[0].ID(): 241 return oracleCoreBlk.opts[0], nil 242 case oracleCoreBlk.opts[1].ID(): 243 return oracleCoreBlk.opts[1], nil 244 default: 245 return nil, database.ErrNotFound 246 } 247 } 248 coreVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { 249 switch { 250 case bytes.Equal(b, snowmantest.GenesisBytes): 251 return snowmantest.Genesis, nil 252 case bytes.Equal(b, oracleCoreBlk.Bytes()): 253 return oracleCoreBlk, nil 254 case bytes.Equal(b, oracleCoreBlk.opts[0].Bytes()): 255 return oracleCoreBlk.opts[0], nil 256 case bytes.Equal(b, oracleCoreBlk.opts[1].Bytes()): 257 return oracleCoreBlk.opts[1], nil 258 default: 259 return nil, errUnknownBlock 260 } 261 } 262 263 parentBlk, err := proVM.BuildBlock(context.Background()) 264 require.NoError(err) 265 266 // accept oracle block 267 require.NoError(parentBlk.Accept(context.Background())) 268 269 coreVM.LastAcceptedF = func(context.Context) (ids.ID, error) { 270 if oracleCoreBlk.Status() == choices.Accepted { 271 return oracleCoreBlk.ID(), nil 272 } 273 return snowmantest.GenesisID, nil 274 } 275 acceptedID, err := proVM.LastAccepted(context.Background()) 276 require.NoError(err) 277 require.Equal(parentBlk.ID(), acceptedID) 278 279 // accept one of the options 280 require.IsType(&postForkBlock{}, parentBlk) 281 postForkOracleBlk := parentBlk.(*postForkBlock) 282 opts, err := postForkOracleBlk.Options(context.Background()) 283 require.NoError(err) 284 285 require.NoError(opts[0].Accept(context.Background())) 286 287 coreVM.LastAcceptedF = func(context.Context) (ids.ID, error) { 288 if oracleCoreBlk.opts[0].Status() == choices.Accepted { 289 return oracleCoreBlk.opts[0].ID(), nil 290 } 291 return oracleCoreBlk.ID(), nil 292 } 293 acceptedID, err = proVM.LastAccepted(context.Background()) 294 require.NoError(err) 295 require.Equal(opts[0].ID(), acceptedID) 296 } 297 298 // ProposerBlock.Reject tests section 299 func TestBlockReject_InnerBlockIsNotRejected(t *testing.T) { 300 require := require.New(t) 301 302 var ( 303 activationTime = time.Unix(0, 0) 304 durangoTime = activationTime 305 ) 306 coreVM, _, proVM, _ := initTestProposerVM(t, activationTime, durangoTime, 0) 307 defer func() { 308 require.NoError(proVM.Shutdown(context.Background())) 309 }() 310 311 // create post fork oracle block ... 312 coreTestBlk := snowmantest.BuildChild(snowmantest.Genesis) 313 oracleCoreBlk := &TestOptionsBlock{ 314 Block: *coreTestBlk, 315 opts: [2]snowman.Block{ 316 snowmantest.BuildChild(coreTestBlk), 317 snowmantest.BuildChild(coreTestBlk), 318 }, 319 } 320 321 coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { 322 return oracleCoreBlk, nil 323 } 324 coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { 325 switch blkID { 326 case snowmantest.GenesisID: 327 return snowmantest.Genesis, nil 328 case oracleCoreBlk.ID(): 329 return oracleCoreBlk, nil 330 case oracleCoreBlk.opts[0].ID(): 331 return oracleCoreBlk.opts[0], nil 332 case oracleCoreBlk.opts[1].ID(): 333 return oracleCoreBlk.opts[1], nil 334 default: 335 return nil, database.ErrNotFound 336 } 337 } 338 coreVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { 339 switch { 340 case bytes.Equal(b, snowmantest.GenesisBytes): 341 return snowmantest.Genesis, nil 342 case bytes.Equal(b, oracleCoreBlk.Bytes()): 343 return oracleCoreBlk, nil 344 case bytes.Equal(b, oracleCoreBlk.opts[0].Bytes()): 345 return oracleCoreBlk.opts[0], nil 346 case bytes.Equal(b, oracleCoreBlk.opts[1].Bytes()): 347 return oracleCoreBlk.opts[1], nil 348 default: 349 return nil, errUnknownBlock 350 } 351 } 352 353 builtBlk, err := proVM.BuildBlock(context.Background()) 354 require.NoError(err) 355 356 // reject oracle block 357 require.NoError(builtBlk.Reject(context.Background())) 358 require.IsType(&postForkBlock{}, builtBlk) 359 proBlk := builtBlk.(*postForkBlock) 360 361 require.Equal(choices.Rejected, proBlk.Status()) 362 363 require.NotEqual(choices.Rejected, proBlk.innerBlk.Status()) 364 365 // reject an option 366 require.IsType(&postForkBlock{}, builtBlk) 367 postForkOracleBlk := builtBlk.(*postForkBlock) 368 opts, err := postForkOracleBlk.Options(context.Background()) 369 require.NoError(err) 370 371 require.NoError(opts[0].Reject(context.Background())) 372 require.IsType(&postForkOption{}, opts[0]) 373 proOpt := opts[0].(*postForkOption) 374 375 require.Equal(choices.Rejected, proOpt.Status()) 376 377 require.NotEqual(choices.Rejected, proOpt.innerBlk.Status()) 378 } 379 380 func TestBlockVerify_PostForkOption_ParentIsNotOracleWithError(t *testing.T) { 381 require := require.New(t) 382 383 // Verify an option once; then show that another verify call would not call coreBlk.Verify() 384 var ( 385 activationTime = time.Unix(0, 0) 386 durangoTime = activationTime 387 ) 388 coreVM, _, proVM, _ := initTestProposerVM(t, activationTime, durangoTime, 0) 389 defer func() { 390 require.NoError(proVM.Shutdown(context.Background())) 391 }() 392 393 coreTestBlk := snowmantest.BuildChild(snowmantest.Genesis) 394 coreBlk := &TestOptionsBlock{ 395 Block: *coreTestBlk, 396 optsErr: snowman.ErrNotOracle, 397 } 398 399 coreChildBlk := snowmantest.BuildChild(coreTestBlk) 400 401 coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { 402 return coreBlk, nil 403 } 404 coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { 405 switch blkID { 406 case snowmantest.GenesisID: 407 return snowmantest.Genesis, nil 408 case coreBlk.ID(): 409 return coreBlk, nil 410 case coreChildBlk.ID(): 411 return coreChildBlk, nil 412 default: 413 return nil, database.ErrNotFound 414 } 415 } 416 coreVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { 417 switch { 418 case bytes.Equal(b, snowmantest.GenesisBytes): 419 return snowmantest.Genesis, nil 420 case bytes.Equal(b, coreBlk.Bytes()): 421 return coreBlk, nil 422 case bytes.Equal(b, coreChildBlk.Bytes()): 423 return coreChildBlk, nil 424 default: 425 return nil, errUnknownBlock 426 } 427 } 428 429 parentBlk, err := proVM.BuildBlock(context.Background()) 430 require.NoError(err) 431 432 require.IsType(&postForkBlock{}, parentBlk) 433 postForkBlk := parentBlk.(*postForkBlock) 434 _, err = postForkBlk.Options(context.Background()) 435 require.Equal(snowman.ErrNotOracle, err) 436 437 // Build the child 438 statelessChild, err := block.BuildOption( 439 postForkBlk.ID(), 440 coreChildBlk.Bytes(), 441 ) 442 require.NoError(err) 443 444 invalidChild, err := proVM.ParseBlock(context.Background(), statelessChild.Bytes()) 445 if err != nil { 446 // A failure to parse is okay here 447 return 448 } 449 450 err = invalidChild.Verify(context.Background()) 451 require.ErrorIs(err, database.ErrNotFound) 452 } 453 454 func TestOptionTimestampValidity(t *testing.T) { 455 require := require.New(t) 456 457 var ( 458 activationTime = time.Unix(0, 0) 459 durangoTime = activationTime 460 ) 461 coreVM, _, proVM, db := initTestProposerVM(t, activationTime, durangoTime, 0) 462 463 coreTestBlk := snowmantest.BuildChild(snowmantest.Genesis) 464 coreOracleBlk := &TestOptionsBlock{ 465 Block: *coreTestBlk, 466 opts: [2]snowman.Block{ 467 snowmantest.BuildChild(coreTestBlk), 468 snowmantest.BuildChild(coreTestBlk), 469 }, 470 } 471 472 oracleBlkTime := proVM.Time().Truncate(time.Second) 473 statelessBlock, err := block.BuildUnsigned( 474 snowmantest.GenesisID, 475 oracleBlkTime, 476 0, 477 coreOracleBlk.Bytes(), 478 ) 479 require.NoError(err) 480 481 coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { 482 switch blkID { 483 case snowmantest.GenesisID: 484 return snowmantest.Genesis, nil 485 case coreOracleBlk.ID(): 486 return coreOracleBlk, nil 487 case coreOracleBlk.opts[0].ID(): 488 return coreOracleBlk.opts[0], nil 489 case coreOracleBlk.opts[1].ID(): 490 return coreOracleBlk.opts[1], nil 491 default: 492 return nil, errUnknownBlock 493 } 494 } 495 coreVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { 496 switch { 497 case bytes.Equal(b, snowmantest.GenesisBytes): 498 return snowmantest.Genesis, nil 499 case bytes.Equal(b, coreOracleBlk.Bytes()): 500 return coreOracleBlk, nil 501 case bytes.Equal(b, coreOracleBlk.opts[0].Bytes()): 502 return coreOracleBlk.opts[0], nil 503 case bytes.Equal(b, coreOracleBlk.opts[1].Bytes()): 504 return coreOracleBlk.opts[1], nil 505 default: 506 return nil, errUnknownBlock 507 } 508 } 509 510 statefulBlock, err := proVM.ParseBlock(context.Background(), statelessBlock.Bytes()) 511 require.NoError(err) 512 513 require.NoError(statefulBlock.Verify(context.Background())) 514 515 statefulOracleBlock, ok := statefulBlock.(snowman.OracleBlock) 516 require.True(ok) 517 518 options, err := statefulOracleBlock.Options(context.Background()) 519 require.NoError(err) 520 521 option := options[0] 522 require.NoError(option.Verify(context.Background())) 523 524 require.NoError(statefulBlock.Accept(context.Background())) 525 526 coreVM.GetBlockF = func(context.Context, ids.ID) (snowman.Block, error) { 527 require.FailNow("called GetBlock when unable to handle the error") 528 return nil, nil 529 } 530 coreVM.ParseBlockF = func(context.Context, []byte) (snowman.Block, error) { 531 require.FailNow("called ParseBlock when unable to handle the error") 532 return nil, nil 533 } 534 535 require.Equal(oracleBlkTime, option.Timestamp()) 536 537 require.NoError(option.Accept(context.Background())) 538 require.NoError(proVM.Shutdown(context.Background())) 539 540 // Restart the node. 541 ctx := proVM.ctx 542 proVM = New( 543 coreVM, 544 Config{ 545 ActivationTime: time.Unix(0, 0), 546 DurangoTime: time.Unix(0, 0), 547 MinimumPChainHeight: 0, 548 MinBlkDelay: DefaultMinBlockDelay, 549 NumHistoricalBlocks: DefaultNumHistoricalBlocks, 550 StakingLeafSigner: pTestSigner, 551 StakingCertLeaf: pTestCert, 552 Registerer: prometheus.NewRegistry(), 553 }, 554 ) 555 556 coreVM.InitializeF = func( 557 context.Context, 558 *snow.Context, 559 database.Database, 560 []byte, 561 []byte, 562 []byte, 563 chan<- common.Message, 564 []*common.Fx, 565 common.AppSender, 566 ) error { 567 return nil 568 } 569 coreVM.LastAcceptedF = func(context.Context) (ids.ID, error) { 570 return coreOracleBlk.opts[0].ID(), nil 571 } 572 573 coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { 574 switch blkID { 575 case snowmantest.GenesisID: 576 return snowmantest.Genesis, nil 577 case coreOracleBlk.ID(): 578 return coreOracleBlk, nil 579 case coreOracleBlk.opts[0].ID(): 580 return coreOracleBlk.opts[0], nil 581 case coreOracleBlk.opts[1].ID(): 582 return coreOracleBlk.opts[1], nil 583 default: 584 return nil, errUnknownBlock 585 } 586 } 587 coreVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { 588 switch { 589 case bytes.Equal(b, snowmantest.GenesisBytes): 590 return snowmantest.Genesis, nil 591 case bytes.Equal(b, coreOracleBlk.Bytes()): 592 return coreOracleBlk, nil 593 case bytes.Equal(b, coreOracleBlk.opts[0].Bytes()): 594 return coreOracleBlk.opts[0], nil 595 case bytes.Equal(b, coreOracleBlk.opts[1].Bytes()): 596 return coreOracleBlk.opts[1], nil 597 default: 598 return nil, errUnknownBlock 599 } 600 } 601 602 require.NoError(proVM.Initialize( 603 context.Background(), 604 ctx, 605 db, 606 nil, 607 nil, 608 nil, 609 nil, 610 nil, 611 nil, 612 )) 613 defer func() { 614 require.NoError(proVM.Shutdown(context.Background())) 615 }() 616 617 statefulOptionBlock, err := proVM.ParseBlock(context.Background(), option.Bytes()) 618 require.NoError(err) 619 620 require.Equal(choices.Accepted, statefulOptionBlock.Status()) 621 622 coreVM.GetBlockF = func(context.Context, ids.ID) (snowman.Block, error) { 623 require.FailNow("called GetBlock when unable to handle the error") 624 return nil, nil 625 } 626 coreVM.ParseBlockF = func(context.Context, []byte) (snowman.Block, error) { 627 require.FailNow("called ParseBlock when unable to handle the error") 628 return nil, nil 629 } 630 631 require.Equal(oracleBlkTime, statefulOptionBlock.Timestamp()) 632 }