runner

package
v0.0.0-...-3d5562a Latest Latest
Warning

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

Go to latest
Published: Oct 30, 2026 License: GPL-3.0, LGPL-3.0 Imports: 35 Imported by: 0

Documentation

Overview

Package runner drives the consensus core in a node. It owns the consensus goroutine and its input queues, per-peer receive queues and checks, the timer scheduler, write-ahead logging and replay, calls to the private validator, message sending, the commit goroutine and the publication of state snapshots.

Index

Constants

View Source
const (
	ProcessingCheck = "receive_check"
	ProcessingCore  = "core_input"
)
View Source
const (
	// WALSyncPeriod bounds how long a buffered write-ahead log record
	// stays unsynced when no own message forces a sync.
	WALSyncPeriod = 200 * time.Millisecond
	// HeadCheckPeriod is how often the head the core works on is compared
	// with the application's head.
	HeadCheckPeriod = 30 * time.Second
)

Periods of the housekeeping tasks.

Variables

View Source
var (
	ErrRunning         = errors.New("runner: already running")
	ErrNotRunning      = errors.New("runner: not running")
	ErrHalted          = errors.New("runner: consensus halted")
	ErrStartPoint      = errors.New("runner: start height is not the head + 1")
	ErrClosed          = errors.New("runner: closed")
	ErrFinalizePending = errors.New("runner: manual finalization pending")
)

Errors of the runner.

View Source
var ErrAppBehind = fmt.Errorf("runner: the application is behind the write-ahead log or the sign state")

ErrAppBehind is the refusal of the handshake.

View Source
var ErrProcessingPending = errors.New("runner: manual core service pending")

Functions

func Handshake

func Handshake(appHead types.Height, st WALState, signHeight *types.Height) (HandshakeAction, *CommitRequest, error)

Handshake compares the application head appHead with the log state and the sign height (the highest signed height or the sign floor, nil if none) and returns the action and, for Refinalize, the block.

A commit request of height appHead + 1 is refinalized only when the log has not left a later height and nothing was signed beyond appHead + 2: the log keeps the requests of earlier heights, and an application that fell further behind than one height is refused even when the log still holds the request of its next height.

Types

type App

type App interface {
	// ReadyToBuild tells the block builder that a round started and how
	// long to wait before building; done is closed when the core leaves the
	// height. It must not block.
	ReadyToBuild(req consensus.RequestBuild, wait time.Duration, done <-chan struct{})
	// FinalizeBlock imports a decided block with its seals. It is called
	// from the commit goroutine, one block at a time in height order, and
	// is idempotent for a block that is already the head.
	FinalizeBlock(b *types.Block, round types.Round) error
}

App is the part of the application the runner drives.

type AppQueueSourceObserver

type AppQueueSourceObserver interface{ ObserveAppQueueSource(journal.AppQueueRec) }

AppQueueSourceObserver receives detached application queue metadata after appMu is released. Calls can arrive concurrently and out of order; Sequence retains the actual mutex order. The observer must return promptly. Mutating its input bytes cannot change a queued input or functional journal record. This optional hook does not enable functional cache replay or app execution.

type Chain

type Chain interface {
	// Head returns the application's canonical head.
	Head() *types.Header
	// ValidatorsAt returns the validator set of block number whose parent
	// has hash parent.
	ValidatorsAt(number types.Height, parent types.Hash) (*validator.Set, error)
	// ValidateProposal verifies a proposal (header.VerifyProposal); a
	// proposal from the future fails with header.ErrFutureBlock and the time
	// to wait.
	ValidateProposal(b *types.Block) (time.Duration, error)
	// IsBadBlock reports whether the block is recorded as bad.
	IsBadBlock(h types.Hash) bool
}

Chain answers the core's questions about the application's chain. The runner records every answer so that a replay needs no application.

type Clock

type Clock interface {
	Now() time.Time
	Mono() time.Duration
	AfterFunc(d time.Duration, f func()) Timer
}

Clock is the runner's source of time. Durations are measured on the monotonic clock; the wall clock is read only for the wait of a block build request and for records.

func SystemClock

func SystemClock() Clock

