chainselection

package
v0.67.0 Latest Latest
Warning

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

Go to latest
Published: Jul 24, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package chainselection implements multi-peer chain selection. It tracks the tip reported by each connected peer and, for Shelley-family headers, uses the Praos select view projected from the observed header: longer chain first, then the reference implementation's equal-length opcert/VRF tiebreaker when it is armed.

Genesis selection mode can be enabled at startup to prefer observed density during bootstrap before automatically falling back to Praos once the local tip is close enough to the best observed peer tip.

ChainSelector is the top-level type. It consumes PeerTipUpdateEvent events from chainsync, compares peer tips against the local tip, and emits ChainSwitchEvent on the EventBus when the selected peer changes. The node's Run loop listens for those events and repoints the active chainsync stream at the new best peer.

This package does not perform any block validation; it only selects which peer's chain to follow. Validation happens downstream in the ledger package after blocks are fetched from the selected peer.

Index

Constants

View Source
const (
	ChainEqual             = praos.ChainEqual
	ChainABetter           = praos.ChainABetter
	ChainBBetter           = praos.ChainBBetter
	ChainComparisonUnknown = praos.ChainComparisonUnknown
)
View Source
const (
	PeerTipUpdateEventType  event.EventType = "chainselection.peer_tip_update"
	PeerActivityEventType   event.EventType = "chainselection.peer_activity"
	PeerRollbackEventType   event.EventType = "chainselection.peer_rollback"
	ChainSwitchEventType    event.EventType = "chainselection.chain_switch"
	ChainSelectionEventType event.EventType = "chainselection.selection"
	PeerEvictedEventType    event.EventType = "chainselection.peer_evicted"

	// GenesisCorroborationFailedEventType is published when the densest
	// Genesis fast source cannot be corroborated by the configured minimum
	// number of independent peers, so it is denied chain selection (stalls)
	// rather than steering the local chain. Subscribers (peer governance,
	// operators) can use this to demote or investigate the source.
	GenesisCorroborationFailedEventType event.EventType = "chainselection.genesis_corroboration_failed"

	// GenesisModeExitedEventType is published when the selector transitions
	// from Genesis density-based selection back to Praos selection because the
	// local tip has caught up to within the Genesis window of the best known
	// peer tip.
	GenesisModeExitedEventType event.EventType = "chainselection.genesis_mode_exited"

	// ChainSelectedNoneEventType is published when the selector transitions to
	// having no selectable best peer (e.g. an uncorroborated Genesis fast source
	// is denied). It is the explicit selected-to-none transition that the
	// ChainSwitchEvent (which always carries a non-nil replacement) cannot
	// express, so subscribers can observe that selection has stalled. Ledger
	// application from uncorroborated peers is separately gated via
	// ShouldApplyIngress; this event is for observability of the stall.
	ChainSelectedNoneEventType event.EventType = "chainselection.selected_none"
)
View Source
const (
	PraosTiebreakerUnknown      = praos.PraosTiebreakerUnknown
	PraosTiebreakerUnrestricted = praos.PraosTiebreakerUnrestricted
	PraosTiebreakerRestricted   = praos.PraosTiebreakerRestricted
)
View Source
const (

	// DefaultMaxTrackedPeers is the maximum number of peers tracked by
	// the ChainSelector. When a new peer is added and the limit is reached,
	// the least-recently-updated peer is evicted. This bounds memory usage
	// and CPU cost of chain selection, preventing Sybil-based resource
	// exhaustion.
	DefaultMaxTrackedPeers = 200
)
View Source
const VRFOutputSize = praos.VRFOutputSize

VRFOutputSize is re-exported from consensus/praos.

Variables

This section is empty.

Functions

func GenesisWindowSlotsForParams added in v0.37.0

func GenesisWindowSlotsForParams(
	securityParam uint64,
	activeSlotsCoeff float64,
) uint64

GenesisWindowSlotsForParams returns the Genesis density window in slots. Shelley-style networks use 3k/f, where k is the security parameter and f is the active slot coefficient.

func GetVRFOutput

func GetVRFOutput(header gledger.BlockHeader) []byte

GetVRFOutput delegates to consensus/praos.

