forging

package
v0.46.2 Latest Latest
Warning

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

Go to latest
Published: May 15, 2026 License: Apache-2.0 Imports: 32 Imported by: 0

Documentation

Overview

Package forging contains types and utilities for block production.

Block Propagation

Block propagation to peers is handled automatically by the chain package. When a forged block is added via chain.AddBlock(), the method closes the chain's waitingChan (see chain/chain.go lines 174-180), which signals any blocking ChainIterators. The ouroboros chainsync server (see ouroboros/chainsync.go chainsyncServerRequestNext) waits on these iterators via ChainIterator.Next(true), which blocks on waitingChan when at chain tip. When the channel is closed, iterators wake up and deliver the new block to connected peers via RollForward messages.

This means there is no need for explicit propagation logic when forging blocks - adding the block to the chain automatically triggers delivery to all subscribed chainsync clients.

Package forging provides block production functionality for Cardano SPOs.

Index

Constants

View Source
const SlotBattleEventType = event.EventType("forging.slot_battle")

SlotBattleEventType is the event type for slot battles (competing blocks)

Variables

View Source
var ErrVRFKeyHashMismatch = errors.New("VRF key hash mismatch")

Functions

func CurrentKESPeriod added in v0.43.0

func CurrentKESPeriod(genesis *shelley.ShelleyGenesis, now time.Time) (uint64, error)

CurrentKESPeriod returns the KES period that wall-clock time `now` falls in, given the chain's Shelley genesis. It is a pure function so callers can drive it with synthetic genesis and timestamps in tests.

Semantics:

  • Before SystemStart, period is 0 (the chain has not begun).
  • SlotLength is a rational number of seconds per slot; the math is done in big.Rat to avoid losing precision on chains where slot length is a fraction (e.g. 1/20s on devnets).
  • Returns an error if SlotsPerKESPeriod is non-positive, SlotLength is non-positive, or the computed period overflows uint64.

Types

type BlockBroadcaster

type BlockBroadcaster interface {
	// AddBlock adds a block to the local chain and propagates to peers.
	AddBlock(block ledger.Block, cbor []byte) error
}

BlockBroadcaster submits built blocks to the chain.

type BlockBuilder

type BlockBuilder interface {
	// BuildBlock creates a new block for the given slot.
	// Returns the block and its CBOR encoding.
	BuildBlock(slot uint64, kesPeriod uint64) (ledger.Block, []byte, error)
}

BlockBuilder constructs blocks from mempool transactions.

type BlockBuilderConfig

type BlockBuilderConfig struct {
	Logger          *slog.Logger
	Mempool         MempoolProvider
	PParamsProvider ProtocolParamsProvider
	ChainTip        ChainTipProvider
	EpochNonce      EpochNonceProvider
	Credentials     *PoolCredentials
	// TxValidator optionally re-validates each transaction against
	// the current ledger state before including it in a block.
	// When nil, ledger-level re-validation is skipped (but
	// intra-block double-spend detection still applies).
	TxValidator TxValidator
}

BlockBuilderConfig holds configuration for the DefaultBlockBuilder.

type BlockForgedObserver added in v0.46.2

type BlockForgedObserver func(
	block ledger.Block,
	cbor []byte,
	latency time.Duration,
)

BlockForgedObserver observes blocks after they are successfully built, before chain adoption is attempted.

type BlockForger

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

BlockForger coordinates block production for a stake pool.

func NewBlockForger

func NewBlockForger(cfg ForgerConfig) (*BlockForger, error)

NewBlockForger creates a new block forger.

func (*BlockForger) IsRunning

func (f *BlockForger) IsRunning() bool

IsRunning returns true if the forger is currently running.

func (*BlockForger) RecordSlotBattle

func (f *BlockForger) RecordSlotBattle()

RecordSlotBattle increments the slot battles counter. This is called from external components (e.g., LedgerState) when a slot battle is detected.

func (*BlockForger) SignBlockHeader

func (f *BlockForger) SignBlockHeader(
	kesPeriod uint64,
	headerBytes []byte,
) ([]byte, error)

SignBlockHeader signs a block header with KES.

func (*BlockForger) SlotTracker

func (f *BlockForger) SlotTracker() *SlotTracker

SlotTracker returns the forger's slot tracker, which can be used by other components (e.g., chainsync) to detect slot battles.

func (*BlockForger) Start

func (f *BlockForger) Start(ctx context.Context) error

Start begins the block forging process. The provided context controls the forger's lifecycle.

func (*BlockForger) Stop

func (f *BlockForger) Stop()

