Documentation ¶
Overview ¶
Package cruisectl implements a "cruise control" system for Flow by adjusting nodes' latest ProposalTiming in response to changes in the measured view rate and target epoch switchover time.
It uses a PID controller with the projected epoch switchover time as the process variable and the set-point computed using epoch length config. The error is the difference between the projected epoch switchover time, assuming an ideal view time τ, and the target epoch switchover time (based on a schedule).
Index ¶
- type BlockTimeController
- func (ctl *BlockTimeController) EpochEmergencyFallbackTriggered()
- func (ctl *BlockTimeController) EpochSetupPhaseStarted(_ uint64, first *flow.Header)
- func (ctl *BlockTimeController) GetProposalTiming() ProposalTiming
- func (ctl *BlockTimeController) OnBlockIncorporated(block *model.Block)
- func (ctl *BlockTimeController) TargetPublicationTime(proposalView uint64, timeViewEntered time.Time, parentBlockId flow.Identifier) time.Time
- type Config
- type ControllerParams
- type EpochTransitionTime
- type Ewma
- type LeakyIntegrator
- type ProposalTiming
- type TimedBlock
- type TimingConfig
- func (ctl *TimingConfig) GetEnabled() bool
- func (ctl *TimingConfig) GetFallbackProposalDuration() time.Duration
- func (ctl *TimingConfig) GetMaxViewDuration() time.Duration
- func (ctl *TimingConfig) GetMinViewDuration() time.Duration
- func (ctl *TimingConfig) SetEnabled(enabled bool) error
- func (ctl *TimingConfig) SetFallbackProposalDuration(dur time.Duration) error
- func (ctl *TimingConfig) SetMaxViewDuration(dur time.Duration) error
- func (ctl *TimingConfig) SetMinViewDuration(dur time.Duration) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type BlockTimeController ¶
type BlockTimeController struct { component.Component protocol.Consumer // consumes protocol state events // contains filtered or unexported fields }
BlockTimeController dynamically adjusts the ProposalTiming of this node, based on the measured view rate of the consensus committee as a whole, in order to achieve a desired switchover time for each epoch. In a nutshell, the controller outputs the block time on the happy path, i.e.
- Suppose the node is observing the parent block B0 at some time `x0`.
- The controller determines the duration `d` of how much later the child block B1 should be observed by the committee.
- The controller internally memorizes the latest B0 it has seen and outputs the tuple `(B0, x0, d)`
This low-level controller output `(B0, x0, d)` is wrapped into a `ProposalTiming` interface, specifically `happyPathBlockTime` on the happy path. The purpose of the `ProposalTiming` wrapper is to translate the raw controller output into a form that is useful for the event handler. Edge cases, such as initialization or EECC are implemented by other implementations of `ProposalTiming`.
func NewBlockTimeController ¶
func NewBlockTimeController(log zerolog.Logger, metrics module.CruiseCtlMetrics, config *Config, state protocol.State, curView uint64) (*BlockTimeController, error)
NewBlockTimeController returns a new BlockTimeController.
func (*BlockTimeController) EpochEmergencyFallbackTriggered ¶
func (ctl *BlockTimeController) EpochEmergencyFallbackTriggered()
EpochEmergencyFallbackTriggered responds to epoch fallback mode being triggered.
func (*BlockTimeController) EpochSetupPhaseStarted ¶
func (ctl *BlockTimeController) EpochSetupPhaseStarted(_ uint64, first *flow.Header)
EpochSetupPhaseStarted responds to the EpochSetup phase starting for the current epoch. The event is queued for async processing by the worker.
func (*BlockTimeController) GetProposalTiming ¶
func (ctl *BlockTimeController) GetProposalTiming() ProposalTiming
GetProposalTiming returns the controller's latest ProposalTiming. Concurrency safe.
func (*BlockTimeController) OnBlockIncorporated ¶
func (ctl *BlockTimeController) OnBlockIncorporated(block *model.Block)
OnBlockIncorporated listens to notification from HotStuff about incorporating new blocks. The event is queued for async processing by the worker. If the channel is full, the event is discarded - since we are taking an average it doesn't matter if we occasionally miss a sample.
func (*BlockTimeController) TargetPublicationTime ¶
func (ctl *BlockTimeController) TargetPublicationTime(proposalView uint64, timeViewEntered time.Time, parentBlockId flow.Identifier) time.Time
type Config ¶
type Config struct { TimingConfig ControllerParams }
Config defines configuration for the BlockTimeController.
func DefaultConfig ¶
func DefaultConfig() *Config
DefaultConfig returns the default config for the BlockTimeController.
type ControllerParams ¶
type ControllerParams struct { // N_ewma defines how historical measurements are incorporated into the EWMA for the proportional error term. // Intuition: Suppose the input changes from x to y instantaneously: // - N_ewma is the number of samples required to move the EWMA output about 2/3 of the way from x to y // Per convention, this must be a _positive_ integer. N_ewma uint // N_itg defines how historical measurements are incorporated into the integral error term. // Intuition: For a constant error x: // - the integrator value will saturate at `x•N_itg` // - an integrator initialized at 0 reaches 2/3 of the saturation value after N_itg samples // Per convention, this must be a _positive_ integer. N_itg uint // KP, KI, KD, are the coefficients to the PID controller and define its response. // KP adjusts the proportional term (responds to the magnitude of error). // KI adjusts the integral term (responds to the error sum over a recent time interval). // KD adjusts the derivative term (responds to the rate of change, i.e. time derivative, of the error). KP, KI, KD float64 }
ControllerParams specifies the BlockTimeController's internal parameters.
type EpochTransitionTime ¶
type EpochTransitionTime struct {
// contains filtered or unexported fields
}
EpochTransitionTime represents the target epoch transition time. Epochs last one week, so the transition is defined in terms of a day-of-week and time-of-day. The target time is always in UTC to avoid confusion resulting from different representations of the same transition time and around daylight savings time.
func DefaultEpochTransitionTime ¶
func DefaultEpochTransitionTime() EpochTransitionTime
DefaultEpochTransitionTime is the default epoch transition target. The target switchover is Wednesday 12:00 PDT, which is 19:00 UTC. The string representation is `wednesday@19:00`.
func ParseTransition ¶
func ParseTransition(s string) (*EpochTransitionTime, error)
ParseTransition parses a transition time string. A transition string must be specified according to the format:
WD@HH:MM
WD is the weekday string as defined by `strings.ToLower(time.Weekday.String)` HH is the 2-character hour of day, in the range [00-23] MM is the 2-character minute of hour, in the range [00-59] All times are in UTC.
A generic error is returned if the input is an invalid transition string.
func (*EpochTransitionTime) String ¶
func (tt *EpochTransitionTime) String() string
String returns the canonical string representation of the transition time. This is the format expected as user input, when this value is configured manually. See ParseSwitchover for details of the format.
type Ewma ¶
type Ewma struct {
// contains filtered or unexported fields
}
Ewma implements the exponentially weighted moving average with smoothing factor α. The Ewma is a filter commonly applied to time-discrete signals. Mathematically, it is represented by the recursive update formula
value ← α·v + (1-α)·value
where `v` the next observation. Intuitively, the loss factor `α` relates to the time window of N observations that we average over. For example, let α ≡ 1/N and consider an input that suddenly changes from x to y as a step function. Then N is _roughly_ the number of samples required to move the output average about 2/3 of the way from x to y. For numeric stability, we require α to satisfy 0 < a < 1. Not concurrency safe.
func NewEwma ¶
NewEwma instantiates a new exponentially weighted moving average. The smoothing factor `alpha` relates to the averaging time window. Let `alpha` ≡ 1/N and consider an input that suddenly changes from x to y as a step function. Then N is roughly the number of samples required to move the output average about 2/3 of the way from x to y. For numeric stability, we require `alpha` to satisfy 0 < `alpha` < 1.
func (*Ewma) AddObservation ¶
AddObservation adds the value `v` to the EWMA. Returns the updated value.
func (*Ewma) AddRepeatedObservation ¶
AddRepeatedObservation adds k consecutive observations with the same value v. Returns the updated value.
type LeakyIntegrator ¶
type LeakyIntegrator struct {
// contains filtered or unexported fields
}
LeakyIntegrator is a filter commonly applied to time-discrete signals. Intuitively, it sums values over a limited time window. This implementation is parameterized by the loss factor `ß`:
value ← v + (1-ß)·value
where `v` the next observation. Intuitively, the loss factor `ß` relates to the time window of N observations that we integrate over. For example, let ß ≡ 1/N and consider a constant input x:
- the integrator value will saturate at x·N
- an integrator initialized at 0 reaches 2/3 of the saturation value after N samples
For numeric stability, we require ß to satisfy 0 < ß < 1. Further details on Leaky Integrator: https://www.music.mcgill.ca/~gary/307/week2/node4.html Not concurrency safe.
func NewLeakyIntegrator ¶
func NewLeakyIntegrator(beta, initialValue float64) (LeakyIntegrator, error)
NewLeakyIntegrator instantiates a new leaky integrator with loss factor `beta`, where `beta relates to window of N observations that we integrate over. For example, let `beta` ≡ 1/N and consider a constant input x. The integrator value will saturate at x·N. An integrator initialized at 0 reaches 2/3 of the saturation value after N samples. For numeric stability, we require `beta` to satisfy 0 < `beta` < 1.
func (*LeakyIntegrator) AddObservation ¶
func (e *LeakyIntegrator) AddObservation(v float64) float64
AddObservation adds the value `v` to the LeakyIntegrator. Returns the updated value.
func (*LeakyIntegrator) AddRepeatedObservation ¶
func (e *LeakyIntegrator) AddRepeatedObservation(v float64, k int) float64
AddRepeatedObservation adds k consecutive observations with the same value v. Returns the updated value.
func (*LeakyIntegrator) Value ¶
func (e *LeakyIntegrator) Value() float64
type ProposalTiming ¶
type ProposalTiming interface { hotstuff.ProposalDurationProvider // ObservationView returns the view of the observation that the controller // processed and generated this ProposalTiming instance in response. ObservationView() uint64 // ObservationTime returns the time, when the controller received the // leading to the generation of this ProposalTiming instance. ObservationTime() time.Time }
ProposalTiming encapsulates the output of the BlockTimeController. On the happy path, the controller observes a block and generates a specific ProposalTiming in response. For the happy path, the ProposalTiming describes when the child proposal should be broadcast. However, observations other than blocks might also be used to instantiate ProposalTiming objects, e.g. controller instantiation, a disabled controller, etc. The purpose of ProposalTiming is to convert the controller output to timing information that the EventHandler understands. By convention, ProposalTiming should be treated as immutable.
type TimedBlock ¶
type TimedBlock struct { Block *model.Block TimeObserved time.Time // timestamp when BlockTimeController received the block, per convention in UTC }
TimedBlock represents a block, with a timestamp recording when the BlockTimeController received the block
type TimingConfig ¶
type TimingConfig struct { // TargetTransition defines the target time to transition epochs each week. TargetTransition EpochTransitionTime // FallbackProposalDelay is the minimal block construction delay. When used, it behaves like the // old command line flag `block-rate-delay`. Specifically, the primary measures the duration from // starting to construct its proposal to the proposal being ready to be published. If this // duration is _less_ than FallbackProposalDelay, the primary delays broadcasting its proposal // by the remainder needed to reach `FallbackProposalDelay` // It is used: // - when Enabled is false // - when epoch fallback has been triggered FallbackProposalDelay *atomic.Duration // MaxViewDuration is a hard maximum on the total view time targeted by ProposalTiming. // If the BlockTimeController computes a larger desired ProposalTiming value // based on the observed error and tuning, this value will be used instead. MaxViewDuration *atomic.Duration // MinViewDuration is a hard maximum on the total view time targeted by ProposalTiming. // If the BlockTimeController computes a smaller desired ProposalTiming value // based on the observed error and tuning, this value will be used instead. MinViewDuration *atomic.Duration // Enabled defines whether responsive control of the GetProposalTiming is enabled. // When disabled, the FallbackProposalDelay is used. Enabled *atomic.Bool }
TimingConfig specifies the BlockTimeController's limits of authority.
func (*TimingConfig) GetEnabled ¶
func (ctl *TimingConfig) GetEnabled() bool
func (*TimingConfig) GetFallbackProposalDuration ¶
func (ctl *TimingConfig) GetFallbackProposalDuration() time.Duration
func (*TimingConfig) GetMaxViewDuration ¶
func (ctl *TimingConfig) GetMaxViewDuration() time.Duration
func (*TimingConfig) GetMinViewDuration ¶
func (ctl *TimingConfig) GetMinViewDuration() time.Duration
func (*TimingConfig) SetEnabled ¶
func (ctl *TimingConfig) SetEnabled(enabled bool) error
func (*TimingConfig) SetFallbackProposalDuration ¶
func (ctl *TimingConfig) SetFallbackProposalDuration(dur time.Duration) error
func (*TimingConfig) SetMaxViewDuration ¶
func (ctl *TimingConfig) SetMaxViewDuration(dur time.Duration) error
func (*TimingConfig) SetMinViewDuration ¶
func (ctl *TimingConfig) SetMinViewDuration(dur time.Duration) error