func PreferPraosCandidate added in v0.46.0

func PreferPraosCandidate(
	ours, cand PraosTiebreakerView,
) bool

PreferPraosCandidate delegates to consensus/praos.

Types

type ChainComparisonResult

type ChainComparisonResult = praos.ChainComparisonResult

ChainComparisonResult is aliased from consensus/praos.

func ComparePraosTips added in v0.46.0

func ComparePraosTips(
	tipA, tipB ochainsync.Tip,
	viewA, viewB PraosTiebreakerView,
) ChainComparisonResult

ComparePraosTips delegates to consensus/praos.

func CompareVRFOutputs

func CompareVRFOutputs(vrfA, vrfB []byte) ChainComparisonResult

CompareVRFOutputs delegates to consensus/praos.

type ChainSelectedNoneEvent added in v0.67.0

type ChainSelectedNoneEvent struct {
	PreviousConnectionId ouroboros.ConnectionId
	GenesisCorroboration bool
}

ChainSelectedNoneEvent is published when best-peer selection transitions from some peer to none (selection stalled).

Fields:

  • PreviousConnectionId: the peer that was selected before the stall (zero value if none was selected).
  • GenesisCorroboration: true when the stall is due to the Genesis corroboration gate (no corroborated peer), as opposed to simply having no eligible peers.

type ChainSelectionEvent

type ChainSelectionEvent struct {
	BestConnectionId ouroboros.ConnectionId
	BestTip          ochainsync.Tip
	PeerCount        int
	SwitchOccurred   bool
}

ChainSelectionEvent is published when chain selection evaluation completes.

type ChainSelector

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

ChainSelector tracks chain tips from multiple peers and selects the best chain using the active mode's comparison rules.

func NewChainSelector

func NewChainSelector(cfg ChainSelectorConfig) *ChainSelector

NewChainSelector creates a new ChainSelector with the given configuration.

func (*ChainSelector) EvaluateAndSwitch

func (cs *ChainSelector) EvaluateAndSwitch() bool

EvaluateAndSwitch evaluates all peer tips and switches to the best chain if it differs from the current best. Returns true if a switch occurred.

func (*ChainSelector) GenesisCorroborationActive added in v0.67.0

func (cs *ChainSelector) GenesisCorroborationActive() bool

GenesisCorroborationActive reports whether the Genesis corroboration gate is currently in effect. Callers use it to decide whether tip observation must be ordered synchronously with the apply-eligibility gate (see ShouldApplyIngress): the gate is only non-trivial while corroboration is active.

func (*ChainSelector) GenesisStatus added in v0.67.0

func (cs *ChainSelector) GenesisStatus() GenesisStatus

GenesisStatus returns a snapshot of current selection mode, Genesis window, selected fast source, and per-peer density/corroboration for observability.

func (*ChainSelector) GenesisWindowSlots added in v0.37.0

func (cs *ChainSelector) GenesisWindowSlots() uint64

GenesisWindowSlots returns the slot window used for Genesis density checks.

func (*ChainSelector) GetAllPeerTips

func (cs *ChainSelector) GetAllPeerTips() map[ouroboros.ConnectionId]*PeerChainTip

GetAllPeerTips returns a deep copy of all tracked peer tips.

func (*ChainSelector) GetBestPeer

func (cs *ChainSelector) GetBestPeer() *ouroboros.ConnectionId

GetBestPeer returns the connection ID of the peer with the best chain, or nil if no suitable peer is available.

func (*ChainSelector) GetPeerTip

func (cs *ChainSelector) GetPeerTip(
	connId ouroboros.ConnectionId,
) *PeerChainTip

GetPeerTip returns a deep copy of the chain tip for a specific peer. Returns nil if the peer is not tracked.

func (*ChainSelector) HandlePeerActivityEvent added in v0.27.7

func (cs *ChainSelector) HandlePeerActivityEvent(evt event.Event)

HandlePeerActivityEvent refreshes a peer's liveness on non-tip protocol activity such as keepalive responses.

func (*ChainSelector) HandlePeerRollbackEvent added in v0.61.2

func (cs *ChainSelector) HandlePeerRollbackEvent(evt event.Event)

