Zerobook.Ledger (zerobook v0.1.0)

Copy Markdown

A tamper-evident, double-entry ledger.

The ledger is a linear chain of Zerobook.Blocks; each block holds the SHA-256 of its own content plus the hash of the previous block. Any edit to a past transaction — or a reordering, insertion or deletion of a block — breaks every subsequent hash, so tampering is detectable by re-verifying the chain.

Usage

ledger =
  Zerobook.Ledger.new()
  |> Zerobook.Ledger.append!(Zerobook.Transaction.new("txn_1", [
    Zerobook.Entry.new("1000.cash", 1_000, "BRL"),
    Zerobook.Entry.new("2000.revenue", -1_000, "BRL")
  ]))

Zerobook.Ledger.verify!(ledger) # => :ok

Ledger state is immutable data (%Zerobook.Ledger{blocks: [...]}); each block is plain data with prev_hash/hash links, so persistence is up to the caller — store blocks in SQLite/Postgres (jsonb) and rebuild the ledger by appending them in order.

Summary

Functions

Validates a transaction and appends it as a new block, returning {:ok, ledger}.

Appends and unwraps: returns the updated ledger or raises.

Computes the running balance of account in currency.

Returns the list of blocks, oldest first.

Returns the last block, or nil on an empty ledger.

Creates an empty ledger.

Returns all postings touching account as {transaction, entry} pairs, oldest first.

Verifies the whole chain: structural hashes and balanced transactions.

Verifies and unwraps: returns :ok or raises.

Types

result()

@type result() :: {:ok, t()} | {:error, term()}

t()

@type t() :: %Zerobook.Ledger{
  blocks: [Zerobook.Block.t()],
  index: non_neg_integer(),
  last_hash: binary()
}

Functions

append(ledger, tx)

Validates a transaction and appends it as a new block, returning {:ok, ledger}.

Rejects unbalanced transactions and duplicate transaction ids.

append!(ledger, tx)

Appends and unwraps: returns the updated ledger or raises.

balance_of(ledger, account, currency)

Computes the running balance of account in currency.

Examples

iex> ledger = Zerobook.Ledger.new() |> Zerobook.Ledger.append!(Zerobook.Transaction.new("t1", [
...>   Zerobook.Entry.new("1000.cash", 1_000, "BRL"),
...>   Zerobook.Entry.new("2000.revenue", -1_000, "BRL")
...> ]))
iex> Zerobook.Ledger.balance_of(ledger, "1000.cash", "BRL")
1000

blocks(ledger)

Returns the list of blocks, oldest first.

last_block(ledger)

Returns the last block, or nil on an empty ledger.

new()

Creates an empty ledger.

statements(ledger, account)

Returns all postings touching account as {transaction, entry} pairs, oldest first.

verify(ledger)

Verifies the whole chain: structural hashes and balanced transactions.

Returns :ok or {:error, reason} where reason is one of (positions are 0-based indexes into the block list):

  • {:bad_hash, position} — the stored hash of the block at position does not match its recomputed content hash (tamper detected)
  • {:broken_link, position} — block at position does not reference the hash of the previous block (reorder/insertion/deletion detected)
  • {:unbalanced_tx, position} — transaction at position no longer sums to zero in every currency

verify!(ledger)

Verifies and unwraps: returns :ok or raises.