mirror of
https://github.com/QuilibriumNetwork/ceremonyclient.git
synced 2026-02-21 10:27:26 +08:00
* wip: conversion of hotstuff from flow into Q-oriented model * bulk of tests * remaining non-integration tests * add integration test, adjust log interface, small tweaks * further adjustments, restore full pacemaker shape * add component lifecycle management+supervisor * further refinements * resolve timeout hanging * mostly finalized state for consensus * bulk of engine swap out * lifecycle-ify most types * wiring nearly complete, missing needed hooks for proposals * plugged in, vetting message validation paths * global consensus, plugged in and verified * app shard now wired in too * do not decode empty keys.yml (#456) * remove obsolete engine.maxFrames config parameter (#454) * default to Info log level unless debug is enabled (#453) * respect config's "logging" section params, remove obsolete single-file logging (#452) * Trivial code cleanup aiming to reduce Go compiler warnings (#451) * simplify range traversal * simplify channel read for single select case * delete rand.Seed() deprecated in Go 1.20 and no-op as of Go 1.24 * simplify range traversal * simplify channel read for single select case * remove redundant type from array * simplify range traversal * simplify channel read for single select case * RC slate * finalize 2.1.0.5 * Update comments in StrictMonotonicCounter Fix comment formatting and clarify description. --------- Co-authored-by: Black Swan <3999712+blacks1ne@users.noreply.github.com>
123 lines
4.2 KiB
Go
123 lines
4.2 KiB
Go
package timeoutcollector
|
|
|
|
import (
|
|
"errors"
|
|
"sync"
|
|
|
|
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
|
|
)
|
|
|
|
var (
|
|
// ErrRepeatedTimeout is emitted, when we receive an identical timeout state
|
|
// for the same state from the same voter multiple times. This error does
|
|
// _not_ indicate equivocation.
|
|
ErrRepeatedTimeout = errors.New("duplicated timeout")
|
|
ErrTimeoutForIncompatibleRank = errors.New("timeout for incompatible rank")
|
|
)
|
|
|
|
// TimeoutStatesCache maintains a _concurrency safe_ cache of timeouts for one
|
|
// particular rank. The cache memorizes the order in which the timeouts were
|
|
// received. Timeouts are de-duplicated based on the following rules:
|
|
// - For each voter (i.e. SignerID), we store the _first_ timeout t0.
|
|
// - For any subsequent timeout t, we check whether t equals t0.
|
|
// If this is the case, we consider the timeout a duplicate and drop it.
|
|
// If t and t0 have different contents, the voter is equivocating, and
|
|
// we return a models.DoubleTimeoutError.
|
|
type TimeoutStatesCache[VoteT models.Unique] struct {
|
|
lock sync.RWMutex
|
|
rank uint64
|
|
timeouts map[models.Identity]*models.TimeoutState[VoteT] // signerID -> first timeout
|
|
}
|
|
|
|
// NewTimeoutStatesCache instantiates a TimeoutStatesCache for the given rank
|
|
func NewTimeoutStatesCache[VoteT models.Unique](
|
|
rank uint64,
|
|
) *TimeoutStatesCache[VoteT] {
|
|
return &TimeoutStatesCache[VoteT]{
|
|
rank: rank,
|
|
timeouts: make(map[models.Identity]*models.TimeoutState[VoteT]),
|
|
}
|
|
}
|
|
|
|
func (vc *TimeoutStatesCache[VoteT]) Rank() uint64 { return vc.rank }
|
|
|
|
// AddTimeoutState stores a timeout in the cache. The following errors are
|
|
// expected during normal operations:
|
|
// - nil: if the timeout was successfully added
|
|
// - models.DoubleTimeoutError is returned if the replica is equivocating
|
|
// - RepeatedTimeoutErr is returned when adding an _identical_ timeout for the
|
|
// same rank from the same voter multiple times.
|
|
// - TimeoutForIncompatibleRankError is returned if the timeout is for a
|
|
// different rank.
|
|
//
|
|
// When AddTimeoutState returns an error, the timeout is _not_ stored.
|
|
func (vc *TimeoutStatesCache[VoteT]) AddTimeoutState(
|
|
timeout *models.TimeoutState[VoteT],
|
|
) error {
|
|
if timeout.Rank != vc.rank {
|
|
return ErrTimeoutForIncompatibleRank
|
|
}
|
|
vc.lock.Lock()
|
|
|
|
// De-duplicated timeouts based on the following rules:
|
|
// * For each voter (i.e. SignerID), we store the _first_ t0.
|
|
// * For any subsequent timeout t, we check whether t equals t0.
|
|
// If this is the case, we consider the timeout a duplicate and drop it.
|
|
// If t and t0 have different contents, the voter is equivocating, and
|
|
// we return a models.DoubleTimeoutError.
|
|
firstTimeout, exists := vc.timeouts[(*timeout.Vote).Identity()]
|
|
if exists {
|
|
vc.lock.Unlock()
|
|
if !firstTimeout.Equals(timeout) {
|
|
return models.NewDoubleTimeoutErrorf(
|
|
firstTimeout,
|
|
timeout,
|
|
"detected timeout equivocation by replica %x at rank: %d",
|
|
(*timeout.Vote).Identity(),
|
|
vc.rank,
|
|
)
|
|
}
|
|
return ErrRepeatedTimeout
|
|
}
|
|
vc.timeouts[(*timeout.Vote).Identity()] = timeout
|
|
vc.lock.Unlock()
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetTimeoutState returns the stored timeout for the given `signerID`. Returns:
|
|
// - (timeout, true) if a timeout state from signerID is known
|
|
// - (nil, false) no timeout state from signerID is known
|
|
func (vc *TimeoutStatesCache[VoteT]) GetTimeoutState(
|
|
signerID models.Identity,
|
|
) (*models.TimeoutState[VoteT], bool) {
|
|
vc.lock.RLock()
|
|
timeout, exists := vc.timeouts[signerID] // if signerID is unknown, its `Vote` pointer is nil
|
|
vc.lock.RUnlock()
|
|
return timeout, exists
|
|
}
|
|
|
|
// Size returns the number of cached timeout states
|
|
func (vc *TimeoutStatesCache[VoteT]) Size() int {
|
|
vc.lock.RLock()
|
|
s := len(vc.timeouts)
|
|
vc.lock.RUnlock()
|
|
return s
|
|
}
|
|
|
|
// All returns all currently cached timeout states. Concurrency safe.
|
|
func (vc *TimeoutStatesCache[VoteT]) All() []*models.TimeoutState[VoteT] {
|
|
vc.lock.RLock()
|
|
defer vc.lock.RUnlock()
|
|
return vc.all()
|
|
}
|
|
|
|
// all returns all currently cached timeout states. NOT concurrency safe
|
|
func (vc *TimeoutStatesCache[VoteT]) all() []*models.TimeoutState[VoteT] {
|
|
timeoutStates := make([]*models.TimeoutState[VoteT], 0, len(vc.timeouts))
|
|
for _, t := range vc.timeouts {
|
|
timeoutStates = append(timeoutStates, t)
|
|
}
|
|
return timeoutStates
|
|
}
|