Skip to content

Latest commit

 

History

History
649 lines (444 loc) · 19 KB

File metadata and controls

649 lines (444 loc) · 19 KB

chainblocks API Reference

The complete public API surface of @chainblocks/core. Every exported symbol is documented here with its signature, behavior, parameters, return value, errors it can throw, and a runnable example.

For the bigger picture, see:

  • STORY.md — why the library exists
  • MODEL.md — the portable data-model spec (Block format, canonicalization, verification algorithm)
  • REQUIREMENTS.md — REQ-CB-* the API satisfies
  • docs/FEATURES.md — FT-CB-* features mapped to REQs

The API is stable across patch and minor versions; breaking changes require a major version bump (per REQ-CB-NF-033).


Table of contents


Opening and closing a ledger

openLedger

Open an existing ledger or create a new one. Returns a Ledger instance ready for append, read, verify, etc.

function openLedger(options: OpenLedgerOptions): Promise<Ledger>

Parameters

Field Type Description
store Store The storage backend. Use FileStore for production, MemoryStore for tests.
name string Ledger name. Bound into the genesis block and into every block's hash input.
writer string? Optional identity of the writing entity (e.g. "app", "system", an agent id). Bound into the genesis block.

ReturnsPromise<Ledger>

If the store is empty, a genesis block is created and persisted. If the store already contains a genesis block, its name and writer are validated against the supplied options.

Throws

  • LockHeldError — another writer is already holding the lockfile (REQ-CB-005)
  • LedgerMismatchError — the on-disk genesis doesn't match {name, writer} (REQ-CB-002)
  • MalformedBlockError — the genesis block is structurally invalid

Example

import { openLedger, FileStore } from '@chainblocks/core';

const ledger = await openLedger({
  store: new FileStore({ path: './audit.ledger' }),
  name: 'app-audit',
  writer: 'app',
});

console.log('Opened at head:', (await ledger.head()).seq);
await ledger.close();

Requirements: REQ-CB-001, REQ-CB-002, REQ-CB-005 · Feature: FT-CB-01


The Ledger class

The runtime handle for an open ledger. Obtained from openLedger. Methods are listed below.

Ledger.append

Atomically append a new block to the end of the chain.

append(input: BlockInput): Promise<Block>

Parameters

Field Type Description
input.kind string Caller-defined event type (e.g. "app.event.recorded"). chainblocks does not interpret this.
input.payload unknown JSON-serializable payload. Stored as supplied.

ReturnsPromise<Block> — the sealed block, with computed seq, ts, prev, and hash.

Behavior

  • seq is monotonically incremented (REQ-CB-011)
  • ts is generated server-side at append time in ISO 8601 UTC ms (REQ-CB-012); callers cannot supply it
  • prev is set to the previous block's hash (or "GENESIS" for the first block after genesis)
  • hash is computed via canonicalHash over the canonical JSON of all other fields
  • The operation is atomic: either the block is durable after return, or an error is thrown and the chain is unchanged (REQ-CB-019)
  • Concurrent append calls on the same ledger are serialized internally (REQ-CB-020)
  • An 'append' event is emitted after the block is persisted (REQ-CB-070)

Throws

  • StoreClosedError — the ledger has been closed
  • TornWriteError, MalformedBlockError, or other I/O-related errors propagated from the underlying store

Example

const block = await ledger.append({
  kind: 'app.event.recorded',
  payload: { eventId: 'e-a8c4', actor: 'alice', action: 'login' },
});

console.log(`seq=${block.seq} ts=${block.ts} hash=${block.hash}`);

Requirements: REQ-CB-010 through REQ-CB-020, REQ-CB-070 · Feature: FT-CB-02


Ledger.read

Iterate over blocks in seq order, optionally filtered.

read(options?: ReadOptions): AsyncIterable<Block>

Parameters

Field Type Description
options.fromSeq number? Start at this seq (inclusive). Clamped to [0, head.seq].
options.toSeq number? End at this seq (inclusive). Clamped to [0, head.seq].
options.kind string? Filter by kind. Supports exact match ("app.event.recorded") or trailing wildcard ("app.event.*").

