This document summarizes the design described in the original paper, Peer-to-peer Non-Consensus Partial Event Ordering Network by Levente Mészáros (local copy: Peer-to-peer Non-Consensus Partial Event Ordering Network.pdf). It is the conceptual background for the simulation model; see implementation.md for how the code realizes these ideas and paper-vs-implementation.md for what is and is not implemented.
Build an information system where all participants can mathematically prove lower and upper bounds for the time of digital events generated by any participant, such that the time interval is shorter than a predefined value (e.g. a minute) according to the local clock of the participant.
The emphasis on the local clock of the participant is essential: the system does not try to establish a single global timeline that everyone agrees on. Instead, each node can independently derive a time interval for any event, expressed in terms of its own clock. Two nodes may derive different intervals, and that is acceptable — the system is "non-consensus" and "disagreement supporting."
The paper lists these design requirements:
- permissionless — anyone can participate without authorization
- trustless — require as little trust in other participants as possible
- dynamic — allow rapid changes in participant relationships and network topology
- decentralized and redundant — no single point of failure
- distributed — efficient use of resources over the whole network
- consistent — different participants must not reach conflicting orderings
- disagreement supporting — anyone may form their own partial ordering
- efficient in memory, disk, CPU, and network bandwidth
And explicit non-requirements:
- global consensus is unnecessary and would require sacrificing too much
- no implicit transfer of value for publishing (unlike proof-of-work blockchains), because resource usage is low
This positions the system deliberately against blockchains: there is no global ledger, no mining, no total order, and no coin. It is closer to a distributed, hash-linked DAG of timestamps that each node grows locally and shares with its neighbors.
| Term | Meaning |
|---|---|
Node (N, Nᵢ) |
A computing device connected in a network. |
Event (E, Eᵢ) |
A data structure generated and stored by a node (a tweet, a photo, a measurement, …). |
Clock event (C, Cᵢ) |
A special event that carries a local timestamp T(C). |
Timestamp (T, Tᵢ) |
A point on some node's local clock. |
- If
Eᵢcontains the hash ofEₖ, this is writtenEₖ ← Eᵢ("Eₖis referenced byEᵢ"; equivalentlyEₖhappened beforeEᵢand is provably older). - An event chain is written
E(underlined) orC, and satisfiesEᵢ ← Eᵢ₊₁for every consecutive pair. A chain is thus a hash-linked path: each element embeds the hash of the previous one, so the whole chain is tamper-evident and strictly ordered.
- Anyone can run as many nodes as they see fit.
- Nodes have private/public keys to prove identity.
- Nodes have neighbors; the topology may change over time.
- Nodes continuously share the hashes of their clock events with current neighbors.
- Nodes may share events (optional; by default events are not distributed).
Any node can create any number of events at any time. An event contains:
- some content (picture, audio, video, text, …),
- some hashes (and locations) of selected local clock events,
- optionally a randomly generated salt,
- optionally a signature.
By embedding the hash of one or more recent local clock events, the event pins itself to a position in that node's clock-event chain: it is provably after the clock events it references.
Any node can create clock events at any time, usually periodically (~1 per second). A clock event contains:
- a local timestamp (no global clock is needed),
- hashes (and locations) of selected local events,
- hashes (and locations) of selected clock events, both local and from neighbors, whose timestamps are smaller than this clock event's timestamp (this ordering is guaranteed for local clock events, but not necessarily for neighbor clock events, because clocks are local and unsynchronized),
- optionally a salt,
- optionally a signature.
After creating a clock event, the node broadcasts its hash to current neighbors.
The result, across the whole network, is an ever-growing, hierarchical, redundant distributed DAG of clock events. Each clock event points backward to the previous local clock event, to the latest known neighbor clock events, and to the local events created since the last clock event. Because everything is hash-linked, an edge in the DAG is a cryptographic proof of "happened-before."
To prove that Eₐ was created earlier than E_b, a node exhibits one or more chains E
such that Eₐ is the first element and E_b is the last:
Eₐ ← E₁ ← E₂ ← … ← Eₙ₋₁ ← Eₙ ← E_b
Because each ← is a hash inclusion, and hashes cannot be predicted, the chain is a proof
that Eₐ existed before E_b.
To prove that an event E was created between Tₐ and T_b according to the local clock
of node N, the node exhibits:
- a clock event
CₐofNwithT(Cₐ) ≥ Tₐsuch thatEwas created later thanCₐ(a chain fromCₐtoE), and - a clock event
C_bofNwithT(C_b) ≤ T_bsuch thatEwas created earlier thanC_b(a chain fromEtoC_b).
Cₐ is the lower bound and C_b is the upper bound of E according to N. The
interval [T(Cₐ), T(C_b)] is N's proven time window for E.
The paper's worked example (see the figure on page 3 of the PDF) has three nodes N₁,
N₂, N₃, each with its own chain of clock events, cross-linked by neighbor references.
An event E₁ is created by N₁ (attached above C₃ of N₁).
Node N₃ can prove that E₁ of N₁ was created between T(C₁) and T(C₆) according to
N₃: C₁ of N₃ is the lower bound and C₆ of N₃ is the upper bound. The chain E
from C₁ of N₃ up to E₁ and back down to C₆ of N₃ is found with the help of the
intermediate node N₂, because N₃ is not directly connected to N₁ in the clock-event
DAG. The single connected chain
C₁(N₃) ← C₂(N₂) ← C₃(N₂) ← C₃(N₁) ← E₁(N₁) [lower half]
E₁(N₁) ← C₄(N₁) ← C₆(N₂) ← C₆(N₃) [upper half]
(both halves being local-to-neighbor-to-…-to-origin and back) is exactly what a chain discovery must reconstruct. The key property: the chain begins and ends at the querying node's own clock events, so the bounds are expressed in that node's local time.
- Nodes maintain a hierarchical routing table over time, similar to IP routing.
- Finding the lower/upper bounds is done with a probabilistic beam search, recursively along the neighbor hierarchy toward the event's originator.
- During the search, each neighbor is either:
- the originator of the event, which can directly provide local lower/upper bounds; or
- not the originator, in which case it extends the lower/upper bounds received from the next neighbor toward the destination, using its own clock-event chain.
- Finding boundaries may require transfer of value to incentivize node operators to answer queries.
- The result is a lower/upper bound pair of clock events that form a single chain
Ewith the target eventEincluded.
- Find the time boundaries of both events using the method above.
- Compare the two intervals:
- if one interval is strictly before the other, the order is determined;
- otherwise (the intervals overlap), the order cannot be determined according to the local clock of the node.
Network assumptions:
- a few billion nodes (comparable to IP nodes on the internet),
- a hierarchical, redundant, ever-growing distributed DAG of clock events,
- average distance of leaf nodes from top-level nodes ~10 hops,
- average ~10 neighbors per node (~10 neighbor clock events referenced),
- nodes generate events as content becomes available,
- clock-event hashes are distributed to neighbors; events are not distributed by default,
- ~1 clock event per second, ~100–1000 bytes each.
Rough approximation:
- nodes store their own local events only,
- nodes store ~3–30 GB of clock events per year,
- a chain between two clock events
Cₐ,C_bofNᵢincluding an arbitrary event of any node is ~40 events long (2 × 2 ×network depth — "there and back again," on both the lower and upper side, due to hierarchical routing), - such a chain, excluding the event content, is roughly
40 × 10 × 100 = 40,000bytes, which fits into a single UDP datagram, - the timestamp of any event can be narrowed to less than ~40 seconds by any node according to that node's local clock.
The estimate above grows without bound — a chain kept forever accumulates ~3–30 GB/year and never shrinks. The implementation avoids this by having a node run several independent clock chains at once, at geometrically spaced intervals (chain 0 fastest, each next chain a fixed factor slower); each is an ordinary clock-event chain in the sense of Components above, just ticking less often.
Every event a node publishes is pinned into every chain, not just the fastest one: at
creation it references each chain's current tip, and each chain's next tick references the
event back. That symmetry is what makes pruning safe — a node can ring-prune a chain down to a
fixed number of retained clock events without ever losing an event's orderability, because a
dropped fast-chain clock event's role is still covered by a slower, still-retained chain. Old
events don't disappear; their provable bound just widens to the coarser chain's own interval —
roughly 1/C of the event's age for C clock events kept per chain. Storage is then flat
(bounded by the number of chains times the ring size per chain) instead of growing with
wall-clock time, while reach — the oldest age still orderable at all — grows exponentially with
each additional chain.
The security rests entirely on the difficulty of inverting/predicting a cryptographic hash.
An attacker wants to insert an event back in time as if it had always been there:
- at
Tₐ, create an auxiliary eventEᵢcontaining a non-existent event hash, - wait until
T_b ≫ Tₐwhen forging the past is needed, - forge
Eₖso thatEₖ ← Eᵢ(i.e.Eᵢ's placeholder hash actually equalsEₖ), - announce
Eₖas if it had been present atTₐall along.
This requires finding a preimage for the hash embedded at Tₐ — the hash function makes
forging the past hard.
An attacker wants to precompute an event and insert it later as if freshly created:
- at
Tₐ, precompute future content, - wait until
T_b ≫ Tₐ, - insert
EᵢatT_bwith the precomputed content, - announce
Eᵢas if created atT_b.
The defense: a digital representation of a real-world event captures only a small subset of the available information, and the hash of a contained (fresh) clock event must be used to designate exactly which subset is being represented. Precomputing all possible digital representations of the future is generally hard (it amounts to simulating the real world), and the hash function makes precomputing only the required subset and pre-including it in the chain hard. (The paper marks the completeness of this argument as an open question: "is this always possible?")