Documentation
¶
Overview ¶
Package chain manages Dingo's blockchain state: the primary chain, any alternate (candidate) chains, fork detection, and rollback orchestration.
ChainManager is the top-level type. It owns the primary Chain, tracks alternate chains observed from peers, and emits events on the global EventBus when a fork is detected, when a rollback occurs, or when a new block is added.
Blocks are persisted through the database package; this package stores only the structural relationships between blocks and the metadata needed to compare competing chains. Chain selection itself (deciding which candidate becomes primary) lives in the chainselection package.
Key event types emitted from this package:
- ChainUpdateEventType — a block was added to the primary chain, or the primary chain was rolled back to a point
- ChainForkEventType — a fork was observed against the primary chain
Index ¶
- Constants
- Variables
- type BlockNotFitChainTipError
- type BlockNotMatchHeaderError
- type BlockNumberNotContiguousError
- type Chain
- func (c *Chain) AddBlock(block ledger.Block, txn *database.Txn) error
- func (c *Chain) AddBlockHeader(header ledger.BlockHeader) error
- func (c *Chain) AddBlockWithPoint(block ledger.Block, point ocommon.Point, txn *database.Txn) error
- func (c *Chain) AddBlockWithPointDeferred(block ledger.Block, point ocommon.Point, txn *database.Txn) (event.Event, error)
- func (c *Chain) AddBlocks(blocks []ledger.Block) error
- func (c *Chain) AddLocalBlock(block ledger.Block) error
- func (c *Chain) AddRawBlocks(blocks []RawBlock) error
- func (c *Chain) AddRawBlocksWithCallback(blocks []RawBlock, callback func(RawBlock, *database.Txn) error) error
- func (c *Chain) AddVerifiedBlockHeader(header ledger.BlockHeader) error
- func (c *Chain) BlockBeforeSlot(slotNumber uint64) (models.Block, error)
- func (c *Chain) BlockByPoint(point ocommon.Point, txn *database.Txn) (models.Block, error)
- func (c *Chain) ClearHeaders()
- func (c *Chain) FirstHeaderMatchesPoint(point ocommon.Point) bool
- func (c *Chain) FirstVerifiedHeaderMatchesPoint(point ocommon.Point) bool
- func (c *Chain) FromPoint(point ocommon.Point, inclusive bool) (*ChainIterator, error)
- func (c *Chain) FromPointContext(ctx context.Context, point ocommon.Point, inclusive bool) (*ChainIterator, error)
- func (c *Chain) FromPointReverse(point ocommon.Point, inclusive bool) (*ChainIterator, error)
- func (c *Chain) FromPointReverseContext(ctx context.Context, point ocommon.Point, inclusive bool) (*ChainIterator, error)
- func (c *Chain) HeaderCount() int
- func (c *Chain) HeaderRange(count int) (ocommon.Point, ocommon.Point)
- func (c *Chain) HeaderTip() ochainsync.Tip
- func (c *Chain) IntersectPoints(count int) []ocommon.Point
- func (c *Chain) MaxQueuedHeaders() int
- func (c *Chain) NotifyIterators()
- func (c *Chain) PointAtDepth(depth uint64) (point ocommon.Point, found bool, err error)
- func (c *Chain) PublishPendingChainUpdates()
- func (c *Chain) RecentPoints(count int) []ocommon.Point
- func (c *Chain) Rollback(point ocommon.Point) error
- func (c *Chain) RollbackDeferred(point ocommon.Point) ([]event.Event, error)
- func (c *Chain) RollbackUnbounded(point ocommon.Point) error
- func (c *Chain) Tip() ochainsync.Tip
- func (c *Chain) ValidateRollback(point ocommon.Point) error
- type ChainBlockEvent
- type ChainForkEvent
- type ChainId
- type ChainIterator
- type ChainIteratorResult
- type ChainManager
- func (cm *ChainManager) BlockByPoint(point ocommon.Point, txn *database.Txn) (models.Block, error)
- func (cm *ChainManager) Chain(id ChainId) *Chain
- func (cm *ChainManager) NewChain(point ocommon.Point) (*Chain, error)
- func (cm *ChainManager) NewChainFromIntersect(points []ocommon.Point) (*Chain, error)
- func (cm *ChainManager) PrimaryChain() *Chain
- func (cm *ChainManager) RewindPrimaryChainAtStartup(point ocommon.Point) error
- func (cm *ChainManager) RewindPrimaryChainToPoint(point ocommon.Point) error
- func (cm *ChainManager) SecurityParam() int
- func (cm *ChainManager) SecurityParamConfigured() bool
- func (cm *ChainManager) SetLedger(ledgerState interface{ ... }) error
- type ChainRollbackEvent
- type RawBlock
Constants ¶
const ( ChainUpdateEventType = "chain.update" ChainForkEventType = "chain.fork_detected" )
const DefaultBlockCacheCapacity = 10000
DefaultBlockCacheCapacity is the default maximum number of blocks to cache. At ~20KB per block, 10K blocks uses ~200MB of memory.
const DefaultMaxQueuedHeaders = 10_000
DefaultMaxQueuedHeaders is the minimum header queue capacity (floor). When the ledger security parameter K is configured, the limit is max(2*K, DefaultMaxQueuedHeaders).
Variables ¶
var ( ErrIntersectNotFound = errors.New("chain intersect not found") ErrRollbackBeyondEphemeralChain = errors.New( "cannot rollback ephemeral chain beyond memory buffer", ) // ErrInvalidSecurityParam is returned by ChainManager.SetLedger when the // ledger reports a non-positive Ouroboros security parameter K. ErrInvalidSecurityParam = errors.New( "ledger security parameter K must be positive", ) // ErrSecurityParamNotConfigured is returned when an operation requires K // but ChainManager.SetLedger has not been called successfully. ErrSecurityParamNotConfigured = errors.New( "chain manager security parameter K is not configured; " + "call SetLedger with a ledger that returns a positive SecurityParam()", ) ErrRollbackExceedsSecurityParam = errors.New( "rollback depth exceeds security parameter K", ) // ErrRollbackPointNotOnChain is returned when a rollback target resolves // to a block that this chain no longer holds at that block index. // Rolled-back blocks stay resolvable through the manager's retained block // cache with their original index, so a point another fork has since // overwritten still looks valid; rolling back to it truncates to a stale // index and moves the tip to a block the chain does not have, splicing a // continuation onto a parent that is absent from the chain (issue #3005). // It wraps models.ErrBlockNotFound so existing callers keep treating an // unusable rollback target as "point not found" and re-intersect. ErrRollbackPointNotOnChain = fmt.Errorf( "%w: rollback point is not on this chain", models.ErrBlockNotFound, ) ErrIteratorChainTip = errors.New( "chain iterator is at chain tip", ) ErrIteratorChainOrigin = errors.New( "chain iterator is at chain origin", ) ErrHeaderQueueFull = errors.New( "header queue at maximum capacity", ) )
Functions ¶
This section is empty.
Types ¶
type BlockNotFitChainTipError ¶ added in v0.4.6
type BlockNotFitChainTipError struct {
// contains filtered or unexported fields
}
func NewBlockNotFitChainTipError ¶ added in v0.4.6
func NewBlockNotFitChainTipError( blockHash string, blockPrevHash string, tipHash string, ) BlockNotFitChainTipError
func (BlockNotFitChainTipError) BlockHash ¶ added in v0.22.0
func (e BlockNotFitChainTipError) BlockHash() string
func (BlockNotFitChainTipError) BlockPrevHash ¶ added in v0.22.0
func (e BlockNotFitChainTipError) BlockPrevHash() string
func (BlockNotFitChainTipError) Error ¶ added in v0.4.6
func (e BlockNotFitChainTipError) Error() string
func (BlockNotFitChainTipError) TipHash ¶ added in v0.22.0
func (e BlockNotFitChainTipError) TipHash() string
type BlockNotMatchHeaderError ¶ added in v0.4.6
type BlockNotMatchHeaderError struct {
// contains filtered or unexported fields
}
func NewBlockNotMatchHeaderError ¶ added in v0.4.6
func NewBlockNotMatchHeaderError( blockHash string, headerHash string, ) BlockNotMatchHeaderError
func (BlockNotMatchHeaderError) Error ¶ added in v0.4.6
func (e BlockNotMatchHeaderError) Error() string
type BlockNumberNotContiguousError ¶ added in v0.61.2
type BlockNumberNotContiguousError struct {
// contains filtered or unexported fields
}
BlockNumberNotContiguousError is returned when a block/header's self-reported block number does not follow its parent's. The block number is a redundant header field that chain selection uses to pick the longer chain, so it must be bound to the actual chain length: a header that chains onto the tip (matching prev hash) but claims a non-contiguous block number is rejected so a forged (e.g. inflated) number cannot win chain selection.
func NewBlockNumberNotContiguousError ¶ added in v0.61.2
func NewBlockNumberNotContiguousError( blockHash string, blockNumber uint64, parentNumber uint64, ) BlockNumberNotContiguousError
func (BlockNumberNotContiguousError) BlockHash ¶ added in v0.61.2
func (e BlockNumberNotContiguousError) BlockHash() string
func (BlockNumberNotContiguousError) BlockNumber ¶ added in v0.61.2
func (e BlockNumberNotContiguousError) BlockNumber() uint64
func (BlockNumberNotContiguousError) Error ¶ added in v0.61.2
func (e BlockNumberNotContiguousError) Error() string
func (BlockNumberNotContiguousError) ParentNumber ¶ added in v0.61.2
func (e BlockNumberNotContiguousError) ParentNumber() uint64
type Chain ¶
type Chain struct {
// contains filtered or unexported fields
}
func (*Chain) AddBlockHeader ¶ added in v0.4.6
func (c *Chain) AddBlockHeader(header ledger.BlockHeader) error
func (*Chain) AddBlockWithPoint ¶ added in v0.27.0
func (c *Chain) AddBlockWithPoint( block ledger.Block, point ocommon.Point, txn *database.Txn, ) error
AddBlockWithPoint adds a block using a caller-supplied point. This avoids recomputing the block hash when the caller already has the canonical slot/hash pair from a validated upstream source such as blockfetch.
func (*Chain) AddBlockWithPointDeferred ¶ added in v0.70.7
func (c *Chain) AddBlockWithPointDeferred( block ledger.Block, point ocommon.Point, txn *database.Txn, ) (event.Event, error)
AddBlockWithPointDeferred adds a block exactly like AddBlockWithPoint but, instead of publishing the resulting chain.update inline, enqueues it on the chain-level sequencer under c.mutex and returns it (the return value is retained for callers/tests that inspect it; publication happens only through the sequencer). The ledger's chainsync/blockfetch drain calls this while holding chainsyncBlockfetchMutex and then drains the sequencer after the mutex is released, so the (potentially backpressured) delivery never runs under the lock. Publishing inline under that mutex is what deadlocked the node: a terminal chain.update subscriber that stopped draining parked the publish with the mutex held, handleEventChainsync then blocked on the same mutex, and the ledger.chainsync buffer filled (blinklabs-io/dingo preview freeze). Enqueuing under c.mutex also keeps this add ordered against a concurrent chainsync rollback in true chain-mutation order; see the pendingUpdates field and PublishPendingChainUpdates. A returned event with an empty Type means there is nothing to publish.
func (*Chain) AddLocalBlock ¶ added in v0.70.0
AddLocalBlock adds a locally forged block without comparing it to queued peer headers. A successful local block invalidates those pending headers; the actual chain-tip and block-number checks remain mandatory.
func (*Chain) AddRawBlocks ¶ added in v0.22.0
AddRawBlocks adds a batch of pre-extracted blocks to the chain.
func (*Chain) AddRawBlocksWithCallback ¶ added in v0.37.0
func (c *Chain) AddRawBlocksWithCallback( blocks []RawBlock, callback func(RawBlock, *database.Txn) error, ) error
AddRawBlocksWithCallback adds a batch of pre-extracted blocks to the chain and runs the callback in the same transaction after each block is persisted. Callers can use this to atomically attach additional blob-side state, such as offset indexes, without reopening the immutable DB on resume.
The callback executes with c.mutex and c.manager.mutex locked, inside the active blob transaction, and BEFORE c.currentTip / c.tipBlockIndex are updated for the just-persisted block. As a result:
- The callback must not call back into Chain or ChainManager methods that acquire those same locks (e.g., c.Tip(), c.HeaderTip(), c.BlockByPoint()) — doing so will deadlock.
- Tip-state observed via fields read under those locks reflects the pre-update tip, not the block being added.
Error semantics: a callback error aborts the entire current batch, not just the offending block. addRawBlocks drives the loop inside txn.Do, which rolls back every block persisted by that transaction when the callback returns non-nil. Callers should make per-batch decisions idempotent so a retry on a later batch does not duplicate effects from a partial earlier attempt.
func (*Chain) AddVerifiedBlockHeader ¶ added in v0.63.0
func (c *Chain) AddVerifiedBlockHeader(header ledger.BlockHeader) error
func (*Chain) BlockBeforeSlot ¶ added in v0.61.1
BlockBeforeSlot returns the highest-slot block before slotNumber on this chain. It walks the chain index instead of scanning blob keys so retained fork or synthetic blobs cannot be returned as canonical blocks.
func (*Chain) BlockByPoint ¶ added in v0.4.6
func (*Chain) ClearHeaders ¶ added in v0.22.0
func (c *Chain) ClearHeaders()
ClearHeaders removes all queued block headers. This is used when the active peer changes and stale headers from the previous peer's chainsync session no longer fit the current chain tip.
func (*Chain) FirstHeaderMatchesPoint ¶ added in v0.27.0
func (*Chain) FirstVerifiedHeaderMatchesPoint ¶ added in v0.63.0
func (*Chain) FromPoint ¶
FromPoint returns a ChainIterator starting at the specified point. If inclusive is true, the iterator will start at the specified point. Otherwise it will start at the point following the specified point
func (*Chain) FromPointContext ¶ added in v0.49.1
func (c *Chain) FromPointContext( ctx context.Context, point ocommon.Point, inclusive bool, ) (*ChainIterator, error)
FromPointContext returns a ChainIterator that inherits cancellation from ctx.
func (*Chain) FromPointReverse ¶ added in v0.47.0
FromPointReverse returns a ChainIterator that walks backward from the specified point toward chain origin. If inclusive is true the iterator yields the start point first; otherwise it yields the block preceding it. Blocking Next calls on a reverse iterator do not wait for new blocks; once origin is reached, Next returns ErrIteratorChainOrigin.
func (*Chain) FromPointReverseContext ¶ added in v0.49.1
func (c *Chain) FromPointReverseContext( ctx context.Context, point ocommon.Point, inclusive bool, ) (*ChainIterator, error)
FromPointReverseContext returns a reverse ChainIterator that inherits cancellation from ctx.
func (*Chain) HeaderCount ¶ added in v0.4.6
func (*Chain) HeaderRange ¶ added in v0.4.6
func (*Chain) HeaderTip ¶ added in v0.4.6
func (c *Chain) HeaderTip() ochainsync.Tip
func (*Chain) IntersectPoints ¶ added in v0.27.1
IntersectPoints returns up to count points in descending order for chainsync FindIntersect. It keeps a dense window near the tip and then samples exponentially older blocks so lagging peers can still find a recent common point without falling all the way back to origin.
func (*Chain) MaxQueuedHeaders ¶ added in v0.22.0
MaxQueuedHeaders returns the maximum number of headers that may be queued. The limit is the larger of securityParam * 2 and DefaultMaxQueuedHeaders. Using the default as a floor ensures the queue is large enough for the chainsync/blockfetch pipeline: headers arrive much faster than blocks, so the queue must accommodate several blockfetch batches worth of headers beyond the accumulation threshold to avoid drops that break the header chain.
func (*Chain) NotifyIterators ¶ added in v0.22.0
func (c *Chain) NotifyIterators()
NotifyIterators wakes all blocked iterators waiting for new blocks. Call this after a DB transaction that adds blocks has been committed to ensure iterators see the newly visible data.
func (*Chain) PointAtDepth ¶ added in v0.70.2
PointAtDepth returns the point depth blocks behind the current tip. A depth of zero returns the tip. When depth reaches beyond the retained chain, the immutable point is origin and found is false.
Unlike RecentPoints, this performs one indexed lookup regardless of depth, which is important for consensus reads at the security-parameter boundary.
func (*Chain) PublishPendingChainUpdates ¶ added in v0.70.7
func (c *Chain) PublishPendingChainUpdates()
PublishPendingChainUpdates drains the chain-level sequencer, publishing every queued deferred event strictly FIFO -- i.e. in chain-mutation order. It is safe to call from any goroutine and must be called only after the caller has released its outer ledger mutex (chainsyncMutex / chainsyncBlockfetchMutex); a drain may publish an event another handler enqueued, so publishing under a ledger mutex would reintroduce the drain deadlock.
publishMutex serializes concurrent drains so their Publish calls preserve pop order; pendingUpdatesMutex is dropped before each (potentially back-pressured) Publish so an enqueue never blocks behind delivery.
func (*Chain) RecentPoints ¶ added in v0.22.0
RecentPoints returns up to count recent chain points in descending order (most recent first) using the in-memory chain state. This includes the current tip and, for non-persistent chains, any blocks stored in the in-memory buffer. For persistent chains, it walks backwards through the database using block indices.
This method is useful for building intersection point lists that remain accurate even when the blob store has not yet been fully flushed, since the chain's in-memory tip is always up-to-date.
func (*Chain) RollbackDeferred ¶ added in v0.70.7
RollbackDeferred rewinds the chain exactly like Rollback but, instead of publishing the resulting chain.update / chain.fork events inline, enqueues them on the chain-level sequencer under c.mutex and returns them (the return value is retained for callers/tests that inspect it; publication happens only through the sequencer). The ledger's rollbackChainAndStateDeferred calls this while holding chainsyncMutex and then drains the sequencer after the mutex is released, so delivery (which can backpressure on a full subscriber buffer) never runs under the lock. Publishing inline under chainsyncMutex risks the same drain deadlock described on AddBlockWithPointDeferred.
Enqueuing under c.mutex is what keeps this rollback's chain.update correctly ordered against a concurrent blockfetch add: the two run under different ledger mutexes and once flushed independent per-handler queues, so a rollback that mutated the chain after an add could otherwise be published before it. The shared sequencer preserves true chain-mutation order across both. See the pendingUpdates field and PublishPendingChainUpdates.
func (*Chain) RollbackUnbounded ¶ added in v0.70.7
RollbackUnbounded behaves like Rollback, but does not require the security parameter K to be configured and does not reject a rollback for exceeding it. It exists for reconciling a persistent chain against a caller's own prior local state -- e.g. the primary chain against the ledger's own applied tip at startup, before ChainManager.SetLedger has configured K -- where the depth is whatever the two locally-durable stores drifted, not a peer-supplied point subject to the same-security-guarantee K exists to enforce. Follow-on rollbacks initiated by an untrusted peer must always use Rollback, never this.
func (*Chain) Tip ¶
func (c *Chain) Tip() ochainsync.Tip
func (*Chain) ValidateRollback ¶ added in v0.27.7
ValidateRollback verifies that Rollback(point) would be accepted without mutating chain state. Callers can use this to avoid applying external side effects before the chain's rollback pre-checks have run.
type ChainForkEvent ¶ added in v0.21.0
type ChainForkEvent struct {
// ForkPoint is the common ancestor where the chains diverge
ForkPoint ocommon.Point
// ForkDepth is the number of blocks rolled back from the canonical chain
ForkDepth uint64
// AlternateHead is the tip of the competing chain
AlternateHead ocommon.Point
// CanonicalHead is the tip of the current canonical chain
CanonicalHead ocommon.Point
}
ChainForkEvent is emitted when a chain fork is detected. This allows subscribers to monitor fork activity for alerting and metrics.
type ChainIterator ¶
type ChainIterator struct {
// contains filtered or unexported fields
}
func (*ChainIterator) Cancel ¶ added in v0.20.0
func (ci *ChainIterator) Cancel()
func (*ChainIterator) Next ¶
func (ci *ChainIterator) Next(blocking bool) (*ChainIteratorResult, error)
type ChainIteratorResult ¶
type ChainManager ¶ added in v0.11.0
type ChainManager struct {
// contains filtered or unexported fields
}
func NewManager ¶ added in v0.11.0
func NewManager( db *database.Database, eventBus *event.EventBus, promRegistry ...prometheus.Registerer, ) (*ChainManager, error)
func (*ChainManager) BlockByPoint ¶ added in v0.11.0
func (*ChainManager) Chain ¶ added in v0.11.0
func (cm *ChainManager) Chain(id ChainId) *Chain
func (*ChainManager) NewChain ¶ added in v0.11.0
func (cm *ChainManager) NewChain(point ocommon.Point) (*Chain, error)
NewChain creates a new Chain that forks from the primary chain at the specified point. This is useful for managing outbound ChainSync clients
func (*ChainManager) NewChainFromIntersect ¶ added in v0.11.0
func (cm *ChainManager) NewChainFromIntersect( points []ocommon.Point, ) (*Chain, error)
NewChainFromIntersect creates a new Chain that forks the primary chain at the latest common point.
func (*ChainManager) PrimaryChain ¶ added in v0.11.0
func (cm *ChainManager) PrimaryChain() *Chain
func (*ChainManager) RewindPrimaryChainAtStartup ¶ added in v0.70.7
func (cm *ChainManager) RewindPrimaryChainAtStartup( point ocommon.Point, ) error
RewindPrimaryChainAtStartup prunes the persistent primary chain back to the specified point without requiring the security parameter K to be configured, for reconciling the primary chain against the ledger's own applied tip during startup -- before SetLedger has run (issue #3516 review). It still publishes ChainRollbackEvent/ChainForkEvent and wakes/marks chain iterators exactly once, the same as RewindPrimaryChainToPoint; it only skips the K bound, since a startup gap between two already-durable local stores is not the untrusted-peer scenario that bound protects against. Never call this for a rollback an untrusted peer requested -- use RewindPrimaryChainToPoint (or SecurityParamConfigured to check readiness first) for anything reachable from chainsync.
func (*ChainManager) RewindPrimaryChainToPoint ¶ added in v0.27.5
func (cm *ChainManager) RewindPrimaryChainToPoint( point ocommon.Point, ) error
RewindPrimaryChainToPoint prunes the persistent primary chain back to the specified point. It is used by the live primary-chain/ledger divergence reconciler, once ChainManager.SetLedger has configured the security parameter K.
It shares its bound and side effects with a live Chain.Rollback rather than pruning blocks directly: the rewind is rejected outright, without touching any state, when it would exceed K, and a successful rewind publishes ChainRollbackEvent (and a ChainForkEvent when it actually removed blocks) and wakes/marks any chain iterators exactly once, the same signal NtC clients rely on for a live rollback. Previously this deleted blocks directly with no depth bound and no rollback/iterator signal, silently truncating the chain out from under downstream consumers (issue #3516).
Do not call this before SetLedger: it returns ErrSecurityParamNotConfigured rather than silently pruning without a bound. RewindPrimaryChainAtStartup is for that case.
func (*ChainManager) SecurityParam ¶ added in v0.70.7
func (cm *ChainManager) SecurityParam() int
SecurityParam returns the configured Ouroboros security parameter K, or zero before SetLedger has run. SetLedger is not confined to startup — the state database can be reloaded while the node is serving chainsync — so readers outside the manager lock must go through here.
func (*ChainManager) SecurityParamConfigured ¶ added in v0.70.7
func (cm *ChainManager) SecurityParamConfigured() bool
SecurityParamConfigured reports whether SetLedger has configured the security parameter K yet. A caller reachable both before and after startup (e.g. the primary-chain/ledger divergence reconciler) uses this to choose between RewindPrimaryChainAtStartup and RewindPrimaryChainToPoint.
This reads securityParam without cm.mutex, matching SetLedger's own unguarded write: both rely on SetLedger completing, during single- threaded startup composition, before any goroutine that could reach either side of this field exists (node.go constructs the ouroboros layer -- and with it every chainsync-reachable goroutine -- only after SetLedger returns).
func (*ChainManager) SetLedger ¶ added in v0.20.0
func (cm *ChainManager) SetLedger( ledgerState interface{ SecurityParam() int }, ) error
SetLedger configures the Ouroboros security parameter K from the ledger. K must be positive; otherwise SetLedger returns ErrInvalidSecurityParam and leaves the previous configuration unchanged.