epochs

package
v0.37.4-pr6379 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 20, 2024 License: AGPL-3.0 Imports: 8 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type EpochStateMachine

type EpochStateMachine struct {
	// contains filtered or unexported fields
}

EpochStateMachine is a hierarchical state machine that encapsulates the logic for protocol-compliant evolution of Epoch-related sub-state. EpochStateMachine processes a subset of service events that are relevant for the Epoch state, and ignores all other events. EpochStateMachine delegates the processing of service events to an embedded StateMachine, which is either a HappyPathStateMachine or a FallbackStateMachine depending on the operation mode of the protocol. It relies on Key-Value Store to read the parent state and to persist the snapshot of the updated Epoch state.

func NewEpochStateMachine

func NewEpochStateMachine(
	candidateView uint64,
	parentBlockID flow.Identifier,
	setups storage.EpochSetups,
	commits storage.EpochCommits,
	epochProtocolStateDB storage.EpochProtocolStateEntries,
	parentState protocol.KVStoreReader,
	mutator protocol_state.KVStoreMutator,
	happyPathStateMachineFactory StateMachineFactoryMethod,
	epochFallbackStateMachineFactory StateMachineFactoryMethod,
) (*EpochStateMachine, error)

NewEpochStateMachine creates a new higher-level hierarchical state machine for protocol-compliant evolution of Epoch-related sub-state. NewEpochStateMachine performs initialization of state machine depending on the operation mode of the protocol. - for the happy path, it initializes a HappyPathStateMachine, - for the epoch fallback mode it initializes a FallbackStateMachine. No errors are expected during normal operations.

func (*EpochStateMachine) Build

Build schedules updates to the protocol state by obtaining the updated state from the active state machine, preparing deferred DB updates and committing updated sub-state ID to the KV store. ATTENTION: In mature implementation all parts of the Dynamic Protocol State will rely on the Key-Value Store as storage but to avoid a large refactoring we are using a hybrid approach where only the epoch state ID is stored in the KV Store but the actual epoch state is stored separately, nevertheless, the epoch state ID is used to sanity check if the epoch state is consistent with the KV Store. Using this approach, we commit the epoch sub-state to the KV Store which in affects the Dynamic Protocol State ID which is essentially hash of the KV Store.

func (*EpochStateMachine) EvolveState

func (e *EpochStateMachine) EvolveState(sealedServiceEvents []flow.ServiceEvent) error

