github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/consensus/hotstuff/signature/randombeacon_signer_store.go (about)

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