HandlePeerRollbackEvent trims Genesis observed history and refreshes the tracked peer tip after a rollback.

func (*ChainSelector) HandlePeerTipUpdateEvent

func (cs *ChainSelector) HandlePeerTipUpdateEvent(evt event.Event)

HandlePeerTipUpdateEvent handles PeerTipUpdateEvent from the event bus.

func (*ChainSelector) PeerCount

func (cs *ChainSelector) PeerCount() int

PeerCount returns the number of peers being tracked.

func (*ChainSelector) RemovePeer

func (cs *ChainSelector) RemovePeer(connId ouroboros.ConnectionId)

RemovePeer removes a peer from tracking.

func (*ChainSelector) SelectBestChain

func (cs *ChainSelector) SelectBestChain() *ouroboros.ConnectionId

SelectBestChain evaluates all peer tips and returns the connection ID of the peer with the best chain.

func (*ChainSelector) SelectionMode added in v0.37.0

func (cs *ChainSelector) SelectionMode() SelectionMode

SelectionMode returns the selector's current mode.

func (*ChainSelector) SetConnectionEligible added in v0.55.0

func (cs *ChainSelector) SetConnectionEligible(
	connId ouroboros.ConnectionId,
	eligible bool,
)

SetConnectionEligible marks whether a peer connection is eligible for chain selection. Ineligible peers are skipped during best-peer evaluation. Calling this triggers a re-evaluation of the best peer.

eligible=true is the default (absent key), so we delete instead of storing it. This prevents a stale entry when an out-of-order eligible=true event arrives after RemovePeer has already cleaned up the maps.

func (*ChainSelector) SetConnectionPriority added in v0.55.0

func (cs *ChainSelector) SetConnectionPriority(
	connId ouroboros.ConnectionId,
	priority int,
)

SetConnectionPriority sets the selection priority for a peer connection. When two peers advertise the same chain tip, the peer with the higher priority wins. Calling this triggers a re-evaluation of the best peer.

priority=0 is the default (absent key), so we delete instead of storing it. This prevents a stale entry when an out-of-order priority=0 event arrives after RemovePeer has already cleaned up the maps.

func (*ChainSelector) SetLocalTip

func (cs *ChainSelector) SetLocalTip(tip ochainsync.Tip)

SetLocalTip updates the local chain tip for comparison.

It also records the last time the APPLIED local tip moved FORWARD (its block number advanced). The anti-flap incumbent pin uses this timestamp as a progress-aware escape: if the applied tip stops advancing while the pin is engaged, the pin releases. Same-tip updates and rollbacks deliberately do NOT reset the stall clock, so a stalled incumbent cannot keep the pin alive by re-reporting an unchanged tip. Forward progress after a rollback does reset the clock because the comparison is against the previous applied tip, not an all-time high-water mark.

func (*ChainSelector) SetSecurityParam

func (cs *ChainSelector) SetSecurityParam(k uint64)

SetSecurityParam updates the security parameter (k) dynamically. This allows the selector to use protocol parameters for density-based comparison.

func (*ChainSelector) ShouldApplyIngress added in v0.67.0

func (cs *ChainSelector) ShouldApplyIngress(
	connId ouroboros.ConnectionId,
) bool

ShouldApplyIngress reports whether headers/blocks from connId may be applied to the ledger. It returns false only while Genesis corroboration is active (Genesis mode with a positive threshold) and the peer is not corroborated, so an uncorroborated or divergent fast source is OBSERVED — its tips still feed corroboration via PeerTipUpdateEvent — but its blocks are not applied and it cannot steer the ledger. Enforcing the stall here (ledger application) rather than only at peer selection is required because ingress is gated on peer- governance eligibility, independent of the selected best peer: clearing the active chainsync driver alone would not stop an eligible uncorroborated peer's headers from being applied.

An unknown peer (no tips observed yet) is treated as uncorroborated and denied while the gate is active — fail closed until corroboration forms. Outside Genesis corroboration it always returns true (no behavior change).

func (*ChainSelector) Start

func (cs *ChainSelector) Start(ctx context.Context) error