EvolveState applies the state change(s) on the Epoch sub-state, based on information from the candidate block (under construction). Information that potentially changes the state (compared to the parent block's state):

  • Service Events sealed in the candidate block
  • the candidate block's view (already provided at construction time)

SAFETY REQUIREMENTS:

  • The seals for the execution results, from which the `sealedServiceEvents` originate, must be protocol compliant.
  • `sealedServiceEvents` must list the service Events in chronological order. This can be achieved by arranging the sealed execution results in order of increasing block height. Within each execution result, the service events are in chronological order.
  • EvolveState MUST be called for all candidate blocks, even if `sealedServiceEvents` is empty! This is because reaching a specific view can also trigger in state changes. (e.g. not having received the EpochCommit event for the next epoch, but approaching the end of the current epoch.)

The block's payload might contain epoch preparation service events for the next epoch. In this case, we need to update the tentative protocol state. We need to validate whether all information is available in the protocol state to go to the next epoch when needed. In cases where there is a bug in the smart contract, it could be that this happens too late, and we should trigger epoch fallback mode. No errors are expected during normal operations.

func (*EpochStateMachine) ParentState

func (e *EpochStateMachine) ParentState() protocol.KVStoreReader

ParentState returns parent state associated with this state machine.

func (*EpochStateMachine) View

func (e *EpochStateMachine) View() uint64

View returns the view associated with this state machine. The view of the state machine equals the view of the block carrying the respective updates.

type EpochStateMachineFactory

type EpochStateMachineFactory struct {
	// contains filtered or unexported fields
}

EpochStateMachineFactory is a factory for creating EpochStateMachine instances. It holds all the necessary data to create a new instance of EpochStateMachine.

func NewEpochStateMachineFactory

func NewEpochStateMachineFactory(
	setups storage.EpochSetups,
	commits storage.EpochCommits,
	epochProtocolStateDB storage.EpochProtocolStateEntries,
	happyPathTelemetryFactory, fallbackTelemetryFactory protocol_state.StateMachineEventsTelemetryFactory,
) *EpochStateMachineFactory

func (*EpochStateMachineFactory) Create

Create creates a new instance of an underlying type that operates on KV Store and is created for a specific candidate block. No errors are expected during normal operations.

type FallbackStateMachine

type FallbackStateMachine struct {
	// contains filtered or unexported fields
}

FallbackStateMachine is a special structure that encapsulates logic for processing service events when protocol is in epoch fallback mode. The FallbackStateMachine ignores EpochSetup and EpochCommit events but still processes ejection events.

Whenever invalid epoch state transition has been observed only epochFallbackStateMachines must be created for subsequent views.

func NewFallbackStateMachine

func NewFallbackStateMachine(
	parentState protocol.KVStoreReader,
	telemetry protocol_state.StateMachineTelemetryConsumer,
	view uint64,
	parentEpochState *flow.RichEpochStateEntry,
) (*FallbackStateMachine, error)

NewFallbackStateMachine constructs a state machine for epoch fallback. It automatically sets EpochFallbackTriggered to true, thereby recording that we have entered epoch fallback mode. See flow.EpochPhase for detailed documentation about EFM and epoch phase transitions. No errors are expected during normal operations.

func (*FallbackStateMachine) Build

func (u *FallbackStateMachine) Build() (updatedState *flow.EpochStateEntry, stateID flow.Identifier, hasChanges bool)

Build returns updated protocol state entry, state ID and a flag indicating if there were any changes. CAUTION: Do NOT call Build, if the baseStateMachine instance has returned a `protocol.InvalidServiceEventError` at any time during its lifetime. After this error, the baseStateMachine is left with a potentially dysfunctional state and should be discarded.

func (*FallbackStateMachine) EjectIdentity

func (u *FallbackStateMachine) EjectIdentity(nodeID flow.Identifier) bool

EjectIdentity updates the identity table by changing the node's participation status to 'ejected' If and only if the node is active in the previous or current or next epoch, the node's ejection status is set to true for all occurrences, and we return true. If `nodeID` is not found, we return false. This method is idempotent and behaves identically for repeated calls with the same `nodeID` (repeated calls with the same input create minor performance overhead though).

func (*FallbackStateMachine) ParentState

func (u *FallbackStateMachine) ParentState() *flow.RichEpochStateEntry

ParentState returns parent protocol state associated with this state machine.

func (*FallbackStateMachine) ProcessEpochCommit

func (m *FallbackStateMachine) ProcessEpochCommit(setup *flow.EpochCommit) (bool, error)

ProcessEpochCommit processes epoch commit service events, for epoch fallback we are ignoring this event.

func (*FallbackStateMachine) ProcessEpochRecover added in v0.37.1

func (m *FallbackStateMachine) ProcessEpochRecover(epochRecover *flow.EpochRecover) (bool, error)

ProcessEpochRecover updates the internally-maintained interim Epoch state with data from epoch recover event in an attempt to recover from Epoch Fallback Mode [EFM] and get back on happy path. Specifically, after successfully processing this event, we will have a next epoch (as specified by the EpochRecover event) in the protocol state, which is in the committed phase. Subsequently, the epoch protocol can proceed following the happy path. Therefore, we set `EpochFallbackTriggered` back to false.

The boolean return indicates if the input event triggered a transition in the state machine or not. For the EpochRecover event, we never return an error to ensure that FallbackStateMachine is robust against any input and doesn't halt the chain even if the Epoch Smart Contract misbehaves. This is a safe choice since the error can only originate from an invalid EpochRecover event, in this case we just ignore the event and continue with the fallback mode.

EDGE CASES: due to manual interventions for Epoch Recovery, there is a notable risk of unintended side-effects in terms of emitted events. Therefore, we aim to be resilient against invalid and/or inconsistent events:

  1. Any amount of setup and commit events being sealed in the same block as an epoch recover event: EpochSetup and EpochCommit are consistently ignored by the FallbackStateMachine, also after a successful recovery. For a detailed explanation why this is safe, see `ProcessEpochSetup` above.
  2. Multiple EpochRecover events sealed in the same block: - Invalid `EpochRecover` events are reported to telemetry and dropped. - The first valid `EpochRecover` event is accepted (if any is sealed in block) - Subsequent valid events are no-ops iff they are identical to the first valid EpochRecover event. Otherwise, they are reported to telemetry and dropped. An `EpochRecover` event is considered valid in this context if it specifies a valid successor of the current epoch (irrespective whether a `NextEpoch` in the `ProtocolStateEntry`)

Error returns:

  • During normal operations, this method internally handle erroneous inputs. Error returns are symptoms of internal state corruption or critical bugs, making continuation impossible.

func (*FallbackStateMachine) ProcessEpochSetup

func (m *FallbackStateMachine) ProcessEpochSetup(setup *flow.EpochSetup) (bool, error)

ProcessEpochSetup processes epoch setup service events, for epoch fallback we are ignoring this event.

func (*FallbackStateMachine) TransitionToNextEpoch

func (u *FallbackStateMachine) TransitionToNextEpoch() error

TransitionToNextEpoch updates the notion of 'current epoch', 'previous' and 'next epoch' in the protocol state. An epoch transition is only allowed when _all_ of the following conditions are satisfied: - next epoch has been set up, - next epoch has been committed, - candidate block is in the next epoch. No errors are expected during normal operations.

func (*FallbackStateMachine) View

func (u *FallbackStateMachine) View() uint64

View returns the view associated with this state machine. The view of the state machine equals the view of the block carrying the respective updates.

type HappyPathStateMachine

type HappyPathStateMachine struct {
	// contains filtered or unexported fields
}

HappyPathStateMachine is a dedicated structure for evolving the Epoch-related portion of the overall Protocol State. Based on the content of a new block, it updates epoch data, including the identity table, on the happy path. The HappyPathStateMachine guarantees protocol-compliant evolution of Epoch-related sub-state via the following state transitions:

  • epoch setup: transitions current epoch from staking to setup phase, creates next epoch protocol state when processed.
  • epoch commit: transitions current epoch from setup to commit phase, commits next epoch protocol state when processed.
  • epoch transition: on the first block of the new epoch (Formally, the block's parent is still in the last epoch, while the new block has a view in the next epoch. Caution: the block's view is not necessarily the first view in the epoch, as there might be leader failures)
  • identity changes: updates identity table for previous (if available), current, and next epoch (if available).

All updates are applied to a copy of parent protocol state, so parent protocol state is not modified. The stateMachine internally tracks the current protocol state. A separate instance should be created for each block to process the updates therein. See flow.EpochPhase for detailed documentation about EFM and epoch phase transitions.

func NewHappyPathStateMachine

func NewHappyPathStateMachine(telemetry protocol_state.StateMachineTelemetryConsumer, view uint64, parentState *flow.RichEpochStateEntry) (*HappyPathStateMachine, error)

NewHappyPathStateMachine creates a new HappyPathStateMachine. An exception is returned in case the `EpochFallbackTriggered` flag is set in the `parentEpochState`. This means that the protocol state evolution has reached an undefined state from the perspective of the happy path state machine. No errors are expected during normal operations.

func (*HappyPathStateMachine) Build

func (u *HappyPathStateMachine) Build() (updatedState *flow.EpochStateEntry, stateID flow.Identifier, hasChanges bool)

Build returns updated protocol state entry, state ID and a flag indicating if there were any changes. CAUTION: Do NOT call Build, if the baseStateMachine instance has returned a `protocol.InvalidServiceEventError` at any time during its lifetime. After this error, the baseStateMachine is left with a potentially dysfunctional state and should be discarded.

func (*HappyPathStateMachine) EjectIdentity

func (u *HappyPathStateMachine) EjectIdentity(nodeID flow.Identifier) bool

EjectIdentity updates the identity table by changing the node's participation status to 'ejected' If and only if the node is active in the previous or current or next epoch, the node's ejection status is set to true for all occurrences, and we return true. If `nodeID` is not found, we return false. This method is idempotent and behaves identically for repeated calls with the same `nodeID` (repeated calls with the same input create minor performance overhead though).

func (*HappyPathStateMachine) ParentState

func (u *HappyPathStateMachine) ParentState() *flow.RichEpochStateEntry

ParentState returns parent protocol state associated with this state machine.

func (*HappyPathStateMachine) ProcessEpochCommit

func (u *HappyPathStateMachine) ProcessEpochCommit(epochCommit *flow.EpochCommit) (bool, error)

ProcessEpochCommit updates current protocol state with data from epoch commit event. Observing an epoch setup commit, transitions protocol state from setup to commit phase. At this point, we have finished construction of the next epoch. As a result of this operation protocol state for next epoch will be committed. Returned boolean indicates if event triggered a transition in the state machine or not. Implementors must never return (true, error). Expected errors indicating that we are leaving the happy-path of the epoch transitions

  • `protocol.InvalidServiceEventError` - if the service event is invalid or is not a valid state transition for the current protocol state. CAUTION: the HappyPathStateMachine is left with a potentially dysfunctional state when this error occurs. Do NOT call the Build method after such error and discard the HappyPathStateMachine!

func (*HappyPathStateMachine) ProcessEpochRecover added in v0.37.1

func (u *HappyPathStateMachine) ProcessEpochRecover(epochRecover *flow.EpochRecover) (bool, error)

ProcessEpochRecover returns the sentinel error `protocol.InvalidServiceEventError`, which indicates that `EpochRecover` are not expected on the happy path of epoch lifecycle.

func (*HappyPathStateMachine) ProcessEpochSetup

func (u *HappyPathStateMachine) ProcessEpochSetup(epochSetup *flow.EpochSetup) (bool, error)

ProcessEpochSetup updates the protocol state with data from the epoch setup event. Observing an epoch setup event also affects the identity table for current epoch:

  • it transitions the protocol state from Staking to Epoch Setup phase
  • we stop returning identities from previous+current epochs and instead returning identities from current+next epochs.

As a result of this operation protocol state for the next epoch will be created. Returned boolean indicates if event triggered a transition in the state machine or not. Implementors must never return (true, error). Expected errors indicating that we are leaving the happy-path of the epoch transitions

  • `protocol.InvalidServiceEventError` - if the service event is invalid or is not a valid state transition for the current protocol state. CAUTION: the HappyPathStateMachine is left with a potentially dysfunctional state when this error occurs. Do NOT call the Build method after such error and discard the HappyPathStateMachine!

func (*HappyPathStateMachine) TransitionToNextEpoch

func (u *HappyPathStateMachine) TransitionToNextEpoch() error

TransitionToNextEpoch updates the notion of 'current epoch', 'previous' and 'next epoch' in the protocol state. An epoch transition is only allowed when _all_ of the following conditions are satisfied: - next epoch has been set up, - next epoch has been committed, - candidate block is in the next epoch. No errors are expected during normal operations.

func (*HappyPathStateMachine) View

func (u *HappyPathStateMachine) View() uint64

View returns the view associated with this state machine. The view of the state machine equals the view of the block carrying the respective updates.

type StateMachine

type StateMachine interface {
	// Build returns updated protocol state entry, state ID and a flag indicating if there were any changes.
	// CAUTION:
	// Do NOT call Build, if the StateMachine instance has returned a `protocol.InvalidServiceEventError`
	// at any time during its lifetime. After this error, the StateMachine is left with a potentially
	// dysfunctional state and should be discarded.
	Build() (updatedState *flow.EpochStateEntry, stateID flow.Identifier, hasChanges bool)

	// ProcessEpochSetup updates the internally-maintained interim Epoch state with data from epoch setup event.
	// Processing an epoch setup event also affects the identity table for the current epoch.
	// Specifically, we transition the Epoch state from staking to setup phase, we stop returning
	// identities from previous+current epochs and start returning identities from current+next epochs.
	// As a result of this operation protocol state for the next epoch will be created.
	// Returned boolean indicates if event triggered a transition in the state machine or not.
	// Implementors must never return (true, error).
	// Expected errors indicating that we are leaving the happy-path of the epoch transitions
	//   - `protocol.InvalidServiceEventError` - if the service event is invalid or is not a valid state transition for the current protocol state.
	//     CAUTION: the StateMachine is left with a potentially dysfunctional state when this error occurs. Do NOT call the Build method
	//     after such error and discard the StateMachine!
	ProcessEpochSetup(epochSetup *flow.EpochSetup) (bool, error)

	// ProcessEpochCommit updates the internally-maintained interim Epoch state with data from epoch commit event.
	// Observing an epoch setup commit, transitions protocol state from setup to commit phase.
	// At this point, we have finished construction of the next epoch.
	// As a result of this operation protocol state for next epoch will be committed.
	// Returned boolean indicates if event triggered a transition in the state machine or not.
	// Implementors must never return (true, error).
	// Expected errors indicating that we are leaving the happy-path of the epoch transitions
	//   - `protocol.InvalidServiceEventError` - if the service event is invalid or is not a valid state transition for the current protocol state.
	//     CAUTION: the StateMachine is left with a potentially dysfunctional state when this error occurs. Do NOT call the Build method
	//     after such error and discard the StateMachine!
	ProcessEpochCommit(epochCommit *flow.EpochCommit) (bool, error)

	// ProcessEpochRecover updates the internally-maintained interim Epoch state with data from epoch recover
	// event in an attempt to recover from Epoch Fallback Mode [EFM] and get back on happy path.
	// Specifically, after successfully processing this event, we will have a next epoch (as specified by the
	// EpochRecover event) in the protocol state, which is in the committed phase. Subsequently, the epoch
	// protocol can proceed following the happy path. Therefore, we set `EpochFallbackTriggered` back to false.
	//
	// The boolean return indicates if the input event triggered a transition in the state machine or not.
	// For the EpochRecover event, we return false if and only if there is an error. The reason is that
	// either the `EpochRecover` event is rejected (leading to `InvalidServiceEventError`) or there is an
	// exception processing the event. Otherwise, an `EpochRecover` event must always lead to a state change.
	// Expected errors during normal operations:
	//   - `protocol.InvalidServiceEventError` - if the service event is invalid or is not a valid state transition for the current protocol state.
	ProcessEpochRecover(epochRecover *flow.EpochRecover) (bool, error)

	// EjectIdentity updates the identity table by changing the node's participation status to 'ejected'
	// If and only if the node is active in the previous or current or next epoch, the node's ejection status
	// is set to true for all occurrences, and we return true.  If `nodeID` is not found, we return false. This
	// method is idempotent and behaves identically for repeated calls with the same `nodeID` (repeated calls
	// with the same input create minor performance overhead though).
	EjectIdentity(nodeID flow.Identifier) bool

	// TransitionToNextEpoch transitions our reference frame of 'current epoch' to the pending but committed epoch.
	// Epoch transition is only allowed when:
	// - next epoch has been committed,
	// - candidate block is in the next epoch.
	// No errors are expected during normal operations.
	TransitionToNextEpoch() error

	// View returns the view associated with this state machine.
	// The view of the state machine equals the view of the block carrying the respective updates.
	View() uint64

	// ParentState returns parent protocol state associated with this state machine.
	ParentState() *flow.RichEpochStateEntry
}

StateMachine implements a low-level interface for state-changing operations on the Epoch state. It is used by higher level logic to coordinate the Epoch handover, evolving its internal state when Epoch-related Service Events are sealed or specific view-thresholds are reached.

The StateMachine is fork-aware, in that it starts with the Epoch state of the parent block and evolves the state, based on the relevant information in the child block (specifically Service Events sealed in the child block and the child block's view). A separate instance must be created for each block that is being processed. Calling `Build()` constructs a snapshot of the resulting Epoch state.

type StateMachineFactoryMethod

type StateMachineFactoryMethod func(candidateView uint64, parentState *flow.RichEpochStateEntry) (StateMachine, error)

StateMachineFactoryMethod is a factory method to create state machines for evolving the protocol's epoch state. Currently, we have `HappyPathStateMachine` and `FallbackStateMachine` as StateMachine implementations, whose constructors both have the same signature as StateMachineFactoryMethod.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL