@ocap/types
Version:
Typescript definitions generated from protobuf
424 lines (346 loc) • 12.7 kB
text/typescript
// 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;