Start begins the chain selector's background evaluation loop and subscribes to relevant events.

func (*ChainSelector) Stop

func (cs *ChainSelector) Stop()

Stop stops the chain selector.

func (*ChainSelector) TouchPeerActivity added in v0.27.7

func (cs *ChainSelector) TouchPeerActivity(connId ouroboros.ConnectionId)

func (*ChainSelector) UpdatePeerTip

func (cs *ChainSelector) UpdatePeerTip(
	connId ouroboros.ConnectionId,
	tip ochainsync.Tip,
	vrfOutput []byte,
) bool

UpdatePeerTip updates the chain tip for a specific peer and triggers evaluation if needed. The vrfOutput parameter is the VRF output from the tip block header, used for tie-breaking when chains have equal block number and slot.

Returns true if the tip was accepted, false if it was rejected as implausible. A tip is considered implausible if it claims a block number more than securityParam (k) blocks ahead of a reference point. For known peers, the reference is the peer's own previous tip; for new peers, the reference is the best known peer tip. This avoids rejecting legitimate peers during sync (where the local tip is far behind).

type ChainSelectorConfig

type ChainSelectorConfig struct {
	Logger             *slog.Logger
	EventBus           *event.EventBus
	EvaluationInterval time.Duration
	StaleTipThreshold  time.Duration
	SecurityParam      uint64
	GenesisMode        bool
	GenesisWindowSlots uint64
	// MinCorroboratingPeers is the number of distinct other eligible peers
	// that must report the same recent blocks as a candidate before that
	// candidate can drive chain selection in Genesis mode. It implements the
	// Ouroboros Genesis trust property that a fast (shallow) block source is
	// followed only while corroborated by independent peers. 0 disables
	// corroboration (density-only Genesis selection, the historical default).
	MinCorroboratingPeers int
	ConnectionLive        func(ouroboros.ConnectionId) bool
	ConnectionEligible    func(ouroboros.ConnectionId) bool
	ConnectionPriority    func(ouroboros.ConnectionId) int
	MaxTrackedPeers       int // 0 means use DefaultMaxTrackedPeers
	// DisableEventSubscriptions leaves EventBus configured for publishing
	// selector events but skips automatic input subscriptions. This is useful
	// for deterministic replay harnesses that feed input events synchronously.
	DisableEventSubscriptions bool
	// BlockfetchLatency returns the EWMA first-block latency for a
	// connection and whether any samples exist. Used only to choose a
	// peer when two peers advertise the exact same selected block.
	BlockfetchLatency func(ouroboros.ConnectionId) (time.Duration, bool)
}

ChainSelectorConfig holds configuration for the ChainSelector.

type ChainSwitchEvent

type ChainSwitchEvent struct {
	PreviousConnectionId ouroboros.ConnectionId
	NewConnectionId      ouroboros.ConnectionId
	NewTip               ochainsync.Tip
	PreviousTip          ochainsync.Tip
	ComparisonResult     ChainComparisonResult
	BlockDifference      int64
}

ChainSwitchEvent is published when the chain selector decides to switch to a different peer's chain.

Fields:

  • PreviousConnectionId: The connection ID of the peer we were following.
  • NewConnectionId: The connection ID of the peer we are now following.
  • NewTip: The chain tip of the new peer.
  • PreviousTip: The chain tip of the previous peer at the time of the switch.
  • ComparisonResult: Why the new chain is better than the previous chain.
  • BlockDifference: NewTip.BlockNumber - PreviousTip.BlockNumber.

type GenesisCorroborationFailedEvent added in v0.67.0

type GenesisCorroborationFailedEvent struct {
	ConnectionId       ouroboros.ConnectionId
	ObservedDensity    uint64
	CorroboratingPeers int
	RequiredPeers      int
	GenesisWindowSlots uint64
}

GenesisCorroborationFailedEvent is published when the densest Genesis fast source lacks the configured minimum corroboration from independent peers.

Fields:

  • ConnectionId: the uncorroborated fast source that was denied selection.
  • ObservedDensity: its observed block density within the Genesis window.
  • CorroboratingPeers: how many independent peers actually corroborate it.
  • RequiredPeers: the configured MinCorroboratingPeers threshold.
  • GenesisWindowSlots: the active Genesis density window in slots.

