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  }