SystemClock returns the clock of the operating system.

type CommitRequest

type CommitRequest struct {
	Block *types.Block
	Round types.Round
}

CommitRequest is a decided block the write-ahead log handed to the application.

type Config

type Config struct {
	// ValidatorHistory overrides the finite history cap of DeferValidatorStart.
	// Nil takes that explicitly selected profile's supported defaults.
	ValidatorHistory *ValidatorHistoryLimits `json:",omitempty"`
	// ReceiveCache registers a received key only after a successful direct
	// core ACCEPT. Nil preserves legacy precheck caching. This staged runtime
	// API requires explicit limits and currently refuses message journals
	// until a compatible cache-policy profile is available.
	ReceiveCache *transport.ReceiveLedgerOptions `json:",omitempty"`
	// ReceiveFailureCache separately memoizes classified failures within one
	// immutable publication. Nil preserves the original receive path.
	ReceiveFailureCache *transport.ReceiveFailureOptions `json:",omitempty"`
	// Core configures the consensus core. Core.Self must be the signer's
	// address, or zero for a node that never signs.
	Core consensus.Options
	// ReplayWAL replays the write-ahead log on Start to restore the lock,
	// the prepared certificate, the sent votes and the round of the height
	// being decided (a restart-safety rule).
	ReplayWAL bool
	// Manual runs no goroutine: the caller drives the runner with Pump.
	// The deterministic simulator uses it.
	Manual bool

	// InboxMessages and InboxBytes bound the receive queue of one peer
	// (defaults 256 and 32 MiB).
	InboxMessages int
	InboxBytes    int64
	// OverflowLimit is the number of dropped messages of one peer within
	// OverflowWindow above which the peer is disconnected (defaults 1000
	// and 10 s), unless every peer overflows.
	OverflowLimit  int
	OverflowWindow time.Duration
	// Workers is the number of receive-check workers (default GOMAXPROCS).
	Workers int
	// NextHeight is an opt-in next-sequence receive lane. It requires WAL
	// replay and explicit positive limits; nil preserves the original path.
	NextHeight *NextHeightLimits
}

Config configures a Runner.

type CoreEnvObservation

type CoreEnvObservation = journal.CoreEnvObservation

Source values use the separate durable diagnostic schema, never StepRec.

type CoreStepMoment

type CoreStepMoment = journal.CoreStepMoment

type CoreStepObservation

type CoreStepObservation = journal.CoreStepRec

type CoreStepObserver

type CoreStepObserver interface{ ObserveCoreStep(CoreStepObservation) }

CoreStepObserver must return promptly. It runs on the consensus loop after execution and may retain/alter its detached value without changing the core, WAL or functional journal. A nil observer avoids serialization and hashing.

type Deps

