lifecycle

package
v0.90.4 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: Apache-2.0 Imports: 43 Imported by: 0

Documentation

Overview

Package lifecycle implements database snapshot, restore, and truncate operations shared by the offline CLI and (later) a live-node code path. It is a pure library: it operates on an already-constructed *database.Database (or, for restore, plugin instances that have not yet been started against their real data directory) and knows nothing about node composition, CLI flags, or gRPC.

Index

Constants

View Source
const (
	TriggerEpochBoundary = "epoch-boundary"
	TriggerManual        = "manual"
)

Trigger values recorded in Manifest.Trigger.

View Source
const (
	BlobBackupFileName     = "blob.bak"
	MetadataBackupFileName = "metadata.sqlite"
)

BlobBackupFileName and MetadataBackupFileName are the fixed file names Snapshot writes a backup's blob and metadata stores under, inside the snapshot directory alongside manifest.json.

View Source
const DefaultBlockDeleteBatchSize = 10_000

DefaultBlockDeleteBatchSize bounds how many blocks are deleted per blob transaction in DeleteBlocksAfter. Chain.Rollback deletes one block per transaction, which is fine for the handful-to-low-hundreds of blocks a normal in-bounds rollback removes, but is too slow for a disaster-recovery truncate that may remove millions of blocks.

View Source
const ManifestFileName = "manifest.json"

ManifestFileName is the name of the manifest file inside a snapshot directory.

View Source
const ManifestFormatVersion = 2

ManifestFormatVersion is the current on-disk schema version of Manifest. Bump it when making a breaking change to the JSON shape so old restore tooling can reject a manifest it doesn't understand instead of silently misreading it. Adding the Gates field did not bump this -- see Gates' doc comment for why that is acceptable today.

Version 2 added the signed snapshot ID. A version 1 manifest carries no ID, so a keyed verification rejects it; see ErrManifestLegacyFormat.

View Source
const MaxManifestBytes = 1 << 20

MaxManifestBytes is the default bound before JSON decoding. Manifests are small metadata files; accepting an arbitrarily large object here would let a malformed local or cloud snapshot consume memory before validation.

View Source
const (

	// SnapshotCatalogPageLimit bounds index rows and manifests materialized by
	// one page request.
	SnapshotCatalogPageLimit = 100
)

Variables

View Source
var (

	// ErrSnapshotCatalogChanged means a page token names a prior catalog
	// generation. The caller must restart pagination from the first page.
	ErrSnapshotCatalogChanged = errors.New("snapshot catalog changed")
	// ErrSnapshotCatalogIncomplete means the catalog was rebuilt with every
	// readable snapshot while one or more directory entries were unreadable.
	ErrSnapshotCatalogIncomplete = errors.New("snapshot catalog incomplete")
	// ErrSnapshotCatalogCorrupt means the persistent catalog contains data
	// that cannot represent a snapshot below its configured root.
	ErrSnapshotCatalogCorrupt = errors.New("snapshot catalog corrupt")
	// ErrSnapshotCatalogUpdate means the durable snapshot was written but its
	// repairable catalog index could not be updated.
	ErrSnapshotCatalogUpdate = errors.New("snapshot catalog update failed")
)
View Source
var ErrCloudSnapshotNotFound = errors.New("cloud snapshot not found")

ErrCloudSnapshotNotFound is what a CloudManifestFetcher implementation wraps around its underlying "object doesn't exist" error (e.g. S3's NoSuchKey/NotFound, GCS's storage.ErrObjectNotExist) — the one case where FetchCloudManifest's non-nil err genuinely means "confirmed absent," as opposed to a real communication failure (auth, network, timeout, throttling) that happens to occur while checking. Callers distinguishing "no such snapshot" from "couldn't check right now" (see bark's cloudSnapshotExists) should check errors.Is(err, ErrCloudSnapshotNotFound) rather than treating every non-nil err the same way.

View Source
var ErrCommitPauseExceeded = errors.New(
	"snapshot exceeded maximum commit pause",
)

ErrCommitPauseExceeded marks a Snapshot aborted because its backups kept the commit barrier held longer than the limit set with WithMaxCommitPause. The commit barrier is released and the partial snapshot directory removed before it is returned.

View Source
var ErrDownloadTooLarge = errors.New("cloud object exceeds its declared size")

ErrDownloadTooLarge reports an object that carried more bytes than the snapshot's manifest declared for it.

View Source
var ErrManifestCorrupted = errors.New(
	"manifest failed checksum validation (corrupted or hand-edited)",
)

ErrManifestCorrupted marks a manifest that was found and read but failed checksum validation — i.e. it (or the snapshot it belongs to) exists but is corrupted or was hand-edited, as distinct from a manifest that simply isn't there. Callers that otherwise treat a ReadManifest/ParseManifest error as "snapshot not found" (e.g. bark's resolveSnapshotSource probing whether a local copy exists) should check errors.Is against this first, so a corrupted snapshot is reported as corrupted rather than missing.

View Source
var ErrManifestIDMismatch = errors.New(
	"manifest snapshot ID does not match the requested snapshot ID",
)

ErrManifestIDMismatch marks an authenticated manifest whose signed snapshot ID differs from the directory name or object prefix it was found under, which is how a complete older snapshot copied under a newer ID presents. It is always joined with ErrManifestUnauthenticated.

View Source
var ErrManifestLegacyFormat = errors.New(
	"snapshot predates signed snapshot IDs and must be re-created",
)

ErrManifestLegacyFormat marks a manifest written before snapshot IDs were signed (format version 1). It carries no ID the trust key authenticates, so it cannot be bound to the directory or object prefix it was found under; the snapshot must be re-created. It is returned only by keyed verification, and always joined with ErrManifestUnauthenticated.

View Source
var ErrManifestTooLarge = errors.New("manifest size limit exceeded")

ErrManifestTooLarge marks a manifest that exceeds its encoded byte budget. Callers can distinguish this resource rejection from missing or corrupt data.

View Source
var ErrManifestUnauthenticated = errors.New(
	"manifest is not authenticated by the configured trust key",
)

ErrManifestUnauthenticated marks a manifest whose authentication tag is missing or does not verify under the configured trust key. Unlike ErrManifestCorrupted, which an unkeyed checksum can only trap by accident, this means the manifest was not produced by a holder of the key.

View Source
var ErrRestoreRollbackPending = errors.New("automatic restore rollback failed")

ErrRestoreRollbackPending means restore mutated an external store and its automatic compensation did not complete. The error includes the retained backup directory. A live caller must not reopen or serve the target until recovery succeeds.

View Source
var ErrSnapshotCatalogScanLimit = errors.New(
	"cloud snapshot catalog scan limit exceeded",
)

ErrSnapshotCatalogScanLimit marks an incomplete provider scan stopped at a configured resource bound. Callers must not publish entries returned with this error as a complete replacement catalog.

View Source
var ErrSnapshotDirectoryReplaced = errors.New(
	"snapshot directory was replaced while it was being written",
)

ErrSnapshotDirectoryReplaced is returned by Snapshot when the snapshot directory path no longer names the directory Snapshot created and is writing through. Snapshot then removes only the contents of the directory it opened and leaves the path, and whatever now occupies it, untouched; no manifest is published for either directory.

View Source
var ErrSnapshotPayloadMismatch = errors.New(
	"snapshot payload does not match its manifest",
)

ErrSnapshotPayloadMismatch marks a snapshot backup file whose size or digest differs from what the manifest declares.

View Source
var ErrSnapshotTrustRequired = errors.New(
	"snapshot trust key is required for this local snapshot location",
)

ErrSnapshotTrustRequired marks an unkeyed local snapshot whose filesystem permissions allow another principal to replace or alter it.

