github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/state/cluster/badger/state.go (about) 1 package badger 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/dgraph-io/badger/v2" 8 9 "github.com/onflow/flow-go/consensus/hotstuff" 10 "github.com/onflow/flow-go/model/flow" 11 "github.com/onflow/flow-go/module" 12 "github.com/onflow/flow-go/state/cluster" 13 "github.com/onflow/flow-go/storage" 14 "github.com/onflow/flow-go/storage/badger/operation" 15 "github.com/onflow/flow-go/storage/badger/procedure" 16 ) 17 18 type State struct { 19 db *badger.DB 20 clusterID flow.ChainID // the chain ID for the cluster 21 epoch uint64 // the operating epoch for the cluster 22 } 23 24 // Bootstrap initializes the persistent cluster state with a genesis block. 25 // The genesis block must have height 0, a parent hash of 32 zero bytes, 26 // and an empty collection as payload. 27 func Bootstrap(db *badger.DB, stateRoot *StateRoot) (*State, error) { 28 isBootstrapped, err := IsBootstrapped(db, stateRoot.ClusterID()) 29 if err != nil { 30 return nil, fmt.Errorf("failed to determine whether database contains bootstrapped state: %w", err) 31 } 32 if isBootstrapped { 33 return nil, fmt.Errorf("expected empty cluster state for cluster ID %s", stateRoot.ClusterID()) 34 } 35 state := newState(db, stateRoot.ClusterID(), stateRoot.EpochCounter()) 36 37 genesis := stateRoot.Block() 38 rootQC := stateRoot.QC() 39 // bootstrap cluster state 40 err = operation.RetryOnConflict(state.db.Update, func(tx *badger.Txn) error { 41 chainID := genesis.Header.ChainID 42 // insert the block 43 err := procedure.InsertClusterBlock(genesis)(tx) 44 if err != nil { 45 return fmt.Errorf("could not insert genesis block: %w", err) 46 } 47 // insert block height -> ID mapping 48 err = operation.IndexClusterBlockHeight(chainID, genesis.Header.Height, genesis.ID())(tx) 49 if err != nil { 50 return fmt.Errorf("failed to map genesis block height to block: %w", err) 51 } 52 // insert boundary 53 err = operation.InsertClusterFinalizedHeight(chainID, genesis.Header.Height)(tx) 54 // insert started view for hotstuff 55 if err != nil { 56 return fmt.Errorf("could not insert genesis boundary: %w", err) 57 } 58 59 safetyData := &hotstuff.SafetyData{ 60 LockedOneChainView: genesis.Header.View, 61 HighestAcknowledgedView: genesis.Header.View, 62 } 63 64 livenessData := &hotstuff.LivenessData{ 65 CurrentView: genesis.Header.View + 1, 66 NewestQC: rootQC, 67 } 68 // insert safety data 69 err = operation.InsertSafetyData(chainID, safetyData)(tx) 70 if err != nil { 71 return fmt.Errorf("could not insert safety data: %w", err) 72 } 73 // insert liveness data 74 err = operation.InsertLivenessData(chainID, livenessData)(tx) 75 if err != nil { 76 return fmt.Errorf("could not insert liveness data: %w", err) 77 } 78 79 return nil 80 }) 81 if err != nil { 82 return nil, fmt.Errorf("bootstrapping failed: %w", err) 83 } 84 85 return state, nil 86 } 87 88 func OpenState(db *badger.DB, _ module.Tracer, _ storage.Headers, _ storage.ClusterPayloads, clusterID flow.ChainID, epoch uint64) (*State, error) { 89 isBootstrapped, err := IsBootstrapped(db, clusterID) 90 if err != nil { 91 return nil, fmt.Errorf("failed to determine whether database contains bootstrapped state: %w", err) 92 } 93 if !isBootstrapped { 94 return nil, fmt.Errorf("expected database to contain bootstrapped state") 95 } 96 state := newState(db, clusterID, epoch) 97 return state, nil 98 } 99 100 func newState(db *badger.DB, clusterID flow.ChainID, epoch uint64) *State { 101 state := &State{ 102 db: db, 103 clusterID: clusterID, 104 epoch: epoch, 105 } 106 return state 107 } 108 109 func (s *State) Params() cluster.Params { 110 params := &Params{ 111 state: s, 112 } 113 return params 114 } 115 116 func (s *State) Final() cluster.Snapshot { 117 // get the finalized block ID 118 var blockID flow.Identifier 119 err := s.db.View(func(tx *badger.Txn) error { 120 var boundary uint64 121 err := operation.RetrieveClusterFinalizedHeight(s.clusterID, &boundary)(tx) 122 if err != nil { 123 return fmt.Errorf("could not retrieve finalized boundary: %w", err) 124 } 125 126 err = operation.LookupClusterBlockHeight(s.clusterID, boundary, &blockID)(tx) 127 if err != nil { 128 return fmt.Errorf("could not retrieve finalized ID: %w", err) 129 } 130 131 return nil 132 }) 133 if err != nil { 134 return &Snapshot{ 135 err: err, 136 } 137 } 138 139 snapshot := &Snapshot{ 140 state: s, 141 blockID: blockID, 142 } 143 return snapshot 144 } 145 146 func (s *State) AtBlockID(blockID flow.Identifier) cluster.Snapshot { 147 snapshot := &Snapshot{ 148 state: s, 149 blockID: blockID, 150 } 151 return snapshot 152 } 153 154 // IsBootstrapped returns whether the database contains a bootstrapped state. 155 func IsBootstrapped(db *badger.DB, clusterID flow.ChainID) (bool, error) { 156 var finalized uint64 157 err := db.View(operation.RetrieveClusterFinalizedHeight(clusterID, &finalized)) 158 if errors.Is(err, storage.ErrNotFound) { 159 return false, nil 160 } 161 if err != nil { 162 return false, fmt.Errorf("retrieving finalized height failed: %w", err) 163 } 164 return true, nil 165 }