ReturnsAsyncIterable<Block> — blocks in seq order from genesis to head (or within the requested range).

Behavior

  • Snapshot-consistent: every yielded block has a valid hash chain back to genesis as of the moment of yield (REQ-CB-034)
  • Safe to call concurrently with append
  • Filters compose: {fromSeq, toSeq, kind} applies all three

Example

for await (const block of ledger.read({ kind: 'app.event.*', fromSeq: 100 })) {
  console.log(block.seq, block.kind, block.payload);
}

Requirements: REQ-CB-030 through REQ-CB-034 · Features: FT-CB-03, FT-CB-04


Ledger.head

Return a snapshot of the current head of the chain.

head(): Promise<LedgerHead>

ReturnsPromise<LedgerHead>{ seq, hash, ts } of the highest-seq block.

For a freshly initialized ledger (genesis only), returns the genesis block's head.

Example

const head = await ledger.head();
console.log(`Chain has ${head.seq + 1} blocks; tip hash = ${head.hash}`);

Requirements: REQ-CB-035 · Feature: FT-CB-05


Ledger.getBySeq

Fetch the block at a specific sequence number.

getBySeq(seq: number): Promise<Block | null>

Parameters

Field Type Description
seq number The sequence number to look up.

ReturnsPromise<Block | null> — the block, or null if seq is out of range.

Note: performance characteristics depend on the store. FileStore is O(N); a SQLite-backed store would be O(log N).

Example

const block = await ledger.getBySeq(1342);
if (block) {
  console.log('Found:', block.kind, block.payload);
}

Requirements: REQ-CB-036 · Feature: FT-CB-06


Ledger.verify

Walk the chain and verify every block's hash. The integrity guarantee at the heart of chainblocks.

verify(options?: VerifyOptions): Promise<VerifyResult>

Parameters

Field Type Description
options.fromSeq number? Start verifying from this seq forward, using the block at fromSeq - 1 as the trust anchor. Useful for incremental verification of large ledgers.

ReturnsPromise<VerifyResult>

type VerifyResult =
  | { ok: true; head: VerifiedHead }
  | { ok: false; tamperedAt: number; reason: VerifyFailureReason }

VerifyFailureReason is one of: "hash_mismatch", "prev_mismatch", "missing_block", "out_of_order", "torn_write", "malformed_block".

Behavior

  • O(N) time in the number of blocks (REQ-CB-043)
  • Stops at the first inconsistency and reports its exact seq and reason (REQ-CB-041)
  • Works on read-only handles (REQ-CB-044)

Example

const result = await ledger.verify();
if (result.ok) {
  console.log(`Valid: ${result.head.seq + 1} blocks, head ${result.head.hash}`);
} else {
  console.error(`Tampered at seq=${result.tamperedAt}: ${result.reason}`);
}

Requirements: REQ-CB-040 through REQ-CB-044 · Features: FT-CB-07, FT-CB-08


Ledger.close

Release resources (file handles, lockfile, in-memory caches).

close(): Promise<void>

Idempotent. Subsequent calls on the same Ledger instance throw StoreClosedError.

Example

await ledger.close();

Requirements: REQ-CB-003 · Feature: FT-CB-01


Ledger.on (events)

Subscribe to ledger events. Currently exposes one event: 'append', emitted after every successful append.

on<E extends keyof LedgerEventMap>(
  event: E,
  listener: (payload: LedgerEventMap[E]) => void,
): this

Behavior

  • Synchronous emission after the block is persisted (REQ-CB-070)
  • A listener that throws does NOT corrupt the chain state, but its error is propagated to the append caller wrapped in AppendListenerError (REQ-CB-071)

Example

ledger.on('append', (block) => {
  console.log(`appended seq=${block.seq} kind=${block.kind}`);
});

Requirements: REQ-CB-070, REQ-CB-071 · Feature: FT-CB-13


Standalone verification

verifyLedger

Convenience helper that opens a store, verifies, and closes — all in one call. Used by the chainblocks-verify CLI.

function verifyLedger(options: VerifyLedgerOptions): Promise<VerifyResult>

Parameters