View Source
var ErrTruncateNotStarted = errors.New(
	"truncate: not started, no data was modified",
)

ErrTruncateNotStarted marks a Truncate failure that occurred entirely during read-only validation — before DeleteBlocksAfter made any on-disk change — as opposed to a failure during or after it, where a batched bulk delete spanning more than one batch may have already partially committed. Callers deciding whether it's safe to resume normal service after a failed live truncate (rather than treat the data directory as possibly inconsistent) should check errors.Is against this.

Functions

func CleanStaleRestoreWorkDirs added in v0.81.0

func CleanStaleRestoreWorkDirs(parentDir string) error

CleanStaleRestoreWorkDirs removes abandoned restore work directories from parentDir. A directory a running restore still holds is never removed. On Unix, a populated staging, payload, or cloud download directory that no process holds is removed at once, because its restore exited without cleaning up. Every other unheld directory, and on Windows every directory, is removed only once it is older than a week, since a recent one may belong to another active operation.

func CloudDestinationDisplay added in v0.82.0

func CloudDestinationDisplay(raw string) string

CloudDestinationDisplay returns a credential-free form of a cloud URI for logs, errors, and API responses. Provider calls and source identity checks continue to use the original URI.

func CloudMirrorMarkerPath

func CloudMirrorMarkerPath(dir string) string

CloudMirrorMarkerPath returns the marker file path for the local snapshot directory dir, per cloudMirrorMarkerName's doc comment.

func DeleteBlocksAfter

func DeleteBlocksAfter(
	ctx context.Context,
	db *database.Database,
	afterID uint64,
	tipID uint64,
	batchSize int,
) (blocksDeleted uint64, err error)

DeleteBlocksAfter removes every block whose internal, sequentially assigned block ID (models.Block.ID — the basis of the blob store's "bi" index, distinct from the chain's Number/height field) falls in (afterID, tipID], deleting bp/bi/bh keys and their metadata companion via BlobStore.DeleteBlock. Deletes are batched batchSize per blob transaction instead of one transaction per block.

On success the returned count is the number of blocks actually found and deleted. On error it is a lower bound: batches that committed are counted, and a batch whose transaction failed is counted only where the store reports its writes cannot be rolled back (types.IrreversibleTxn). A commit that applied part of a batch and could not compensate (types.ErrPartialCommit) contributes nothing, because how much of it survived is not reported. Either way the caller retries the identical range, which is idempotent.

The count may be far fewer than tipID-afterID: IDs are assigned sequentially by BlockCreate for any chain built entirely through it, but a chain bootstrapped/drained from a Mithril snapshot can leave large gaps of never-imported IDs in that range (see database.BlockAtOrAfterIndex's doc comment) — every ID in (afterID, tipID] is only an upper bound on how many blocks exist there, not a count of how many actually do.

Each batch walks the ordered "bi" index keys with a single iterator seeked to the batch's start, rather than probing every numeric ID in [start, end] individually: cost is therefore proportional to how many blocks are actually stored in the batch's range, not to how wide a never-imported gap it spans. Probing one ID at a time would turn a truncate across a large sparse gap into one remote lookup per absent ID for a cloud-backed blob store (GCS/S3) — catastrophic for a deep disaster-recovery truncate that spans a big Mithril-imported gap.

This is a bulk-performance variant of what Chain.Rollback already does one block at a time via ChainManager.removeBlockByIndex — it performs no chain-manager or fork bookkeeping, so it is only safe to call against a database that is not concurrently owned by a live Chain/ChainManager (i.e. the offline CLI path, or the live path after quiescing the node).

func DeleteCloudSnapshot

func DeleteCloudSnapshot(
	ctx context.Context,
	registry *DestinationRegistry,
	snapshotURI string,
) (ok bool, err error)

DeleteCloudSnapshot resolves the CloudDestination at the given exact snapshot URI and deletes it, if that destination type implements CloudDeleter. ok=false (nil error) means the destination type doesn't support deletion — distinct from a real deletion failure, which is a non-nil err.

func EffectiveCloudDestination added in v0.82.0

func EffectiveCloudDestination(base string, prefix string) string

EffectiveCloudDestination appends the configured per-node prefix before any snapshot ID. Manual snapshots, automatic snapshots, and Bark catalog discovery must all use this same destination.

func EnsureSnapshotCatalog added in v0.82.0

func EnsureSnapshotCatalog(baseDir string, opts ...ManifestOption) error

EnsureSnapshotCatalog atomically rebuilds the persistent local snapshot index. Callers run it during service initialization, outside request handling. It imports snapshot directories created while the service was not running.

func EnsureSnapshotCatalogContext added in v0.82.0

func EnsureSnapshotCatalogContext(
	ctx context.Context,
	baseDir string,
	opts ...ManifestOption,
) error

EnsureSnapshotCatalogContext atomically rebuilds the persistent local snapshot index using ctx for catalog operations.

func IsCloudMirrored

func IsCloudMirrored(dir string) bool

IsCloudMirrored reports whether dir's cloud mirror marker is present — i.e. whether a previous MirrorToCloud call for this exact directory actually completed successfully, as opposed to dir merely existing locally.

func IsCloudMirroredTo

func IsCloudMirroredTo(dir string, cloudDest string) bool

IsCloudMirroredTo reports whether dir's cloud mirror marker records the destination cloudDest resolves to right now, not merely that some marker is present. This matters because cloudDest is operator-configured and can change (e.g. pointed at a new bucket) between when a snapshot was mirrored and now: a marker left over from a since-abandoned destination must not be mistaken for "already mirrored to the currently configured destination" -- that destination has never actually received this snapshot's data, and treating it as done would silently skip mirroring it there.

func IsSafeCloudObjectFileName

func IsSafeCloudObjectFileName(fileName string) bool

IsSafeCloudObjectFileName reports whether fileName is safe to join onto a local restore directory (via filepath.Join/os.Create). A cloud object key is attacker- or corruption-controlled input, not a trusted local path component, so both destination_s3.go's and destination_gcs.go's DownloadFiles use this rather than only checking for "/": a bare ".." resolves outside the target directory via filepath.Join's own cleaning even with no separator present, and a literal "\" is a path separator on Windows (but not Unix, where a "/"-only check would otherwise miss it) regardless of which OS actually wrote the object.

func JoinCloudURI

func JoinCloudURI(base string, sub string) string

JoinCloudURI appends sub as an additional path segment to base (e.g. "s3://bucket/prefix" + "abc123" -> "s3://bucket/prefix/abc123"). base is parsed as a URI and sub is appended to its Path specifically (not filepath.Join, which would use the host OS's separator, and not plain string concatenation onto the whole URI, which would land sub after any query string or fragment base carries instead of before it — turning "s3://bucket/prefix?region=us-east-1" + "abc123" into ".../prefix?region=us-east-1/abc123" rather than ".../prefix/abc123?region=us-east-1", silently sending every snapshot to the same base prefix regardless of sub). Exported so callers building a per-snapshot cloud location for display (e.g. bark's ListAvailableSnapshots) use the exact same join logic SnapshotToCloud uses for the actual upload.

func LabelSnapshot

func LabelSnapshot(dir string, name string, description string, opts ...ManifestOption) error

LabelSnapshot sets name/description on the manifest at dir and rewrites it (recomputing its checksum), without touching the snapshot's blob or metadata backups. Intended for callers that only learn a human-readable label after Snapshot has already produced dir (e.g. bark's CreateSnapshot RPC receives name/description in the same request but Snapshot itself has no such parameters).

func ListAvailableSnapshotPageContext added in v0.82.0

func ListAvailableSnapshotPageContext(
	ctx context.Context,
	baseDir string,
	cloudDest string,
	pageSize int,
	cursor *SnapshotCatalogCursor,
	opts ...ManifestOption,
) (entries []AvailableSnapshotCatalogEntry, next *SnapshotCatalogCursor, err error)

