rewardstate

package
v0.66.0 Latest Latest
Warning

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

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

Documentation

Overview

Package rewardstate holds reward_live_stake and reward snapshot metadata query logic shared by the sqlite, mysql, and postgres metadata plugins. The SQL is identical across backends except for a handful of dialect literals (integer cast type, "transaction" table quoting, boolean literals, and whether a bound parameter needs an explicit cast). Those literals are defined once in internal/sqldialect and exposed here through the thin wrappers below, so this package cannot drift from internal/stakequery and internal/accounthistory when a backend is added or corrected.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddStakeRef

func AddStakeRef(
	refs map[string]CredentialSlotRef,
	ref models.StakeCredentialRef,
	slot uint64,
)

AddStakeRef merges one (credential, slot) pair into refs, keeping the highest slot recorded for that credential. A ref with an empty key (no staking credential on the UTxO/output) is ignored.

func ClaimFallbackSnapshot added in v0.66.0

func ClaimFallbackSnapshot(
	db *gorm.DB,
	snapshot *models.RewardSnapshot,
	txn types.Txn,
) (bool, error)

ClaimFallbackSnapshot atomically reserves the (epoch, snapshot_type) reward snapshot marker for a fallback (non-authoritative) capture. snapshot must carry Authoritative=false. It returns proceed=false when an authoritative snapshot already occupies the slot, so the caller must abandon the fallback capture instead of overwriting it.

The claim is an INSERT ... ON CONFLICT DO NOTHING followed, on conflict, by a locking (SELECT ... FOR UPDATE) recheck. The row lock is what a concurrent authoritative writer blocks on under MySQL/Postgres READ COMMITTED, closing the check-then-write race; SQLite drops the lock clause but its single-writer transaction semantics provide the same serialization. A prior non-authoritative row (e.g. a slot-clock provisional) is replaced in place under the held lock.

That lock only survives between statements while a transaction is open, so the whole claim MUST run in one transaction. When the caller supplies an open transaction (txn != nil) db already carries it; otherwise the claim is wrapped in db.Transaction so a transactionless call cannot silently drop the lock after the recheck and clobber an authoritative row a concurrent SaveSnapshot committed in between. This mirrors the txn handling in DeleteInputsForEpoch and DeleteState{After,Before}* below.

func ClaimFallbackSnapshotGuard added in v0.66.0

func ClaimFallbackSnapshotGuard(
	db *gorm.DB,
	epoch uint64,
	snapshotType string,
) (bool, uint, error)

ClaimFallbackSnapshotGuard serializes a fallback snapshot capture that has no reward-input bundle against the authoritative capture without leaving a reward_snapshot row behind. It claims the same unique (epoch, snapshot_type) key used by SaveSnapshot. When the key is absent, it inserts a temporary row and returns its ID; the caller must delete that row in the same transaction after its other snapshot writes are staged. When a provisional row already exists, it is locked and left untouched. An authoritative row refuses the fallback.

The insert-or-lock sequence must remain inside the caller's open transaction: the temporary row is the lockable key that makes a concurrent authoritative SaveSnapshot wait until the fallback either commits or rolls back.

func CreditLiveStakeDelta

func CreditLiveStakeDelta(
	db *gorm.DB,
	ref models.StakeCredentialRef,
	amount uint64,
	slot uint64,
) (bool, error)

CreditLiveStakeDelta adds amount to an existing reward_live_stake row's reward_stake and total_stake in a single UPDATE, avoiding the full account SELECT + UTxO SUM + upsert that RefreshLiveStakeAggregate performs. This is safe only for a pure reward-stake credit: pool delegation, registered, and utxo_stake are left untouched, and the row's existing utxo_stake + (old reward_stake + amount) must still equal the new total_stake, which holds because total_stake is only ever set from a full refresh's utxoTotal+rewardStake sum.

reward_stake/total_stake are types.Uint64 columns stored as decimal-string TEXT (see types.Uint64.Value) on every backend, so the update casts to the backend's native integer type, adds, and casts back to a string type rather than relying on column affinity or implicit numeric-to-string conversion.

CreditLiveStakeDelta must run inside an active transaction. MySQL and Postgres take a SELECT ... FOR UPDATE row lock for the overflow check; SQLite drops that clause, but its single-writer transaction semantics provide the required serialization. All callers use a caller-supplied transaction or db.Transaction so the check and UPDATE remain one atomic operation.