type Deps struct {
	// ObservationSeals receives detached verified extra-seal entries after an
	// ordinary core step. It is a separately enabled functional witness sink,
	// independent of diagnostic metadata and replay authority.
	ObservationSeals func(prepared, committed []consensus.ExtraSealEntry)
	// ReceiveCacheRecorded observes a direct core ACCEPT retained in the
	// received cache. It runs after the cache mutex is released and must not
	// block. Nil changes no functional cache ownership or state.
	ReceiveCacheRecorded func(code uint64)
	// ReceiveRecoveryChecked observes an actual complete Recover invocation,
	// never a cache hit. It grants no signer or acceptance authority.
	ReceiveRecoveryChecked func(code codec.Code, failure consensus.RecoveryFailure)
	// ReceiveFailureHit observes reuse of an earlier failed check, not a new
	// Recover, signing operation, or core acceptance. Callbacks must not block.
	ReceiveFailureHit func(code uint64, class transport.ReceiveFailureClass)
	Chain             Chain
	App               App
	// Transport sends and deduplicates; nil sends nothing.
	Transport *transport.Dedup
	// Net is the transport under Transport, used to disconnect peers; nil
	// disconnects nothing.
	Net transport.Transport
	// Signer signs own messages; nil never signs.
	Signer privval.Signer
	// WAL is the write-ahead log; nil keeps none.
	WAL   *wal.Log
	Clock Clock
	// Events receives the event records; nil drops them.
	Events EventSink
	// Journal receives step and outcome records; nil keeps no journal.
	Journal journal.Writer
	// CoreSteps observes detached actual live core execution for diagnostics.
	// It is independent of Journal and does not enable unsupported replay.
	CoreSteps CoreStepObserver
	// TimerSources observes detached scheduler operations after its mutex is
	// released. Nil preserves the original journal and timer behavior.
	TimerSources TimerSourceObserver
	// AppQueueSources observes detached application-input queue mutations.
	AppQueueSources AppQueueSourceObserver
	// FinalizeSources observes detached commit-queue and application-call boundaries.
	FinalizeSources FinalizeSourceObserver
	// InboxSources observes detached receive queue and slot boundaries.
	InboxSources InboxSourceObserver
	// ReceiveFailureDiagnostics explicitly binds one declared source writer to
	// core, inbox, accepted receive and separate failure observations.
	ReceiveFailureDiagnostics ReceiveFailureDiagnosticObserver
	// InboxFailureSources separately observes reuse of earlier failed checks.
	// It does not declare a diagnostic profile or enable functional replay.
	InboxFailureSources InboxFailureSourceObserver
	// InternalSources observes original internal queue output/selection boundaries.
	InternalSources InternalSourceObserver
	// SelectionSources observes original conditional next probes and actual dispatch.
	SelectionSources SelectionSourceObserver
	// ReceiveDispatchSources observes original lookup ownership and actual completion.
	ReceiveDispatchSources ReceiveDispatchSourceObserver
	// WALReadSources observes original restore reads with explicit copy bounds.
	SignerCalls         SignerCallObserver
	WALReadSources      WALReadSourceObserver
	WALReadSourceLimits wal.ReadSourceLimits
	// Synchronising reports whether the application is synchronising the
	// chain; consensus messages that arrive while the engine is stopped are
	// then dropped instead of closing the connection.
	Synchronising func() bool
	// Rand draws the choice between the timer queue and the peer queues and
	// the order of receive checks in manual mode; nil uses math/rand/v2.
	Rand func(n int) int
	// Faults is called at the crash points (builds with wbft_faults).
	Faults faultpoint.Handler
	// Logger logs; nil discards.
	Logger *slog.Logger
	// ModuleLogger, when set, gives the loggers of the log modules: the
	// runner writes a line of the logcat catalog for each event record,
	// under consensus.round or consensus.msg, when that module's level
	// lets it through (observe.md 7.1).
	ModuleLogger func(logcat.Module) *slog.Logger
	// Refused, when set, is called for every signature privval refuses,
	// with the code of the message (sign floor skips included; the HEALTH
	// record of a skip is written once per height).
	Refused func(code uint64)
	// ManualProcessing is an optional deterministic service model. It is
	// accepted only in manual mode and never changes the production loop.
	ManualProcessing *ManualProcessing
}

Deps are the runner's collaborators.

type EventSink

type EventSink interface {
	Write(r event.Record, at event.Stamp) error
}

EventSink takes event records; *event.Writer is one.

type FinalizeSourceObserver

type FinalizeSourceObserver interface{ ObserveFinalizeSource(journal.FinalizeRec) }

FinalizeSourceObserver receives detached call-boundary metadata after all source/commit/application mutexes are released. Calls may arrive out of order; Sequence retains instrumentation order. Observers must return promptly. This hook does not execute or authenticate the application, or enable cache replay.

type HandshakeAction

type HandshakeAction uint8

HandshakeAction is what the node does before it starts the engine.

const (
	// StartNormal: start at the application head + 1.
	StartNormal HandshakeAction = iota
	// Refinalize: the log handed a decided block of height head + 1 to the
	// application, which did not store it; finalize it again and start at
	// head + 2.
	Refinalize
	// StartAfterRollback: the log is one height ahead without a commit
	// request (the application rolled back); start at head + 1 and warn.
	StartAfterRollback
	// Refuse: the log or the sign state is more than one height ahead of
	// the application.
	Refuse
)

Handshake actions.

type InboxFailureSourceObserver