type GenesisModeExitedEvent added in v0.67.0

type GenesisModeExitedEvent struct {
	LocalSlot          uint64
	BestKnownSlot      uint64
	GenesisWindowSlots uint64
}

GenesisModeExitedEvent is published when the selector leaves Genesis mode.

Fields:

  • LocalSlot: the local tip slot at the time of exit.
  • BestKnownSlot: the best known selectable peer tip slot.
  • GenesisWindowSlots: the active Genesis density window in slots.

type GenesisPeerStatus added in v0.67.0

type GenesisPeerStatus struct {
	ConnectionId       ouroboros.ConnectionId
	ObservedDensity    uint64
	CorroboratingPeers int
	Corroborated       bool
	Selectable         bool
}

GenesisPeerStatus is the per-peer Genesis observability snapshot.

type GenesisStatus added in v0.67.0

type GenesisStatus struct {
	Mode                  SelectionMode
	WindowSlots           uint64
	MinCorroboratingPeers int
	BestSource            *ouroboros.ConnectionId
	Peers                 []GenesisPeerStatus
}

GenesisStatus is a point-in-time snapshot of Genesis selection state for observability (metrics, logs, operator tooling).

type PeerActivityEvent added in v0.27.7

type PeerActivityEvent struct {
	ConnectionId ouroboros.ConnectionId
}

PeerActivityEvent is published when a peer has recent protocol activity (for example, a keepalive response) without a tip change. This refreshes selector liveness for healthy but temporarily quiet peers.

type PeerChainTip

type PeerChainTip struct {
	ConnectionId ouroboros.ConnectionId
	Tip          ochainsync.Tip
	ObservedTip  ochainsync.Tip
	VRFOutput    []byte // VRF output from tip block for tie-breaking
	PraosView    PraosTiebreakerView
	LastUpdated  time.Time
	// contains filtered or unexported fields
}

PeerChainTip tracks the chain tip reported by a specific peer.

func NewPeerChainTip

func NewPeerChainTip(
	connId ouroboros.ConnectionId,
	tip ochainsync.Tip,
	vrfOutput []byte,
) *PeerChainTip

NewPeerChainTip creates a new PeerChainTip with the given connection ID, tip, and VRF output.

func (*PeerChainTip) ApplyRollback added in v0.37.0

func (p *PeerChainTip) ApplyRollback(
	point ocommon.Point,
	tip ochainsync.Tip,
)

ApplyRollback trims observed history at the rollback point and refreshes the peer tip to the chainsync tip reported with the rollback.

func (*PeerChainTip) IsStale

func (p *PeerChainTip) IsStale(threshold time.Duration) bool

IsStale returns true if the peer's tip hasn't been updated within the given duration.

func (*PeerChainTip) SelectionTip added in v0.27.7

func (p *PeerChainTip) SelectionTip() ochainsync.Tip

SelectionTip returns the best locally observed frontier for this peer. When available, prefer the latest block the peer has actually delivered to us over its remote advertised tip. This avoids switching to peers whose far-end tip is high while their chainsync cursor is still lagging.

func (*PeerChainTip) Touch added in v0.27.7

func (p *PeerChainTip) Touch()

Touch marks the peer as recently active without changing its advertised tip.

func (*PeerChainTip) UpdateTip

func (p *PeerChainTip) UpdateTip(tip ochainsync.Tip, vrfOutput []byte)

UpdateTip updates the peer's chain tip, VRF output, and last updated timestamp.

func (*PeerChainTip) UpdateTipWithObserved added in v0.27.7

func (p *PeerChainTip) UpdateTipWithObserved(
	tip ochainsync.Tip,
	observedTip ochainsync.Tip,
	vrfOutput []byte,
)

UpdateTipWithObserved updates both the remote advertised tip and the latest locally observed frontier for the peer.

func (*PeerChainTip) UpdateTipWithObservedPraosView added in v0.46.0

func (p *PeerChainTip) UpdateTipWithObservedPraosView(
	tip ochainsync.Tip,
	observedTip ochainsync.Tip,
	vrfOutput []byte,
	praosView PraosTiebreakerView,
)