Returns true if a row was updated; false (with no error) when no reward_live_stake row exists yet for this credential, in which case the caller must fall back to RefreshLiveStakeAggregate.

func CurrentTipSlot

func CurrentTipSlot(db *gorm.DB) (uint64, error)

CurrentTipSlot returns the metadata tip slot using the caller's transaction. A fresh database has no tip row and therefore returns slot zero.

func DedupePoolKeyHashes added in v0.66.0

func DedupePoolKeyHashes(poolKeyHashes [][]byte) [][]byte

DedupePoolKeyHashes returns poolKeyHashes with duplicates removed.

func DeleteInputsForEpoch added in v0.66.0

func DeleteInputsForEpoch(db *gorm.DB, epoch uint64, txn types.Txn) error

DeleteInputsForEpoch deletes reward-calculation input rows for an epoch.

func DeleteOutputsForEpoch added in v0.66.0

func DeleteOutputsForEpoch(db *gorm.DB, epoch uint64, txn types.Txn) error

DeleteOutputsForEpoch deletes reward-calculation output rows for an epoch.

func DeleteStateAfterSlot

func DeleteStateAfterSlot(
	db *gorm.DB,
	slot uint64,
	txn types.Txn,
) error

DeleteStateAfterSlot deletes reward-state rows captured from rolled-back blocks. When txn is non-nil, db is used as-is; otherwise the deletes are wrapped in their own transaction.

func DeleteStateBeforeEpoch

func DeleteStateBeforeEpoch(
	db *gorm.DB,
	epoch uint64,
	txn types.Txn,
) error

DeleteStateBeforeEpoch deletes reward-state rows older than the retained snapshot window. When txn is non-nil, db is used as-is; otherwise the deletes are wrapped in their own transaction.

func GetAccountOutputs added in v0.66.0

func GetAccountOutputs(db *gorm.DB, epoch uint64) ([]*models.RewardAccountOutput, error)

GetAccountOutputs retrieves per-account reward calculation outputs.

func GetAccountsActiveAtSlot added in v0.66.0

func GetAccountsActiveAtSlot[C AccountCertificateHistory](
	refs []models.StakeCredentialRef,
	slot uint64,
	txn types.Txn,
	resolveReadDB func(types.Txn) (*gorm.DB, error),
	fetchCertificateHistory func(
		*gorm.DB,
		[]models.StakeCredentialRef,
		uint64,
	) (C, error),
) (map[string]struct{}, error)

GetAccountsActiveAtSlot returns credentials active according to registration and deregistration certificate history at or before slot.

func GetAdaPots

func GetAdaPots(
	db *gorm.DB,
	epoch uint64,
) (*models.RewardAdaPots, error)

GetAdaPots retrieves reward-related ADA pots for an epoch.

func GetPoolInputs

func GetPoolInputs(
	db *gorm.DB,
	epoch uint64,
) ([]*models.RewardPoolInput, error)

GetPoolInputs retrieves all per-pool reward inputs for an epoch.

func GetPoolOutputs added in v0.66.0

func GetPoolOutputs(db *gorm.DB, epoch uint64) ([]*models.RewardPoolOutput, error)

GetPoolOutputs retrieves per-pool reward calculation outputs.

func GetSnapshot

func GetSnapshot(
	db *gorm.DB,
	epoch uint64,
	snapshotType string,
) (*models.RewardSnapshot, error)

GetSnapshot retrieves reward snapshot metadata for an epoch.

func GetStakeInputs added in v0.66.0

func GetStakeInputs(db *gorm.DB, epoch uint64) ([]*models.RewardStakeInput, error)

GetStakeInputs retrieves all per-credential reward inputs for an epoch.

func GroupRefsBySlot

func GroupRefsBySlot(
	refs map[string]CredentialSlotRef,
) (map[uint64][]models.StakeCredentialRef, error)

GroupRefsBySlot validates every ref in refs and groups them by the slot at which their reward-live-stake aggregate should be recomputed, skipping refs with no stake credential key. Callers use the returned map to batch-fetch certificate state (batchFetchCerts) once per slot before calling RefreshLiveStakeAggregate for each ref.

