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 }