Skip to content

Repository files navigation

crdt Alire crate badge SPARK DO-178C Tests docs

Ada CRDTs logo

The logo is in the public domain (see Credits). We are not affiliated with AdaCore.

CRDT

CRDT (Conflict-Free Replicated Data Types) library for Ada/SPARK.

The canonical repository is on GitHub. The Codeberg repository was archived because of Codeberg terms-of-service changes on AI-assisted code. We do not accept issues or pull requests there. Submit your proposed changes on GitHub instead.

LLM Usage disclosure

AI assistance was used for this project.

Install

Alire Community Index

alr with crdt

View on Alire Community Index.

Quick Reference

Component Package API Docs
PN-Counter CRDT.Pn_Counters docs
LWW Set (Lamport, deprecated) CRDT.Lww_Element_Sets docs
LWW Set (any clock) CRDT.Lww_Sets docs
RGA Sequence CRDT.Rga docs
Clock strategies CRDT.Clocks docs
State-based sync CRDT.Sync.State_Based docs
Op-based sync CRDT.Sync.Op_Based docs
Thread-safe wrappers CRDT.Protected docs
Bounded wrappers CRDT.Bounded docs
HLC CRDT.HLC docs

Local Index

alr index --add git+https://github.com/bladeacer/Ada_CRDT.git --name crdt
alr with crdt

Then, include with "crdt"; in your .gpr file.

Development

Clone and build locally:

git clone https://github.com/bladeacer/Ada_CRDT.git
cd ada_crdt
make build
make test

Documentation

Full API reference: docs/api-docs/index.md The reference is generated from docstring annotations via make doc. It covers all public and private entities.

DO-178C compliance artifacts (PSAC, HLR, LLR, traceability): docs/compliance/index.md.

CI/CD workflows, jobs, and their local equivalents: docs/ci-cd.md.

Upgrading

See changelogs and migration guide before bumping your alire.toml dependency. Wire format is auto-detected (V1/V2/V3).


Core Types

PN-Counter (Actor Map)

Per-replica increments/decrements. Fixed memory (3 replicas = 3 slots), regardless of op count. See API docs for full interface reference.

with CRDT.Pn_Counters;

A : CRDT.Pn_Counters.PN_Counter (Max_Actors => 5);
B : CRDT.Pn_Counters.PN_Counter (Max_Actors => 5);

CRDT.Pn_Counters.Increment (A, 5, Actor => 1);
CRDT.Pn_Counters.Increment (B, 10, Actor => 2);

CRDT.Pn_Counters.Merge (A, B);  -- value = 15

Package: CRDT.Pn_Counters

LWW-Clocked-Set (any clock strategy)

Last-Writer-Wins set parameterised over any clock strategy (Lamport, Vector, or Matrix). See API docs for full interface reference.

with CRDT.Lww_Sets;
with CRDT.Clocks.Vector;
package V is new CRDT.Clocks.Vector (Max_Replicas => 8);
package S is new CRDT.Lww_Sets (Integer, 100, V.Clock_Time,
  Clk_Kind     => CRDT.Clocks.Clock_Vector,
  ">"          => V.">",
  Max          => V.Max,
  Write_Clock  => V.Write_Clock,
  Read_Clock   => V.Read_Clock);

Set : S.LWW_Clocked_Set (Capacity => 100);
TS  : V.Clock_Time := (others => 0);

S.Add (Set, 42, TS);
S.Add (Set, 99, TS);
S.Remove (Set, 42, TS);

Package: CRDT.Lww_Sets (generic over any CRDT.Clocks.* strategy)

RGA Sequence

Three backend engines, same API. See API docs for full details.

with CRDT.Rga;
package Seq is new CRDT.Rga (Character, 100);
use Seq;

A : RGA (Capacity => 100);
B : RGA (Capacity => 100);

Insert (A, 1, (Replica => 1, Seq => 1), 'a');
Insert (A, 2, (Replica => 1, Seq => 2), 'b');
Insert (B, 1, (Replica => 2, Seq => 1), 'x');

Merge (A, B);  -- convergent state

-- Iterate
Pos : Cursor := First (A);
while Has_Element (Pos) loop
   Put (Element (A, Pos));
   Next (A, Pos);
end loop;

Package: CRDT.Rga (default engine) or CRDT.Sequences.<Engine>

Engine Comparison

Engine Package Design Trade-off
Yjs (default) CRDT.Rga / CRDT.Sequences.Yjs Chunk-based blocks, structural splitting Fast bulk ops, larger tombstone overhead
Naive CRDT.Sequences.Naive Flat linked-list per element Simple, O(n) lookups
Fugue CRDT.Sequences.Fugue BST tree with Depth ordering Anti-interleaving, no GC rebalancing yet

Understand how suitable each backend is for a given use case.

-- Switch engine by changing the with line
with CRDT.Sequences.Naive;
package S is new CRDT.Sequences.Naive (Character, 100);

Sync Layer

See API docs and docs for full interface reference.

State-based (CvRDT) with delta sync and HLC:

with CRDT.Sync.State_Based;

Config : Sync_Config := (Max_Replicas => 4, Delta_Sync => True, HLC_Node => 1);
Local  : Replica_State := Create (Config);
Remote : Replica_State := Create (Config);

Merge (Local, Remote);