type InboxFailureSourceObserver interface {
	ObserveInboxFailureHit(journal.InboxFailureHitRec)
}

InboxFailureSourceObserver receives detached evidence of a reused failure, never an executed recovery or successful signer. Like the ordinary inbox observer, callbacks run outside the mutex and may arrive out of order. This observer alone does not enable a supported diagnostic/replay profile.

type InboxSourceObserver

type InboxSourceObserver interface{ ObserveInboxSource(journal.InboxRec) }

InboxSourceObserver receives detached metadata after the inbox mutex is released. Delivery may be reordered; Sequence retains actual mutex order. Observers must return promptly and never become functional cache authority.

type InternalSourceObserver

type InternalSourceObserver interface{ ObserveInternalSource(journal.InternalRec) }

InternalSourceObserver runs on the original consensus loop after a queue operation. Input bytes are detached; observers must return promptly. Nil neither encodes inputs nor retains diagnostic payloads.

type ManualFinalizer

type ManualFinalizer interface {
	FinalizeBlockManual(b *types.Block, round types.Round, complete func(error))
}

ManualFinalizer is an optional application actor for manual runners. It returns control to the external scheduler while modelling a blocking commit call. Complete must be invoked by that scheduler once; Pump pauses core input processing and Stop/Close refuse to discard the outstanding call meanwhile. Non-manual runners continue to use App.FinalizeBlock synchronously.

type ManualProcessing

type ManualProcessing struct {
	Delay    func(ProcessingWork) time.Duration
	Schedule func(time.Duration, func())
}

ManualProcessing reserves work, occupies its worker for Delay, then executes the existing atomic operation at completion. This is a declared pre-dispatch service approximation, not a claim about an operation's internal milestones. Schedule must serialize callbacks with Pump on the manual driver's goroutine; it must not invoke a positive-delay callback inline. Engine controls wait behind reserved core work; receive checks have independent worker capacity.

type NextHeightLimits

type NextHeightLimits struct {
	Entries   uint64 `json:"entries,string"`
	Bytes     uint64 `json:"bytes,string"`
	PerPeer   uint64 `json:"per_peer,string"`
	PerSource uint64 `json:"per_source,string"`
}

NextHeightLimits enables a separate receive lane for exactly the next sequence. All limits are explicit and positive; nil Config.NextHeight keeps the original receive path. Bytes counts owned raw payloads throughout their lifetime, including queued, checking and core-dispatch work. It is not a process heap, CPU or WAL disk bound. One additional receive worker performs outer authentication, independently of the current-height workers.

type ProcessingWork

type ProcessingWork struct {
	Phase string
	Kind  string
	Code  uint64
}

ProcessingWork identifies a reserved receive check or core input. Code is nonzero only for a wire message. Kind uses the inputlog kind vocabulary.

type ReceiveDispatchSourceObserver

type ReceiveDispatchSourceObserver interface {
	ObserveReceiveDispatch(journal.ReceiveDispatchRec)
}

ReceiveDispatchSourceObserver sees detached preparation/completion values after the original checking mutex is released. It must return promptly and cannot grant cache ownership or turn selection into completed core execution.

type ReceiveFailureDiagnosticObserver

ReceiveFailureDiagnosticObserver coordinates one explicit source writer. It observes original execution and separately retained failures; it does not authorize accepted inputs, functional replay, or physical reconstruction. CoreDeclaration must describe the frozen writer header, not mutable defaults.

type ReplayInfo

type ReplayInfo struct {
	Records  int    // input records fed to the core
	Stopped  string // why the replay stopped early; "" if it did not
	Replayed bool   // the core continues from the replayed state
}

ReplayInfo reports the WAL replay of the last Start.

type RestoreSourceObserver

type RestoreSourceObserver interface{ ObserveRestore(journal.RestoreRec) }

RestoreSourceObserver is an optional extension of WALReadSourceObserver. Its detached terminal record is published before the next actual live step.

type Runner

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

Runner drives one consensus core.

func New

func New(cfg Config, d Deps) (*Runner, error)

New returns a stopped runner.

func (*Runner) Close

func (r *Runner) Close() error