func LiveStakeNeedsBackfill

func LiveStakeNeedsBackfill(db *gorm.DB) (bool, error)

LiveStakeNeedsBackfill reports whether any canonical account or live-UTxO credential is missing from reward_live_stake. Testing for missing keys, not merely an empty aggregate, also repairs upgraded databases where post-upgrade writes populated only part of the table before the first restart.

func RebuildLiveStake

func RebuildLiveStake(db *gorm.DB, slot uint64, utxoJoinHint string) error

RebuildLiveStake fully repopulates the reward_live_stake table from current account/UTxO/certificate state. Callers are responsible for transaction scoping (db is used as-is, with no internal transaction/savepoint).

utxoJoinHint is an optional backend-specific fragment inserted immediately after `LEFT JOIN utxo` (e.g. sqlite's `INDEXED BY <index>` query-planner hint); pass "" when the backend has no equivalent syntax.

The dialect casts below are dispatched once per call via integerCastType, transactionTableName, and boolLiteral: this is the single shared SQL that replaces three near-identical per-backend copies, one of which (mysql) had drifted to omit the CAST on account.reward for the reward_stake column that postgres already applied. Casting account.reward uniformly here removes that drift by construction.

func RefreshLiveStakeAggregate

func RefreshLiveStakeAggregate(
	db *gorm.DB,
	ref models.StakeCredentialRef,
	slot uint64,
	poolDelegationCache PoolDelegationCache,
) error

RefreshLiveStakeAggregate recomputes and upserts the reward_live_stake row for a single stake credential from current account/UTxO state. This is the per-credential engine shared by RebuildLiveStake's full rebuild and every incremental refresh path (account changes, UTxO create/spend, rollback).

poolDelegationCache is the (optional) batch-fetched certificate state for this slot; nil (or a missing entry) falls back to the account row's own AddedSlot for the pool-delegation ordering, which is used for accounts imported without certificate history (e.g. genesis/Mithril bootstrap).

func ReleaseFallbackSnapshotGuard added in v0.66.0

func ReleaseFallbackSnapshotGuard(db *gorm.DB, guardID uint) error

ReleaseFallbackSnapshotGuard deletes a temporary guard row by primary key. The caller still holds the row lock in the same transaction, so no authoritative writer can replace the row between the claim and this delete.

func SaveAccountOutputs added in v0.66.0

func SaveAccountOutputs(db *gorm.DB, outputs []*models.RewardAccountOutput) error

SaveAccountOutputs saves per-account reward calculation outputs.

func SaveAdaPots

func SaveAdaPots(db *gorm.DB, pots *models.RewardAdaPots) error

SaveAdaPots saves reward-related ADA pots for an epoch.

func SavePoolInputs

func SavePoolInputs(db *gorm.DB, inputs []*models.RewardPoolInput) error

SavePoolInputs saves per-pool reward inputs for an epoch.

func SavePoolOutputs added in v0.66.0

func SavePoolOutputs(db *gorm.DB, outputs []*models.RewardPoolOutput) error

SavePoolOutputs saves per-pool reward calculation outputs.

func SaveSnapshot

func SaveSnapshot(db *gorm.DB, snapshot *models.RewardSnapshot) error

SaveSnapshot saves reward snapshot metadata for an epoch. It overwrites any existing row for the (epoch, snapshot_type) pair, including its authoritative flag, so the authoritative epoch-rollover capture always wins over a provisional fallback row. The fallback path must use ClaimFallbackSnapshot instead, which refuses to overwrite an authoritative row.

func SaveStakeInputs added in v0.66.0

func SaveStakeInputs(db *gorm.DB, inputs []*models.RewardStakeInput) error

SaveStakeInputs saves per-credential reward snapshot inputs.

func StakeInputsForPools added in v0.66.0

func StakeInputsForPools(
	db *gorm.DB,
	poolKeyHashes [][]byte,
	chunkSize int,
) ([]*models.RewardStakeInput, error)

StakeInputsForPools returns positive registered stake from the maintained live reward aggregate for the requested pools. The live aggregate is maintained transactionally, so the result reflects the caller's db/txn view rather than any historical slot.

func StakeRefsFromLiveUtxoIDs

func StakeRefsFromLiveUtxoIDs(
	db *gorm.DB,
	utxos []models.UtxoId,
	slot uint64,
	chunkSize int,
) (map[string]CredentialSlotRef, error)

StakeRefsFromLiveUtxoIDs is equivalent to StakeRefsFromUtxoIDs but ignores already-spent rows. Physical cleanup of spent UTxOs must not recompute live stake: the spend path already removed their value, and recomputing at a historical DeletedSlot can replace a newer delegation aggregate.

func StakeRefsFromUtxoIDs

func StakeRefsFromUtxoIDs(
	db *gorm.DB,
	utxos []models.UtxoId,
	slot uint64,
	chunkSize int,
) (map[string]CredentialSlotRef, error)

StakeRefsFromUtxoIDs resolves a batch of UTxO identifiers (tx hash + output index) to their stake credentials and merges them into a fresh credential/slot map. When slot is 0, the ref's slot is derived per-row from the UTxO's own AddedSlot/DeletedSlot (used by rollback-driven refreshes, where each UTxO may need a different recompute slot). chunkSize bounds how many UTxOs are placed in a single SQL statement.

func StakeRefsFromUtxoSpends

func StakeRefsFromUtxoSpends(
	db *gorm.DB,
	spends []UtxoSpendRef,
	chunkSize int,
) (map[string]CredentialSlotRef, error)

StakeRefsFromUtxoSpends resolves a batch of just-spent UTxOs to their stake credentials, keyed to the slot each spend was recorded at, and merges them into a fresh credential/slot map. chunkSize bounds how many spends are placed in a single SQL statement.

func ValidateLiveStakeRef

func ValidateLiveStakeRef(ref models.StakeCredentialRef) error

ValidateLiveStakeRef reports whether ref is a well-formed stake credential for the reward_live_stake table: an empty key is allowed (nothing to touch), but a non-empty key must have a valid tag (0=key, 1=script) and be exactly 28 bytes.

func ValidateLiveStakeSourceCredentials

func ValidateLiveStakeSourceCredentials(db *gorm.DB) error

ValidateLiveStakeSourceCredentials checks that every account and live-UTxO credential feeding the reward_live_stake rebuild is well-formed (tag 0 or 1, exactly 28 bytes), returning a descriptive error naming which source table and how many rows are invalid.

Types

type AccountCertificateHistory added in v0.66.0

type AccountCertificateHistory interface {
	RewardRegistrationState(key string) (
		hasRegistration bool,
		registrationActive bool,
		hasDeregistration bool,
	)
}

AccountCertificateHistory exposes the registration state needed to decide whether a reward account was active at a slot. Metadata backends implement this on their private certificate cache types.

type CredentialSlotRef

type CredentialSlotRef struct {
	Ref  models.StakeCredentialRef
	Slot uint64
}

CredentialSlotRef pairs a stake credential with the slot at which its reward-live-stake aggregate should be recomputed. Each backend keeps its own local map type (keyed the same way, via models.StakeCredentialRef.MapKey) because callers outside the scope of this package (account.go, batch_accumulator.go, transaction.go, utxo.go) destructure the map value's fields directly; this type exists for the query helpers in this file that don't need the backend-local field names.

type PoolDelegationCache

type PoolDelegationCache map[string]PoolDelegationRecord

PoolDelegationCache maps a stake credential's MapKey (see models.StakeCredentialRef.MapKey) to its most recent pool delegation certificate, as populated by each backend's batchFetchCerts. A nil cache (or a missing entry) means "no certificate history available"; callers fall back to the account row's own AddedSlot.

type PoolDelegationRecord

type PoolDelegationRecord struct {
	Pool       []byte
	AddedSlot  uint64
	BlockIndex uint64
	CertIndex  uint32
}

PoolDelegationRecord is the subset of a certificate record needed to order and apply a stake credential's most recent pool delegation. It mirrors each backend's local certRecord (defined in account.go) restricted to the fields RefreshLiveStakeAggregate needs.

type UtxoSpendRef

type UtxoSpendRef struct {
	TxId      []byte
	OutputIdx uint32
	Slot      uint64
}

UtxoSpendRef mirrors the subset of each backend's local utxoSpend row (defined in batch_accumulator.go) that StakeRefsFromUtxoSpends needs: the spent UTxO's identity and the slot the spend was recorded at.

Jump to

Keyboard shortcuts

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