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 }