ceremonyclient/consensus/consensus_timeout.go
2025-11-02 01:14:43 -06:00

128 lines
6.2 KiB
Go

package consensus
import (
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
"source.quilibrium.com/quilibrium/monorepo/lifecycle"
)
// TimeoutAggregator verifies and aggregates timeout states to build timeout
// certificates [TCs]. When enough timeout states are collected, it builds a TC
// and sends it to the EventLoop TimeoutAggregator also detects protocol
// violation, including invalid timeouts, double timeout, etc and notifies a
// HotStuff consumer for slashing.
type TimeoutAggregator[VoteT models.Unique] interface {
lifecycle.Component
// AddTimeout verifies and aggregates a timeout state.
// This method can be called concurrently, timeouts will be queued and
// processed asynchronously.
AddTimeout(timeoutState *models.TimeoutState[VoteT])
// PruneUpToRank deletes all `TimeoutCollector`s _below_ to the given rank, as
// well as related indices. We only retain and process `TimeoutCollector`s,
// whose rank is equal or larger than `lowestRetainedRank`. If
// `lowestRetainedRank` is smaller than the previous value, the previous value
// is kept and the method call is a NoOp. This value should be set to the
// latest active rank maintained by `Pacemaker`.
PruneUpToRank(lowestRetainedRank uint64)
}
// TimeoutCollector collects all timeout states for a specified rank. On the
// happy path, it generates a TimeoutCertificate when enough timeouts have been
// collected. The TimeoutCollector is a higher-level structure that orchestrates
// deduplication, caching and processing of timeouts, delegating those tasks to
// underlying modules (such as TimeoutProcessor). Implementations of
// TimeoutCollector must be concurrency safe.
type TimeoutCollector[VoteT models.Unique] interface {
// AddTimeout adds a Timeout State to the collector. When TSs from
// strictly more than 1/3 of consensus participants (measured by weight) were
// collected, the callback for partial TC will be triggered. After collecting
// TSs from a supermajority, a TC will be created and passed to the EventLoop.
// Expected error returns during normal operations:
// * timeoutcollector.ErrTimeoutForIncompatibleRank - submitted timeout for
// incompatible rank
// All other exceptions are symptoms of potential state corruption.
AddTimeout(timeoutState *models.TimeoutState[VoteT]) error
// Rank returns the rank that this instance is collecting timeouts for.
// This method is useful when adding the newly created timeout collector to
// timeout collectors map.
Rank() uint64
}
// TimeoutProcessor ingests Timeout States for a particular rank. It
// implements the algorithms for validating TSs, orchestrates their low-level
// aggregation and emits `OnPartialTimeoutCertificateCreated` and `OnTimeoutCertificateConstructedFromTimeouts`
// notifications. TimeoutProcessor cannot deduplicate TSs (this should be
// handled by the higher-level TimeoutCollector) and errors instead. Depending
// on their implementation, a TimeoutProcessor might drop timeouts or attempt to
// construct a TC.
type TimeoutProcessor[VoteT models.Unique] interface {
// Process performs processing of single timeout state. This function is safe
// to call from multiple goroutines. Expected error returns during normal
// operations:
// * timeoutcollector.ErrTimeoutForIncompatibleRank - submitted timeout for
// incompatible rank
// * models.InvalidTimeoutError - submitted invalid timeout(invalid structure
// or invalid signature)
// * models.DuplicatedSignerError if a timeout from the same signer was
// previously already added. It does _not necessarily_ imply that the
// timeout is invalid or the sender is equivocating.
// All other errors should be treated as exceptions.
Process(timeout *models.TimeoutState[VoteT]) error
}
// TimeoutCollectorFactory performs creation of TimeoutCollector for a given
// rank
type TimeoutCollectorFactory[VoteT models.Unique] interface {
// Create is a factory method to generate a TimeoutCollector for a given rank
// Expected error returns during normal operations:
// * models.ErrRankUnknown no rank containing the given rank is known
// All other errors should be treated as exceptions.
Create(rank uint64) (TimeoutCollector[VoteT], error)
}
// TimeoutProcessorFactory performs creation of TimeoutProcessor for a given
// rank
type TimeoutProcessorFactory[VoteT models.Unique] interface {
// Create is a factory method to generate a TimeoutProcessor for a given rank
// Expected error returns during normal operations:
// * models.ErrRankUnknown no rank containing the given rank is known
// All other errors should be treated as exceptions.
Create(rank uint64) (TimeoutProcessor[VoteT], error)
}
// TimeoutCollectors encapsulates the functionality to generate, store and prune
// `TimeoutCollector` instances (one per rank). Its main purpose is to provide a
// higher-level API to `TimeoutAggregator` for managing and interacting with the
// rank-specific `TimeoutCollector` instances. Implementations are concurrency
// safe.
type TimeoutCollectors[VoteT models.Unique] interface {
// GetOrCreateCollector retrieves the TimeoutCollector for the specified
// rank or creates one if none exists. When creating a timeout collector,
// the rank is used to query the consensus committee for the respective
// Rank the rank belongs to.
// It returns:
// - (collector, true, nil) if no collector can be found by the rank, and a
// new collector was created.
// - (collector, false, nil) if the collector can be found by the rank.
// - (nil, false, error) if running into any exception creating the timeout
// collector.
// Expected error returns during normal operations:
// * models.BelowPrunedThresholdError if rank is below the pruning threshold
// * models.ErrRankUnknown if rank is not yet pruned but no rank containing
// the given rank is known
GetOrCreateCollector(rank uint64) (
collector TimeoutCollector[VoteT],
created bool,
err error,
)
// PruneUpToRank prunes the timeout collectors with ranks _below_ the given
// value, i.e. we only retain and process timeout collectors, whose ranks are
// equal or larger than `lowestRetainedRank`. If `lowestRetainedRank` is
// smaller than the previous value, the previous value is kept and the method
// call is a NoOp.
PruneUpToRank(lowestRetainedRank uint64)
}