Skip to main content

Event-sourced, eventually-consistent dataset that converges over a partition-prone Reticulum (LoRa) mesh.

Project description

meshlog

An event-sourced, eventually-consistent dataset that converges over a frequently-partitioning, low-bandwidth mesh. Built for the "trusted collaborators, no internet, private radio" case: a base camp and field teams (humanitarian assessment, SAR, expeditions) collecting a shared dataset with no server and no connectivity, reconciling whenever radios come into range.

The transport is Reticulum on real hardware (LoRa/packet radio/anything), but the engine is transport-agnostic and ships with an in-memory transport so you can run, test, and watch it converge with no radios at all.

Install

pip install meshlogd            # core engine (stdlib-only)
pip install "meshlogd[radio]"   # + Reticulum, for the real LoRa/packet-radio transport

Naming: the distribution and daemon command are meshlogd; the importable library is meshlog (import meshlog) — the trailing d follows the Unix daemon convention (cf. ssh/sshd), like how pyyaml installs but imports as yaml.

The idea in one breath

KoboToolbox already solves offline data entry but syncs to a central server over the internet. meshlog replaces that central sync with peer-to-peer anti-entropy gossip over a radio mesh, so the shared dataset converges with no server and no internet. Kobo (or any form UI) owns the forms; meshlog owns the replication layer Kobo doesn't have.

Why event sourcing makes this easy

The whole database is a grow-only set of immutable, per-author events. That single decision hands you the hard distributed-systems properties almost for free:

  • Merging is set union. Union is commutative, associative, and idempotent, so the dataset is a CRDT: it converges regardless of the order or grouping in which events arrive across a partitioning network.
  • Per-author contiguous feeds (the Secure-Scuttlebutt trick) collapse "what do you have?" down to one integer per author — a high-water-mark frontier. That digest is tiny enough to ride inside a Reticulum announce.
  • Hash-chained feeds make history tamper-evident (we're trusted-collaborator, so we want integrity — just not anonymity).
  • The materialized view is a pure fold over the event set, so two nodes with the same events compute byte-identical state. Conflicts on mutable scalar fields resolve by Last-Writer-Wins keyed on a Hybrid Logical Clock, not wall time and not arrival order — so concurrent edits from two islands pick the same winner everywhere.

Anti-entropy protocol (the entire thing)

when two peers can talk:
    each sends its FRONTIER  = {author: high_water_seq}
    on receiving a peer's frontier:
        push the slice of every feed above the peer's marks   (chunked)
    ingest is idempotent + gap-buffering, so overlaps/reorders are harmless

Multi-hop convergence is automatic: a node relays every author's events it holds, not just its own, so A's events reach C through B even if A and C never meet. That's the store-and-forward property, achieved without special-casing it.

Layout

meshlog/
  hlc.py            Hybrid Logical Clock
  event.py          immutable, hash-chained, content-addressed Event
  feedstore.py      append-only per-author log; frontier + delta computation
  reducer.py        deterministic fold -> materialized records (Kobo-style)
  transport.py      Transport interface + in-memory SimTransport (partitions)
  node.py           Node + the anti-entropy protocol
  rns_transport.py  real Reticulum transport (Link + Resource + announce)
demo_sim.py         base camp + 4 teams converge across staged partitions
test_convergence.py 200 randomized partition trials + fold-determinism check
meshlog/cli.py      CLI to run a node over a live Reticulum mesh (the `meshlogd` command)

Run it (no radios needed)

python -m demo_sim          # scripted scenario, prints the converged dataset
python -m test_convergence  # 200 randomized partition/merge trials

The demo islands three groups, has each collect independently, stages a conflicting concurrent edit, then heals the network in stages and verifies every node ends with identical frontiers, identical views, and the same LWW winner.

Run it for real (over Reticulum)

pip install "meshlogd[radio]"          # pulls in Reticulum (rns)
# Machine A:
meshlogd --name base   --data base.jsonl   --create R-100 site_assessment
# Machine B (same LAN to start; add a LoRa interface in ~/.reticulum/config later):
meshlogd --name team_a --data team_a.jsonl --create R-200 water_point
# type `view` at either prompt to watch the dataset converge

Verification status

  • feedstore, reducer, hlc, node, transport, the demo, and the 200 randomized trials run and pass as committed.
  • rns_transport.py / meshlog/cli.py are written against the Reticulum 1.3.x API (Identity, Destination, Link, Resource, Packet, announce handlers) and are faithful to the docs/examples, but have not been exercised on a live mesh here. Stand up two instances to validate end-to-end before field use.

Deliberately left as extension points

  • Attachments / blobs. Events carry a content hash + size; the bytes are not inlined. Add a content-addressed blob store and transfer blobs lazily over RNS Resources, after structured events, so photos never starve assessment data on a slow link.
  • Signatures. Event.is_authentic() currently checks the content hash. With RNS you already hold each author's Ed25519 key — sign the event id and verify on ingest to close the trusted-but-verify loop.
  • Wire codec. Prototype uses JSON for readability. Swap _encode/_decode for MessagePack or CBOR to cut bytes on the radio.
  • Compaction / snapshots. Grow-only logs grow. Add periodic signed snapshots
    • feed truncation once a quorum has acknowledged a watermark.
  • Kobo bridge. Poll the KoboToolbox/ODK submission API (or read KoboCollect instances) and translate each submission into record.create + field.set events. That's the actual on-ramp to the HHI use case.

Reading list (steal from these)

  • Secure Scuttlebutt — append-only per-author feeds + gossip replication for intermittent offline networks. This design is a close cousin.
  • CRDTs — why union-based / LWW-register merges converge.
  • Kulkarni et al. 2014, Hybrid Logical Clocks — the clock used here.
  • Reticulum manual, Understanding + API Reference chapters — Link, Resource, and the announce mechanism.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

meshlogd-0.2.0.tar.gz (23.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

meshlogd-0.2.0-py3-none-any.whl (24.3 kB view details)

Uploaded Python 3

File details

Details for the file meshlogd-0.2.0.tar.gz.

File metadata

  • Download URL: meshlogd-0.2.0.tar.gz
  • Upload date:
  • Size: 23.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for meshlogd-0.2.0.tar.gz
Algorithm Hash digest
SHA256 e3d89b87142332e6212bbeaec1745c841fc5e0602fc96de3e4bb728cef23bb81
MD5 5ba5e75fcf7a1dd11f26552110723471
BLAKE2b-256 46f507ba7ea745b7917165ca0622f85ae0caa72adee22ba06029767a06d16474

See more details on using hashes here.

Provenance

The following attestation bundles were made for meshlogd-0.2.0.tar.gz:

Publisher: publish.yml on meshlogd/meshlogd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file meshlogd-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: meshlogd-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 24.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for meshlogd-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c28e958a07afb92c87cae0749c5fb56c0272f3c0b5378da24e77166b78fe410a
MD5 c90e8fc30024f6aae6a2f662f305f940
BLAKE2b-256 8be57c5f6e8c27b261f08a186a95ec94ed2deedbc9bba71bc22de585cae89aec

See more details on using hashes here.

Provenance

The following attestation bundles were made for meshlogd-0.2.0-py3-none-any.whl:

Publisher: publish.yml on meshlogd/meshlogd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page