github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/consensus/hotstuff/timeoutcollector/timeout_cache.go (about) 1 package timeoutcollector 2 3 import ( 4 "errors" 5 "sync" 6 7 "github.com/onflow/flow-go/consensus/hotstuff/model" 8 "github.com/onflow/flow-go/model/flow" 9 ) 10 11 var ( 12 // ErrRepeatedTimeout is emitted, when we receive an identical timeout object for the same block 13 // from the same voter multiple times. This error does _not_ indicate 14 // equivocation. 15 ErrRepeatedTimeout = errors.New("duplicated timeout") 16 ErrTimeoutForIncompatibleView = errors.New("timeout for incompatible view") 17 ) 18 19 // TimeoutObjectsCache maintains a _concurrency safe_ cache of timeouts for one particular 20 // view. The cache memorizes the order in which the timeouts were received. Timeouts 21 // are de-duplicated based on the following rules: 22 // - For each voter (i.e. SignerID), we store the _first_ timeout t0. 23 // - For any subsequent timeout t, we check whether t.ID() == t0.ID(). 24 // If this is the case, we consider the timeout a duplicate and drop it. 25 // If t and t0 have different checksums, the voter is equivocating, and 26 // we return a model.DoubleTimeoutError. 27 type TimeoutObjectsCache struct { 28 lock sync.RWMutex 29 view uint64 30 timeouts map[flow.Identifier]*model.TimeoutObject // signerID -> first timeout 31 } 32 33 // NewTimeoutObjectsCache instantiates a TimeoutObjectsCache for the given view 34 func NewTimeoutObjectsCache(view uint64) *TimeoutObjectsCache { 35 return &TimeoutObjectsCache{ 36 view: view, 37 timeouts: make(map[flow.Identifier]*model.TimeoutObject), 38 } 39 } 40 41 func (vc *TimeoutObjectsCache) View() uint64 { return vc.view } 42 43 // AddTimeoutObject stores a timeout in the cache. The following errors are expected during 44 // normal operations: 45 // - nil: if the timeout was successfully added 46 // - model.DoubleTimeoutError is returned if the replica is equivocating 47 // - RepeatedTimeoutErr is returned when adding an _identical_ timeout for the same view from 48 // the same voter multiple times. 49 // - TimeoutForIncompatibleViewError is returned if the timeout is for a different view. 50 // 51 // When AddTimeoutObject returns an error, the timeout is _not_ stored. 52 func (vc *TimeoutObjectsCache) AddTimeoutObject(timeout *model.TimeoutObject) error { 53 if timeout.View != vc.view { 54 return ErrTimeoutForIncompatibleView 55 } 56 vc.lock.Lock() 57 58 // De-duplicated timeouts based on the following rules: 59 // * For each voter (i.e. SignerID), we store the _first_ t0. 60 // * For any subsequent timeout t, we check whether t.ID() == t0.ID(). 61 // If this is the case, we consider the timeout a duplicate and drop it. 62 // If t and t0 have different checksums, the voter is equivocating, and 63 // we return a model.DoubleTimeoutError. 64 firstTimeout, exists := vc.timeouts[timeout.SignerID] 65 if exists { 66 vc.lock.Unlock() 67 // TODO: once we have signer indices, implement Equals methods for QC, TC 68 // and TimeoutObjects, to avoid the comparatively very expensive ID computation. 69 if firstTimeout.ID() != timeout.ID() { 70 return model.NewDoubleTimeoutErrorf(firstTimeout, timeout, "detected timeout equivocation by replica %x at view: %d", timeout.SignerID, vc.view) 71 } 72 return ErrRepeatedTimeout 73 } 74 vc.timeouts[timeout.SignerID] = timeout 75 vc.lock.Unlock() 76 77 return nil 78 } 79 80 // GetTimeoutObject returns the stored timeout for the given `signerID`. Returns: 81 // - (timeout, true) if a timeout object from signerID is known 82 // - (nil, false) no timeout object from signerID is known 83 func (vc *TimeoutObjectsCache) GetTimeoutObject(signerID flow.Identifier) (*model.TimeoutObject, bool) { 84 vc.lock.RLock() 85 timeout, exists := vc.timeouts[signerID] // if signerID is unknown, its `Vote` pointer is nil 86 vc.lock.RUnlock() 87 return timeout, exists 88 } 89 90 // Size returns the number of cached timeout objects 91 func (vc *TimeoutObjectsCache) Size() int { 92 vc.lock.RLock() 93 s := len(vc.timeouts) 94 vc.lock.RUnlock() 95 return s 96 } 97 98 // All returns all currently cached timeout objects. Concurrency safe. 99 func (vc *TimeoutObjectsCache) All() []*model.TimeoutObject { 100 vc.lock.RLock() 101 defer vc.lock.RUnlock() 102 return vc.all() 103 } 104 105 // all returns all currently cached timeout objects. NOT concurrency safe 106 func (vc *TimeoutObjectsCache) all() []*model.TimeoutObject { 107 timeoutObjects := make([]*model.TimeoutObject, 0, len(vc.timeouts)) 108 for _, t := range vc.timeouts { 109 timeoutObjects = append(timeoutObjects, t) 110 } 111 return timeoutObjects 112 }