UNPKG

@ocap/types

Version:
424 lines (346 loc) 12.7 kB
// core/types/interfaces/ledger.ts // Ledger state and operation types for Ledger - Verifiable Ledger Kernel // ============================================================================ // Ledger Entry Types // ============================================================================ /** * Ledger Entry - Immutable record of a signed transaction */ export interface ILedgerEntry { /** Global incrementing sequence number */ sequence: number; /** Transaction hash (0x + 64 hex chars) */ hash: string; /** Signer DID */ from: string; /** Anti-replay nonce */ nonce: number; /** Public key */ pk: string; /** Signature */ signature: string; /** Base64 encoded raw transaction bytes */ txRaw: string; /** Decoded JSON object (redundant storage for querying) */ tx: Record<string, unknown>; /** Transaction type (e.g., 'fg:t:transfer') */ txType: string; /** Entry timestamp (ms) */ timestamp: number; /** Chain DID this entry belongs to */ chainId: string; /** hash(sequence + txRaw) for Merkle Tree leaf */ entryHash: string; /** Transaction verification context (passkey, gasStakeHeaders, etc.) */ txExtra?: Record<string, unknown>; /** State commit hash (e.g. Dolt commit) that contains this tx's state changes */ stateCommitHash?: string; /** Whether the entry has been finalized (stateCommitHash set and entryHash recomputed) */ finalized: boolean; } /** * Storage row type for ledger entries (used by adapters) * Uses camelCase to match TypeScript conventions and statedb-dolt patterns */ export interface ILedgerEntryRow { sequence: number; hash: string; from: string; nonce: number; pk: string; signature: string; txRaw: string; tx: string; // JSON string txType: string; timestamp: number; chainId: string; entryHash: string; txExtra: string | null; // JSON string for verification context stateCommitHash: string | null; finalized: number; // 0 or 1 for SQL compatibility } // ============================================================================ // Checkpoint Types // ============================================================================ /** * Checkpoint - Verifiable snapshot proof at a Ledger position */ export interface ICheckpoint { /** Checkpoint incrementing number */ height: number; /** Checkpoint's own hash */ hash: string; /** Last entry sequence covered */ sequence: number; /** Previous checkpoint's sequence */ prevSequence: number; /** Merkle root of ledger entries */ txRoot: string; /** Cumulative transaction count */ txCount: number; /** State root (optional, e.g., Dolt commit hash) */ stateRoot?: string; /** Creation timestamp (ms) */ timestamp: number; /** Previous checkpoint hash */ prevCheckpoint: string | null; /** Proposer DID */ proposer: string; /** Proposer signature */ signature: string; } /** * Storage row type for checkpoints (used by adapters) * Uses camelCase to match TypeScript conventions and statedb-dolt patterns */ export interface ICheckpointRow { height: number; hash: string; sequence: number; prevSequence: number; txRoot: string; txCount: number; stateRoot: string | null; timestamp: number; prevCheckpoint: string | null; proposer: string; signature: string; } // ============================================================================ // Merkle Proof Types // ============================================================================ /** * Merkle Proof - Proves an entry belongs to a checkpoint */ export interface IMerkleProof { /** Entry position (sequence - 1) */ index: number; /** Entry hash (leaf) */ leaf: string; /** Merkle path (sibling hashes) */ proof: string[]; /** Checkpoint's txRoot */ root: string; } // ============================================================================ // Health Metrics // ============================================================================ /** * Ledger health snapshot used by /ledger-health monitoring endpoint. * * Designed to surface the "stuck checkpoint" failure mode (see ledger.ts * createCheckpointIfNeeded): if any entry in the pending range stays * finalized=0, createCheckpoint is permanently skipped and lag grows * unbounded. */ export interface ILedgerHealth { /** Latest entry sequence in the ledger */ latestSequence: number; /** Latest checkpoint (null if none created yet) */ latestCheckpoint: { height: number; sequence: number; timestamp: number; } | null; /** latestSequence - latestCheckpoint.sequence (0 if no checkpoint) */ checkpointLag: number; /** ms since latest checkpoint.timestamp (null if no checkpoint) */ checkpointAgeMs: number | null; /** Count of entries with finalized=0 in the pending range (after latest checkpoint) */ unfinalizedCount: number; /** Timestamp (ms) of oldest unfinalized entry in pending range, null if none */ oldestUnfinalizedTimestamp: number | null; } // ============================================================================ // Configuration Types // ============================================================================ /** * Checkpoint configuration */ export interface ICheckpointConfig { /** Trigger batch size: create checkpoint every N transactions */ batchSize: number; /** Time trigger: max time since last checkpoint (ms) */ timeoutMs: number; /** Enable auto checkpoint */ auto: boolean; } /** * Ledger capabilities for testing */ export interface ILedgerCapabilities { /** Persistent storage */ persistence: boolean; /** True transaction semantics */ transaction: boolean; /** Supports stateRoot */ stateRoot: boolean; /** Supports schema migration */ migration: boolean; } // ============================================================================ // Input Types // ============================================================================ /** * Signed transaction input for ledger.append() */ export interface ISignedTransactionInput { txRaw: string; tx: Record<string, unknown>; txHash: string; txType: string; from: string; nonce: number | string; pk: string; signature: string; chainId: string; timestamp: number; /** Transaction verification context (passkey, gasStakeHeaders, etc.) */ txExtra?: Record<string, unknown>; } // ============================================================================ // Storage Interface // ============================================================================ /** * Storage adapter interface - SQLite/Dolt implement this */ export interface ILedgerStorage { // Entry operations appendEntries(entries: ILedgerEntry[]): Promise<void>; /** * Atomically append an entry with database-generated sequence and entryHash. * This method is concurrency-safe: sequence is generated within a transaction * using MAX(sequence)+1, ensuring no conflicts under multi-instance writes. * * The entry is created with finalized=false. Call finalizeEntry() after * statedb commit to set stateCommitHash, recompute entryHash, and mark finalized. * * @param entry - Entry data without sequence, entryHash, and finalized (set by storage) * @returns Complete entry with sequence, entryHash, and finalized=false */ appendEntryAtomic(entry: Omit<ILedgerEntry, 'sequence' | 'entryHash' | 'finalized'>): Promise<ILedgerEntry>; getEntryByHash(hash: string): Promise<ILedgerEntry | null>; getEntryBySequence(seq: number): Promise<ILedgerEntry | null>; getEntriesByRange(from: number, to: number): Promise<ILedgerEntry[]>; getLatestSequence(): Promise<number>; /** * Iterate entry hashes in a sequence range (inclusive). * * @param fromSeq - Start sequence (inclusive) * @param toSeq - End sequence (inclusive) * @param callback - Called for each entry in ascending sequence order */ iterateEntryHashes( fromSeq: number, toSeq: number, callback: (seq: number, entryHash: string) => Promise<void> ): Promise<void>; /** * Finalize an entry: set stateCommitHash, recompute entryHash, mark as finalized. * * @param txHash - Transaction hash identifying the entry * @param stateCommitHash - State commit hash from statedb * @param sequence - Entry sequence (needed for entryHash recomputation) * @param txRaw - Raw transaction bytes (needed for entryHash recomputation) */ finalizeEntry(txHash: string, stateCommitHash: string, sequence: number, txRaw: string): Promise<void>; /** * Check if there are any unfinalized entries in the given sequence range (inclusive). * * @returns true if at least one entry in [fromSeq, toSeq] has finalized=false */ hasUnfinalizedInRange(fromSeq: number, toSeq: number): Promise<boolean>; /** * Count unfinalized entries and find oldest timestamp in range. * Used by the health endpoint to detect stuck checkpoints. * * @returns count and oldest timestamp (null if count=0) */ getUnfinalizedStats(fromSeq: number, toSeq: number): Promise<{ count: number; oldestTimestamp: number | null }>; // Checkpoint operations saveCheckpoint(checkpoint: ICheckpoint): Promise<void>; getCheckpointByHeight(height: number): Promise<ICheckpoint | null>; getLatestCheckpoint(): Promise<ICheckpoint | null>; getCheckpointForSequence(seq: number): Promise<ICheckpoint | null>; // Schema migration getMigrationVersion(): Promise<number>; runMigrations(): Promise<void>; rollbackMigration(targetVersion: number): Promise<void>; // Lifecycle init(): Promise<void>; close(): Promise<void>; reset(): Promise<void>; } // ============================================================================ // Ledger Interface // ============================================================================ /** * Ledger interface - Main API */ export interface ILedger { readonly chainId: string; // Write (Append-only) append(tx: ISignedTransactionInput): Promise<ILedgerEntry>; /** * Batch append for offline / single-process scenarios (e.g. data migration, import). * * NOT concurrency-safe: sequences are computed in memory from a single * getLatestSequence() call, so concurrent writers would produce conflicts. * Use `append()` for online multi-instance workloads. */ appendBatch(txs: ISignedTransactionInput[]): Promise<ILedgerEntry[]>; // Read get(hash: string): Promise<ILedgerEntry | null>; getBySequence(seq: number): Promise<ILedgerEntry | null>; getRange(from: number, to: number): Promise<ILedgerEntry[]>; // Metadata getLatestSequence(): Promise<number>; // State commit hash /** * Finalize a ledger entry: set stateCommitHash, recompute entryHash, mark finalized. * * @param txHash - Transaction hash identifying the entry * @param stateCommitHash - State commit hash from statedb * @param sequence - Entry sequence (needed for entryHash recomputation) * @param txRaw - Raw transaction bytes (needed for entryHash recomputation) */ finalizeEntry(txHash: string, stateCommitHash: string, sequence: number, txRaw: string): Promise<void>; // Checkpoint createCheckpoint(): Promise<ICheckpoint>; /** Create a checkpoint if batchSize/timeoutMs threshold is reached. Returns checkpoint or null. */ createCheckpointIfNeeded(): Promise<ICheckpoint | null>; /** Get checkpoint by height, or latest if height is undefined */ getCheckpoint(height?: number): Promise<ICheckpoint | null>; getCheckpointForSequence(seq: number): Promise<ICheckpoint | null>; // Merkle getMerkleProof(sequence: number): Promise<IMerkleProof>; verifyMerkleProof(proof: IMerkleProof): boolean; // Monitoring getHealth(): Promise<ILedgerHealth>; // Lifecycle initialize(): Promise<void>; close(): Promise<void>; } // ============================================================================ // Factory Interface // ============================================================================ /** * Ledger factory interface for testing */ export interface ILedgerFactory { name: string; capabilities: ILedgerCapabilities; create(): Promise<ILedger>; cleanup(ledger: ILedger): Promise<void>; } // ============================================================================ // Table Type Mapping // ============================================================================ /** * Maps ledger table names to their row types */ export type LedgerTableTypeMap = { entries: ILedgerEntryRow; checkpoints: ICheckpointRow; }; export type LedgerTableName = keyof LedgerTableTypeMap;