github.com/koko1123/flow-go-1@v0.29.6/state/protocol/badger/state.go (about) 1 // (c) 2019 Dapper Labs - ALL RIGHTS RESERVED 2 3 package badger 4 5 import ( 6 "errors" 7 "fmt" 8 9 "github.com/dgraph-io/badger/v3" 10 11 "github.com/koko1123/flow-go-1/model/flow" 12 "github.com/koko1123/flow-go-1/module" 13 statepkg "github.com/koko1123/flow-go-1/state" 14 "github.com/koko1123/flow-go-1/state/protocol" 15 "github.com/koko1123/flow-go-1/state/protocol/invalid" 16 "github.com/koko1123/flow-go-1/storage" 17 "github.com/koko1123/flow-go-1/storage/badger/operation" 18 "github.com/koko1123/flow-go-1/storage/badger/transaction" 19 ) 20 21 type State struct { 22 metrics module.ComplianceMetrics 23 db *badger.DB 24 headers storage.Headers 25 blocks storage.Blocks 26 results storage.ExecutionResults 27 seals storage.Seals 28 epoch struct { 29 setups storage.EpochSetups 30 commits storage.EpochCommits 31 statuses storage.EpochStatuses 32 } 33 } 34 35 type BootstrapConfig struct { 36 // SkipNetworkAddressValidation flags allows skipping all the network address related validations not needed for 37 // an unstaked node 38 SkipNetworkAddressValidation bool 39 } 40 41 func defaultBootstrapConfig() *BootstrapConfig { 42 return &BootstrapConfig{ 43 SkipNetworkAddressValidation: false, 44 } 45 } 46 47 type BootstrapConfigOptions func(conf *BootstrapConfig) 48 49 func SkipNetworkAddressValidation(conf *BootstrapConfig) { 50 conf.SkipNetworkAddressValidation = true 51 } 52 53 func Bootstrap( 54 metrics module.ComplianceMetrics, 55 db *badger.DB, 56 headers storage.Headers, 57 seals storage.Seals, 58 results storage.ExecutionResults, 59 blocks storage.Blocks, 60 setups storage.EpochSetups, 61 commits storage.EpochCommits, 62 statuses storage.EpochStatuses, 63 root protocol.Snapshot, 64 options ...BootstrapConfigOptions, 65 ) (*State, error) { 66 67 config := defaultBootstrapConfig() 68 for _, opt := range options { 69 opt(config) 70 } 71 72 isBootstrapped, err := IsBootstrapped(db) 73 if err != nil { 74 return nil, fmt.Errorf("failed to determine whether database contains bootstrapped state: %w", err) 75 } 76 if isBootstrapped { 77 return nil, fmt.Errorf("expected empty database") 78 } 79 80 state := newState(metrics, db, headers, seals, results, blocks, setups, commits, statuses) 81 82 if err := IsValidRootSnapshot(root, !config.SkipNetworkAddressValidation); err != nil { 83 return nil, fmt.Errorf("cannot bootstrap invalid root snapshot: %w", err) 84 } 85 86 segment, err := root.SealingSegment() 87 if err != nil { 88 return nil, fmt.Errorf("could not get sealing segment: %w", err) 89 } 90 91 err = operation.RetryOnConflictTx(db, transaction.Update, func(tx *transaction.Tx) error { 92 // sealing segment is in ascending height order, so the tail is the 93 // oldest ancestor and head is the newest child in the segment 94 // TAIL <- ... <- HEAD 95 highest := segment.Highest() // reference block of the snapshot 96 lowest := segment.Lowest() // last sealed block 97 98 // 1) bootstrap the sealing segment 99 err = state.bootstrapSealingSegment(segment, highest)(tx) 100 if err != nil { 101 return fmt.Errorf("could not bootstrap sealing chain segment blocks: %w", err) 102 } 103 104 // 2) insert the root quorum certificate into the database 105 qc, err := root.QuorumCertificate() 106 if err != nil { 107 return fmt.Errorf("could not get root qc: %w", err) 108 } 109 err = transaction.WithTx(operation.InsertRootQuorumCertificate(qc))(tx) 110 if err != nil { 111 return fmt.Errorf("could not insert root qc: %w", err) 112 } 113 114 // 3) initialize the current protocol state height/view pointers 115 err = transaction.WithTx(state.bootstrapStatePointers(root))(tx) 116 if err != nil { 117 return fmt.Errorf("could not bootstrap height/view pointers: %w", err) 118 } 119 120 // 4) initialize values related to the epoch logic 121 err = state.bootstrapEpoch(root, !config.SkipNetworkAddressValidation)(tx) 122 if err != nil { 123 return fmt.Errorf("could not bootstrap epoch values: %w", err) 124 } 125 126 // 5) initialize spork params 127 err = transaction.WithTx(state.bootstrapSporkInfo(root))(tx) 128 if err != nil { 129 return fmt.Errorf("could not bootstrap spork info: %w", err) 130 } 131 132 // 6) set metric values 133 err = state.updateEpochMetrics(root) 134 if err != nil { 135 return fmt.Errorf("could not update epoch metrics: %w", err) 136 } 137 state.metrics.BlockSealed(lowest) 138 state.metrics.SealedHeight(lowest.Header.Height) 139 state.metrics.FinalizedHeight(highest.Header.Height) 140 for _, block := range segment.Blocks { 141 state.metrics.BlockFinalized(block) 142 } 143 144 return nil 145 }) 146 if err != nil { 147 return nil, fmt.Errorf("bootstrapping failed: %w", err) 148 } 149 150 return state, nil 151 } 152 153 // bootstrapSealingSegment inserts all blocks and associated metadata for the 154 // protocol state root snapshot to disk. 155 func (state *State) bootstrapSealingSegment(segment *flow.SealingSegment, head *flow.Block) func(tx *transaction.Tx) error { 156 return func(tx *transaction.Tx) error { 157 158 for _, result := range segment.ExecutionResults { 159 err := transaction.WithTx(operation.SkipDuplicates(operation.InsertExecutionResult(result)))(tx) 160 if err != nil { 161 return fmt.Errorf("could not insert execution result: %w", err) 162 } 163 err = transaction.WithTx(operation.IndexExecutionResult(result.BlockID, result.ID()))(tx) 164 if err != nil { 165 return fmt.Errorf("could not index execution result: %w", err) 166 } 167 } 168 169 // insert the first seal (in case the segment's first block contains no seal) 170 if segment.FirstSeal != nil { 171 err := transaction.WithTx(operation.InsertSeal(segment.FirstSeal.ID(), segment.FirstSeal))(tx) 172 if err != nil { 173 return fmt.Errorf("could not insert first seal: %w", err) 174 } 175 } 176 177 for i, block := range segment.Blocks { 178 blockID := block.ID() 179 height := block.Header.Height 180 181 err := state.blocks.StoreTx(block)(tx) 182 if err != nil { 183 return fmt.Errorf("could not insert root block: %w", err) 184 } 185 err = transaction.WithTx(operation.InsertBlockValidity(blockID, true))(tx) 186 if err != nil { 187 return fmt.Errorf("could not mark root block as valid: %w", err) 188 } 189 err = transaction.WithTx(operation.IndexBlockHeight(height, blockID))(tx) 190 if err != nil { 191 return fmt.Errorf("could not index root block segment (id=%x): %w", blockID, err) 192 } 193 194 // index the latest seal as of this block 195 latestSealID, ok := segment.LatestSeals[blockID] 196 if !ok { 197 return fmt.Errorf("missing latest seal for sealing segment block (id=%s)", blockID) 198 } 199 // sanity check: make sure the seal exists 200 var latestSeal flow.Seal 201 err = transaction.WithTx(operation.RetrieveSeal(latestSealID, &latestSeal))(tx) 202 if err != nil { 203 return fmt.Errorf("could not verify latest seal for block (id=%x) exists: %w", blockID, err) 204 } 205 err = transaction.WithTx(operation.IndexLatestSealAtBlock(blockID, latestSealID))(tx) 206 if err != nil { 207 return fmt.Errorf("could not index block seal: %w", err) 208 } 209 210 // for all but the first block in the segment, index the parent->child relationship 211 if i > 0 { 212 err = transaction.WithTx(operation.InsertBlockChildren(block.Header.ParentID, []flow.Identifier{blockID}))(tx) 213 if err != nil { 214 return fmt.Errorf("could not insert child index for block (id=%x): %w", blockID, err) 215 } 216 } 217 } 218 219 // insert an empty child index for the final block in the segment 220 err := transaction.WithTx(operation.InsertBlockChildren(head.ID(), nil))(tx) 221 if err != nil { 222 return fmt.Errorf("could not insert child index for head block (id=%x): %w", head.ID(), err) 223 } 224 225 return nil 226 } 227 } 228 229 // bootstrapStatePointers instantiates special pointers used to by the protocol 230 // state to keep track of special block heights and views. 231 func (state *State) bootstrapStatePointers(root protocol.Snapshot) func(*badger.Txn) error { 232 return func(tx *badger.Txn) error { 233 segment, err := root.SealingSegment() 234 if err != nil { 235 return fmt.Errorf("could not get sealing segment: %w", err) 236 } 237 highest := segment.Highest() 238 lowest := segment.Lowest() 239 // find the finalized seal that seals the lowest block, meaning seal.BlockID == lowest.ID() 240 seal, err := segment.FinalizedSeal() 241 if err != nil { 242 return fmt.Errorf("could not get finalized seal from sealing segment: %w", err) 243 } 244 245 // insert initial views for HotStuff 246 err = operation.InsertStartedView(highest.Header.ChainID, highest.Header.View)(tx) 247 if err != nil { 248 return fmt.Errorf("could not insert started view: %w", err) 249 } 250 err = operation.InsertVotedView(highest.Header.ChainID, highest.Header.View)(tx) 251 if err != nil { 252 return fmt.Errorf("could not insert started view: %w", err) 253 } 254 255 // insert height pointers 256 err = operation.InsertRootHeight(highest.Header.Height)(tx) 257 if err != nil { 258 return fmt.Errorf("could not insert root height: %w", err) 259 } 260 err = operation.InsertFinalizedHeight(highest.Header.Height)(tx) 261 if err != nil { 262 return fmt.Errorf("could not insert finalized height: %w", err) 263 } 264 err = operation.InsertSealedHeight(lowest.Header.Height)(tx) 265 if err != nil { 266 return fmt.Errorf("could not insert sealed height: %w", err) 267 } 268 err = operation.IndexFinalizedSealByBlockID(seal.BlockID, seal.ID())(tx) 269 if err != nil { 270 return fmt.Errorf("could not index sealed block: %w", err) 271 } 272 273 return nil 274 } 275 } 276 277 // bootstrapEpoch bootstraps the protocol state database with information about 278 // the previous, current, and next epochs as of the root snapshot. 279 // 280 // The root snapshot's sealing segment must not straddle any epoch transitions 281 // or epoch phase transitions. 282 func (state *State) bootstrapEpoch(root protocol.Snapshot, verifyNetworkAddress bool) func(*transaction.Tx) error { 283 return func(tx *transaction.Tx) error { 284 previous := root.Epochs().Previous() 285 current := root.Epochs().Current() 286 next := root.Epochs().Next() 287 288 // build the status as we go 289 status := new(flow.EpochStatus) 290 var setups []*flow.EpochSetup 291 var commits []*flow.EpochCommit 292 293 // insert previous epoch if it exists 294 _, err := previous.Counter() 295 if err == nil { 296 // if there is a previous epoch, both setup and commit events must exist 297 setup, err := protocol.ToEpochSetup(previous) 298 if err != nil { 299 return fmt.Errorf("could not get previous epoch setup event: %w", err) 300 } 301 commit, err := protocol.ToEpochCommit(previous) 302 if err != nil { 303 return fmt.Errorf("could not get previous epoch commit event: %w", err) 304 } 305 306 if err := verifyEpochSetup(setup, verifyNetworkAddress); err != nil { 307 return fmt.Errorf("invalid setup: %w", err) 308 } 309 310 if err := isValidEpochCommit(commit, setup); err != nil { 311 return fmt.Errorf("invalid commit") 312 } 313 314 setups = append(setups, setup) 315 commits = append(commits, commit) 316 status.PreviousEpoch.SetupID = setup.ID() 317 status.PreviousEpoch.CommitID = commit.ID() 318 } else if !errors.Is(err, protocol.ErrNoPreviousEpoch) { 319 return fmt.Errorf("could not retrieve previous epoch: %w", err) 320 } 321 322 // insert current epoch - both setup and commit events must exist 323 setup, err := protocol.ToEpochSetup(current) 324 if err != nil { 325 return fmt.Errorf("could not get current epoch setup event: %w", err) 326 } 327 commit, err := protocol.ToEpochCommit(current) 328 if err != nil { 329 return fmt.Errorf("could not get current epoch commit event: %w", err) 330 } 331 332 if err := verifyEpochSetup(setup, verifyNetworkAddress); err != nil { 333 return fmt.Errorf("invalid setup: %w", err) 334 } 335 336 if err := isValidEpochCommit(commit, setup); err != nil { 337 return fmt.Errorf("invalid commit") 338 } 339 340 setups = append(setups, setup) 341 commits = append(commits, commit) 342 status.CurrentEpoch.SetupID = setup.ID() 343 status.CurrentEpoch.CommitID = commit.ID() 344 345 // insert next epoch, if it exists 346 _, err = next.Counter() 347 if err == nil { 348 // either only the setup event, or both the setup and commit events must exist 349 setup, err := protocol.ToEpochSetup(next) 350 if err != nil { 351 return fmt.Errorf("could not get next epoch setup event: %w", err) 352 } 353 354 if err := verifyEpochSetup(setup, verifyNetworkAddress); err != nil { 355 return fmt.Errorf("invalid setup: %w", err) 356 } 357 358 setups = append(setups, setup) 359 status.NextEpoch.SetupID = setup.ID() 360 commit, err := protocol.ToEpochCommit(next) 361 if err != nil && !errors.Is(err, protocol.ErrEpochNotCommitted) { 362 return fmt.Errorf("could not get next epoch commit event: %w", err) 363 } 364 if err == nil { 365 if err := isValidEpochCommit(commit, setup); err != nil { 366 return fmt.Errorf("invalid commit") 367 } 368 commits = append(commits, commit) 369 status.NextEpoch.CommitID = commit.ID() 370 } 371 } else if !errors.Is(err, protocol.ErrNextEpochNotSetup) { 372 return fmt.Errorf("could not get next epoch: %w", err) 373 } 374 375 // sanity check: ensure epoch status is valid 376 err = status.Check() 377 if err != nil { 378 return fmt.Errorf("bootstrapping resulting in invalid epoch status: %w", err) 379 } 380 381 // insert all epoch setup/commit service events 382 for _, setup := range setups { 383 err = state.epoch.setups.StoreTx(setup)(tx) 384 if err != nil { 385 return fmt.Errorf("could not store epoch setup event: %w", err) 386 } 387 } 388 for _, commit := range commits { 389 err = state.epoch.commits.StoreTx(commit)(tx) 390 if err != nil { 391 return fmt.Errorf("could not store epoch commit event: %w", err) 392 } 393 } 394 395 // NOTE: as specified in the godoc, this code assumes that each block 396 // in the sealing segment in within the same phase within the same epoch. 397 segment, err := root.SealingSegment() 398 if err != nil { 399 return fmt.Errorf("could not get sealing segment: %w", err) 400 } 401 for _, block := range segment.Blocks { 402 blockID := block.ID() 403 err = state.epoch.statuses.StoreTx(blockID, status)(tx) 404 if err != nil { 405 return fmt.Errorf("could not store epoch status for block (id=%x): %w", blockID, err) 406 } 407 } 408 409 return nil 410 } 411 } 412 413 // bootstrapSporkInfo bootstraps the protocol state with information about the 414 // spork which is used to disambiguate Flow networks. 415 func (state *State) bootstrapSporkInfo(root protocol.Snapshot) func(*badger.Txn) error { 416 return func(tx *badger.Txn) error { 417 params := root.Params() 418 419 sporkID, err := params.SporkID() 420 if err != nil { 421 return fmt.Errorf("could not get spork ID: %w", err) 422 } 423 err = operation.InsertSporkID(sporkID)(tx) 424 if err != nil { 425 return fmt.Errorf("could not insert spork ID: %w", err) 426 } 427 428 version, err := params.ProtocolVersion() 429 if err != nil { 430 return fmt.Errorf("could not get protocol version: %w", err) 431 } 432 err = operation.InsertProtocolVersion(version)(tx) 433 if err != nil { 434 return fmt.Errorf("could not insert protocol version: %w", err) 435 } 436 437 return nil 438 } 439 } 440 441 func OpenState( 442 metrics module.ComplianceMetrics, 443 db *badger.DB, 444 headers storage.Headers, 445 seals storage.Seals, 446 results storage.ExecutionResults, 447 blocks storage.Blocks, 448 setups storage.EpochSetups, 449 commits storage.EpochCommits, 450 statuses storage.EpochStatuses, 451 ) (*State, error) { 452 isBootstrapped, err := IsBootstrapped(db) 453 if err != nil { 454 return nil, fmt.Errorf("failed to determine whether database contains bootstrapped state: %w", err) 455 } 456 if !isBootstrapped { 457 return nil, fmt.Errorf("expected database to contain bootstrapped state") 458 } 459 state := newState(metrics, db, headers, seals, results, blocks, setups, commits, statuses) 460 461 // report last finalized and sealed block height 462 finalSnapshot := state.Final() 463 head, err := finalSnapshot.Head() 464 if err != nil { 465 return nil, fmt.Errorf("unexpected error to get finalized block: %w", err) 466 } 467 metrics.FinalizedHeight(head.Height) 468 469 sealed, err := state.Sealed().Head() 470 if err != nil { 471 return nil, fmt.Errorf("could not get latest sealed block: %w", err) 472 } 473 metrics.SealedHeight(sealed.Height) 474 475 // update all epoch related metrics 476 err = state.updateEpochMetrics(finalSnapshot) 477 if err != nil { 478 return nil, fmt.Errorf("failed to update epoch metrics: %w", err) 479 } 480 481 return state, nil 482 } 483 484 func (state *State) Params() protocol.Params { 485 return &Params{state: state} 486 } 487 488 func (state *State) Sealed() protocol.Snapshot { 489 // retrieve the latest sealed height 490 var sealed uint64 491 err := state.db.View(operation.RetrieveSealedHeight(&sealed)) 492 if err != nil { 493 // sealed height must always be set, all errors here are critical 494 return invalid.NewSnapshotf("could not retrieve sealed height: %w", err) 495 } 496 return state.AtHeight(sealed) 497 } 498 499 func (state *State) Final() protocol.Snapshot { 500 // retrieve the latest finalized height 501 var finalized uint64 502 err := state.db.View(operation.RetrieveFinalizedHeight(&finalized)) 503 if err != nil { 504 // finalized height must always be set, so all errors here are critical 505 return invalid.NewSnapshot(fmt.Errorf("could not retrieve finalized height: %w", err)) 506 } 507 return state.AtHeight(finalized) 508 } 509 510 func (state *State) AtHeight(height uint64) protocol.Snapshot { 511 // retrieve the block ID for the finalized height 512 var blockID flow.Identifier 513 err := state.db.View(operation.LookupBlockHeight(height, &blockID)) 514 if errors.Is(err, storage.ErrNotFound) { 515 return invalid.NewSnapshotf("unknown finalized height %d: %w", height, statepkg.ErrUnknownSnapshotReference) 516 } 517 if err != nil { 518 // critical storage error 519 return invalid.NewSnapshotf("could not look up block by height: %w", err) 520 } 521 return state.AtBlockID(blockID) 522 } 523 524 func (state *State) AtBlockID(blockID flow.Identifier) protocol.Snapshot { 525 // TODO should return invalid.NewSnapshot(ErrUnknownSnapshotReference) if block doesn't exist 526 return NewSnapshot(state, blockID) 527 } 528 529 // newState initializes a new state backed by the provided a badger database, 530 // mempools and service components. 531 // The parameter `expectedBootstrappedState` indicates whether or not the database 532 // is expected to contain a an already bootstrapped state or not 533 func newState( 534 metrics module.ComplianceMetrics, 535 db *badger.DB, 536 headers storage.Headers, 537 seals storage.Seals, 538 results storage.ExecutionResults, 539 blocks storage.Blocks, 540 setups storage.EpochSetups, 541 commits storage.EpochCommits, 542 statuses storage.EpochStatuses, 543 ) *State { 544 return &State{ 545 metrics: metrics, 546 db: db, 547 headers: headers, 548 results: results, 549 seals: seals, 550 blocks: blocks, 551 epoch: struct { 552 setups storage.EpochSetups 553 commits storage.EpochCommits 554 statuses storage.EpochStatuses 555 }{ 556 setups: setups, 557 commits: commits, 558 statuses: statuses, 559 }, 560 } 561 } 562 563 // IsBootstrapped returns whether or not the database contains a bootstrapped state 564 func IsBootstrapped(db *badger.DB) (bool, error) { 565 var finalized uint64 566 err := db.View(operation.RetrieveFinalizedHeight(&finalized)) 567 if errors.Is(err, storage.ErrNotFound) { 568 return false, nil 569 } 570 if err != nil { 571 return false, fmt.Errorf("retrieving finalized height failed: %w", err) 572 } 573 return true, nil 574 } 575 576 // updateEpochMetrics update the `consensus_compliance_current_epoch_counter` and the 577 // `consensus_compliance_current_epoch_phase` metric 578 func (state *State) updateEpochMetrics(snap protocol.Snapshot) error { 579 580 // update epoch counter 581 counter, err := snap.Epochs().Current().Counter() 582 if err != nil { 583 return fmt.Errorf("could not get current epoch counter: %w", err) 584 } 585 state.metrics.CurrentEpochCounter(counter) 586 587 // update epoch phase 588 phase, err := snap.Phase() 589 if err != nil { 590 return fmt.Errorf("could not get current epoch counter: %w", err) 591 } 592 state.metrics.CurrentEpochPhase(phase) 593 594 // update committed epoch final view 595 err = state.updateCommittedEpochFinalView(snap) 596 if err != nil { 597 return fmt.Errorf("could not update committed epoch final view") 598 } 599 600 currentEpochFinalView, err := snap.Epochs().Current().FinalView() 601 if err != nil { 602 return fmt.Errorf("could not update current epoch final view: %w", err) 603 } 604 state.metrics.CurrentEpochFinalView(currentEpochFinalView) 605 606 dkgPhase1FinalView, dkgPhase2FinalView, dkgPhase3FinalView, err := protocol.DKGPhaseViews(snap.Epochs().Current()) 607 if err != nil { 608 return fmt.Errorf("could not get dkg phase final view: %w", err) 609 } 610 611 state.metrics.CurrentDKGPhase1FinalView(dkgPhase1FinalView) 612 state.metrics.CurrentDKGPhase2FinalView(dkgPhase2FinalView) 613 state.metrics.CurrentDKGPhase3FinalView(dkgPhase3FinalView) 614 615 // EECC - check whether the epoch emergency fallback flag has been set 616 // in the database. If so, skip updating any epoch-related metrics. 617 epochFallbackTriggered, err := state.isEpochEmergencyFallbackTriggered() 618 if err != nil { 619 return fmt.Errorf("could not check epoch emergency fallback flag: %w", err) 620 } 621 if epochFallbackTriggered { 622 state.metrics.EpochEmergencyFallbackTriggered() 623 } 624 625 return nil 626 } 627 628 // updateCommittedEpochFinalView updates the `committed_epoch_final_view` metric 629 // based on the current epoch phase of the input snapshot. It should be called 630 // at startup and during transitions between EpochSetup and EpochCommitted phases. 631 // 632 // For example, suppose we have epochs N and N+1. 633 // If we are in epoch N's Staking or Setup Phase, then epoch N's final view should be the value of the metric. 634 // If we are in epoch N's Committed Phase, then epoch N+1's final view should be the value of the metric. 635 func (state *State) updateCommittedEpochFinalView(snap protocol.Snapshot) error { 636 637 phase, err := snap.Phase() 638 if err != nil { 639 return fmt.Errorf("could not get epoch phase: %w", err) 640 } 641 642 // update metric based of epoch phase 643 switch phase { 644 case flow.EpochPhaseStaking, flow.EpochPhaseSetup: 645 646 // if we are in Staking or Setup phase, then set the metric value to the current epoch's final view 647 finalView, err := snap.Epochs().Current().FinalView() 648 if err != nil { 649 return fmt.Errorf("could not get current epoch final view from snapshot: %w", err) 650 } 651 state.metrics.CommittedEpochFinalView(finalView) 652 case flow.EpochPhaseCommitted: 653 654 // if we are in Committed phase, then set the metric value to the next epoch's final view 655 finalView, err := snap.Epochs().Next().FinalView() 656 if err != nil { 657 return fmt.Errorf("could not get next epoch final view from snapshot: %w", err) 658 } 659 state.metrics.CommittedEpochFinalView(finalView) 660 default: 661 return fmt.Errorf("invalid phase: %s", phase) 662 } 663 664 return nil 665 } 666 667 func (m *State) isEpochEmergencyFallbackTriggered() (bool, error) { 668 var triggered bool 669 err := m.db.View(operation.CheckEpochEmergencyFallbackTriggered(&triggered)) 670 return triggered, err 671 }