github.com/onflow/flow-go@v0.33.17/consensus/hotstuff/signature/randombeacon_signer_store.go (about)

     1  package signature
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/onflow/flow-go/crypto"
     8  	"github.com/onflow/flow-go/module"
     9  	"github.com/onflow/flow-go/storage"
    10  )
    11  
    12  // EpochAwareRandomBeaconKeyStore provides an abstraction to query random beacon private key
    13  // for a given view.
    14  // Internally it indexes and caches the private keys by epoch.
    15  type EpochAwareRandomBeaconKeyStore struct {
    16  	epochLookup module.EpochLookup     // used to fetch epoch counter by view
    17  	keys        storage.SafeBeaconKeys // used to fetch DKG private key by epoch
    18  
    19  	privateKeys map[uint64]crypto.PrivateKey // cache of privateKeys by epoch
    20  }
    21  
    22  func NewEpochAwareRandomBeaconKeyStore(epochLookup module.EpochLookup, keys storage.SafeBeaconKeys) *EpochAwareRandomBeaconKeyStore {
    23  	return &EpochAwareRandomBeaconKeyStore{
    24  		epochLookup: epochLookup,
    25  		keys:        keys,
    26  		privateKeys: make(map[uint64]crypto.PrivateKey),
    27  	}
    28  }
    29  
    30  // ByView returns the random beacon signer for signing objects at a given view.
    31  // The view determines the epoch, which determines the DKG private key underlying the signer.
    32  // It returns:
    33  //   - (signer, nil) if DKG succeeded locally in the epoch of the view, signer is not nil
    34  //   - (nil, model.ErrViewForUnknownEpoch) if no epoch found for given view
    35  //   - (nil, module.ErrNoBeaconKeyForEpoch) if beacon key for epoch is unavailable
    36  //   - (nil, error) if there is any exception
    37  func (s *EpochAwareRandomBeaconKeyStore) ByView(view uint64) (crypto.PrivateKey, error) {
    38  	epoch, err := s.epochLookup.EpochForViewWithFallback(view)
    39  	if err != nil {
    40  		return nil, fmt.Errorf("could not get epoch by view %v: %w", view, err)
    41  	}
    42  
    43  	// When DKG has completed,
    44  	//   - if a node successfully generated the DKG key, the valid private key will be stored in database.
    45  	//   - if a node failed to generate the DKG key, we will save a record in database to indicate this
    46  	//      node has no private key for this epoch.
    47  	// Within the epoch, we can look up my random beacon private key for the epoch. There are 3 cases:
    48  	//   1. DKG has completed, and the private key is stored in database, and we can retrieve it (happy path)
    49  	//   2. DKG has completed, but we failed to generate a private key (unhappy path)
    50  	//   3. DKG has not completed locally yet
    51  	key, found := s.privateKeys[epoch]
    52  	if found {
    53  		// a nil key means that we don't (and will never) have a beacon key for this epoch
    54  		if key == nil {
    55  			// case 2:
    56  			return nil, fmt.Errorf("beacon key for epoch %d (queried view: %d) never available: %w", epoch, view, module.ErrNoBeaconKeyForEpoch)
    57  		}
    58  		// case 1:
    59  		return key, nil
    60  	}
    61  
    62  	privKey, safe, err := s.keys.RetrieveMyBeaconPrivateKey(epoch)
    63  	if err != nil {
    64  		if errors.Is(err, storage.ErrNotFound) {
    65  			// case 3:
    66  			return nil, fmt.Errorf("beacon key for epoch %d (queried view: %d) not available yet: %w", epoch, view, module.ErrNoBeaconKeyForEpoch)
    67  		}
    68  		return nil, fmt.Errorf("[unexpected] could not retrieve beacon key for epoch %d (queried view: %d): %w", epoch, view, err)
    69  	}
    70  
    71  	// If DKG ended without a safe end state, there will never be a valid beacon key for this epoch.
    72  	// Since this fact never changes, we cache a nil signer for this epoch, so that when this function
    73  	// is called again for the same epoch, we don't need to query the database.
    74  	if !safe {
    75  		// case 2:
    76  		s.privateKeys[epoch] = nil
    77  		return nil, fmt.Errorf("DKG for epoch %d ended without safe beacon key (queried view: %d): %w", epoch, view, module.ErrNoBeaconKeyForEpoch)
    78  	}
    79  
    80  	// case 1: DKG succeeded and a beacon key is available -> cache the key for future queries
    81  	s.privateKeys[epoch] = privKey
    82  
    83  	return privKey, nil
    84  }