Field Type Description
store Store The store to verify.
name string Expected ledger name (validated against the genesis block).
writer string? Expected writer (validated against the genesis block, if supplied).

ReturnsPromise<VerifyResult> — same shape as Ledger.verify.

Example

import { verifyLedger, FileStore } from '@chainblocks/core';

const result = await verifyLedger({
  store: new FileStore({ path: './audit.ledger', readonly: true }),
  name: 'app-audit',
});

console.log(result.ok ? 'valid' : `tampered at ${result.tamperedAt}`);

Requirements: REQ-CB-044, REQ-CB-NF-002 · Feature: FT-CB-07


verifyChain

Lower-level: verify a sequence of blocks given a Store and ledger metadata. Used by Ledger.verify internally and exposed for advanced consumers who want to drive verification themselves.

function verifyChain(
  store: Store,
  meta: LedgerMeta,
  options?: VerifyOptions,
): Promise<VerifyResult>

Parameters

Field Type Description
store Store The store to read from.
meta LedgerMeta { name, writer } — must match the genesis block.
options.fromSeq number? Incremental-verification anchor.

ReturnsPromise<VerifyResult>.

Example

import { verifyChain, FileStore } from '@chainblocks/core';

const store = new FileStore({ path: './audit.ledger', readonly: true });
await store.init();
const result = await verifyChain(store, { name: 'app-audit', writer: 'app' });
await store.close();

Requirements: REQ-CB-040, REQ-CB-NF-002 · Feature: FT-CB-07


Stores

Store interface

The pluggable storage contract. Any object implementing this interface can be a chainblocks backend.

interface Store {
  init(genesisIfMissing: Block): Promise<{ head: Block }>;
  append(block: Block): Promise<void>;
  readAll(): AsyncIterable<Block>;
  readFromSeq(seq: number): AsyncIterable<Block>;
  getHead(): Promise<Block | null>;
  close(): Promise<void>;
}

Implementations MUST satisfy:

  • append is atomic: success ⇒ durable; failure ⇒ unchanged (REQ-CB-051)
  • readAll and readFromSeq yield blocks in seq order (REQ-CB-052)
  • getHead returns the highest-seq block, or null if empty (REQ-CB-053)
  • close is idempotent

Requirements: REQ-CB-050 through REQ-CB-053 · Feature: FT-CB-09


MemoryStore

In-memory store backed by a Map<number, Block>. Lost on close.

class MemoryStore implements Store {
  constructor()
}

Intended for tests, ephemeral ledgers, and as a reference implementation of the Store contract.

Example

import { openLedger, MemoryStore } from '@chainblocks/core';

const ledger = await openLedger({
  store: new MemoryStore(),
  name: 'ephemeral-test',
});

Requirements: REQ-CB-054 · Feature: FT-CB-11


FileStore

JSONL file with O_EXCL lockfile. The production default.

class FileStore implements Store {
  constructor(options: FileStoreOptions)
}

Parameters

Field Type Description
options.path string Absolute or relative path to the .ledger file. The lockfile is placed at <path>.lock.
options.readonly boolean? Open read-only (no lockfile acquired). Used by verifier flows.

Behavior

  • Each block is one JSONL line, written in canonical JSON form, followed by \n (REQ-CB-055)
  • fsync is called after every append, before return (REQ-CB-057)
  • The lockfile contains {pid, createdAt} and is acquired via O_EXCL; concurrent opens fail with LockHeldError (REQ-CB-056)
  • Stale locks (PID no longer alive AND lockfile older than the configurable threshold, default 60s) are reclaimed automatically (REQ-CB-NF-012)

Example

import { openLedger, FileStore } from '@chainblocks/core';

const ledger = await openLedger({
  store: new FileStore({ path: '/var/audit/service.ledger' }),
  name: 'service-audit',
  writer: 'service',
});

Requirements: REQ-CB-054 through REQ-CB-057, REQ-CB-NF-012 · Feature: FT-CB-10


Hashing primitives

These are exposed so cross-language verifiers can match exactly. Most consumers do not need to call them directly.

canonicalHash

Compute a "sha256:" + hex(SHA256(...)) over a canonical-JSON serialization of the input.

function canonicalHash(value: unknown): string

