chain

package
v0.70.7 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

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

View Source
const (
	ChainUpdateEventType = "chain.update"
	ChainForkEventType   = "chain.fork_detected"
)
View Source
const DefaultBlockCacheCapacity = 10000

DefaultBlockCacheCapacity is the default maximum number of blocks to cache. At ~20KB per block, 10K blocks uses ~200MB of memory.

View Source
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

View Source
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 (BlockNumberNotContiguousError) ParentNumber added in v0.61.2

func (e BlockNumberNotContiguousError) ParentNumber() uint64

type Chain

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

func (*Chain) AddBlock

func (c *Chain) AddBlock(
	block ledger.Block,
	txn *database.Txn,
) error

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) AddBlocks added in v0.5.0

func (c *Chain) AddBlocks(blocks []ledger.Block) error

func (*Chain) AddLocalBlock added in v0.70.0

func (c *Chain) AddLocalBlock(block ledger.Block) error

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

func (c *Chain) AddRawBlocks(blocks []RawBlock) error

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

func (c *Chain) BlockBeforeSlot(slotNumber uint64) (models.Block, error)

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 (c *Chain) BlockByPoint(
	point ocommon.Point,
	txn *database.Txn,
) (models.Block, error)

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 (c *Chain) FirstHeaderMatchesPoint(point ocommon.Point) bool

func (*Chain) FirstVerifiedHeaderMatchesPoint added in v0.63.0

func (c *Chain) FirstVerifiedHeaderMatchesPoint(point ocommon.Point) bool

func (*Chain) FromPoint

func (c *Chain) FromPoint(
	point ocommon.Point,
	inclusive bool,
) (*ChainIterator, error)

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

func (c *Chain) FromPointReverse(
	point ocommon.Point,
	inclusive bool,
) (*ChainIterator, error)

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 (c *Chain) HeaderCount() int

func (*Chain) HeaderRange added in v0.4.6

func (c *Chain) HeaderRange(count int) (ocommon.Point, ocommon.Point)

func (*Chain) HeaderTip added in v0.4.6

func (c *Chain) HeaderTip() ochainsync.Tip

func (*Chain) IntersectPoints added in v0.27.1

func (c *Chain) IntersectPoints(count int) []ocommon.Point

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

func (c *Chain) MaxQueuedHeaders() int

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

func (c *Chain) PointAtDepth(
	depth uint64,
) (point ocommon.Point, found bool, err error)

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

func (c *Chain) RecentPoints(count int) []ocommon.Point

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) Rollback

func (c *Chain) Rollback(point ocommon.Point) error

func (*Chain) RollbackDeferred added in v0.70.7

func (c *Chain) RollbackDeferred(
	point ocommon.Point,
) ([]event.Event, error)

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

func (c *Chain) RollbackUnbounded(point ocommon.Point) error

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

func (c *Chain) ValidateRollback(point ocommon.Point) error

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 ChainBlockEvent

type ChainBlockEvent struct {
	Point ocommon.Point
	Block models.Block
}

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 ChainId added in v0.11.0

type ChainId uint64

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 ChainIteratorResult struct {
	Point    ocommon.Point
	Block    models.Block
	Rollback bool
}

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 (cm *ChainManager) BlockByPoint(
	point ocommon.Point,
	txn *database.Txn,
) (models.Block, error)

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.

type ChainRollbackEvent

type ChainRollbackEvent struct {
	Point            ocommon.Point
	RolledBackBlocks []models.Block // Blocks that were rolled back, in reverse order (newest first)
}

type RawBlock added in v0.22.0

type RawBlock struct {
	Slot        uint64
	Hash        []byte
	BlockNumber uint64
	Type        uint
	PrevHash    []byte
	Cbor        []byte
}

RawBlock contains pre-extracted block fields for direct storage without requiring a full ledger.Block decode.

Jump to

Keyboard shortcuts

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