This commit is contained in:
Raven Scott
2026-05-20 23:36:32 -04:00
parent a020270cb1
commit be94546cd3
218 changed files with 9189 additions and 3078 deletions
@@ -1,117 +1,137 @@
# API: hyper-p2p-causal-consensus
**Protocol:** `hyper-p2p-causal-consensus/v1`
**Protocol:** `hyper-p2p-causal-consensus/v1` (`CONSENSUS_PROTOCOL`)
**Export:** `HyperP2PCausalConsensus`
**Export:** `HyperP2PCausalConsensus` (default class export from `index.js`)
## Overview
HyperP2PCausalConsensus Novel BFT causal consensus primitive for Bare/Pear P2P. Key Innovations (never-before-seen in Bare ecosystem): - Hybrid causal + total ordering: Uses vector clocks for causality + cryptographic
`HyperP2PCausalConsensus` proposes opaque `data` payloads with an embedded vector clock, collects signed votes, and finalizes when accept votes reach `ceil(peers.size * quorumThreshold)`. Decided events are stored in `decidedOrders` and optionally persisted to an injected `hyperbee`. With `enableGossip: true` and `topic`, proposals and votes can flow over `initModuleSwarm`; otherwise use local `propose` / `vote` / `receive*` for simulation.
Extends `bare-events` `EventEmitter`. Call `close()` to stop timers and mark the instance closed.
## Constructor
```js
const mod = new HyperP2PCausalConsensus(opts)
const HyperP2PCausalConsensus = require('hyper-p2p-causal-consensus')
const c = new HyperP2PCausalConsensus(options)
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `topic` | `string` | `Buffer` | `null` | Hyperswarm topic; required for P2P `ready()` |
| `keyPair` | KeyPair | random | Ed25519 key pair |
| `localId` | `string` \| `Buffer` | random 8 bytes (encoded) | Logical node id |
| `keyPair` | `KeyPair` | `hypercore-crypto.keyPair()` | Signing + swarm identity |
| `quorumThreshold` | `number` | `0.67` | Fraction of `peers.size` for quorum |
| `maxPeers` | `number` | `32` | Documented cap (not enforced in hot path) |
| `proposalTimeoutMs` | `number` | `10000` | Pending → `expired` timer |
| `voteTimeoutMs` | `number` | `5000` | Reserved for future use |
| `enableSigning` | `boolean` | `true` | Ed25519 sign/verify proposals and votes |
| `persistDecided` | `boolean` | `true` | Write decided events to `hyperbee` when set |
| `idEncoding` | `string` | `'hex'` | Buffer id encoding |
| `topic` | `string` \| `Buffer` \| `null` | `null` | Hyperswarm topic when gossip enabled |
| `enableGossip` | `boolean` | `false` | Starts gossip timer + swarm when `true` |
| `hyperbee` | `Hyperbee` \| `null` | `null` | Optional persistence |
| `swarm` / `protomux` | — | `null` | Injection hooks for tests |
## Methods
### `propose(data, causalDeps = {})`
### `async propose(data, causalDeps = {})`
- **Returns:** `Promise`
- **Throws:**
- `Error: Consensus instance closed`
Creates a signed proposal, self-votes, starts expiry timer.
### `vote(proposalId, accept = true)`
- **Returns:** `Promise<string | null>``proposalId` or `null` if local fork detected
- **Throws:** `Error: Consensus instance closed`
- **Emits:** `proposal`, optionally `error` on fork
- **Returns:** `Promise`
- **Throws:** — (none documented in method body)
### `async vote(proposalId, accept = true)`
### `receiveProposal(proposal, fromPeerId)`
Cast local vote; may finalize quorum.
- **Returns:** `Promise`
- **Throws:** — (none documented in method body)
- **Returns:** `Promise<boolean>`
- **Emits:** `vote`, `consensus` / `order-decided` on quorum
### `receiveVote(proposalId, vote)`
### `async receiveProposal(proposal, fromPeerId)`
- **Returns:** `Promise`
- **Throws:** — (none documented in method body)
Inbound proposal path (wire or tests). Dedups by id, verifies signature, auto-votes if causal check passes.
### `getDecidedOrder(order)`
- **Returns:** `Promise<boolean>`
- **Emits:** `proposal-received`, `invalid-signature` on bad sig
- **Returns:** `value`
- **Throws:** — (none documented in method body)
### `async receiveVote(proposalId, vote)`
### `getAllDecided(—)`
Merge vote; finalize on quorum.
- **Returns:** `value`
- **Throws:** — (none documented in method body)
### `getMetrics(—)`
- **Returns:** `value`
- **Throws:** — (none documented in method body)
- **Returns:** `Promise<boolean>`
### `addPeer(peerId, publicKey)`
- **Returns:** `value`
- **Throws:** — (none documented in method body)
Registers peer for quorum size and signature verification.
### `getStats(—)`
- **Returns:** `boolean`
- **Returns:** `object`
- **Throws:** — (none documented in method body)
### `getDecidedOrder(order)` / `getAllDecided()`
### `close(—)`
Query decided log by sequence or sorted array.
- **Returns:** `Promise<void>`
- **Throws:** — (none documented in method body)
### `getMetrics()`
### `integrateVectorClock(vcModule)`
| Field | Description |
|-------|-------------|
| `proposals`, `votesReceived`, `quorumsAchieved`, `forksDetected`, `decided`, `signed`, `verified` | Counters |
| `peers`, `pendingProposals` | Derived sizes |
- **Returns:** `Promise`
- **Throws:** — (none documented in method body)
### `getStats()`
Returns `{ ...this._stats }` (compatibility stub; prefer `getMetrics()`).
### `async integrateVectorClock(vcModule)`
Stores optional `hyper-p2p-crdt-version-vector` module reference.
### `async close()`
Sets `_isClosed`, clears gossip and proposal timers, emits `closed`.
## Events
| Event | Payload |
|-------|---------|
| `closed` | no payload |
| `consensus` | decidedEvent |
| `error` | err |
| `fork-detected` | proposals |
| `gossip` | type |
| `hyperswarm-gossip` | topic |
| `invalid-signature` | proposalId, from |
| `order-decided` | event |
| `proposal` | proposal |
| `proposal-expired` | payload object |
| `proposal-received` | proposalId, from |
| `protomux-send` | payload |
| `vote` | payload object |
| Event | Payload | When |
|-------|---------|------|
| `proposal` | proposal object | Local propose |
| `vote` | `{ proposalId, voter, accept }` | Vote cast |
| `consensus` | decided event | Quorum reached |
| `order-decided` | `{ order, event }` | Same as finalize |
| `proposal-expired` | `{ proposalId }` | Timeout |
| `fork-detected` | `{ peerId, proposals }` | Conflicting issuer data |
| `invalid-signature` | `{ proposalId, from }` | Verify failed |
| `gossip` | `{ type, proposal }` | Gossip timer (pending) |
| `error` | `Error` | Persistence or fork errors |
| `closed` | — | `close()` |
## getStats()
## getStats() / getMetrics()
Returns `{ ...this._stats }` — typically `ops`, `errors`, and module-specific counters (`created`, `relays`, `open`, `peers`, etc.).
Library-only modules may include `mode: 'local'`.
Use **`getMetrics()`** for operational counters. `getStats()` spreads `_stats` (may be empty).
## Wire (enableGossip + topic)
| type | fields | direction | behavior |
|------|--------|-----------|----------|
| `proposal` | `proposal` object | inbound | `receiveProposal` |
| `vote` | `proposalId`, `vote` | inbound | `receiveVote` |
Outbound proposals also use `gossipSend` from `_sendProposalViaProtomux`.
## Errors
Stable message substrings: see [`../_shared/ERROR_CODES.md`](../../_shared/ERROR_CODES.md).
## P2P
When `topic` is set, `ready()` joins Hyperswarm and opens Protomux `hyper-p2p-causal-consensus/v1`.
| Message | Source |
|---------|--------|
| `Consensus instance closed` | `propose` when closed |
## Testing
```bash
cd modules/consensus-coordination/hyper-p2p-causal-consensus
npm install && npm test
bare examples/basic.js
```
Integration: [`../../real_tests/integration/causal-consensus-two-node.js`](../../../real_tests/integration/causal-consensus-two-node.js)
Register enough `addPeer` entries to satisfy quorum in unit tests without a live swarm.
@@ -1,44 +1,33 @@
# Architecture: hyper-p2p-causal-consensus
**Category:** Consensus & coordination
**Protocol:** `hyper-p2p-causal-consensus/v1`
```mermaid
flowchart LR
App[Application] --> Mod[HyperP2PCausalConsensus]
Mod --> Mux[Protomux hyper-p2p-causal-consensus/v1]
Mux --> Swarm[Hyperswarm]
```
## Sequence (P2P)
```mermaid
sequenceDiagram
participant App
participant Mod as Module
participant SW as Hyperswarm
participant Peer
App->>Mod: ready(topic)
Mod->>SW: join(topic)
SW->>Peer: connection
Mod->>Peer: gossip / Protomux
Peer-->>Mod: onmessage
Mod-->>App: emit(event)
flowchart TB
App --> CC[HyperP2PCausalConsensus]
CC --> Proposals[proposals Map]
CC --> Decided[decidedOrders Map]
CC --> VC[vectorClock Map]
CC --> HB[optional hyperbee]
CC --> SW[initModuleSwarm when enableGossip]
```
## Wire messages
| type | fields | direction | behavior |
|------|--------|-----------|----------|
| `proposal` | proposal, proposalId, type, vote | gossip | Handled in onmessage / gossipSend |
| `vote` | proposalId, vote | gossip | Handled in onmessage / gossipSend |
| `proposal` | `proposal` | inbound | Dedup, verify, vote, maybe finalize |
| `vote` | `proposalId`, `vote` | inbound | Merge vote map, quorum check |
## State model
- In-memory `Map` / `Set` structures for hot path
- Optional Hyperbee/Hypercore persistence when `storageDir` or `memoryOnly` is configured
- `close()` tears down swarm, timers, and clears ephemeral state
| Store | Content |
|-------|---------|
| `proposals` | Pending/decided proposal + votes Map |
| `decidedOrders` | Total order sequence → event |
| `vectorClock` | Per-peer counters |
| `forksDetected` | Set of peer ids |
## Composition
Composes with: `hyper-p2p-distributed-lock`, `hyper-p2p-quorum-pool`.
Integrate `hyper-p2p-crdt-version-vector` via `integrateVectorClock`; use `hyper-p2p-presence` for peer discovery before `addPeer`.