LCBMessage & DAGNode Schema
weave beginner 4 min read
ELI5
LCBMessage is a parcel label: sender address (sender), tracking number (msg_id), send-date sequence counter (lamport), list of parcels this one depends on (parents), and the parcel contents (payload). DAGNode is the depot’s internal slip stapled to the label — it adds when the parcel arrived and whether it’s been handed to the customer.
Technical Deep Dive
Class Diagram
classDiagramclass MsgID { +[u8; 32] 0 +from_bytes(bytes: [u8;32]) MsgID +bytes() [u8;32]}
class PeerID { +u32 0 +new(id: u32) PeerID +value() u32}
class LCBMessage { +MsgID msg_id +PeerID sender +u64 lamport +Vec~MsgID~ parents +Vec~u8~ payload +new(msg_id, sender, lamport, parents, payload) LCBMessage +to_bytes() Vec~u8~}
class DAGNode { +LCBMessage message +u64 received_at +bool delivered +usize dependencies_remaining +new(message, received_at) DAGNode +satisfy_dependency() +ready_to_deliver() bool}
LCBMessage --> MsgIDLCBMessage --> PeerIDDAGNode --> LCBMessageSource: mesh-node/src/proto.rs lines 10–121.
Wire Format (to_bytes layout)
---config: packet: bitsPerRow: 8---packet-beta title LCBMessage to_bytes wire layout 0-7: "msg_id [32 B Blake3]" 8-15: "sender [u8 PeerID]" 16-23: "lamport [u64 LE]" 24-31: "parents_len [u8]" 32-39: "parents[] [32 B × N]" 40-47: "payload [variable]"Note: sender is serialised as a single byte (self.sender.value() as u8), capping PeerID to 0–255 on the wire. Larger peer sets would require a format change.
DAGNode Initialisation
DAGNode::new(message, received_at) sets:
delivered = falsedependencies_remaining = message.parents.len()received_at= Unix microseconds (provided by caller; not set internally)
Field Semantics
| Field | Type | Role |
|---|---|---|
msg_id | MsgID ([u8;32]) | Globally unique — Blake3 hash of content. Deduplication key in DAGState.messages. |
sender | PeerID (u32) | Identifies originating peer. Stored in DAGState.peer_messages for per-peer tracking. |
lamport | u64 | Logical clock at send time. Used to update DAGState.lamport_clock. |
parents | Vec<MsgID> | Causal dependencies. Length initialises dependencies_remaining. |
payload | Vec<u8> | Opaque application bytes — typically a serialised Automerge change. |
received_at | u64 (DAGNode) | Unix microseconds when message was received. Used for latency measurement. |
Key Terms
- MsgID → 32-byte Blake3-based identifier; immutable once created; used as BTreeMap key (Ord derived)
- PeerID →
u32newtype; identifies a peer in the local mesh session - lamport → Logical send-time counter; not a wall clock; updated per
max(local, received) + 1rule - parents → Slice of MsgIDs that must be delivered before this message; empty for genesis messages
- payload → Opaque bytes handed to the application after causal order is satisfied
Q&A
Q: Why is MsgID [u8; 32] rather than a u64 integer?
A: Blake3 produces a 32-byte digest. Using the full digest as the ID enables content-addressing (peers verify the ID matches the hash of the operation) and makes collisions computationally infeasible. A u64 sequence number would require a central coordinator to assign.
Q: Can parents be empty for messages other than the genesis?
A: Yes — any message whose sender considers no causal dependency necessary will have parents = []. This is valid but unusual in collaborative editing; it would appear as a concurrent root and be delivered immediately without waiting on any prior message.
Q: What does received_at measure and who sets it?
A: It is Unix microseconds, provided by the caller of DAGNode::new. It is not set by DAGState itself. This design allows tests to inject deterministic timestamps.
Examples
Minimal message construction in a test (from proto.rs lines 255–262):
LCBMessage { msg_id: MsgID::from_bytes([1; 32]), sender: PeerID::new(1), lamport: 1, parents: vec![], // genesis — no dependencies payload: vec![1],}A child message referencing the above as a dependency:
LCBMessage { msg_id: MsgID::from_bytes([2; 32]), sender: PeerID::new(2), lamport: 2, parents: vec![MsgID::from_bytes([1; 32])], // waits for msg [1;32] payload: vec![2],}neighbors on the map
- LCB DAG State Machine debugging why a message is stuck in the DAG and never delivered to the application
- Automerge IPLD CRDT Integration debugging why two peers have diverged state_hash values after a merge
- ProtocolMessage Envelope adding a new wire message type
- CRDT Operation Message adding a new operation type
- FNP Lamport Clocks & Causal Ordering understanding causal ordering in distributed systems