Operation-based (CmRDT) with bounded op log and ack/GC:

with CRDT.Sync.Op_Based;

Log : Op_Log (Capacity => 1000);

Append (Log, (Kind => Op_Insert, Seq => 1, Node => 1, Position => 1));
Append (Log, (Kind => Op_Delete, Seq => 2, Node => 1, Del_Position => 1));

Acknowledge (Log, Up_To_Seq => 1);  -- mark delivered
Compact (Log);                       -- purge acknowledged ops

Wrappers

  • CRDT.Protected: Thread-safe protected-object wrappers (no locking).
  • CRDT.Bounded: Compile-time bounded, zero-heap allocation.
with CRDT.Bounded;
package Bnd is new CRDT.Bounded.Bounded_RGA (Character, 100);
R : Bnd.Sequence;

Supporting Types

Package Role
CRDT.Core Replica_Id, Lamport_Time, Protocol_Version, VTime types
CRDT.HLC Hybrid Logical Clock (physical + logical timestamp)
CRDT.Rgas Multi-RGA container

HLC Example

with CRDT.HLC;

Clock : CRDT.HLC.Instance := CRDT.HLC.Create (Node => 1);
CRDT.HLC.Tick (Clock);   -- before sending
CRDT.HLC.Recv (Clock, Remote);  -- on receive, reconcile with remote time

Wire Protocol

All serialised CRDT state begins with a protocol version byte. The version is detected automatically on read.

V1: [4-byte Natural version][4-byte Total][4-byte Count]...
V2: [LEB128 version=2][LEB128 Total][LEB128 Count]...
V3: [LEB128 version=3][clock_kind byte][LEB128 Total][LEB128 Count]...
Version Format Auto-detected
V1 (legacy) Fixed-width Natural'Read/Write for all fields Yes (first 4 bytes)
V2 (default write) LEB128 for all integer fields Yes (first byte = 2)
V3 (clocked) LEB128 + clock kind discriminator byte Yes (first byte = 3)

Legacy types (LWW_Element_Sets, RGA, PN_Counters) continue to write V2 for maximum backward compatibility. New generic types (Lww_Sets) write V3 with the appropriate clock strategy identifier.

V1/V2 data can be migrated to V3 via CRDT.Serialization.Migrate_Header_To_V3.

LEB128 Encoding

All Natural fields in the wire format use LEB128 variable-length encoding (CRDT.Core.LEB128), producing 1-5 bytes per value instead of the fixed 4-byte Natural'Write. Small values (common for clocks, positions, and counts) use 1-2 bytes.

Fields encoded with LEB128:

  • Protocol version (1 byte)
  • Clock kind byte (V3 only)
  • Per-node element count in sets
  • Sequence length and replica/sequence ID pairs
  • Tombstone and strut counts in RGA chunks

This replaces the earlier fixed-width Natural'Write / Natural'Read format (Protocol_Version 1). Read_RGA rejects mismatched versions, enabling safe rolling upgrades.


Building

Command Action
make build Build library + tests
make test Run test suite (see test results)
make prove SPARK proofs via alr gnatprove
make demo Run Conway Game of Life Demo
make doc Generate Markdown API docs (See API docs
make clean Remove build artifacts

Prerequisites: Alire (manages GNAT automatically), Python 3 for make doc.


Demo

Conway's Game of Life Demo

The demo is a real-time TUI simulation. It stress-tests eventual consistency across three independent nodes. It uses LWW_Element_Set for cell state and Yjs RGA for text rows.

make demo

Controls: Q Quit, R Reset, P Pause, M Toggle Engine, C Cycle Clock_Kind.


SPARK Proof

Core packages (CRDT.Core, CRDT.Pn_Counters, CRDT.Clocks.*) are SPARK-proven at the Gold level (Stone + Bronze + Silver + Gold). Current proof statistics are auto-generated by make compliance. See docs/compliance/VERIFICATION.md. Generics (Sequences, LWW, RGA) depend on their instantiations. Platform dependencies (wall clock, RNG, stream I/O) are excluded from formal proof. Runtime assertions (-gnata) give defensive coverage for generic bodies.

SPARK_Mode coverage (every SPARK_Mode => Off location with justification): docs/api-docs/crdt-spark-coverage.md.


Credits

Logo:

  • Ada Logo Editor: The Ada Horizon logo and Aileron Bold font are both released under Creative Common Public Domain (CC0).

Technology Stack:

  • SPARK / Ada 2012: (AdaCore) language and dialect of choice
  • gnatprove: (AdaCore) formal verification of source code
  • Alire: (AdaCore) Ada/SPARK package manager
  • gnatformat: (AdaCore) code formatter for Ada
  • gnatdoc: (AdaCore) API documentation generator, interfaces docstrings with our Python script
  • VT100: Minimal Ada VT100 API library

Inspired by:

Badges:

  • adacovex: Ada/SPARK code/proof coverage, SPARK level, DO-178C HAL status tool

Contributing

We welcome contributions. Please read our Contributing Guide and Code of Conduct before you open an issue or pull request. Use the issue templates in .github/ISSUE_TEMPLATE/ for bug reports, feature requests, and security reports.

License

MIT License.

About

CRDT (Conflict-Free Replicated Data Types) library for Ada/SPARK.

Topics

Resources

Code of conduct

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Contributors

Languages