Documentation
¶
Overview ¶
Package chainselection implements Ouroboros Praos multi-peer chain selection. It tracks the tip reported by each connected peer and picks the best candidate chain to follow using the standard Praos rules: longer chain wins, then density within the security window, then VRF tie-break.
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
- func GenesisWindowSlotsForParams(securityParam uint64, activeSlotsCoeff float64) uint64
- func GetVRFOutput(header ledger.BlockHeader) []byte
- func IsBetterChain(newTip, currentTip ochainsync.Tip) bool
- func IsBetterChainWithDensity(newTip, currentTip ochainsync.Tip, ...) bool
- func IsBetterChainWithVRF(newTip, currentTip ochainsync.Tip, ...) bool
- func IsSignificantlyBetter(newTip, currentTip ochainsync.Tip, minBlockDiff uint64) bool
- type ChainComparisonResult
- func CompareChains(tipA, tipB ochainsync.Tip) ChainComparisonResult
- func CompareChainsWithDensity(tipA, tipB ochainsync.Tip, blocksInWindowA, blocksInWindowB uint64) ChainComparisonResult
- func CompareChainsWithVRF(tipA, tipB ochainsync.Tip, blocksInWindowA, blocksInWindowB uint64, ...) ChainComparisonResult
- func CompareHeaders(headerA, headerB ledger.BlockHeader) ChainComparisonResult
- func CompareVRFOutputs(vrfA, vrfB []byte) ChainComparisonResult
- type ChainSelectionEvent
- type ChainSelector
- func (cs *ChainSelector) EvaluateAndSwitch() bool
- func (cs *ChainSelector) GenesisWindowSlots() uint64
- func (cs *ChainSelector) GetAllPeerTips() map[ouroboros.ConnectionId]*PeerChainTip
- func (cs *ChainSelector) GetBestPeer() *ouroboros.ConnectionId
- func (cs *ChainSelector) GetPeerTip(connId ouroboros.ConnectionId) *PeerChainTip
- func (cs *ChainSelector) HandlePeerActivityEvent(evt event.Event)
- func (cs *ChainSelector) HandlePeerTipUpdateEvent(evt event.Event)
- func (cs *ChainSelector) PeerCount() int
- func (cs *ChainSelector) RemovePeer(connId ouroboros.ConnectionId)
- func (cs *ChainSelector) SelectBestChain() *ouroboros.ConnectionId
- func (cs *ChainSelector) SelectionMode() SelectionMode
- func (cs *ChainSelector) SetLocalTip(tip ochainsync.Tip)
- func (cs *ChainSelector) SetSecurityParam(k uint64)
- func (cs *ChainSelector) Start(ctx context.Context) error
- func (cs *ChainSelector) Stop()
- func (cs *ChainSelector) TouchPeerActivity(connId ouroboros.ConnectionId)
- func (cs *ChainSelector) UpdatePeerTip(connId ouroboros.ConnectionId, tip ochainsync.Tip, vrfOutput []byte) bool
- type ChainSelectorConfig
- type ChainSwitchEvent
- type PeerActivityEvent
- type PeerChainTip
- func (p *PeerChainTip) ApplyRollback(point ocommon.Point, tip ochainsync.Tip)
- func (p *PeerChainTip) IsStale(threshold time.Duration) bool
- func (p *PeerChainTip) SelectionTip() ochainsync.Tip
- func (p *PeerChainTip) Touch()
- func (p *PeerChainTip) UpdateTip(tip ochainsync.Tip, vrfOutput []byte)
- func (p *PeerChainTip) UpdateTipWithObserved(tip ochainsync.Tip, observedTip ochainsync.Tip, vrfOutput []byte)
- type PeerEvictedEvent
- type PeerRollbackEvent
- type PeerTipUpdateEvent
- type SelectionMode
Constants ¶
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" )
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 )
const VRFOutputSize = 64
VRFOutputSize is the expected size of a VRF output in bytes.
Variables ¶
This section is empty.
Functions ¶
func GenesisWindowSlotsForParams ¶ added in v0.37.0
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 ledger.BlockHeader) []byte
GetVRFOutput extracts the VRF output from a block header. Returns nil if the header type is not supported or doesn't contain VRF data.
The VRF output is used for tie-breaking in chain selection when two chains have equal block numbers and equal density. The chain with the LOWER VRF output (lexicographically) wins.
func IsBetterChain ¶
func IsBetterChain(newTip, currentTip ochainsync.Tip) bool
IsBetterChain returns true if newTip represents a better chain than currentTip according to Ouroboros Praos rules.
func IsBetterChainWithDensity ¶
func IsBetterChainWithDensity( newTip, currentTip ochainsync.Tip, blocksInWindowNew, blocksInWindowCurrent uint64, ) bool
IsBetterChainWithDensity returns true if newTip represents a better chain than currentTip according to Ouroboros Praos density-based rules.
func IsBetterChainWithVRF ¶
func IsBetterChainWithVRF( newTip, currentTip ochainsync.Tip, blocksInWindowNew, blocksInWindowCurrent uint64, vrfOutputNew, vrfOutputCurrent []byte, ) bool
IsBetterChainWithVRF returns true if newTip represents a better chain than currentTip according to the complete Ouroboros Praos rules including VRF.
func IsSignificantlyBetter ¶
func IsSignificantlyBetter( newTip, currentTip ochainsync.Tip, minBlockDiff uint64, ) bool
IsSignificantlyBetter returns true if newTip is better than currentTip by at least the specified minimum block difference. This can be used to avoid frequent chain switches for marginal improvements.
Types ¶
type ChainComparisonResult ¶
type ChainComparisonResult int
ChainComparisonResult indicates the result of comparing two chains.
const ( ChainEqual ChainComparisonResult = 0 ChainABetter ChainComparisonResult = 1 ChainBBetter ChainComparisonResult = -1 ChainComparisonUnknown ChainComparisonResult = 2 )
func CompareChains ¶
func CompareChains(tipA, tipB ochainsync.Tip) ChainComparisonResult
CompareChains compares two chain tips according to Ouroboros Praos rules:
- Higher block number wins (longer chain)
- At equal block number, lower slot wins (denser chain)
- At equal block number AND equal slot (a slot battle between two pools' competing forges), lower block hash wins.
Rule 3 is the deterministic tiebreak the chainsync handler needs to pick a side at a slot battle. Without it, two competing forges at the same slot+length collapse to ChainEqual, the handler refuses to switch in either direction, and the local chain permanently keeps whichever block was adopted first. The losing block's VRF output then keeps folding into the local evolving nonce, and the drift compounds across epochs until eta0 disagreement breaks header verification at the next epoch boundary.
Block hash is a deterministic function of every input the full Praos select-view comparator (chain length → slot → self-issued → issuer hash → VRF output → opcert counter) considers, so two peers independently applying "lower hash wins" to the same pair of competing tips arrive at the same decision and the chains converge.
Returns:
- ChainABetter (1) if tipA represents a better chain
- ChainBBetter (-1) if tipB represents a better chain
- ChainEqual (0) only when both tips refer to the same block
func CompareChainsWithDensity ¶
func CompareChainsWithDensity( tipA, tipB ochainsync.Tip, blocksInWindowA, blocksInWindowB uint64, ) ChainComparisonResult
CompareChainsWithDensity compares two chain tips using density-based comparison according to Ouroboros Praos rules: 1. Higher block number wins (longer chain) 2. At equal block number, higher density wins (more blocks in 3k/f window) 3. At equal density, lower slot wins (tie-breaker)
The blocksInWindowA and blocksInWindowB parameters represent the number of blocks in the stability window (3k/f slots) for each chain tip. These values should be computed by counting blocks within the window ending at each tip.
Returns:
- ChainABetter (1) if tipA represents a better chain
- ChainBBetter (-1) if tipB represents a better chain
- ChainEqual (0) if they are equal
func CompareChainsWithVRF ¶
func CompareChainsWithVRF( tipA, tipB ochainsync.Tip, blocksInWindowA, blocksInWindowB uint64, vrfOutputA, vrfOutputB []byte, ) ChainComparisonResult
CompareChainsWithVRF compares two chain tips using the complete Ouroboros Praos chain selection algorithm including VRF tie-breaking: 1. Higher block number wins (longer chain) 2. At equal block number, higher density wins (more blocks in 3k/f window) 3. At equal density, lower VRF output wins (deterministic tie-breaker)
The vrfOutputA and vrfOutputB parameters are the VRF outputs from the tip blocks of each chain. These should be extracted using GetVRFOutput() from the block headers. If VRF outputs are nil, falls back to slot-based comparison.
Returns:
- ChainABetter (1) if tipA represents a better chain
- ChainBBetter (-1) if tipB represents a better chain
- ChainEqual (0) if they are equal
func CompareHeaders ¶
func CompareHeaders(headerA, headerB ledger.BlockHeader) ChainComparisonResult
CompareHeaders compares two block headers for chain selection tie-breaking using their VRF outputs. This is a convenience wrapper around GetVRFOutput and CompareVRFOutputs.
func CompareVRFOutputs ¶
func CompareVRFOutputs(vrfA, vrfB []byte) ChainComparisonResult
CompareVRFOutputs compares two VRF outputs for chain selection tie-breaking. Returns:
- ChainABetter (1) if vrfA is lower (A wins)
- ChainBBetter (-1) if vrfB is lower (B wins)
- ChainEqual (0) if equal, either is nil, or either has invalid length
Per Ouroboros Praos: the chain with the LOWER VRF output wins the tie-break. Both outputs must be exactly VRFOutputSize (64) bytes; outputs of any other length are treated the same as nil to prevent truncated values from winning.
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 according to Ouroboros Praos 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) 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) 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) SetLocalTip ¶
func (cs *ChainSelector) SetLocalTip(tip ochainsync.Tip)
SetLocalTip updates the local chain tip for comparison.
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) 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) 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
MinSwitchBlockDiff uint64
SecurityParam uint64
GenesisMode bool
GenesisWindowSlots uint64
ConnectionLive func(ouroboros.ConnectionId) bool
ConnectionEligible func(ouroboros.ConnectionId) bool
ConnectionPriority func(ouroboros.ConnectionId) int
MaxTrackedPeers int // 0 means use DefaultMaxTrackedPeers
// BlockfetchLatency returns the EWMA first-block latency for a
// connection and whether any samples exist. Used as a tiebreaker
// when two peers share the same SelectionTip and VRF output.
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 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
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.
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
}
PeerTipUpdateEvent is published when a peer's chain tip is updated via chainsync roll forward.
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