Close stops the engine if it runs and ends the runner's goroutines. A later Start returns ErrClosed: it would hand its work to the loop that is gone and wait for it forever (a node whose sync loop restarts the engine while the node stops). Close refuses Start before it stops the engine, so no Start slips in between.

func (*Runner) EngineRun

func (r *Runner) EngineRun() uint64

EngineRun returns the number of the current engine run.

func (*Runner) Halted

func (r *Runner) Halted() error

Halted returns the error that halted consensus, or nil.

func (*Runner) InboundBytes

func (r *Runner) InboundBytes() int64

InboundBytes returns the payload bytes waiting in the per-peer receive queues (the wbft_peer_inbound_queue_bytes metric).

func (*Runner) LastReplay

func (r *Runner) LastReplay() ReplayInfo

LastReplay reports the WAL replay of the last Start.

func (*Runner) NewHead

func (r *Runner) NewHead(h *types.Header)

NewHead reports that the application's head changed. Notifications that arrive before the core handles the previous one are coalesced: the core sees the latest head.

func (*Runner) NextHeightUsage

func (r *Runner) NextHeightUsage() (uint64, uint64)

NextHeightUsage reports reserved raw payload entries and bytes in all stages.

func (*Runner) PeerConnected

func (r *Runner) PeerConnected(a types.Address)

PeerConnected reports that a validator peer (re)connected.

func (*Runner) ProcessingPending

func (r *Runner) ProcessingPending() bool

ProcessingPending reports core work reserved by the manual driver. Like Pump and Vars, callers must serialize this with the manual scheduler.

func (*Runner) Pump

func (r *Runner) Pump() bool

Pump drives a manual runner until nothing is left to do: receive checks, core inputs and commits. It reports whether anything was done.

func (*Runner) Receiver

func (r *Runner) Receiver() transport.Receiver

Receiver returns the sink the transport adapter offers received messages to.

func (*Runner) Running

func (r *Runner) Running() bool

Running reports whether the engine runs.

func (*Runner) Snapshot

func (r *Runner) Snapshot() *consensus.Snapshot

Snapshot returns the summary the consensus goroutine published last.

func (*Runner) Start

func (r *Runner) Start(ctx context.Context, from types.Height) error

Start starts the engine at height from, which must be the application's head + 1. With ReplayWAL it first replays the write-ahead log records of that height (restoring the lock, the prepared certificate, the sent votes and the round), re-arms the live timers and sends the last own messages of the current view again.

func (*Runner) Stop

func (r *Runner) Stop() error

Stop stops the engine: the core state is discarded, armed timers and queued inputs are dropped. A later Start replays the write-ahead log.

func (*Runner) Submit

func (r *Runner) Submit(b *types.Block)

Submit hands a proposal of the block builder to the core.

func (*Runner) Vars

func (r *Runner) Vars() *consensus.Vars

Vars returns the state variables of the core (tests and simulation). It must be called from the goroutine that drives a manual runner.

type SelectionSourceObserver

type SelectionSourceObserver interface{ ObserveInputSelection(journal.SelectionRec) }

SelectionSourceObserver sees detached original conditional probes and the selected input after all lane mutexes are released. It must return promptly. This hook neither merges those mutexes nor draws additional randomness.

type SignerCallObserver

type SignerCallObserver interface{ ObserveSignerCall(journal.SignerCallRec) }

SignerCallObserver sees detached runner-to-signer/WAL call results, after the original operation. It does not enable functional replay or signing itself.

type SourceFinalizer

type SourceFinalizer interface {
	FinalizeBlockWithSource(*types.Block, types.Round, app.FinalizeSource) error
}

SourceFinalizer is an optional application bridge for original task ownership. The ordinary App and manual completion contracts remain unchanged. A bridge must preserve the original request and context, including cancellation.

type Timer

type Timer interface {
	// Stop prevents the timer from firing; it reports false if the timer
	// already fired or was stopped.
	Stop() bool
}

Timer is a timer of a Clock.

type TimerQueue

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

TimerQueue uses the runner's timer reservation and expiry implementation. It preserves full timeout context and already-queued expiries after cancel. Arm and cancel calls are serialized by the execution owner; clock expiry callbacks may run concurrently with that owner.