Stop stops the block forging process. It blocks until the runLoop goroutine has exited.

func (*BlockForger) VRFProofForSlot

func (f *BlockForger) VRFProofForSlot(
	slot uint64,
	epochNonce []byte,
) ([]byte, []byte, error)

VRFProofForSlot generates a VRF proof for leader election at the given slot. Returns (proof, output, error).

type ChainTipProvider

type ChainTipProvider interface {
	Tip() ochainsync.Tip
}

ChainTipProvider provides access to the current chain tip.

type DefaultBlockBuilder

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

DefaultBlockBuilder implements BlockBuilder using LedgerState components.

func NewDefaultBlockBuilder

func NewDefaultBlockBuilder(cfg BlockBuilderConfig) (*DefaultBlockBuilder, error)

NewDefaultBlockBuilder creates a new DefaultBlockBuilder.

func (*DefaultBlockBuilder) BuildBlock

func (b *DefaultBlockBuilder) BuildBlock(
	slot uint64,
	kesPeriod uint64,
) (ledger.Block, []byte, error)

BuildBlock creates a new block for the given slot. Returns the block and its CBOR encoding.

type EpochNonceProvider

type EpochNonceProvider interface {
	// CurrentEpoch returns the current epoch number.
	CurrentEpoch() uint64
	// EpochForSlot returns the epoch containing the given slot.
	EpochForSlot(slot uint64) (uint64, error)
	// EpochNonce returns the nonce for the given epoch.
	EpochNonce(epoch uint64) []byte
}

EpochNonceProvider provides the epoch nonce for VRF proof generation.

type ForgedBlockRecord

type ForgedBlockRecord struct {
	BlockHash []byte
}

ForgedBlockRecord stores the hash of a block we forged for a given slot.

type ForgerConfig

type ForgerConfig struct {
	Mode         Mode
	Logger       *slog.Logger
	SlotDuration time.Duration

	// Production mode configuration
	Credentials      *PoolCredentials
	LeaderChecker    LeaderChecker
	BlockBuilder     BlockBuilder
	BlockBroadcaster BlockBroadcaster
	BlockForged      BlockForgedObserver
	SlotClock        SlotClockProvider

	// ForgeSyncToleranceSlots controls how far the local chain can lag the
	// upstream tip before forging is skipped. Zero uses the default.
	ForgeSyncToleranceSlots uint64
	// ForgeStaleGapThresholdSlots controls when to log an error if the
	// chain tip is far ahead of the slot clock. Zero uses the default.
	ForgeStaleGapThresholdSlots uint64

	// Prometheus metrics registry (optional)
	PromRegistry prometheus.Registerer
}

ForgerConfig holds configuration for the block forger.

type LeaderChecker

type LeaderChecker interface {
	// ShouldProduceBlock returns true if this pool is the leader for the slot.
	ShouldProduceBlock(slot uint64) bool
	// NextLeaderSlot returns the next slot where this pool is leader.
	NextLeaderSlot(fromSlot uint64) (uint64, bool)
}

LeaderChecker determines if the pool should produce a block for a given slot.

type LedgerView added in v0.43.0

type LedgerView interface {
	// PoolRegistrationVRFKeyHash returns the VRF key hash recorded on
	// the most recent active pool registration certificate for poolID.
	// found is false when the pool has no on-chain registration yet.
	PoolRegistrationVRFKeyHash(poolID [28]byte) (vrfKeyHash [32]byte, found bool, err error)
	// LatestOpCertSequence returns the highest opcert IssueNumber
	// observed on chain for poolID. found is false when on-chain
	// counter tracking is not implemented or this pool has never
	// minted a block.
	LatestOpCertSequence(poolID [28]byte) (sequence uint64, found bool, err error)
}

LedgerView is the subset of ledger state the post-startup credential cross-check needs. The forging package depends on it as a small interface so the package itself stays free of a ledger dependency, and tests can drive the logic with a fake.

type MempoolProvider

type MempoolProvider interface {
	Transactions() []MempoolTransaction
}

MempoolProvider provides access to mempool transactions.

type MempoolTransaction

type MempoolTransaction struct {
	Hash string
	Cbor []byte
	Type uint
}

MempoolTransaction represents a transaction in the mempool.

type Mode

type Mode int

Mode represents the forging mode.

const (
	// ModeDev is a simplified mode where the node produces all blocks on a
	// fixed interval without real VRF/KES. Used for single-node devnets.
	ModeDev Mode = iota

	// ModeProduction uses real VRF leader election and KES signing.
	// Requires loaded pool credentials.
	ModeProduction
)