Uses RFC 8785 JSON Canonicalization Scheme (JCS) for the serialization step.

Example

import { canonicalHash } from '@chainblocks/core';

const h = canonicalHash({ seq: 0, kind: 'ledger.genesis', payload: {} });
console.log(h); // "sha256:abc..."

Requirements: REQ-CB-016, REQ-CB-NF-020 · Feature: FT-CB-15


canonicalJson

Serialize a value to its RFC 8785 canonical JSON string.

function canonicalJson(value: unknown): string

Example

import { canonicalJson } from '@chainblocks/core';

console.log(canonicalJson({ b: 1, a: 2 })); // '{"a":2,"b":1}'

Requirements: REQ-CB-016 · Feature: FT-CB-15


Block helpers

These are exposed for consumers writing custom Store implementations or migration tooling. Normal consumers never call them; Ledger.append handles all sealing internally.

sealBlock

Compute the hash for a non-genesis block.

function sealBlock(
  input: BlockInput,
  prev: PrevContext,
  meta: LedgerMeta,
): Block

sealGenesisBlock

Compute the hash for the genesis block.

function sealGenesisBlock(
  payload: GenesisPayload,
  meta: LedgerMeta,
  ts: string,
): Block

recomputeHash

Recompute the hash of an existing block. Used by verifyChain to detect tampering.

function recomputeHash(block: Block, meta: LedgerMeta): string

Requirements: REQ-CB-016, REQ-CB-017 · Feature: FT-CB-02


Types

The following types are exported for use in consumer code:

Type Description
Block A sealed block: {seq, ts, prev, kind, payload, hash}
BlockInput The caller-supplied portion: {kind, payload}
LedgerMeta {name, writer?} — the ledger-level metadata bound into every hash
LedgerHead {seq, hash, ts} — a head snapshot
OpenLedgerOptions {store, name, writer?} — arguments to openLedger
ReadOptions {fromSeq?, toSeq?, kind?} — filters for Ledger.read
VerifyOptions {fromSeq?} — options for verify
VerifyResult The discriminated {ok: true, ...} | {ok: false, ...} verify result
VerifyFailureReason "hash_mismatch" | "prev_mismatch" | "missing_block" | "out_of_order" | "torn_write" | "malformed_block"
VerifiedHead {seq, hash} — the verified-head shape inside VerifyResult.ok = true
LedgerEventMap { 'append': Block } — typed event-listener map
GenesisPayload {name, writer: string | null, createdAt} — the genesis-block payload
PrevContext {seq, hash} — the predecessor info sealBlock needs
Store The pluggable storage interface
FileStoreOptions {path, readonly?} — constructor args for FileStore

Errors

All chainblocks errors extend a common base class with a stable name, structured context, and a cause chain (when applicable).

Class Thrown when Recoverable?
ChainblocksError Base class for all chainblocks errors
LockHeldError Another writer holds the lockfile (REQ-CB-005) Yes — wait or close the other writer
LedgerMismatchError Genesis block on disk does not match the supplied {name, writer} No — wrong ledger path or wrong opening args
MalformedBlockError A block read from the store fails structural validation No — the ledger is corrupted
TornWriteError The last line of the JSONL file is incomplete (REQ-CB-NF-011) Yes — manual repair via a future repair command
ChainTamperedError A verify call escalated a tampering finding via throw rather than return Investigation required
AppendListenerError An 'append' event listener threw (REQ-CB-071) Yes — fix the listener
StoreClosedError A method was called on a closed ledger Yes — reopen

Every error carries a .context object with relevant fields (block, seq, expected, actual, path, etc.). The exact shape per class is documented in the source.

Requirements: REQ-CB-005, REQ-CB-071, REQ-CB-NF-011 · Feature: FT-CB-14


Constants

Constant Value Description
GENESIS_PREV "GENESIS" The literal prev value of the genesis block
GENESIS_KIND "ledger.genesis" The kind of the genesis block
HASH_PREFIX "sha256:" The prefix on every hash value (REQ-CB-016)

Requirements: REQ-CB-016, REQ-CB-017 · Feature: FT-CB-02


See also

🖇️