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  }