We define a block as a signed message that declares:
- The block’s accepted parents in the DAG.
- The set of blocks rejected by this block.
- A CID of arbitrary body data.
- Metadata to interpret the body (schema and media type).
Each block is composed of a payload and a signature, matching the following conceptual structure:
type Header struct {
Payload Payload // metadata for block body
Issuer string // DID of the issuer
Signature []byte // signature by the issuer
}
type Payload struct {
Version int64
Accept []cid.Cid // accepted parent blocks
Reject []cid.Cid // rejected blocks
Body cid.Cid // content-addressed arbitrary data
SchemaUri string // schema URI
MediaType string // MIME type
Comment string // arbitrary commentary
}Let
Each block is a tuple:
where:
-
$\text{id} \in \mathcal{C}$ : the content identifier of the block itself. -
$\mathsf{accept}(B) \subset \mathcal{C}$ : set of parent block CIDs that this block explicitly extends. -
$\mathsf{reject}(B) \subset \mathcal{C}$ : set of block CIDs that this block rejects. -
$\mathsf{body}(B) \in \mathcal{C}$ : CID of the block’s body (arbitrary content). -
$\mathsf{schema}(B) \in \mathbb{S}$ : schema URI string. -
$\mathsf{media}(B) \in \mathbb{M}$ : media type string. -
$\mathsf{comment}(B)$ : comment string. -
$\mathsf{issuer}(B) \in \mathcal{P}$ : DID of the block’s issuer. -
$\mathsf{sig}(B)$ : digital signature over the payload fields, created using the issuer’s secret key.
Let the global DAG be a graph
A block
- All parent CIDs in
$\mathsf{accept}(B)$ exist in the DAG. - The signature
$\mathsf{sig}(B)$ verifies under$\mathsf{issuer}(B)$ against a canonical serialization of the payload. -
$B$ does not create a cycle when added to$\mathcal{D}$ .
Each participant
- A trust function:
- A set of trusted tips
$\mathcal{T}_u \subseteq \mathcal{C}$ used as starting points for state computation.
For a participant
- Start from trusted tips
$\mathcal{T}_u$ . - Traverse backward along
$\mathsf{accept}$ edges recursively. - Whenever a block
$B$ is encountered:- If any ancestor block
$A$ declares$B \in \mathsf{reject}(A)$ , then:- Omit
$B$ and all descendants reachable via$\mathsf{accept}$ edges unless they are also reachable via a separate path that does not pass through a rejecting ancestor.
- Omit
- If any ancestor block
This defines rejection as a pruning operator on traversal.
Formally, the visible blocks for participant
A path is non-rejected if no intermediate block
The following non-normative pseudo-code sketches the subjective DAG traversal and rejection. See Pruning.lean for Lean4 code and examples of pruning rejected blocks. One can experiment with the pruning algorithm here.
import Std.Data.HashMap
import Std.Data.HashSet
open Std
abbrev Cid := String
structure Block where
cid : Cid
accept : List Cid
reject : List Cid
abbrev BlockMap := HashMap Cid Block
abbrev RejectionContext := HashMap Cid (HashSet Cid)
partial def computeVisibleView
(blocks : BlockMap)
(trustedTips : List Cid)
: HashSet Cid :=
let rec visit
(stack : List Cid)
(visited : HashSet Cid)
(visible : HashSet Cid)
(context : RejectionContext)
: HashSet Cid :=
match stack with
| [] => visible
| cid :: rest =>
if visited.contains cid then
visit rest visited visible context
else
match blocks.get? cid with
| none => visit rest (visited.insert cid) visible context
| some block =>
let inheritedRej :=
blocks.fold (init := ∅) fun acc _ child =>
if child.accept.contains cid then
acc ∪ context.getD child.cid ∅
else acc
let fullRej := inheritedRej ∪ HashSet.ofList block.reject
let context' := context.insert cid fullRej
if fullRej.contains cid then
visit rest (visited.insert cid) visible context'
else
let visited' := visited.insert cid
let visible' := visible.insert cid
let stack' := block.accept ++ rest
visit stack' visited' visible' context'
visit trustedTips ∅ ∅ (HashMap.emptyWithCapacity 10)Each participant
- Block bodies
$\mathsf{body}(B)$ , using$\mathsf{schema}(B)$ and$\mathsf{media}(B)$ - Provenance (issuer of each block)
- Ordering (topological or heuristic over the DAG)
This may include reconstructing RDF graphs, applying CRDT logic, filtering by issuer trust, etc.
- Rejection is local and non-monotonic: a descendant of a rejected block can later be re-accepted explicitly.
- Rejection is non-destructive: rejected blocks are still available in the DAG but ignored in a participant’s traversal.
| Concept | Formally |
|---|---|
| Block | Tuple of accepted/rejected parents, body CID, issuer, signature |
| DAG | Induced by accepted parent links |
| Trust | Local function |
| View | Blocks visible from tips via non-rejected paths |
| Rejection | Explicitly prunes traversal unless overridden later |
| State | Application-defined interpretation of visible block bodies |