github.com/MetalBlockchain/metalgo@v1.11.9/vms/proposervm/vm.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 "context" 8 "errors" 9 "fmt" 10 "time" 11 12 "github.com/prometheus/client_golang/prometheus" 13 "go.uber.org/zap" 14 15 "github.com/MetalBlockchain/metalgo/cache" 16 "github.com/MetalBlockchain/metalgo/cache/metercacher" 17 "github.com/MetalBlockchain/metalgo/database" 18 "github.com/MetalBlockchain/metalgo/database/prefixdb" 19 "github.com/MetalBlockchain/metalgo/database/versiondb" 20 "github.com/MetalBlockchain/metalgo/ids" 21 "github.com/MetalBlockchain/metalgo/snow" 22 "github.com/MetalBlockchain/metalgo/snow/choices" 23 "github.com/MetalBlockchain/metalgo/snow/consensus/snowman" 24 "github.com/MetalBlockchain/metalgo/snow/engine/common" 25 "github.com/MetalBlockchain/metalgo/snow/engine/snowman/block" 26 "github.com/MetalBlockchain/metalgo/utils/constants" 27 "github.com/MetalBlockchain/metalgo/utils/math" 28 "github.com/MetalBlockchain/metalgo/utils/timer/mockable" 29 "github.com/MetalBlockchain/metalgo/utils/units" 30 "github.com/MetalBlockchain/metalgo/vms/proposervm/proposer" 31 "github.com/MetalBlockchain/metalgo/vms/proposervm/scheduler" 32 "github.com/MetalBlockchain/metalgo/vms/proposervm/state" 33 "github.com/MetalBlockchain/metalgo/vms/proposervm/tree" 34 35 statelessblock "github.com/MetalBlockchain/metalgo/vms/proposervm/block" 36 ) 37 38 const ( 39 // DefaultMinBlockDelay should be kept as whole seconds because block 40 // timestamps are only specific to the second. 41 DefaultMinBlockDelay = time.Second 42 // DefaultNumHistoricalBlocks as 0 results in never deleting any historical 43 // blocks. 44 DefaultNumHistoricalBlocks uint64 = 0 45 46 checkIndexedFrequency = 10 * time.Second 47 innerBlkCacheSize = 64 * units.MiB 48 ) 49 50 var ( 51 _ block.ChainVM = (*VM)(nil) 52 _ block.BatchedChainVM = (*VM)(nil) 53 _ block.StateSyncableVM = (*VM)(nil) 54 55 dbPrefix = []byte("proposervm") 56 ) 57 58 func cachedBlockSize(_ ids.ID, blk snowman.Block) int { 59 return ids.IDLen + len(blk.Bytes()) + constants.PointerOverhead 60 } 61 62 type VM struct { 63 block.ChainVM 64 Config 65 blockBuilderVM block.BuildBlockWithContextChainVM 66 batchedVM block.BatchedChainVM 67 ssVM block.StateSyncableVM 68 69 state.State 70 71 proposer.Windower 72 tree.Tree 73 scheduler.Scheduler 74 mockable.Clock 75 76 ctx *snow.Context 77 db *versiondb.Database 78 toScheduler chan<- common.Message 79 80 // Block ID --> Block 81 // Each element is a block that passed verification but 82 // hasn't yet been accepted/rejected 83 verifiedBlocks map[ids.ID]PostForkBlock 84 // Stateless block ID --> inner block. 85 // Only contains post-fork blocks near the tip so that the cache doesn't get 86 // filled with random blocks every time this node parses blocks while 87 // processing a GetAncestors message from a bootstrapping node. 88 innerBlkCache cache.Cacher[ids.ID, snowman.Block] 89 preferred ids.ID 90 consensusState snow.State 91 context context.Context 92 onShutdown func() 93 94 // lastAcceptedTime is set to the last accepted PostForkBlock's timestamp 95 // if the last accepted block has been a PostForkOption block since having 96 // initialized the VM. 97 lastAcceptedTime time.Time 98 99 // lastAcceptedHeight is set to the last accepted PostForkBlock's height. 100 lastAcceptedHeight uint64 101 102 // proposerBuildSlotGauge reports the slot index when this node may attempt 103 // to build a block. 104 proposerBuildSlotGauge prometheus.Gauge 105 106 // acceptedBlocksSlotHistogram reports the slots that accepted blocks were 107 // proposed in. 108 acceptedBlocksSlotHistogram prometheus.Histogram 109 } 110 111 // New performs best when [minBlkDelay] is whole seconds. This is because block 112 // timestamps are only specific to the second. 113 func New( 114 vm block.ChainVM, 115 config Config, 116 ) *VM { 117 blockBuilderVM, _ := vm.(block.BuildBlockWithContextChainVM) 118 batchedVM, _ := vm.(block.BatchedChainVM) 119 ssVM, _ := vm.(block.StateSyncableVM) 120 return &VM{ 121 ChainVM: vm, 122 Config: config, 123 blockBuilderVM: blockBuilderVM, 124 batchedVM: batchedVM, 125 ssVM: ssVM, 126 } 127 } 128 129 func (vm *VM) Initialize( 130 ctx context.Context, 131 chainCtx *snow.Context, 132 db database.Database, 133 genesisBytes []byte, 134 upgradeBytes []byte, 135 configBytes []byte, 136 toEngine chan<- common.Message, 137 fxs []*common.Fx, 138 appSender common.AppSender, 139 ) error { 140 vm.ctx = chainCtx 141 vm.db = versiondb.New(prefixdb.New(dbPrefix, db)) 142 baseState, err := state.NewMetered(vm.db, "state", vm.Config.Registerer) 143 if err != nil { 144 return err 145 } 146 vm.State = baseState 147 vm.Windower = proposer.New(chainCtx.ValidatorState, chainCtx.SubnetID, chainCtx.ChainID) 148 vm.Tree = tree.New() 149 innerBlkCache, err := metercacher.New( 150 "inner_block_cache", 151 vm.Config.Registerer, 152 cache.NewSizedLRU( 153 innerBlkCacheSize, 154 cachedBlockSize, 155 ), 156 ) 157 if err != nil { 158 return err 159 } 160 vm.innerBlkCache = innerBlkCache 161 162 scheduler, vmToEngine := scheduler.New(vm.ctx.Log, toEngine) 163 vm.Scheduler = scheduler 164 vm.toScheduler = vmToEngine 165 166 go chainCtx.Log.RecoverAndPanic(func() { 167 scheduler.Dispatch(time.Now()) 168 }) 169 170 vm.verifiedBlocks = make(map[ids.ID]PostForkBlock) 171 detachedCtx := context.WithoutCancel(ctx) 172 context, cancel := context.WithCancel(detachedCtx) 173 vm.context = context 174 vm.onShutdown = cancel 175 176 err = vm.ChainVM.Initialize( 177 ctx, 178 chainCtx, 179 db, 180 genesisBytes, 181 upgradeBytes, 182 configBytes, 183 vmToEngine, 184 fxs, 185 appSender, 186 ) 187 if err != nil { 188 return err 189 } 190 191 if err := vm.repairAcceptedChainByHeight(ctx); err != nil { 192 return err 193 } 194 195 if err := vm.setLastAcceptedMetadata(ctx); err != nil { 196 return err 197 } 198 199 if err := vm.pruneOldBlocks(); err != nil { 200 return err 201 } 202 203 forkHeight, err := vm.GetForkHeight() 204 switch err { 205 case nil: 206 chainCtx.Log.Info("initialized proposervm", 207 zap.String("state", "after fork"), 208 zap.Uint64("forkHeight", forkHeight), 209 zap.Uint64("lastAcceptedHeight", vm.lastAcceptedHeight), 210 ) 211 case database.ErrNotFound: 212 chainCtx.Log.Info("initialized proposervm", 213 zap.String("state", "before fork"), 214 ) 215 default: 216 return err 217 } 218 219 vm.proposerBuildSlotGauge = prometheus.NewGauge(prometheus.GaugeOpts{ 220 Name: "block_building_slot", 221 Help: "the slot that this node may attempt to build a block", 222 }) 223 vm.acceptedBlocksSlotHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{ 224 Name: "accepted_blocks_slot", 225 Help: "the slot accepted blocks were proposed in", 226 // define the following ranges: 227 // (-inf, 0] 228 // (0, 1] 229 // (1, 2] 230 // (2, inf) 231 // the usage of ".5" before was to ensure we work around the limitation 232 // of comparing floating point of the same numerical value. 233 Buckets: []float64{0.5, 1.5, 2.5}, 234 }) 235 236 return errors.Join( 237 vm.Config.Registerer.Register(vm.proposerBuildSlotGauge), 238 vm.Config.Registerer.Register(vm.acceptedBlocksSlotHistogram), 239 ) 240 } 241 242 // shutdown ops then propagate shutdown to innerVM 243 func (vm *VM) Shutdown(ctx context.Context) error { 244 vm.onShutdown() 245 246 vm.Scheduler.Close() 247 248 if err := vm.db.Commit(); err != nil { 249 return err 250 } 251 return vm.ChainVM.Shutdown(ctx) 252 } 253 254 func (vm *VM) SetState(ctx context.Context, newState snow.State) error { 255 if err := vm.ChainVM.SetState(ctx, newState); err != nil { 256 return err 257 } 258 259 oldState := vm.consensusState 260 vm.consensusState = newState 261 if oldState != snow.StateSyncing { 262 return nil 263 } 264 265 // When finishing StateSyncing, if state sync has failed or was skipped, 266 // repairAcceptedChainByHeight rolls back the chain to the previously last 267 // accepted block. If state sync has completed successfully, this call is a 268 // no-op. 269 if err := vm.repairAcceptedChainByHeight(ctx); err != nil { 270 return err 271 } 272 return vm.setLastAcceptedMetadata(ctx) 273 } 274 275 func (vm *VM) BuildBlock(ctx context.Context) (snowman.Block, error) { 276 preferredBlock, err := vm.getBlock(ctx, vm.preferred) 277 if err != nil { 278 vm.ctx.Log.Error("unexpected build block failure", 279 zap.String("reason", "failed to fetch preferred block"), 280 zap.Stringer("parentID", vm.preferred), 281 zap.Error(err), 282 ) 283 return nil, err 284 } 285 286 return preferredBlock.buildChild(ctx) 287 } 288 289 func (vm *VM) ParseBlock(ctx context.Context, b []byte) (snowman.Block, error) { 290 if blk, err := vm.parsePostForkBlock(ctx, b); err == nil { 291 return blk, nil 292 } 293 return vm.parsePreForkBlock(ctx, b) 294 } 295 296 func (vm *VM) GetBlock(ctx context.Context, id ids.ID) (snowman.Block, error) { 297 return vm.getBlock(ctx, id) 298 } 299 300 func (vm *VM) SetPreference(ctx context.Context, preferred ids.ID) error { 301 if vm.preferred == preferred { 302 return nil 303 } 304 vm.preferred = preferred 305 306 blk, err := vm.getPostForkBlock(ctx, preferred) 307 if err != nil { 308 return vm.ChainVM.SetPreference(ctx, preferred) 309 } 310 311 if err := vm.ChainVM.SetPreference(ctx, blk.getInnerBlk().ID()); err != nil { 312 return err 313 } 314 315 pChainHeight, err := blk.pChainHeight(ctx) 316 if err != nil { 317 return err 318 } 319 320 var ( 321 childBlockHeight = blk.Height() + 1 322 parentTimestamp = blk.Timestamp() 323 nextStartTime time.Time 324 ) 325 if vm.IsDurangoActivated(parentTimestamp) { 326 currentTime := vm.Clock.Time().Truncate(time.Second) 327 if nextStartTime, err = vm.getPostDurangoSlotTime( 328 ctx, 329 childBlockHeight, 330 pChainHeight, 331 proposer.TimeToSlot(parentTimestamp, currentTime), 332 parentTimestamp, 333 ); err == nil { 334 vm.proposerBuildSlotGauge.Set(float64(proposer.TimeToSlot(parentTimestamp, nextStartTime))) 335 } 336 } else { 337 nextStartTime, err = vm.getPreDurangoSlotTime( 338 ctx, 339 childBlockHeight, 340 pChainHeight, 341 parentTimestamp, 342 ) 343 } 344 if err != nil { 345 vm.ctx.Log.Debug("failed to fetch the expected delay", 346 zap.Error(err), 347 ) 348 349 // A nil error is returned here because it is possible that 350 // bootstrapping caused the last accepted block to move past the latest 351 // P-chain height. This will cause building blocks to return an error 352 // until the P-chain's height has advanced. 353 return nil 354 } 355 vm.Scheduler.SetBuildBlockTime(nextStartTime) 356 357 vm.ctx.Log.Debug("set preference", 358 zap.Stringer("blkID", blk.ID()), 359 zap.Time("blockTimestamp", parentTimestamp), 360 zap.Time("nextStartTime", nextStartTime), 361 ) 362 return nil 363 } 364 365 func (vm *VM) getPreDurangoSlotTime( 366 ctx context.Context, 367 blkHeight, 368 pChainHeight uint64, 369 parentTimestamp time.Time, 370 ) (time.Time, error) { 371 delay, err := vm.Windower.Delay(ctx, blkHeight, pChainHeight, vm.ctx.NodeID, proposer.MaxBuildWindows) 372 if err != nil { 373 return time.Time{}, err 374 } 375 376 // Note: The P-chain does not currently try to target any block time. It 377 // notifies the consensus engine as soon as a new block may be built. To 378 // avoid fast runs of blocks there is an additional minimum delay that 379 // validators can specify. This delay may be an issue for high performance, 380 // custom VMs. Until the P-chain is modified to target a specific block 381 // time, ProposerMinBlockDelay can be configured in the subnet config. 382 delay = max(delay, vm.MinBlkDelay) 383 return parentTimestamp.Add(delay), nil 384 } 385 386 func (vm *VM) getPostDurangoSlotTime( 387 ctx context.Context, 388 blkHeight, 389 pChainHeight, 390 slot uint64, 391 parentTimestamp time.Time, 392 ) (time.Time, error) { 393 delay, err := vm.Windower.MinDelayForProposer( 394 ctx, 395 blkHeight, 396 pChainHeight, 397 vm.ctx.NodeID, 398 slot, 399 ) 400 // Note: The P-chain does not currently try to target any block time. It 401 // notifies the consensus engine as soon as a new block may be built. To 402 // avoid fast runs of blocks there is an additional minimum delay that 403 // validators can specify. This delay may be an issue for high performance, 404 // custom VMs. Until the P-chain is modified to target a specific block 405 // time, ProposerMinBlockDelay can be configured in the subnet config. 406 switch { 407 case err == nil: 408 delay = max(delay, vm.MinBlkDelay) 409 return parentTimestamp.Add(delay), err 410 case errors.Is(err, proposer.ErrAnyoneCanPropose): 411 return parentTimestamp.Add(vm.MinBlkDelay), err 412 default: 413 return time.Time{}, err 414 } 415 } 416 417 func (vm *VM) LastAccepted(ctx context.Context) (ids.ID, error) { 418 lastAccepted, err := vm.State.GetLastAccepted() 419 if err == database.ErrNotFound { 420 return vm.ChainVM.LastAccepted(ctx) 421 } 422 return lastAccepted, err 423 } 424 425 func (vm *VM) repairAcceptedChainByHeight(ctx context.Context) error { 426 innerLastAcceptedID, err := vm.ChainVM.LastAccepted(ctx) 427 if err != nil { 428 return err 429 } 430 innerLastAccepted, err := vm.ChainVM.GetBlock(ctx, innerLastAcceptedID) 431 if err != nil { 432 return err 433 } 434 proLastAcceptedID, err := vm.State.GetLastAccepted() 435 if err == database.ErrNotFound { 436 // If the last accepted block isn't indexed yet, then the underlying 437 // chain is the only chain and there is nothing to repair. 438 return nil 439 } 440 if err != nil { 441 return err 442 } 443 proLastAccepted, err := vm.getPostForkBlock(ctx, proLastAcceptedID) 444 if err != nil { 445 return err 446 } 447 448 proLastAcceptedHeight := proLastAccepted.Height() 449 innerLastAcceptedHeight := innerLastAccepted.Height() 450 if proLastAcceptedHeight < innerLastAcceptedHeight { 451 return fmt.Errorf("proposervm height index (%d) should never be lower than the inner height index (%d)", proLastAcceptedHeight, innerLastAcceptedHeight) 452 } 453 if proLastAcceptedHeight == innerLastAcceptedHeight { 454 // There is nothing to repair - as the heights match 455 return nil 456 } 457 458 vm.ctx.Log.Info("repairing accepted chain by height", 459 zap.Uint64("outerHeight", proLastAcceptedHeight), 460 zap.Uint64("innerHeight", innerLastAcceptedHeight), 461 ) 462 463 // The inner vm must be behind the proposer vm, so we must roll the 464 // proposervm back. 465 forkHeight, err := vm.State.GetForkHeight() 466 if err != nil { 467 return err 468 } 469 470 if forkHeight > innerLastAcceptedHeight { 471 // We are rolling back past the fork, so we should just forget about all 472 // of our proposervm indices. 473 if err := vm.State.DeleteLastAccepted(); err != nil { 474 return err 475 } 476 return vm.db.Commit() 477 } 478 479 newProLastAcceptedID, err := vm.State.GetBlockIDAtHeight(innerLastAcceptedHeight) 480 if err != nil { 481 // This fatal error can happen if NumHistoricalBlocks is set too 482 // aggressively and the inner vm rolled back before the oldest 483 // proposervm block. 484 return fmt.Errorf("proposervm failed to rollback last accepted block to height (%d): %w", innerLastAcceptedHeight, err) 485 } 486 487 if err := vm.State.SetLastAccepted(newProLastAcceptedID); err != nil { 488 return err 489 } 490 return vm.db.Commit() 491 } 492 493 func (vm *VM) setLastAcceptedMetadata(ctx context.Context) error { 494 lastAcceptedID, err := vm.GetLastAccepted() 495 if err == database.ErrNotFound { 496 // If the last accepted block wasn't a PostFork block, then we don't 497 // initialize the metadata. 498 vm.lastAcceptedHeight = 0 499 vm.lastAcceptedTime = time.Time{} 500 return nil 501 } 502 if err != nil { 503 return err 504 } 505 506 lastAccepted, err := vm.getPostForkBlock(ctx, lastAcceptedID) 507 if err != nil { 508 return err 509 } 510 511 // Set the last accepted height 512 vm.lastAcceptedHeight = lastAccepted.Height() 513 514 if _, ok := lastAccepted.getStatelessBlk().(statelessblock.SignedBlock); ok { 515 // If the last accepted block wasn't a PostForkOption, then we don't 516 // initialize the time. 517 return nil 518 } 519 520 acceptedParent, err := vm.getPostForkBlock(ctx, lastAccepted.Parent()) 521 if err != nil { 522 return err 523 } 524 vm.lastAcceptedTime = acceptedParent.Timestamp() 525 return nil 526 } 527 528 func (vm *VM) parsePostForkBlock(ctx context.Context, b []byte) (PostForkBlock, error) { 529 statelessBlock, err := statelessblock.Parse(b, vm.ctx.ChainID) 530 if err != nil { 531 return nil, err 532 } 533 534 // if the block already exists, then make sure the status is set correctly 535 blkID := statelessBlock.ID() 536 blk, err := vm.getPostForkBlock(ctx, blkID) 537 if err == nil { 538 return blk, nil 539 } 540 if err != database.ErrNotFound { 541 return nil, err 542 } 543 544 innerBlkBytes := statelessBlock.Block() 545 innerBlk, err := vm.parseInnerBlock(ctx, blkID, innerBlkBytes) 546 if err != nil { 547 return nil, err 548 } 549 550 if statelessSignedBlock, ok := statelessBlock.(statelessblock.SignedBlock); ok { 551 blk = &postForkBlock{ 552 SignedBlock: statelessSignedBlock, 553 postForkCommonComponents: postForkCommonComponents{ 554 vm: vm, 555 innerBlk: innerBlk, 556 status: choices.Processing, 557 }, 558 } 559 } else { 560 blk = &postForkOption{ 561 Block: statelessBlock, 562 postForkCommonComponents: postForkCommonComponents{ 563 vm: vm, 564 innerBlk: innerBlk, 565 status: choices.Processing, 566 }, 567 } 568 } 569 return blk, nil 570 } 571 572 func (vm *VM) parsePreForkBlock(ctx context.Context, b []byte) (*preForkBlock, error) { 573 blk, err := vm.ChainVM.ParseBlock(ctx, b) 574 return &preForkBlock{ 575 Block: blk, 576 vm: vm, 577 }, err 578 } 579 580 func (vm *VM) getBlock(ctx context.Context, id ids.ID) (Block, error) { 581 if blk, err := vm.getPostForkBlock(ctx, id); err == nil { 582 return blk, nil 583 } 584 return vm.getPreForkBlock(ctx, id) 585 } 586 587 func (vm *VM) getPostForkBlock(ctx context.Context, blkID ids.ID) (PostForkBlock, error) { 588 block, exists := vm.verifiedBlocks[blkID] 589 if exists { 590 return block, nil 591 } 592 593 statelessBlock, status, err := vm.State.GetBlock(blkID) 594 if err != nil { 595 return nil, err 596 } 597 598 innerBlkBytes := statelessBlock.Block() 599 innerBlk, err := vm.parseInnerBlock(ctx, blkID, innerBlkBytes) 600 if err != nil { 601 return nil, err 602 } 603 604 if statelessSignedBlock, ok := statelessBlock.(statelessblock.SignedBlock); ok { 605 return &postForkBlock{ 606 SignedBlock: statelessSignedBlock, 607 postForkCommonComponents: postForkCommonComponents{ 608 vm: vm, 609 innerBlk: innerBlk, 610 status: status, 611 }, 612 }, nil 613 } 614 return &postForkOption{ 615 Block: statelessBlock, 616 postForkCommonComponents: postForkCommonComponents{ 617 vm: vm, 618 innerBlk: innerBlk, 619 status: status, 620 }, 621 }, nil 622 } 623 624 func (vm *VM) getPreForkBlock(ctx context.Context, blkID ids.ID) (*preForkBlock, error) { 625 blk, err := vm.ChainVM.GetBlock(ctx, blkID) 626 return &preForkBlock{ 627 Block: blk, 628 vm: vm, 629 }, err 630 } 631 632 func (vm *VM) acceptPostForkBlock(blk PostForkBlock) error { 633 height := blk.Height() 634 blkID := blk.ID() 635 636 vm.lastAcceptedHeight = height 637 delete(vm.verifiedBlocks, blkID) 638 639 // Persist this block, its height index, and its status 640 if err := vm.State.SetLastAccepted(blkID); err != nil { 641 return err 642 } 643 if err := vm.State.PutBlock(blk.getStatelessBlk(), choices.Accepted); err != nil { 644 return err 645 } 646 if err := vm.updateHeightIndex(height, blkID); err != nil { 647 return err 648 } 649 return vm.db.Commit() 650 } 651 652 func (vm *VM) verifyAndRecordInnerBlk(ctx context.Context, blockCtx *block.Context, postFork PostForkBlock) error { 653 innerBlk := postFork.getInnerBlk() 654 postForkID := postFork.ID() 655 originalInnerBlock, previouslyVerified := vm.Tree.Get(innerBlk) 656 if previouslyVerified { 657 innerBlk = originalInnerBlock 658 // We must update all of the mappings from postFork -> innerBlock to 659 // now point to originalInnerBlock. 660 postFork.setInnerBlk(originalInnerBlock) 661 vm.innerBlkCache.Put(postForkID, originalInnerBlock) 662 } 663 664 var ( 665 shouldVerifyWithCtx = blockCtx != nil 666 blkWithCtx block.WithVerifyContext 667 err error 668 ) 669 if shouldVerifyWithCtx { 670 blkWithCtx, shouldVerifyWithCtx = innerBlk.(block.WithVerifyContext) 671 if shouldVerifyWithCtx { 672 shouldVerifyWithCtx, err = blkWithCtx.ShouldVerifyWithContext(ctx) 673 if err != nil { 674 return err 675 } 676 } 677 } 678 679 // Invariant: If either [Verify] or [VerifyWithContext] returns nil, this 680 // function must return nil. This maintains the inner block's 681 // invariant that successful verification will eventually result 682 // in accepted or rejected being called. 683 if shouldVerifyWithCtx { 684 // This block needs to know the P-Chain height during verification. 685 // Note that [VerifyWithContext] with context may be called multiple 686 // times with multiple contexts. 687 err = blkWithCtx.VerifyWithContext(ctx, blockCtx) 688 } else if !previouslyVerified { 689 // This isn't a [block.WithVerifyContext] so we only call [Verify] once. 690 err = innerBlk.Verify(ctx) 691 } 692 if err != nil { 693 return err 694 } 695 696 // Since verification passed, we should ensure the inner block tree is 697 // populated. 698 if !previouslyVerified { 699 vm.Tree.Add(innerBlk) 700 } 701 vm.verifiedBlocks[postForkID] = postFork 702 return nil 703 } 704 705 // notifyInnerBlockReady tells the scheduler that the inner VM is ready to build 706 // a new block 707 func (vm *VM) notifyInnerBlockReady() { 708 select { 709 case vm.toScheduler <- common.PendingTxs: 710 default: 711 vm.ctx.Log.Debug("dropping message to consensus engine") 712 } 713 } 714 715 func (vm *VM) optimalPChainHeight(ctx context.Context, minPChainHeight uint64) (uint64, error) { 716 minimumHeight, err := vm.ctx.ValidatorState.GetMinimumHeight(ctx) 717 if err != nil { 718 return 0, err 719 } 720 721 return max(minimumHeight, minPChainHeight), nil 722 } 723 724 // parseInnerBlock attempts to parse the provided bytes as an inner block. If 725 // the inner block happens to be cached, then the inner block will not be 726 // parsed. 727 func (vm *VM) parseInnerBlock(ctx context.Context, outerBlkID ids.ID, innerBlkBytes []byte) (snowman.Block, error) { 728 if innerBlk, ok := vm.innerBlkCache.Get(outerBlkID); ok { 729 return innerBlk, nil 730 } 731 732 innerBlk, err := vm.ChainVM.ParseBlock(ctx, innerBlkBytes) 733 if err != nil { 734 return nil, err 735 } 736 vm.cacheInnerBlock(outerBlkID, innerBlk) 737 return innerBlk, nil 738 } 739 740 // Caches proposervm block ID --> inner block if the inner block's height 741 // is within [innerBlkCacheSize] of the last accepted block's height. 742 func (vm *VM) cacheInnerBlock(outerBlkID ids.ID, innerBlk snowman.Block) { 743 diff := math.AbsDiff(innerBlk.Height(), vm.lastAcceptedHeight) 744 if diff < innerBlkCacheSize { 745 vm.innerBlkCache.Put(outerBlkID, innerBlk) 746 } 747 }