Wire Protocol
Every message type with field-level documentation
gossipsub pub/sub broadcast
stream point-to-point
req/resp request-response
internal actor messages
BroadcastMessage gossipsub 8 variants
Top-level enum for all messages published via gossipsub topics. Each variant is Borsh-serialized before being sent as a gossipsub message payload. Context topics use context/<hex>, group topics use group/<hex>.
StateDelta — application state replication
Carries a WASM execution result (state diff) to all peers in a context. Published on context gossipsub topics after a successful method execution and state commit.
struct StateDelta {
context_id: ContextId,
author_id: PublicKey,
delta_id: [u8; 32],
parent_ids: Vec<[u8; 32]>,
hlc: HybridTimestamp,
root_hash: Hash,
artifact: Cow<[u8]>,
nonce: Nonce,
events: Option<Cow<[u8]>>,
governance_epoch: Vec<[u8; 32]>,
}
Borsh serialization · delta_id = SHA-256(artifact + metadata) · parent_ids capped by DAG head limit
HashHeartbeat — context state comparison
Periodic heartbeat published on context topics. Peers compare root_hash and dag_heads to detect divergence and trigger sync.
struct HashHeartbeat {
context_id: ContextId,
root_hash: Hash,
dag_heads: Vec<[u8; 32]>,
}
SpecializedNodeDiscovery — peer role announcement
struct SpecializedNodeDiscovery {
nonce: Nonce,
node_type: NodeType,
}
SpecializedNodeJoinConfirmation — role join ack
struct SpecializedNodeJoinConfirmation {
nonce: Nonce,
}
GroupMutationNotification — group change signal
Lightweight notification that a group was mutated. Does not contain the full op — peers use this to decide whether to fetch details or update caches.
struct GroupMutationNotification {
group_id: GroupId,
mutation_kind: GroupMutationKind,
}
SignedGroupOpV1 — governance operation (v1 wire)
Carries a Borsh-serialized SignedGroupOp as an opaque byte vector. Published on group gossipsub topics. Receivers deserialize, verify signature, and apply.
struct SignedGroupOpV1 {
payload: Vec<u8>,
}
Inner payload is borsh(SignedGroupOp) — see SignedGroupOp schema section below for full structure
GroupGovernanceDelta — governance DAG delta
Alternative governance replication format used by the DAG sync layer. Carries the same data as SignedGroupOpV1 but with explicit DAG metadata for the governance DAG.
struct GroupGovernanceDelta {
group_id: GroupId,
delta_id: [u8; 32],
parent_ids: Vec<[u8; 32]>,
payload: Vec<u8>,
}
GroupStateHeartbeat — group governance comparison
Periodic heartbeat for governance DAG state. Published every 30s on group topics. Peers compare dag_heads to detect missing ops and trigger governance catch-up.
struct GroupStateHeartbeat {
group_id: GroupId,
dag_heads: Vec<[u8; 32]>,
member_count: u64,
}
GroupMutationKind 18 variants
Enum carried inside GroupMutationNotification. Describes what kind of group mutation occurred, allowing receivers to react selectively without deserializing the full op.
enum GroupMutationKind {
MembersAdded,
MembersRemoved,
Upgraded,
Deleted,
ContextDetached,
SettingsUpdated,
MemberRoleUpdated,
VisibilityUpdated,
ContextAttached,
ContextAliasSet,
MemberCapabilitySet,
DefaultCapabilitiesSet,
ContextVisibilitySet,
DefaultVisibilitySet,
ContextAllowlistSet,
MemberAliasSet,
GroupAliasSet,
ContextRegistered,
}
StreamMessage stream 3 variants
Framing protocol for all point-to-point stream communication. Each stream begins with an Init message, followed by zero or more Message frames, and can terminate with OpaqueError.
enum StreamMessage {
Init {
payload: InitPayload,
},
Message {
payload: MessagePayload,
},
OpaqueError,
}
Borsh serialization · length-prefixed framing over libp2p streams (QUIC/TCP)
InitPayload stream init 11 variants
Sent as the first message on a new stream. Determines the protocol/purpose of the stream and carries initial handshake data.
BlobShare — transfer application binary
struct BlobShare {
blob_id: BlobId,
blob: Cow<[u8]>,
}
KeyShare — exchange encryption key
struct KeyShare {
context_id: ContextId,
holder_id: PublicKey,
encrypted_key: Cow<[u8]>,
}
DeltaRequest — fetch specific delta
struct DeltaRequest {
context_id: ContextId,
delta_id: [u8; 32],
}
DagHeadsRequest — get current heads
struct DagHeadsRequest {
context_id: ContextId,
}
SnapshotBoundaryRequest — negotiate snapshot range
struct SnapshotBoundaryRequest {
context_id: ContextId,
root_hash: Hash,
}
SnapshotStreamRequest — start full snapshot transfer
struct SnapshotStreamRequest {
context_id: ContextId,
}
TreeNodeRequest — fetch Merkle tree node
struct TreeNodeRequest {
context_id: ContextId,
node_hash: Hash,
}
LevelWiseRequest — level-wise tree comparison
struct LevelWiseRequest {
context_id: ContextId,
level: u32,
hashes: Vec<Hash>,
}
EntityPush — push entities to peer
struct EntityPush {
context_id: ContextId,
entities: Vec<u8>,
}
GroupDeltaRequest — fetch governance op
Requests a specific governance operation from the peer's op log. Used during governance catch-up when heartbeat reveals missing ops.
struct GroupDeltaRequest {
group_id: GroupId,
delta_id: [u8; 32],
}
MessagePayload stream data 14 variants
Subsequent data frames sent after the initial Init on a stream. The variant matches the stream type established by InitPayload.
DeltaResponse — delta fetch result
struct DeltaResponse {
delta: Option<StateDelta>,
}
DagHeadsResponse — current DAG heads
struct DagHeadsResponse {
heads: Vec<[u8; 32]>,
}
SnapshotBoundaryResponse — snapshot range info
struct SnapshotBoundaryResponse {
root_hash: Hash,
dag_heads: Vec<[u8; 32]>,
entity_count: u64,
}
SnapshotStreamData — snapshot chunk
struct SnapshotStreamData {
chunk: Cow<[u8]>,
is_last: bool,
}
SnapshotStreamEnd — snapshot complete
struct SnapshotStreamEnd {
root_hash: Hash,
dag_heads: Vec<[u8; 32]>,
}
TreeNodeResponse — Merkle tree node data
struct TreeNodeResponse {
node: Option<TreeNode>,
}
LevelWiseResponse — level comparison result
struct LevelWiseResponse {
differing: Vec<Hash>,
missing: Vec<Hash>,
}
EntityPushAck — entity push acknowledgment
struct EntityPushAck {
accepted: u64,
rejected: u64,
}
GroupDeltaResponse — governance op data
struct GroupDeltaResponse {
payload: Option<Vec<u8>>,
}
BlobShareAck — blob received confirmation
struct BlobShareAck {
blob_id: BlobId,
accepted: bool,
}
KeyShareAck — key share confirmation
struct KeyShareAck {
accepted: bool,
}
ContextStateCheckResponse — state check result
struct ContextStateCheckResponse {
root_hash: Hash,
in_sync: bool,
}
StateSyncChunk — incremental state sync data
struct StateSyncChunk {
chunk_id: u64,
data: Cow<[u8]>,
is_last: bool,
}
StateSyncComplete — state sync finalization
struct StateSyncComplete {
root_hash: Hash,
dag_heads: Vec<[u8; 32]>,
total_entities: u64,
}
GroupOp gossipsub 24 variants
The actual governance operation inside a SignedGroupOp. Each variant represents a specific group mutation. Applied deterministically on all nodes for convergence.
Member Management — 9 variants
MemberAdded {
member_id: PublicKey,
role: MemberRole,
capabilities: Capabilities,
}
MemberRemoved {
member_id: PublicKey,
}
MemberRoleSet {
member_id: PublicKey,
role: MemberRole,
}
MemberCapabilitySet {
member_id: PublicKey,
capabilities: Capabilities,
}
DefaultCapabilitiesSet {
capabilities: Capabilities,
}
JoinWithInvitationClaim {
claim: InvitationClaim,
}
SubgroupCreated {
child_group_id: [u8; 32],
}
SubgroupRemoved {
child_group_id: [u8; 32],
}
MemberJoinedViaContextInvitation {
context_id: ContextId,
inviter_id: PublicKey,
invitation_payload: Vec<u8>,
inviter_signature: String,
}
Context Lifecycle — 3 variants
ContextRegistered {
context_id: ContextId,
application_id: Option<ApplicationId>,
}
ContextDetached {
context_id: ContextId,
}
TargetApplicationSet {
application_id: ApplicationId,
}
Visibility & Access Control — 5 variants
DefaultVisibilitySet {
visibility: Visibility,
}
ContextVisibilitySet {
context_id: ContextId,
visibility: Visibility,
}
ContextAllowlistReplaced {
context_id: ContextId,
allowlist: Vec<PublicKey>,
}
ContextCapabilityGranted {
context_id: ContextId,
member_id: PublicKey,
capability: Capability,
}
ContextCapabilityRevoked {
context_id: ContextId,
member_id: PublicKey,
capability: Capability,
}
Aliases — 3 variants
ContextAliasSet {
context_id: ContextId,
alias: Option<String>,
}
MemberAliasSet {
member_id: PublicKey,
alias: Option<String>,
}
GroupAliasSet {
alias: Option<String>,
}
Group Lifecycle & Special — 4 variants
GroupDelete {
}
GroupMigrationSet {
migration: MigrationConfig,
}
UpgradePolicySet {
policy: UpgradePolicy,
}
Noop {
}
SignedGroupOp Schema v3
The complete signed governance operation structure. Each op is content-addressed (SHA-256 of signable bytes), Ed25519 signed, and forms a DAG via parent hashes.
Wire Layout
struct SignedGroupOp {
version: u8,
group_id: [u8; 32],
parent_op_hashes: Vec<[u8; 32]>,
state_hash: [u8; 32],
signer: PublicKey,
nonce: u64,
op: GroupOp,
signature: [u8; 64],
}
Signable Subset
struct SignableGroupOp {
version: u8,
group_id: [u8; 32],
parent_op_hashes: Vec<[u8; 32]>,
state_hash: [u8; 32],
signer: PublicKey,
nonce: u64,
op: GroupOp,
}
Signing Process
1. Build signable: Construct SignableGroupOp (all fields except signature).
2. Domain prefix: Prepend domain separator b"calimero-signed-group-op-v3" to Borsh-serialized bytes.
3. Sign: signature = Ed25519::sign(private_key, domain_prefix ++ borsh(signable))
4. Content hash: op_hash = SHA-256(borsh(signable)) — used as the DAG delta ID.
Validation Order
- Signature verification (Ed25519)
- Schema version check (must be 3)
- Nonce ≥ last seen nonce for signer
- state_hash matches current group state (or all-zeros to bypass)
- Signer has sufficient role/capabilities for the op
- parent_op_hashes.len() ≤ 256
Key Invariants
- parent_op_hashes — capped at 256 entries
- nonce — monotonically increasing per signer
- state_hash — all-zeros means skip validation
- max_dag_heads — capped at 64 per group
- Content hash is deterministic: same signable bytes → same hash
NetworkMessage internal 15 variants
The Actix message enum sent to the NetworkManager actor. These are internal to the node — never serialized on the wire. Each variant triggers a specific libp2p operation.
enum NetworkMessage {
Publish { topic: TopicHash, data: Vec<u8> },
Subscribe { topic: TopicHash },
Unsubscribe { topic: TopicHash },
OpenStream { peer: PeerId, protocol: StreamProtocol },
SendMessage { stream_id: StreamId, data: Vec<u8> },
CloseStream { stream_id: StreamId },
ProvideRecord { key: RecordKey, value: Vec<u8> },
GetRecord { key: RecordKey },
GetProviders { key: RecordKey },
GetPeers,
GetPeerInfo { peer: PeerId },
Bootstrap,
DialPeer { peer: PeerId, addrs: Vec<Multiaddr> },
GetListenAddrs,
GetMeshPeers { topic: TopicHash },
}
NetworkEvent internal 11 variants
Events emitted by the NetworkManager actor and handled by NodeManager. These represent observed network activity — gossip messages, peer changes, stream events.
enum NetworkEvent {
GossipMessage {
topic: TopicHash,
source: PeerId,
data: Vec<u8>,
},
StreamOpened {
stream_id: StreamId,
peer: PeerId,
protocol: StreamProtocol,
},
StreamMessage {
stream_id: StreamId,
data: Vec<u8>,
},
StreamClosed {
stream_id: StreamId,
},
PeerConnected {
peer: PeerId,
addrs: Vec<Multiaddr>,
},
PeerDisconnected {
peer: PeerId,
},
TopicSubscribed {
topic: TopicHash,
},
TopicUnsubscribed {
topic: TopicHash,
},
RecordFound {
key: RecordKey,
value: Vec<u8>,
},
ProvidersFound {
key: RecordKey,
providers: Vec<PeerId>,
},
ListenAddrExpired {
addr: Multiaddr,
},
}
These are NOT serialized on the wire — they are Actix messages exchanged internally between NetworkManager and NodeManager actors via LazyRecipient<NodeMessage>