UpdateTipWithObservedPraosView updates the remote advertised tip, the latest locally observed frontier, the VRF output, and the Praos tiebreaker view for the peer. Callers must provide a PraosTiebreakerView derived from observedTip and the supplied vrfOutput when that VRF output participates in the view. The view is stored as supplied; this method does not validate consistency, so an inconsistent view can make later chain-selection comparisons incorrect.

type PeerEvictedEvent added in v0.22.0

type PeerEvictedEvent struct {
	ConnectionId ouroboros.ConnectionId
}

PeerEvictedEvent is published when a tracked peer is evicted from the chain selector to make room for a new peer. Subscribers (e.g. connection manager) can use this to close the evicted peer's connection.

type PeerRollbackEvent added in v0.37.0

type PeerRollbackEvent struct {
	ConnectionId ouroboros.ConnectionId
	Point        ocommon.Point
	Tip          ochainsync.Tip
}

PeerRollbackEvent is published when an ingress-eligible chainsync peer reports a rollback. Point is the rollback point; Tip is the peer's current chainsync tip after the rollback.

type PeerTipUpdateEvent

type PeerTipUpdateEvent struct {
	ConnectionId ouroboros.ConnectionId
	Tip          ochainsync.Tip
	ObservedTip  ochainsync.Tip
	VRFOutput    []byte // VRF output from observed block header for tie-breaking
	PraosView    PraosTiebreakerView
}

PeerTipUpdateEvent is published when a peer's chain tip is updated via chainsync roll forward.

type PraosTiebreakerConfig added in v0.46.0

type PraosTiebreakerConfig = praos.PraosTiebreakerConfig

PraosTiebreakerConfig is aliased from consensus/praos.

func PraosTiebreakerConfigBeforeConway added in v0.46.0

func PraosTiebreakerConfigBeforeConway() PraosTiebreakerConfig

PraosTiebreakerConfigBeforeConway delegates to consensus/praos.

func PraosTiebreakerConfigConway added in v0.46.0

func PraosTiebreakerConfigConway() PraosTiebreakerConfig

PraosTiebreakerConfigConway delegates to consensus/praos.

func PraosTiebreakerConfigUnknown added in v0.46.0

func PraosTiebreakerConfigUnknown() PraosTiebreakerConfig

PraosTiebreakerConfigUnknown delegates to consensus/praos.

type PraosTiebreakerFlavor added in v0.46.0

type PraosTiebreakerFlavor = praos.PraosTiebreakerFlavor

PraosTiebreakerFlavor and its constants are type-aliased from consensus/praos so that chainselection.PraosTiebreakerFlavor and praos.PraosTiebreakerFlavor are the same type.

type PraosTiebreakerView added in v0.46.0

type PraosTiebreakerView = praos.PraosTiebreakerView

PraosTiebreakerView is aliased from consensus/praos.

func GetPraosTiebreakerView added in v0.46.0

func GetPraosTiebreakerView(
	header gledger.BlockHeader,
) (PraosTiebreakerView, bool)

GetPraosTiebreakerView delegates to consensus/praos.

func NewPraosTiebreakerViewFull added in v0.55.0

func NewPraosTiebreakerViewFull(
	tip ochainsync.Tip,
	issuer []byte,
	issueNo uint64,
	vrfOutput []byte,
	config PraosTiebreakerConfig,
) PraosTiebreakerView

NewPraosTiebreakerViewFull delegates to consensus/praos.

func PraosTiebreakerViewFromTip added in v0.46.0

func PraosTiebreakerViewFromTip(
	tip ochainsync.Tip,
	vrfOutput []byte,
	config PraosTiebreakerConfig,
) PraosTiebreakerView

PraosTiebreakerViewFromTip delegates to consensus/praos.

type SelectionMode added in v0.37.0

type SelectionMode uint8

SelectionMode describes the chain-selection strategy currently in use.

const (
	SelectionModePraos SelectionMode = iota
	SelectionModeGenesis
)

func (SelectionMode) String added in v0.37.0

func (m SelectionMode) String() string

Jump to

Keyboard shortcuts

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