func NewTimerQueue

func NewTimerQueue(clock Clock) *TimerQueue

func (*TimerQueue) Arm

func (q *TimerQueue) Arm(a consensus.ArmTimer, from time.Duration)

func (*TimerQueue) Cancel

func (q *TimerQueue) Cancel(kinds []consensus.TimerKind) []consensus.ArmTimer

func (*TimerQueue) Clear

func (q *TimerQueue) Clear()

func (*TimerQueue) Pending

func (q *TimerQueue) Pending() bool

func (*TimerQueue) Pop

func (q *TimerQueue) Pop() (consensus.Timeout, bool)

func (*TimerQueue) PopRecorded

func (q *TimerQueue) PopRecorded(recorded consensus.Timeout) (consensus.Timeout, bool, error)

PopRecorded reproduces an observed dequeue order among already-queued expiries. Real timer callbacks can queue two overdue timers in a different order from their deadlines. This only consumes an existing full-context expiry; it never fires a timer, changes a deadline or manufactures input. Experiments without recorded ordering must use Pop instead.

func (*TimerQueue) Status

func (q *TimerQueue) Status() TimerQueueStatus

type TimerQueueStatus

type TimerQueueStatus struct{ Armed, Queued int }

TimerQueueStatus describes the finite execution horizon without advancing it.

type TimerSourceObserver

type TimerSourceObserver interface{ ObserveTimerSource(journal.TimerRec) }

TimerSourceObserver receives detached scheduler mutations independently of the functional journal. It runs after the timer mutex is released and must return promptly. Clock callbacks can deliver concurrently and out of order; Sequence records the original mutex order. Retaining or altering the value never changes timer ownership, input bytes or a legacy journal record. Zero Sequence marks exhausted observation metadata and is never reused. This optional diagnostic hook does not enable functional runtime replay.

type ValidatorHistoryLimits

type ValidatorHistoryLimits struct {
	Bytes   uint64 `json:"bytes,string"`
	Records uint64 `json:"records,string"`
}

ValidatorHistoryLimits bounds retained functional frames of an explicitly selected validator-start profile. It is not a heap, CPU or whole-disk cap. Reaching either bound halts progress while preserving the original history.

func DefaultValidatorHistoryLimits

func DefaultValidatorHistoryLimits() ValidatorHistoryLimits

DefaultValidatorHistoryLimits returns the finite experimental history cap. Normal progress also consumes it; it is not an uptime recommendation.

func (ValidatorHistoryLimits) Validate

func (limits ValidatorHistoryLimits) Validate() error

Validate checks explicit limits without changing them or selecting a profile.

type WALReadSourceObserver

type WALReadSourceObserver interface{ ObserveWALRead(journal.WALReadRec) }

WALReadSourceObserver sees detached original restore reads. It must return promptly. Reading evidence does not enable functional cache replay or prove an independently executed restored prefix, signer state or application state.

type WALState

type WALState struct {
	// LastEnd is the height of the last end-of-height record: the highest
	// height the core left after a new head; nil when there is none.
	LastEnd *types.Height
	// Commits are the commit requests in the log, in order, including those
	// of heights the core has already left.
	Commits []CommitRequest
	// contains filtered or unexported fields
}

WALState is what the start-up handshake reads from the write-ahead log.

func InspectWAL

func InspectWAL(fs fsys.FS, dir string) (WALState, error)

InspectWAL reads the write-ahead log in dir for the start-up handshake.

func InspectWALForConfig

func InspectWALForConfig(fs fsys.FS, dir string, cfg Config) (WALState, error)

InspectWALForConfig validates the retry profile before returning handshake actions. The caller must use the same configuration to start the runner; application refinalization must not precede this validation.

func (WALState) OpenValidatedWAL

func (s WALState) OpenValidatedWAL(opt wal.Options) (*wal.Log, wal.Recovery, error)

OpenValidatedWAL materializes the same strict prefix that passed the selected profile inspection. Ordinary InspectWAL results cannot open it.

Jump to

Keyboard shortcuts

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