type OpCert

type OpCert struct {
	KESVKey     []byte // KES verification key (32 bytes)
	IssueNumber uint64 // Certificate sequence number
	KESPeriod   uint64 // KES period when certificate was created
	Signature   []byte // Cold key signature (64 bytes)
	ColdVKey    []byte // Cold verification key (32 bytes)
}

OpCert represents an operational certificate that binds a KES key to a pool.

type PoolCredentials

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

PoolCredentials holds the cryptographic keys required for block production. All keys are loaded using Bursa from standard cardano-cli format files. Fields are unexported to enforce thread-safe access via the mutex.

func NewPoolCredentials

func NewPoolCredentials() *PoolCredentials

NewPoolCredentials creates an empty PoolCredentials instance.

func (*PoolCredentials) GetKESPeriod

func (pc *PoolCredentials) GetKESPeriod() uint64

GetKESPeriod returns the current KES period of the loaded key. Returns 0 if the KES key is not loaded.

func (*PoolCredentials) GetKESVKey

func (pc *PoolCredentials) GetKESVKey() []byte

GetKESVKey returns a copy of the KES verification key.

func (*PoolCredentials) GetOpCert

func (pc *PoolCredentials) GetOpCert() *OpCert

GetOpCert returns a copy of the operational certificate. Returns nil if no certificate is loaded.

func (*PoolCredentials) GetPoolID

func (pc *PoolCredentials) GetPoolID() lcommon.PoolId

GetPoolID returns the pool ID (Blake2b-224 of cold vkey).

func (*PoolCredentials) GetVRFSKey

func (pc *PoolCredentials) GetVRFSKey() []byte

GetVRFSKey returns a copy of the VRF secret key (seed).

func (*PoolCredentials) GetVRFVKey

func (pc *PoolCredentials) GetVRFVKey() []byte

GetVRFVKey returns a copy of the VRF verification key.

func (*PoolCredentials) IsLoaded

func (pc *PoolCredentials) IsLoaded() bool

IsLoaded returns true if all credentials have been loaded.

func (*PoolCredentials) KESSign

func (pc *PoolCredentials) KESSign(period uint64, message []byte) ([]byte, error)

KESSign signs a message with the KES key at the specified ABSOLUTE period.

IMPORTANT: Callers must ensure UpdateKESPeriod(period) was called before KESSign to evolve the key to the correct period. The kes.Sign function expects the key to already be at the relative period within the opcert window when an opcert is loaded.

func (*PoolCredentials) LoadFromFiles

func (pc *PoolCredentials) LoadFromFiles(
	vrfSKeyPath string,
	kesSKeyPath string,
	opCertPath string,
) error

LoadFromFiles loads all pool credentials from the specified file paths. Uses Bursa to parse cardano-cli format key files.

func (*PoolCredentials) OpCertExpiryPeriod

func (pc *PoolCredentials) OpCertExpiryPeriod() uint64

OpCertExpiryPeriod returns the KES period at which the OpCert expires. For depth 6, max periods = 2^6 = 64, so expiry = startPeriod + 64.

func (*PoolCredentials) PeriodsRemaining

func (pc *PoolCredentials) PeriodsRemaining(currentPeriod uint64) uint64

PeriodsRemaining returns how many KES periods remain before expiry.

func (*PoolCredentials) UpdateKESPeriod

func (pc *PoolCredentials) UpdateKESPeriod(period uint64) error

UpdateKESPeriod evolves the KES key to the specified ABSOLUTE period. The secret key itself tracks the relative period within the opcert window, so we translate chain KES periods by subtracting the opcert start period when an opcert is loaded.

func (*PoolCredentials) VRFProve

func (pc *PoolCredentials) VRFProve(alpha []byte) ([]byte, []byte, error)

VRFProve generates a VRF proof for leader election. alpha should be MkInputVrf(slot, epochNonce).

func (*PoolCredentials) ValidateAgainstLedger added in v0.43.0

func (pc *PoolCredentials) ValidateAgainstLedger(
	view LedgerView,
) (registered, vrfMatched bool, err error)

ValidateAgainstLedger cross-checks the loaded credentials against ledger state once it is available. It is best-effort: a missing pool registration is not fatal because operators commonly stage their keys before submitting the registration certificate.

Three return values describe the outcome:

  • registered: true if the pool registration was found on chain.
  • vrfMatched: true if registered AND the on-chain VRF key hash matched our loaded VRF verification key. False otherwise (also false when registered is false or the VRF verification key is unavailable, e.g. for a seed-only VRF skey).
  • err: a non-nil error means the ledger view disagrees with the loaded credentials. Normal networks refuse startup for these; devnet callers may choose to warn on ErrVRFKeyHashMismatch.