ListAvailableSnapshotPageContext reads a bounded, stable page across local and cloud snapshots. A local row wins when both stores contain the same ID.

func ListSnapshotPage added in v0.82.0

func ListSnapshotPage(
	baseDir string,
	pageSize int,
	cursor *SnapshotCatalogCursor,
	opts ...ManifestOption,
) (entries []SnapshotEntry, next *SnapshotCatalogCursor, err error)

ListSnapshotPage reads at most pageSize+1 index rows and pageSize manifests. Entries preserve ListSnapshots' newest-first ordering.

func ListSnapshotPageContext added in v0.82.0

func ListSnapshotPageContext(
	ctx context.Context,
	baseDir string,
	pageSize int,
	cursor *SnapshotCatalogCursor,
	opts ...ManifestOption,
) (entries []SnapshotEntry, next *SnapshotCatalogCursor, err error)

ListSnapshotPageContext reads one bounded snapshot catalog page using ctx.

func MirrorToCloud

func MirrorToCloud(
	ctx context.Context,
	registry *DestinationRegistry,
	dir string,
	cloudDest string,
	opts ...ManifestOption,
) error

MirrorToCloud uploads dir's contents to cloudDest (a base URI like "s3://bucket/prefix" or "gcs://bucket/prefix"; see DestinationRegistry), nested one level under this snapshot's own ID (dir's base name), mirroring the local SnapshotDir/<snapshotID> layout — see SnapshotToCloud's doc comment for why. Writes CloudMirrorMarkerPath(dir) after the upload and catalog update or repair succeed, so a caller retries both an incomplete upload and a completed upload whose catalog could not be reconciled.

cloudDest == "" is a no-op (success, no marker written): nothing to mirror.

func RebuildCloudSnapshotCatalogContext added in v0.82.0

func RebuildCloudSnapshotCatalogContext(
	ctx context.Context,
	baseDir string,
	cloudDest string,
	entries []SnapshotEntry,
) error

RebuildCloudSnapshotCatalogContext replaces the disposable cloud side of the snapshot catalog after a provider compatibility scan at service startup.

func ReconcileCloudSnapshotCatalogContext added in v0.82.0

func ReconcileCloudSnapshotCatalogContext(
	ctx context.Context,
	baseDir string,
	registry *DestinationRegistry,
	cloudDest string,
	opts ...ManifestOption,
) (current bool, err error)

ReconcileCloudSnapshotCatalogContext scans the provider outside the catalog gate, then publishes only if no catalog mutation occurred during the scan. current is true only when the catalog was completely reconciled with cloudDest. An incomplete scan leaves the previous cloud set unchanged.

func RecoverInterruptedRestore added in v0.82.0

func RecoverInterruptedRestore(
	ctx context.Context,
	host *plugin.Host,
	recoveryDir string,
	targetDataDir string,
	storageConfig RestoreStorageConfig,
	accept bool,
) error

RecoverInterruptedRestore disposes a durable live-restore recovery record. accept keeps the restored external stores and reclaims the prior blob namespace. Otherwise both external stores are returned to their prior state.

func RegisterBuiltinDestinations

func RegisterBuiltinDestinations(*DestinationRegistry, ...ManifestOption)

RegisterBuiltinDestinations is a no-op in this build: S3/GCS support is only compiled in with -tags dingo_extra_plugins.

func RemoveCloudSnapshotCatalogEntryContext added in v0.82.0

func RemoveCloudSnapshotCatalogEntryContext(
	ctx context.Context,
	baseDir string,
	id string,
	registry *DestinationRegistry,
	cloudDest string,
	opts ...ManifestOption,
) error

RemoveCloudSnapshotCatalogEntryContext removes a deleted cloud copy while retaining any local row with the same snapshot ID.

func RemoveRestoreWorkDir added in v0.90.4

func RemoveRestoreWorkDir(path string) error

RemoveRestoreWorkDir removes the restore work directory at path, if one is present. It validates the parent with ValidateRestoreWorkParent first, then removes the directory's contents through a handle opened on it and removes the emptied name only while it still identifies that directory. A symlink or non-directory at path is refused, and a directory substituted at path during removal is never traversed.

func RemoveSnapshot added in v0.82.0

func RemoveSnapshot(dir string) error

RemoveSnapshot removes a local snapshot directory and its persistent catalog entry.

func RemoveSnapshotContext added in v0.82.0

func RemoveSnapshotContext(ctx context.Context, dir string) error

RemoveSnapshotContext removes a local snapshot directory and its persistent catalog entry, using ctx for catalog operations.

func RepairSnapshotCatalogContext added in v0.82.0

func RepairSnapshotCatalogContext(
	ctx context.Context,
	baseDir string,
	registry *DestinationRegistry,
	cloudDest string,
	opts ...ManifestOption,
) error

RepairSnapshotCatalogContext rebuilds the disposable local and cloud snapshot indexes from their authoritative sources.

func ResolveTargetByHash

func ResolveTargetByHash(
	ctx context.Context,
	db *database.Database,
	hash []byte,
) (models.Block, error)

ResolveTargetByHash resolves a truncate target identified by block hash.

func ResolveTargetByNumber

func ResolveTargetByNumber(
	ctx context.Context,
	db *database.Database,
	number uint64,
) (models.Block, error)

ResolveTargetByNumber resolves a truncate target identified by chain block number (height). Block numbers are not directly indexed in the blob store (only slot, hash, and internal sequential ID are), so this binary-searches the contiguous internal-ID space bounded by the current tip, comparing each candidate's Number field, mirroring the technique Chain.BlockBeforeSlot uses for slot-ordered lookups.

func ResolveTargetBySlot

func ResolveTargetBySlot(
	ctx context.Context,
	db *database.Database,
	slot uint64,
) (models.Block, error)

ResolveTargetBySlot resolves a truncate target as the highest-slot block at or before the given slot, against whatever chain the local database currently has. Slots without a block of their own (the common case — cardano-node's ~20s average slot time means most slots are empty) resolve to their nearest ancestor, since an operator invoking a disaster-recovery truncate is very unlikely to know a block-populated slot exactly and should not have to.

func RestoreRecoverable added in v0.70.1

func RestoreRecoverable(
	ctx context.Context,
	host *plugin.Host,
	registry *DestinationRegistry,
	snapshotDir string,
	targetDataDir string,
	validate func(Manifest) error,
	storageConfig RestoreStorageConfig,
	opts ...ManifestOption,
) (Manifest, *RestoreRecovery, error)

RestoreRecoverable performs the same validated restore but retains any original external-store backups after the replacement passes lifecycle validation. The caller must call Commit after its own enclosing operation succeeds, or Rollback if a later step fails. This is used by live node restore so a local directory-swap failure cannot strand new remote metadata beside the original local blob store (or the inverse).

The supplied plugin host must remain active until Commit or Rollback returns; Rollback resolves the same providers through it to reload the retained copy. If automatic compensation fails, this returns both ErrRestoreRollbackPending and a non-nil recovery handle so a caller can recognize that the target is unsafe and retry Rollback while the host is still active.

func RestoreRecoveryDir added in v0.82.0

func RestoreRecoveryDir(targetDataDir string) string

RestoreRecoveryDir returns the durable recovery directory associated with a live restore target.

func Truncate

func Truncate(
	ctx context.Context,
	db *database.Database,
	target models.Block,
	batchSize int,
	delegatorInactivityEnabled bool,
	delegatorInactivity uint64,
) (blocksRemoved uint64, err error)

Truncate reverts the database to target: target becomes the new chain tip, every block with a strictly greater internal ID is removed from the blob store, and every metadata row (and blob-referenced UTxO/tx CBOR) added after target's slot is removed or restored to its pre-target state via database.TruncateAfterSlot.

Unlike Chain.Rollback, this does not reject a target beyond the configured security parameter — that guard protects automatic rollback during normal sync; an operator explicitly invoking Truncate (e.g. for CIP-0135 disaster recovery from a long network partition) is the informed-consent replacement for it. It still refuses to truncate to a point before the Mithril trust boundary, if one is recorded: that boundary reflects what UTxO history is actually available locally, not a policy choice, and going below it would leave the database unable to validate the first block past the (now missing) boundary.

This is an offline operation in the sense that it performs no chain- manager or in-memory ledger-state bookkeeping — it is safe to call against a database not concurrently owned by a live Chain/LedgerState (the offline CLI path, or the live path after quiescing the node).

blocksRemoved is the number of blocks DeleteBlocksAfter actually found and deleted in (target.ID, tipBlock.ID] — not simply tipBlock.ID - target.ID, since that range is only an upper bound: a chain bootstrapped/drained from a Mithril snapshot can leave gaps of never-imported IDs in it (see DeleteBlocksAfter's own doc comment), and subtracting index values there would wildly overcount how many blocks actually existed to remove.

DeleteBlocksAfter deletes blob-store blocks by ID range, while database.TruncateAfterSlot deletes metadata by slot cutoff; these agree for any normal chain (slots strictly increase with ID) except same-slot blocks — notably Byron epoch boundary blocks — where a later block sharing target's own slot would be removed from the blob store (ID > target.ID) but retained in metadata (slot not > target.Slot), diverging the two. Truncate refuses such a target outright (see the same-slot check above) rather than let that divergence happen.

func ValidateRestoreWorkParent added in v0.90.4

func ValidateRestoreWorkParent(dir string) error

ValidateRestoreWorkParent reports an error unless dir is a real directory whose absolute ancestor chain only the current user or root can modify. On Unix, each ancestor must be owned by one of them and may be writable by group or other only with the sticky bit set. Anyone else who could write dir could rename a live data directory onto a work directory's name before a removal or rename by that name runs.

func WriteManifest

func WriteManifest(dir string, m Manifest, opts ...ManifestOption) error

WriteManifest computes m's checksum and writes it as indented JSON to dir/ManifestFileName. dir must already exist.

Written via a same-directory temp file plus an atomic rename, not a direct write to the final path: a direct write truncates any existing file before writing the new content, so an interruption partway through would leave a corrupt, partially-written manifest.json in its place. That matters most for LabelSnapshot, which rewrites the manifest of an already-complete snapshot purely to update its Name/Description — a truncated manifest fails ReadManifest's checksum validation, and catalog scanning (ListSnapshots) treats that identically to "this snapshot doesn't exist," silently disappearing an otherwise perfectly good snapshot from the catalog over what should have been a harmless label update. Renaming a fully-written temp file over the target is atomic on the same filesystem, so a reader always observes either the complete old manifest or the complete new one, never a partial one.

Types

type AvailableSnapshotCatalogEntry added in v0.82.0

type AvailableSnapshotCatalogEntry struct {
	Entry    SnapshotEntry
	Location string
}

AvailableSnapshotCatalogEntry is one local-or-cloud catalog result. Local entries carry their filesystem path; cloud-only entries carry a redacted display URI that cannot be used as an authenticated provider location.

type CloudDeleter

type CloudDeleter interface {
	Delete(ctx context.Context) error
}

CloudDeleter is optionally implemented by a CloudDestination to delete everything at its own configured location — used by DeleteSnapshot to remove a snapshot's cloud copy. Like CloudManifestFetcher, meaningful on a CloudDestination parsed from a specific snapshot's own URI, not a base destination (which would have no single well-defined "everything" to delete).

type CloudDestination

type CloudDestination interface {
	// UploadDir uploads every regular file directly inside localDir
	// (Snapshot's manifest.json/blob.bak/metadata.sqlite — it is not
	// recursive) to the destination. MirrorToCloud passes a private copy
	// of the snapshot, already authenticated and named for the snapshot ID,
	// not the snapshot directory itself, so the bytes read here are the
	// ones that were verified.
	UploadDir(ctx context.Context, localDir string) error
	// DownloadFiles downloads exactly the named objects into localDir, which
	// must already exist. A snapshot's prefix is writable by anyone with
	// access to the bucket, so objects that were not asked for are never
	// listed or read, and a named object that is larger than its MaxBytes is
	// rejected during transfer rather than written out in full. A missing
	// object wraps ErrCloudSnapshotNotFound.
	DownloadFiles(
		ctx context.Context,
		localDir string,
		files []DownloadFile,
	) error
}

CloudDestination mirrors a snapshot directory to/from object storage, in addition to (not instead of) the local copy Snapshot/Restore already produce/consume — see SnapshotToCloud and Restore's cloud-source handling. Implementations live in build-tag-gated files (destination_s3.go, destination_gcs.go); composition code registers the ones it wants available on a *DestinationRegistry via RegisterS3/RegisterGCS (or RegisterBuiltinDestinations for all schemes compiled into this build).

func ParseCloudDestination

func ParseCloudDestination(
	r *DestinationRegistry,
	uri string,
) (CloudDestination, error)

ParseCloudDestination resolves uri to a CloudDestination using r's registered schemes. uri's scheme must have a factory registered on r (see RegisterS3/RegisterGCS/RegisterBuiltinDestinations) or this returns an error. r may be nil, which behaves as an empty registry.

type CloudDestinationCloser

type CloudDestinationCloser interface {
	Close() error
}

CloudDestinationCloser is optionally implemented by a CloudDestination that holds a resource needing explicit cleanup once a caller is done with it — e.g. GCS's implementation owns a persistent gRPC connection (storage.NewGRPCClient) that client.Bucket's returned handle doesn't itself expose a way to close. S3's implementation has no such resource and doesn't implement this. Every ParseCloudDestination call site in this file (and SnapshotToCloud) closes the destination via closeCloudDestination once it's done using it.

type CloudDestinationFactory

type CloudDestinationFactory func(uri *url.URL) (CloudDestination, error)

CloudDestinationFactory constructs a CloudDestination from a parsed URI (e.g. "s3://bucket/prefix"). Registered per-scheme via RegisterCloudDestinationScheme.

type CloudManifestFetcher

type CloudManifestFetcher interface {
	FetchManifest(ctx context.Context) (Manifest, error)
}

CloudManifestFetcher is optionally implemented by a CloudDestination to fetch just its own manifest.json, without downloading the rest of a (possibly very large) snapshot — used to cheaply check whether a specific snapshot exists at a destination (DeleteSnapshot's and Restore's cloud-fallback path, used when no local copy exists). Unlike SnapshotLister, this is meaningful on a CloudDestination parsed from a specific snapshot's own URI (base destination + snapshot ID), the same one UploadDir/DownloadFiles operate on.

type ConfigurableCloudManifestFetcher added in v0.70.11

type ConfigurableCloudManifestFetcher interface {
	FetchManifestWithOptions(context.Context, ...ManifestOption) (Manifest, error)
}

ConfigurableCloudManifestFetcher supports a caller's manifest limit in place of the destination's configured default. Implementations must bound the encoded input before reading or decoding it.

type DestinationRegistry

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

DestinationRegistry holds the set of cloud destination schemes (e.g. "s3", "gcs") a caller has explicitly chosen to make available, resolved at construction time rather than through a process-global registry. Composition code (the node or CLI's startup path) owns creating one via NewDestinationRegistry and registering whichever schemes this build and configuration should support (RegisterS3, RegisterGCS, or RegisterBuiltinDestinations for everything compiled in), then threads it explicitly into every lifecycle call that needs cloud destination support. A nil *DestinationRegistry is valid and behaves as if it had no schemes registered — every method here is nil-safe — so callers with no use for cloud destinations at all (e.g. a purely local restore) are not forced to construct an empty one.

func NewDestinationRegistry

func NewDestinationRegistry() *DestinationRegistry

NewDestinationRegistry returns an empty registry with no cloud destination schemes registered.

func (*DestinationRegistry) Register

func (r *DestinationRegistry) Register(
	scheme string,
	factory CloudDestinationFactory,
)

Register adds factory as the constructor for CloudDestination URIs with the given scheme (e.g. "s3", "gcs"). Panics on a duplicate scheme registration within this registry, matching the fail-fast-at- composition-time convention already used by plugin.Host.Register. r may be nil (see DestinationRegistry's doc comment on nil-safety), in which case this is a no-op: a nil registry has no map to register into and behaves as if it will always have no schemes registered, the same as an empty one.

type DownloadFile added in v0.81.0

type DownloadFile struct {
	Name     string
	MaxBytes int64
}

DownloadFile names one object of a snapshot and the most bytes it may carry.

type Manifest

type Manifest struct {
	FormatVersion int `json:"formatVersion"`

	// ID is the snapshot ID, the base name of the snapshot directory or cloud
	// prefix the snapshot was written to. It is covered by Authentication, so
	// a keyed verification can bind the manifest to the ID it is requested by.
	// Empty in a format version 1 manifest.
	ID string `json:"id,omitempty"`

	CreatedAt time.Time `json:"createdAt"`
	Trigger   string    `json:"trigger"`

	// Name/Description are operator-supplied labels, set after the fact
	// via LabelSnapshot (Snapshot itself has no caller-facing label
	// parameters) — bark's CreateSnapshot RPC is the current writer of
	// these fields. Both are empty for a snapshot that was never labeled.
	Name        string `json:"name,omitempty"`
	Description string `json:"description,omitempty"`

	// StorageMode/Network mirror types.NodeSettings so a restore can
	// refuse an incompatible target before opening the restored store;
	// database.New's own CheckNodeSettings then re-validates them for
	// free once the restored store is opened for real.
	StorageMode string `json:"storageMode"`
	Network     string `json:"network"`

	// CommitTimestamp is the value SetCommitTimestamp wrote to both
	// stores at backup time, so database.New's checkCommitTimestamp
	// passes immediately after restore rather than reporting a
	// mismatch that was actually just "restore in progress".
	CommitTimestamp int64 `json:"commitTimestamp"`

	// Chain position captured at backup time, for CLI/gRPC display and
	// for a post-restore sanity check against the restored tip.
	TipSlot        uint64 `json:"tipSlot"`
	TipHash        []byte `json:"tipHash"`
	TipBlockNumber uint64 `json:"tipBlockNumber"`

	// BlobPlugin/MetadataPlugin identify which plugin produced the
	// backup. Restoring a badger backup into a gcs-configured store (or
	// vice versa) is meaningless, so restore refuses on a mismatch.
	BlobPlugin     string `json:"blobPlugin"`
	MetadataPlugin string `json:"metadataPlugin"`

	// Gates mirrors the source database's persisted node settings gates
	// (database/nodesettings.Gates) at backup time — network_magic,
	// start_era, the era genesis hashes, the ledger-semantics gates, and
	// so on — so CheckGateMatch can refuse restoring a snapshot whose
	// consensus-relevant configuration disagrees with what the caller
	// intends to run it with (e.g. a snapshot taken with CIP-0163
	// full-pot rewards enabled, restored onto a node configured without
	// it, would otherwise silently diverge rewards from that point
	// forward).
	//
	// This field is optional (omitempty), and its addition did not bump
	// ManifestFormatVersion, but it is not actually backward compatible:
	// checksum() covers the whole struct, so a build that predates Gates
	// ignores the unknown "gates" key on unmarshal (ParseManifest decodes
	// with a plain json.Unmarshal, not DisallowUnknownFields) and then
	// recomputes the checksum without a field it never saw, getting a
	// different digest and rejecting an otherwise-valid manifest as
	// ErrManifestCorrupted. That is acceptable only because this manifest
	// format is unreleased: no build that predates Gates is deployed
	// anywhere to hit it.
	Gates nodesettings.Values `json:"gates,omitempty"`

	// DingoVersion records the dingo build that produced the backup, so
	// a restore across incompatible dingo versions is at least detectable rather
	// than failing schema migration in a confusing way partway through.
	DingoVersion string `json:"dingoVersion"`

	// BlobBytes/MetadataBytes are informational (display, progress),
	// not required for correctness.
	BlobBytes     int64 `json:"blobBytes"`
	MetadataBytes int64 `json:"metadataBytes"`

	// BlobSHA256/MetadataSHA256 are the hex SHA-256 digests of the blob and
	// metadata backup files (BlobBackupFileName, MetadataBackupFileName). With
	// BlobBytes/MetadataBytes they declare the only objects a restore will
	// fetch and the sizes it will accept. Empty in a manifest written before
	// they existed.
	BlobSHA256     string `json:"blobSha256,omitempty"`
	MetadataSHA256 string `json:"metadataSha256,omitempty"`

	// Authentication is the hex HMAC-SHA256, under the operator trust key
	// (WithManifestKey), of the manifest's JSON encoding with Checksum and
	// Authentication blanked. Empty when no key was configured at snapshot time.
	// Like Gates, its addition did not bump ManifestFormatVersion: a build
	// that predates these fields recomputes Checksum without them and rejects
	// such a manifest as corrupted.
	Authentication string `json:"authentication,omitempty"`

	// Checksum is a SHA-256 hex digest of the manifest's own JSON
	// encoding with Checksum itself blanked out, guarding against a
	// corrupted or hand-edited manifest file. It is not a security
	// mechanism (no key), just a corruption/typo trap.
	Checksum string `json:"checksum"`
}

Manifest describes a single database snapshot: what produced it, what point in the chain it captures, and enough about the source database's configuration to detect an incompatible restore before touching any data. It is written as JSON (not CBOR) since it is operator/tooling facing metadata, not chain data.

func FetchCloudManifest

func FetchCloudManifest(
	ctx context.Context,
	registry *DestinationRegistry,
	snapshotURI string,
	opts ...ManifestOption,
) (m Manifest, ok bool, err error)

FetchCloudManifest resolves the CloudDestination at the given exact snapshot URI (a specific snapshot's own location — see JoinCloudURI, not a base destination) and fetches its manifest, if that destination type implements ConfigurableCloudManifestFetcher. A manifest trust key is required. ok=false (nil error) means the destination type doesn't support authenticated fetches. ok=true with a non-nil err means an actual fetch was attempted and failed — check errors.Is(err, ErrCloudSnapshotNotFound) to tell "confirmed absent" apart from a real communication failure (auth, network, timeout); only the former should ever be treated as equivalent to "doesn't exist" by a caller.

func ParseManifest

func ParseManifest(data []byte, opts ...ManifestOption) (Manifest, error)

ParseManifest validates and decodes a manifest already read into memory — the same validation ReadManifest performs against a local file, but against bytes fetched however the caller obtained them (e.g. a cloud destination's ListSnapshots fetching a single remote manifest.json object without downloading the whole snapshot).

func PeekManifest

func PeekManifest(
	ctx context.Context,
	registry *DestinationRegistry,
	snapshotDir string,
	opts ...ManifestOption,
) (Manifest, error)

PeekManifest resolves snapshotDir (a local path or a cloud destination URI — see Restore's doc comment) and reads its manifest, without restoring anything. Intended for a caller that needs to validate a snapshot's recorded plugins/network/storage mode (Manifest. CheckPluginMatch, or comparing StorageMode/Network directly) against a target's actual configuration before Restore ever touches targetDataDir — Restore's own validateRestoredDatabase only checks the manifest against itself, since it opens the restored copy using the manifest's own recorded plugins, not necessarily whatever the caller actually intends to run it with afterward.

For a cloud snapshotDir, this tries FetchCloudManifest first — which fetches just the one manifest.json object via CloudManifestFetcher, without downloading the (possibly very large) blob/metadata backups alongside it — before falling back to resolveManifest below, which downloads manifest.json alone. FetchCloudManifest's ok=false covers two distinct cases resolveManifest already handles correctly on its own: snapshotDir isn't a recognized cloud URI at all (a plain local path), or it is one but that destination type doesn't implement CloudManifestFetcher — either way, falling through to resolveManifest is the right move, so its own error (if any) from the failed FetchCloudManifest attempt is deliberately discarded here rather than duplicating PeekManifest's cloud-vs-local branching a second time.

func ReadManifest

func ReadManifest(dir string, opts ...ManifestOption) (Manifest, error)

ReadManifest reads and validates the manifest at dir/ManifestFileName, rejecting it if the checksum doesn't match its contents or if its format version is newer than this build understands.

func Restore

func Restore(
	ctx context.Context,
	host *plugin.Host,
	registry *DestinationRegistry,
	snapshotDir string,
	targetDataDir string,
	storageConfig RestoreStorageConfig,
	opts ...ManifestOption,
) (Manifest, error)

Restore populates targetDataDir (which must not already exist, or must be empty) from the snapshot at snapshotDir, then opens the result with database.New to confirm it passes the same startup consistency checks (CheckNodeSettings, checkCommitTimestamp) any other dingo startup does, and that its tip matches what the manifest recorded. The opened database is closed again before returning — the caller is responsible for (re)opening it for real use.

The actual restore work happens in a sibling staging directory, only atomically renamed into targetDataDir once fully validated — see RestoreValidated's doc comment for why: an interruption (including one the caller's process cannot catch, e.g. a plain, non-signal-handled process kill) leaves targetDataDir completely untouched rather than half-restored.

snapshotDir may instead be a cloud destination URI (s3://bucket/prefix or gcs://bucket/prefix; see DestinationRegistry) — Restore downloads it into a local temp directory first, then proceeds exactly as it would for a local snapshotDir. This is also how a snapshot created on one node can be restored onto another, since the two never need to share a filesystem. registry may be nil if snapshotDir is always a local path.

host resolves the manifest-recorded blob/metadata plugins against targetDataDir; composition code (node.go, cmd/dingo, internal/dblifecycle, bark) builds and owns it — typically a fresh, single-use host built just for this call via internal/plugins.NewHost, mirroring how internal/plugins.OpenDatabase builds its own scratch host for a temporary open elsewhere. This package never constructs one itself: a domain package under the database import boundary registering providers or owning a plugin host of its own would split provider ownership away from the application's composition root.

storageConfig propagates the caller's actual configured provider settings into the plugins this resolves — see RestoreStorageConfig's doc comment. Pass the zero value if the caller has no such configuration to propagate.

This is an offline operation: targetDataDir must not be concurrently held open by another *database.Database (e.g. a running node), since it restores the metadata store before starting it (metadata.Restorer) and the blob store immediately after starting it empty (blob.Restorer) — two-phase orchestration that a live store's own Start/Stop lifecycle cannot safely interleave with.

func RestoreValidated

func RestoreValidated(
	ctx context.Context,
	host *plugin.Host,
	registry *DestinationRegistry,
	snapshotDir string,
	targetDataDir string,
	validate func(Manifest) error,
	storageConfig RestoreStorageConfig,
	opts ...ManifestOption,
) (Manifest, error)

RestoreValidated is Restore, but — when validate is non-nil — calls validate(manifest) immediately after resolving the snapshot's manifest and before targetDataDir is touched in any way (not even the empty/absent check), returning validate's error without doing anything destructive if it fails.

This is the hook an offline caller (see internal/dblifecycle.Service. Restore) uses to run Manifest.CheckCompatibility against its own configured plugins/network/storage mode before committing to a restore, without paying for a second cloud download to re-resolve the manifest it already checked: calling PeekManifest and then Restore separately would download a cloud snapshotDir twice.

Interruption safety: every actual restore step (metadata restore, blob restore, validateRestoredDatabase) runs against a sibling staging directory with a unique .dingo-restore-staging- name, never targetDataDir itself. Only once all of them succeed is the staging directory atomically renamed into targetDataDir's place. A caller whose context is cancelled mid-restore, or whose process is killed outright (a plain SIGKILL, or any termination path that skips Go's deferred cleanup — notably, the offline `dingo database restore` CLI's default process termination does not install a signal-aware context, so an operator's Ctrl+C takes this path, not graceful cancellation), is left with targetDataDir exactly as it was before the call: absent, or the same empty directory opened and confirmed. The half-restored staging and payload directories are orphaned siblings. The next restore into the same parent removes them through retained directory handles before it starts: on Unix at once, since the killed process no longer holds them (see CleanStaleRestoreWorkDirs), and on Windows after the age cutoff.

Crash durability (power loss, not just an interrupted process that leaves the OS itself running): atomic rename alone only guarantees targetDataDir never shows a partially-visible directory -- it says nothing about whether the renamed files, or the rename itself, survive a power loss. Before activating the rename, every directory in the staged tree is fsynced (syncDirTree); after the rename, the parent directory is fsynced too, since the rename changed its own entries. If either sync fails, this returns an error without pretending the restore completed durably; a failed pre-rename sync leaves targetDataDir untouched exactly like any other failure above, and a failed post-rename sync is still surfaced even though targetDataDir was already activated, since durability could not be confirmed.

func Snapshot

func Snapshot(
	ctx context.Context,
	db *database.Database,
	dir string,
	trigger string,
	dingoVersion string,
	blobPluginName string,
	metadataPluginName string,
	opts ...ManifestOption,
) (m Manifest, err error)

Snapshot captures a point-in-time backup of db's blob and metadata stores into dir, which must not already exist, writing a manifest alongside them. Both stores are backed up via their native MVCC/versioned mechanism (blob.Backuper, metadata.Backuper), each independently consistent as of whenever it runs — but the two backup calls run concurrently (not sequentially: see the comment further down on why neither exposes a way to separate "capture a consistent point" from "stream/copy it"), so without synchronization a commit landing during either one's own window would write its commit timestamp to one store's backup and not the other's, and the restored copy would fail its cross-store consistency check. Snapshot closes that window with Database.PauseCommitsContext, which blocks new read-write Txns from being constructed (not reads, and not a quiesce — nothing is torn down or disconnected) while both backups capture their stores. A successful snapshot holds the barrier through the slower backup. An aborted snapshot releases it as soon as cancellation or a backup failure makes the result unusable, then waits for both providers before removing partial files. This is safe to call against a database a live node is actively writing to.

The hold is observed in the dingo_snapshot_commit_pause_seconds histogram and can be bounded with WithMaxCommitPause.

dingoVersion is recorded in the manifest for cross-version restore detection; pass the running binary's version string. blobPluginName and metadataPluginName are recorded in the manifest for Restore's later plugin-match check (Manifest.CheckPluginMatch) — db itself no longer knows which provider names resolved its injected Stores, so the caller (which does know, from its own plugin selection) must supply them.

func SnapshotToCloud

func SnapshotToCloud(
	ctx context.Context,
	registry *DestinationRegistry,
	db *database.Database,
	dir string,
	trigger string,
	dingoVersion string,
	blobPluginName string,
	metadataPluginName string,
	cloudDest string,
	name string,
	description string,
	opts ...ManifestOption,
) (Manifest, error)

SnapshotToCloud calls Snapshot to produce the local copy at dir exactly as before, then — if name or description is non-empty — labels it (see LabelSnapshot), then — if cloudDest is non-empty — additionally uploads dir's contents to that destination (a base URI like "s3://bucket/prefix" or "gcs://bucket/prefix"; see DestinationRegistry), nested one level under this snapshot's own ID (dir's base name), mirroring the local SnapshotDir/<snapshotID> layout: the actual upload target is cloudDest + "/" + filepath.Base(dir), not cloudDest itself. This is what makes ListCloudSnapshots able to enumerate multiple snapshots stored at the same configured cloudDest — a flat, unnested upload would silently overwrite every previous snapshot's files with the newest one's. The local copy is always kept; cloudDest is a mirror, not a replacement.

Labeling happens before mirroring, not after: MirrorToCloud uploads whatever is on disk at dir the moment it runs and then writes the cloud-mirrored marker recording that destination as fully done. A caller that labeled the local manifest only after this returned would leave the already-uploaded remote copy permanently without the name/description — and since the marker already says this destination is mirrored, nothing would ever retry the upload to pick up the label. Labeling first means the directory MirrorToCloud uploads already carries it.

cloudDest == "" skips the upload — existing local-only callers are unaffected, and registry may be nil in that case. A recognized cloud destination requires WithManifestKey; the request is rejected before the local snapshot is written when the key is absent.

If the upload fails, the local snapshot is still valid and left in place, but this still returns an error: the operator asked for both copies, so a cloud-only failure is a real (partial) failure, not a silent degrade to local-only.

func (Manifest) Authenticate added in v0.81.0

func (m Manifest) Authenticate(opts ...ManifestOption) error

Authenticate verifies m against the trust key in opts. It returns nil when no key is configured, since there is then nothing to verify against, and ErrManifestUnauthenticated when m's tag is absent or does not match. Under a key it also rejects a format version 1 manifest (ErrManifestLegacyFormat) and, when WithManifestID names the requested snapshot, a manifest whose signed ID differs (ErrManifestIDMismatch).

func (Manifest) CheckCompatibility

func (m Manifest) CheckCompatibility(
	blobPlugin, metadataPlugin, storageMode, network string,
	gates nodesettings.Values,
) error

CheckCompatibility returns an error if the manifest's recorded plugins, storage mode, network, or gates are incompatible with the target values given. Offline restore call sites should call this (directly, or via RestoreValidated) before targetDataDir is touched in any way: unlike the live-node restore path, which always opens the restored copy through database.New using the node's own real configured plugins (so CheckNodeSettings catches a mismatch immediately), an offline restore has no such automatic check — Restore's own validateRestoredDatabase only opens the result using the manifest's own recorded plugins, which is a self-consistency check, not a check against what the caller actually intends to run the restored store with.

func (Manifest) CheckGateMatch

func (m Manifest) CheckGateMatch(configured nodesettings.Values) error

CheckGateMatch returns an error if any gate present in both m.Gates and configured is incompatible under the registry's own decision policy (nodesettings.Evaluate/Gate.apply) — the same policy startup enforcement applies via evaluateAndPersistGates — rather than raw equality. Raw equality was too strict for a LatchBool gate: restoring a snapshot recorded "off" onto a target configured "on" is exactly the one-way latch upgrade Evaluate permits at startup, and rejecting it here would make offline restore refuse a resume that a live database.New against the same manifest values would accept.

A gate present in only one of the two maps is not an error: that is what lets an older snapshot (missing a gate a newer dingo would record) restore under that newer dingo, and a newer snapshot restore when the caller has no way to supply every gate it recorded (e.g. no cardano config loaded, so no genesis hashes). Evaluate already treats a gate absent from configured as skipped, and a gate absent from persisted (the manifest) as an ordinary first-write with no mismatch, so both directions of "only one side has it" fall out of Evaluate for free.

blob_store_id is never compared, even when present in both maps: the restored blob store IS the snapshot's, so its identity always differs from whatever the caller had, and comparing it would fail every restore. metadata_plugin and blob_plugin are excluded too, since CheckPluginMatch already reports those — comparing them again here would just report the same mismatch twice. All three are filtered out of configured before Evaluate ever sees them, rather than skipped after-the-fact, so Evaluate cannot mint a Write for them either.

Every gate configured supplies is marked explicit for this call, the same as database.New's own strict validation: CheckGateMatch is a compatibility check, not a resume, so OverrideEligible's "fall back to whatever is persisted" behavior must never mask a real mismatch here.

func (Manifest) CheckPluginMatch

func (m Manifest) CheckPluginMatch(blobPlugin, metadataPlugin string) error

CheckPluginMatch returns an error if the manifest was produced by a different blob or metadata plugin than the ones given.

type ManifestOption added in v0.70.11

type ManifestOption func(*manifestConfig)

ManifestOption configures snapshot and manifest operations. Each option is consumed only by the operations that use it.

func WithManifestID added in v0.87.0

func WithManifestID(id string) ManifestOption

WithManifestID sets the snapshot ID (local directory name or cloud object prefix segment) that a keyed verification requires the manifest to carry. ReadManifest defaults it to the base name of the directory it reads, so this is needed only where the manifest is read from a location whose name is not the snapshot ID, such as a temporary download directory or a cached catalog row. An empty id is the same as passing no option.

func WithManifestKey added in v0.81.0

func WithManifestKey(key []byte) ManifestOption

WithManifestKey sets the operator trust root: a shared secret that WriteManifest uses to authenticate a manifest, and that ParseManifest, ReadManifest and Manifest.Authenticate require a manifest to verify against. A manifest carries the digests of its payload files, so authenticating it authenticates them. An empty key is the same as passing no option.

func WithManifestMaxBytes added in v0.70.11

func WithManifestMaxBytes(maxBytes int64) ManifestOption

WithManifestMaxBytes sets the maximum encoded manifest size. Zero uses MaxManifestBytes (1 MiB); negative values are rejected before I/O.

func WithMaxCommitPause added in v0.79.1

func WithMaxCommitPause(limit time.Duration) ManifestOption

WithMaxCommitPause bounds how long Snapshot may hold the commit barrier once it has acquired it, including snapshot-state reads. When work remains at the limit, backups are cancelled and the barrier is released. Snapshot waits for both providers to stop before removing partial files and returning ErrCommitPauseExceeded, so its total call duration can exceed the barrier limit. Time spent waiting to acquire the barrier is not counted: that wait is bounded by the caller's context. Zero means no limit; negative values are rejected before any I/O.

type PendingTruncate

type PendingTruncate struct {
	TargetID     uint64 `json:"targetId"`
	TargetSlot   uint64 `json:"targetSlot"`
	TargetHash   []byte `json:"targetHash"`
	TipID        uint64 `json:"tipId"`
	TipSlot      uint64 `json:"tipSlot"`
	TipHash      []byte `json:"tipHash"`
	MithrilFloor uint64 `json:"mithrilFloor"`
	Checksum     []byte `json:"checksum"`
}

PendingTruncate records enough information to resume a truncate whose batched blob deletion was interrupted before metadata was truncated. Checksum protects every field that controls the resumed delete range. The recorded blob tip may already have been deleted when a truncate resumes, so a lower current tip is valid partial progress; an equal or newer current tip must still agree with the marker.

func GetPendingTruncate

func GetPendingTruncate(db *database.Database) (*PendingTruncate, error)

GetPendingTruncate reports a previously-started truncate that still needs completion. Its durable metadata marker prevents a partially committed blob deletion from going unnoticed on restart.

type RestoreRecovery added in v0.70.1

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

RestoreRecovery retains the original external stores after a successful RestoreRecoverable call until its caller completes the enclosing live-node operation. It is also returned with ErrRestoreRollbackPending so the caller can retry an unsuccessful automatic rollback while the plugin host remains active. It is safe to call Commit or Rollback more than once; the first successful disposition wins.

func (*RestoreRecovery) BackupDir added in v0.70.1

func (r *RestoreRecovery) BackupDir() string

BackupDir returns the directory holding the original external-store backups. It remains useful in an error message if automatic rollback cannot complete.

func (*RestoreRecovery) Commit added in v0.70.1

func (r *RestoreRecovery) Commit() error

Commit accepts the replacement and removes the retained original backups.

func (*RestoreRecovery) Rollback added in v0.70.1

func (r *RestoreRecovery) Rollback(ctx context.Context) error

Rollback rejects the replacement and restores both original external stores. Cancellation of the initiating operation is deliberately detached so it cannot interrupt compensation after a destructive reset has begun.

type RestoreStorageConfig

type RestoreStorageConfig struct {
	Blob     map[string]any
	Metadata map[string]any
	// ActiveDataDir identifies the currently active local directory during a
	// live restore. Cloud blob restores use its marker to retain the namespace
	// that Commit may reclaim or Rollback must preserve.
	ActiveDataDir string
	// RecoveryMarkerDirs are trusted local directories where a live restore's
	// operation marker may reside after an interrupted directory swap.
	RecoveryMarkerDirs []string
	// Logger, when set, receives the restore's deferred-index repair
	// progress. A full manifest rebuild on a multi-million-row table takes
	// minutes and is otherwise silent. Nil disables that reporting; nothing
	// else in this package logs.
	Logger *slog.Logger
	// AlonzoLovelacePerUtxoWord is passed through to the validating open's
	// database.Config. It is here rather than derived because this package
	// sits under the database import boundary and cannot read a
	// cardano-node config: without it the validating open of a snapshot
	// taken from a pre-gouroboros-v0.205.7 database refuses the restore
	// and demands a resync, exactly as a real startup would that omitted
	// it. Zero preserves that refusal for a caller that has no genesis.
	AlonzoLovelacePerUtxoWord uint64
}

RestoreStorageConfig carries the caller's configured provider-owned settings (plugin.Selection.Config, e.g. Badger/SQLite tuning options) for the blob/metadata plugins Restore resolves, mirroring what a normal dingo startup (internal/plugins.ResolveStorage) already passes instead of the nil config every resolve here used before this existed. The zero value (both nil) preserves the original behavior exactly.

A per-plugin "dataDir" override specifically is deliberately rejected (see checkNoDataDirOverride) rather than honored: restoreMetadataStore/ restoreBlobStore write into a sibling staging directory, never targetDataDir directly, so the atomic-rename interruption-safety RestoreValidated's own doc comment describes holds; a provider redirected by its own config to some other directory entirely would write outside that staging directory, bypassing the same guarantee. Refusing the restore outright when this is configured is safer than either silently writing to the wrong place (the original gap this type exists to close) or silently ignoring the override (a different, equally silent divergence from what a real subsequent startup would do).

type SnapshotCatalogCursor added in v0.82.0

type SnapshotCatalogCursor struct {
	Generation uint64
	CreatedSec int64
	CreatedNS  int
	ID         string
}

SnapshotCatalogCursor identifies the last row in one immutable catalog generation. The next page starts after this CreatedAt and ID tuple.

type SnapshotCatalogLister added in v0.82.0

type SnapshotCatalogLister interface {
	ListSnapshotCatalog(
		context.Context,
		SnapshotCatalogScanBudget,
	) ([]SnapshotEntry, error)
}

SnapshotCatalogLister performs the same provider discovery as SnapshotLister while enforcing the supplied reconciliation budget. Returning entries with an error means the scan is incomplete; callers must not publish those entries as a replacement catalog.

type SnapshotCatalogScanBudget added in v0.82.0

type SnapshotCatalogScanBudget struct {
	// MaxPages bounds provider list requests.
	MaxPages int
	// MaxPrefixes bounds provider list results inspected, including objects
	// that are not snapshot prefixes.
	MaxPrefixes  int
	MaxManifests int
	MaxEntries   int
	MaxProblems  int
}

SnapshotCatalogScanBudget bounds provider work performed while rebuilding the persistent snapshot catalog.

type SnapshotEntry

type SnapshotEntry struct {
	ID       string
	Manifest Manifest
}

SnapshotEntry is one catalog entry produced by ListSnapshots: a snapshot's directory name (its ID, in callers like bark's DatabaseService that key snapshots by directory name under a fixed base directory) paired with its manifest.

func ListCloudSnapshots

func ListCloudSnapshots(
	ctx context.Context,
	registry *DestinationRegistry,
	cloudDest string,
	opts ...ManifestOption,
) (entries []SnapshotEntry, ok bool, err error)

ListCloudSnapshots lists the snapshots already stored at the base cloud destination URI cloudDest, if its scheme's implementation supports listing (SnapshotLister). ok reports whether listing was actually attempted: false (with a nil error) means cloudDest is empty or its destination type doesn't implement SnapshotLister, which callers like ListAvailableSnapshots should treat as "nothing to add," not a failure — cloud listing is an optional capability, not every CloudDestination implementation provides it. Invalid or unauthenticated entries are omitted; valid entries may be returned alongside an error describing omitted entries or an incomplete listing.

func ListSnapshots

func ListSnapshots(baseDir string, opts ...ManifestOption) ([]SnapshotEntry, error)

ListSnapshots scans baseDir's immediate subdirectories for a valid manifest.json, returning one SnapshotEntry per readable snapshot, newest first (by Manifest.CreatedAt). This covers both manually and automatically (epoch-boundary) triggered snapshots, since both are written as ordinary subdirectories of the same configured snapshot directory — there is no separate catalog store.

A subdirectory that exists but has no manifest.json at all (a snapshot still being written) is silently skipped rather than treated as a hard error, since that is an expected transient state, not corruption of the catalog itself. baseDir not existing yet (no snapshot has ever been taken) returns an empty result, not an error.

Any other ReadManifest failure for a given subdirectory — a malformed manifest, a checksum mismatch (ErrManifestCorrupted), a permission error — is a real problem, not the expected in-progress case, and is not silently swallowed the same way: that subdirectory is still left out of the returned entries (one broken snapshot must not hide every other, otherwise-valid one from the catalog), but its error is accumulated and returned via errors.Join alongside the entries found, so a caller can log or surface it instead of the catalog silently looking one snapshot smaller than it should.

func ListSnapshotsContext added in v0.82.0

func ListSnapshotsContext(
	ctx context.Context,
	baseDir string,
	opts ...ManifestOption,
) ([]SnapshotEntry, error)

ListSnapshotsContext scans the local snapshot directory using ctx.

type SnapshotLister

type SnapshotLister interface {
	// ListSnapshots returns parsed manifests for snapshots found under this
	// destination. ListCloudSnapshots applies the configured trust key before
	// exposing any entries to its caller.
	ListSnapshots(
		ctx context.Context,
		opts ...ManifestOption,
	) ([]SnapshotEntry, error)
}

SnapshotLister is optionally implemented by a CloudDestination to enumerate snapshots already stored under it — used by ListCloudSnapshots (in turn used by bark's ListAvailableSnapshots RPC). Only meaningful when the CloudDestination was parsed from the base destination URI operators configure (databaseLifecycle. snapshotCloudDestination), not a specific snapshot's per-ID sub-path: each snapshot lives one level under that base, mirroring the local SnapshotDir/<snapshotID> layout (see SnapshotToCloud).

Jump to

Keyboard shortcuts

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