func (*PoolCredentials) ValidateKESPeriod added in v0.43.0

func (pc *PoolCredentials) ValidateKESPeriod(
	genesis *shelley.ShelleyGenesis,
	now time.Time,
) error

ValidateKESPeriod checks that the loaded operational certificate's KES period is plausible at wall-clock time `now`, given the chain's Shelley genesis. A non-nil result means the node should refuse to start: either the opcert claims a period that hasn't started yet (rotated key staged too early, or wrong network) or the opcert has expired and needs to be rotated.

The protocol-level expiry uses MaxKESEvolutions from genesis rather than the raw 2^depth ceiling, so this matches the chain's view of when an opcert stops being valid.

func (*PoolCredentials) ValidateOpCert

func (pc *PoolCredentials) ValidateOpCert() error

ValidateOpCert validates that the operational certificate matches the KES key and that the cold key signature over the certificate body is valid.

type ProtocolParamsProvider

type ProtocolParamsProvider interface {
	GetCurrentPParams() lcommon.ProtocolParameters
	// ProtocolParamsForSlot returns the pparams that should govern a
	// block forged at the given slot. When the slot is in an epoch
	// beyond a scheduled fork that has not yet been applied to the
	// in-memory ledger state, the returned pparams are the
	// post-fork pparams. The forger uses this to produce
	// era-correct blocks at fork boundaries.
	ProtocolParamsForSlot(slot uint64) lcommon.ProtocolParameters
}

ProtocolParamsProvider provides access to protocol parameters.

type SlotBattleEvent

type SlotBattleEvent struct {
	// Slot is the slot number where the battle occurred
	Slot uint64
	// LocalBlockHash is the hash of our locally forged block (if any)
	LocalBlockHash []byte
	// RemoteBlockHash is the hash of the competing block from peers
	RemoteBlockHash []byte
	// Won indicates whether our local block was selected for the chain
	Won bool
}

SlotBattleEvent is emitted when the node detects competing blocks for the same slot, either from receiving an external block while preparing to forge or when detecting a fork at the same slot height.

type SlotClockProvider

type SlotClockProvider interface {
	// CurrentSlot returns the current slot number based on wall-clock time.
	CurrentSlot() (uint64, error)
	// SlotsPerKESPeriod returns the number of slots in a KES period.
	SlotsPerKESPeriod() uint64
	// ChainTipSlot returns the slot number of the current chain tip.
	ChainTipSlot() uint64
	// NextSlotTime returns the wall-clock time when the next slot begins.
	NextSlotTime() (time.Time, error)
	// UpstreamTipSlot returns the latest known tip slot from upstream peers.
	// Returns 0 if no upstream tip is known.
	UpstreamTipSlot() uint64
}

SlotClockProvider provides current slot information from the slot clock.

type SlotTracker

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

SlotTracker is a thread-safe tracker for recently forged block slots and their hashes. It allows chainsync to detect slot battles when an incoming block from a peer occupies a slot for which the local node has already forged a block.

func NewSlotTracker

func NewSlotTracker() *SlotTracker

NewSlotTracker creates a new SlotTracker with the default capacity.

func NewSlotTrackerWithCapacity

func NewSlotTrackerWithCapacity(maxSlots int) *SlotTracker

NewSlotTrackerWithCapacity creates a new SlotTracker with the given maximum capacity.

func (*SlotTracker) Len

func (st *SlotTracker) Len() int

Len returns the number of tracked forged slots.

func (*SlotTracker) RecordForgedBlock

func (st *SlotTracker) RecordForgedBlock(slot uint64, blockHash []byte)

RecordForgedBlock records that the local node forged a block with the given hash at the given slot. If the tracker is at capacity, the oldest entry is evicted.

func (*SlotTracker) WasForgedByUs

func (st *SlotTracker) WasForgedByUs(
	slot uint64,
) (blockHash []byte, ok bool)

WasForgedByUs checks whether the local node forged a block for the given slot. If so, it returns the block hash and true. Otherwise it returns nil, false.

type TxValidator

type TxValidator interface {
	ValidateTx(tx ledger.Transaction) error
}

TxValidator re-validates a transaction against the current ledger state at block assembly time. This catches transactions whose inputs have been consumed since they entered the mempool, protocol parameter changes, or other state mutations that invalidate previously-accepted transactions.

Jump to

Keyboard shortcuts

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