Skip to content

Architecture

The Quidnug reference node is a single Go binary organized around six cooperating subsystems, an HTTP+signature REST API, and a peer-to-peer gossip surface.

┌────────── Client SDKs (Py · Go · JS · Rust · …) ─────────┐
│ │
▼ ▼
┌───────────────────────────────────────────────────────────────┐
│ HTTP REST API · /api/v1 · /api/v2 │
└───────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ QuidnugNode (Go) │
│ │
│ Trust Engine · Nonce Ledger · Guardian Registry │
│ Block Engine · Push Gossip · Bootstrap / Forks │
└───────────────────────────────────────────────────────────────┘
▲ ▲ ▲ ▲
HTTP+sig Gossip Probes Snapshot pull
│ │ │ │
▼ ▼ ▼ ▼
┌───────────────────────────────────────────────────────────────┐
│ Peer QuidnugNode instances (P2P) │
└───────────────────────────────────────────────────────────────┘

Trust Engine, computes trust along depth-bounded BFS paths in the trust graph. Paths are cached and invalidated when new trust edges land. See trust.

Nonce Ledger, enforces per-signer monotonic nonce ordering. See QDP-0001.

Guardian Registry, tracks the M-of-N quorum for each subject, in-flight recoveries, time-locks, and resignations. See QDP-0002 and QDP-0006.

Block Engine, validates, tiers, and applies blocks using Proof-of-Trust acceptance. See consensus.

Push Gossip, propagates fresh rotations to interested peers in seconds rather than polling cycles. See QDP-0005.

Bootstrap / Forks, fresh nodes seed state from a quorum of trusted peers (K-of-K); operators coordinate protocol upgrades at future block heights; light clients verify anchors via compact Merkle proofs. See QDP-0008, QDP-0009, QDP-0010.

  • cmd/quidnug/, the binary entry point.
  • internal/core/, the node runtime (subsystems above).
  • pkg/client/, the Go SDK.
  • pkg/signer/{hsm,webauthn}/, pluggable signing backends.
  • schemas/, language-agnostic JSON schemas for canonical-bytes verification across SDKs.
  • /api/v1, core: identities, trust, titles, events, anchors, queries.
  • /api/v2, guardians, push gossip, bootstrap, Merkle proofs.
  • /api/health, liveness + readiness.
  • /api/info, node version and feature flags.
  • /metrics, Prometheus scrape endpoint.

The full OpenAPI 3.0 spec lives in docs/openapi.yaml. A Postman collection at docs/postman/quidnug.postman_collection.json mirrors every endpoint.

  • Rogue-node security, how the protocol handles hostile peers in a consortium.
  • Integration guide, deployment topologies (single node, three-node consortium, TLS termination, HMAC auth, observability).
  • Design proposals, the numbered QDPs that govern protocol evolution.

Quidnug is a trust protocol implementation consisting of a Go-based node server and optional client libraries. This document describes the internal architecture for developers implementing or extending Quidnug.

┌─────────────────────────────────────────────────────────────┐
│ HTTP Layer │
│ handlers.go (API endpoints) + middleware.go (rate limiting) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Business Logic │
│ node.go (QuidnugNode) - Transaction processing, blocks │
└─────────────────────────────────────────────────────────────┘
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ validation.go │ │ registry.go │ │ crypto.go │
│ Tx validation │ │ State storage │ │ ECDSA signing │
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Network Layer │
│ network.go - Node discovery, broadcasting, cross-domain │
└─────────────────────────────────────────────────────────────┘

The central struct managing all node state:

  • Blockchain: Ordered list of validated blocks
  • PendingTxs: Transactions awaiting inclusion in blocks
  • TrustDomains: Domains this node manages/participates in
  • KnownNodes: Discovered network peers
  • Registries: Materialized views of blockchain state (Trust, Identity, Title)

Each registry has its own sync.RWMutex for granular concurrent access.

Trust in Quidnug is relational, not absolute. There is no global “trust score” for any quid-trust is always computed dynamically from an observer’s perspective to a target.

  1. Observer → Target: Every trust query specifies an observer (who is asking) and a target (who is being assessed). The same target may have different trust levels when viewed by different observers.

  2. Transitive with Multiplicative Decay: Trust propagates through the network with decay. If A trusts B at 0.8 and B trusts C at 0.7, then A’s transitive trust in C is 0.8 × 0.7 = 0.56.

  3. Best Path Selection: When multiple paths exist, the algorithm finds and returns the path with maximum trust.

  4. Same Entity: An observer has full trust (1.0) in itself.

  5. No Path: If no path exists from observer to target, trust level is 0.

Trust edges are stored in the registry:

// TrustRegistry maps: truster -> trustee -> trustLevel
TrustRegistry map[string]map[string]float64

Trust values are computed on-demand via ComputeRelationalTrust():

type RelationalTrustResult struct {
Observer string // Who is asking
Target string // Who is being assessed
TrustLevel float64 // Computed transitive trust (0.0-1.0)
TrustPath []string // Path of quid IDs for best route
PathDepth int // Number of hops
Domain string // Trust domain (optional)
}
TypePurposeKey Fields
TRUSTEstablish trust between quidstruster, trustee, trustLevel (0.0-1.0)
IDENTITYDefine quid attributesquidId, name, attributes, updateNonce
TITLEDeclare asset ownershipassetId, owners (must sum to 100%)
EVENTRecord events for subjectssubjectId, subjectType, sequence, eventType, payload/payloadCid

All transactions require cryptographic signatures (ECDSA P-256).

Transactions are validated before entering the pending pool:

  1. Trust Domain Check: Domain must exist or be empty (default domain)
  2. Signature Verification: ECDSA P-256 signature must be valid
  3. Format Validation: Quid IDs must be 16-char lowercase hex
  4. Business Rules:
    • Trust levels: 0.0 <= level <= 1.0, no NaN/Inf
    • Identity updates: updateNonce must increase
    • Titles: ownership percentages must sum to exactly 100.0
    • Events: see Event Transaction validation below

Event transactions have additional validation rules:

RuleDescription
TrustDomainMust exist (no empty/default domain for events)
SubjectIDMust be valid 16-character hex quid format
SubjectTypeMust be "QUID" or "TITLE"
EventTypeRequired, maximum 64 characters
Payload/PayloadCIDEither payload or payloadCid must be provided (not both empty)
PayloadCID FormatIf provided, must be a valid IPFS CID (CIDv0 or CIDv1)
Payload SizeInline payload maximum 64KB (MaxPayloadSize)
SequenceMust be monotonically increasing (> LatestSequence, or 0/1 for new streams)
SignatureRequired, signer must be the subject owner

Validators assess blocks based on their relational trust in the block creator:

type TrustProof struct {
TrustDomain string // Domain for this block
ValidatorID string // QuidID of the validator
ValidatorTrustInCreator float64 // Validator's relational trust in block creator
ValidatorSigs []string
ValidationTime int64
}

The ValidatorTrustInCreator field is computed at validation time using ComputeRelationalTrust(validatorID, creatorID, maxDepth). This means:

  • Different validators may have different trust levels for the same block creator
  • Trust is evaluated dynamically, reflecting the current state of the trust graph
  • There is no static “trust score” stored for any quid

Blocks are generated periodically (configurable via BLOCK_INTERVAL):

  1. Filter pending transactions by trust domain
  2. Create block with trust proof (validator signature)
  3. Add to blockchain
  4. Process transactions to update registries
  5. Broadcast to domain peers

Node Discovery: Periodically queries seed nodes for peer lists.

Transaction Broadcasting: Fire-and-forget POST to domain peers.

Cross-Domain Queries: Hierarchical domain walking (e.g., sub.domain.com -> domain.com -> com) to find authoritative nodes.

Nodes advertise their supported domains to the network using a gossip protocol. This enables efficient discovery of which nodes support which domains without requiring centralized coordination.

The Domain Gossip Protocol allows nodes to:

  • Announce the trust domains they support to the network
  • Discover other nodes that support specific domains
  • Route domain-specific queries to appropriate nodes
  • Maintain an up-to-date view of the network’s domain topology
type DomainGossip struct {
NodeID string `json:"nodeId"` // QuidID of the originating node
Domains []string `json:"domains"` // List of domains the node supports
Timestamp int64 `json:"timestamp"` // Unix timestamp when gossip was created
TTL int `json:"ttl"` // Time-to-live (maximum hop count)
HopCount int `json:"hopCount"` // Current hop count (incremented on forward)
MessageID string `json:"messageId"` // Unique identifier for deduplication
}
FieldDescription
NodeIDQuidID of the node that originally created this gossip message
DomainsArray of domain names the originating node supports
TimestampUnix timestamp (seconds) when the gossip was created
TTLMaximum number of hops before the message stops propagating
HopCountNumber of hops this message has traveled (starts at 0)
MessageIDUnique identifier used to prevent duplicate processing
┌─────────────────────────────────────────────────────────────────┐
│ Domain Gossip Flow │
└─────────────────────────────────────────────────────────────────┘
Node A Node B Node C
│ │ │
│ 1. Create gossip │ │
│ (HopCount=0) │ │
│ │ │
│──── POST /api/gossip/domains ────►│ │
│ │ │
│ 2. Check MessageID │
│ in GossipSeen │
│ │ │
│ 3. Process & store │
│ domain info │
│ │ │
│ 4. HopCount < TTL? │
│ Yes: Forward │
│ │ │
│ │──── Forward (HopCount+1) ────►│
│ │ │
│ │ 5. Check MessageID
│ │ (already seen?)
│ │ │
│ │ 6. Process if new
  1. Periodic Broadcasting: Each node runs a background goroutine (runDomainGossip) that broadcasts domain info at configurable intervals
  2. Gossip Creation: createDomainGossip() generates a new message with:
    • The node’s QuidID
    • Current supported domains (from SupportedDomains config or registered TrustDomains)
    • Current Unix timestamp
    • Configured TTL from GossipTTL
    • HopCount starting at 0
    • Unique MessageID (UUID)
  3. Broadcast: BroadcastDomainInfo() sends the gossip to all known nodes

When a node receives gossip via ReceiveDomainGossip():

  1. Validation: Rejects gossip with empty NodeID or negative TTL
  2. Self-Check: Ignores gossip originating from itself
  3. Deduplication: Checks GossipSeen map using hasSeenGossip(messageID)
    • If seen: Silently ignore (prevents loops and duplicate processing)
    • If new: Mark as seen with markGossipSeen(messageID) and continue
  4. Processing: processDomainGossip() updates the DomainRegistry with the originator’s domain information
  5. Forwarding: If HopCount < TTL, increment HopCount and forward to known nodes (excluding the originator)

The GossipSeen map tracks processed messages:

GossipSeen map[string]int64 // MessageID -> timestamp when seen
GossipSeenMutex sync.RWMutex // Protects concurrent access
  • hasSeenGossip(messageID): Returns true if the message was already processed
  • markGossipSeen(messageID): Records the message ID with current timestamp
  • cleanupGossipSeen(): Periodically removes old entries to prevent unbounded growth
Environment VariableDefaultDescription
DOMAIN_GOSSIP_INTERVAL2mInterval between gossip broadcasts
DOMAIN_GOSSIP_TTL3Maximum hop count before gossip stops propagating

Example configuration:

Terminal window
# Broadcast domain info every 5 minutes, allow up to 5 hops
DOMAIN_GOSSIP_INTERVAL=5m DOMAIN_GOSSIP_TTL=5 ./quidnug-node

Or in config.yaml:

domain_gossip_interval: "2m"
domain_gossip_ttl: 3
MethodEndpointDescription
GET/api/node/domainsGet this node’s currently supported domains
POST/api/node/domainsUpdate this node’s supported domains (triggers immediate gossip)
POST/api/gossip/domainsReceive domain gossip from another node

Returns the node’s supported domains:

{
"nodeId": "a1b2c3d4e5f6g7h8",
"domains": ["example.com", "api.example.com"]
}

Update supported domains and trigger immediate gossip broadcast:

{
"domains": ["example.com", "api.example.com", "new.example.com"]
}

Receives gossip from other nodes (node-to-node communication):

{
"nodeId": "b2c3d4e5f6g7h8i9",
"domains": ["other.com"],
"timestamp": 1699900000,
"ttl": 3,
"hopCount": 1,
"messageId": "550e8400-e29b-41d4-a716-446655440000"
}

The gossip protocol uses dedicated mutexes:

MutexProtects
GossipSeenMutexGossipSeen map for deduplication
DomainRegistryMutexDomainRegistry map storing node→domains mappings
KnownNodesMutexKnownNodes map when iterating for broadcast
FunctionPurpose
SignData(data []byte)Sign with node’s private key (ECDSA P-256)
VerifySignature(pubKey, data, sig)Verify signature against public key
calculateBlockHash(block)SHA-256 hash of block contents

Key Format: Public keys are uncompressed P-256 (65 bytes: 0x04 || X || Y), hex-encoded.

Signature Format: 64 bytes (r || s, each 32 bytes), hex-encoded.

Quidnug integrates with IPFS for storing large event payloads that exceed the inline size limit.

type IPFSClient interface {
Pin(ctx context.Context, data []byte) (cid string, err error)
Get(ctx context.Context, cid string) (data []byte, err error)
IsAvailable() bool
}
MethodPurpose
Pin(ctx, data)Store data in IPFS and return the content identifier (CID)
Get(ctx, cid)Retrieve data from IPFS by CID
IsAvailable()Check if IPFS integration is enabled and reachable
ImplementationDescription
HTTPIPFSClientProduction client that communicates with an IPFS gateway via HTTP API
NoOpIPFSClientStub implementation when IPFS is disabled; IsAvailable() returns false

The implementation is selected at startup based on the QUIDNUG_IPFS_ENABLED configuration:

  • When QUIDNUG_IPFS_ENABLED=true: HTTPIPFSClient connects to QUIDNUG_IPFS_GATEWAY_URL
  • When QUIDNUG_IPFS_ENABLED=false (default): NoOpIPFSClient is used

Event payloads can be stored in two ways:

StrategyConditionStorage
InlinePayload ≤ 64KB (MaxPayloadSize)Stored directly in the payload field
IPFSPayload > 64KB or explicitly pinnedContent stored in IPFS, CID in payloadCid field

When processing an event with payloadCid:

  1. The node retrieves the content from IPFS using IPFSClient.Get()
  2. Content is validated and processed
  3. Applications can cache retrieved payloads locally

Note: If IPFS is unavailable and only payloadCid is provided, the event data cannot be fully retrieved until IPFS becomes available.

The node persists everything that should survive a restart to DATA_DIR. File-by-file:

FileOwnerLifecycle
node_key.jsonstate_persist.go:loadOrCreateNodeKeyGenerated on first boot; loaded on every subsequent boot. NodeID = sha256(publicKey)[:16] is stable across restarts. ID is cross-checked against the public key, a tampered file refuses boot.
blockchain.jsonstate_persist.go:SaveBlockchainSnapshot of the full block history. Saved every 30 s by runStatePersistLoop + on shutdown. Loaded after NewQuidnugNode builds the genesis-only chain, replacing it with the persisted history.
trust_domains.jsonstate_persist.go:SaveTrustDomainsSnapshot of TrustDomains + DomainRegistry. Same lifecycle as blockchain.json. Dynamic POST /api/v1/domains registrations land here.
pending_transactions.jsonpersistence.go:SavePendingTransactionsPending tx queue. Saved on shutdown, restored on boot.
peer_scores.jsonpeer_score.go:persistOncePer-peer composite scores, severe-event totals, quarantine state, and the recent-event ring. Snapshot every 5 min + on shutdown.
audit-log.jsonl (optional)internal/auditAppend-only operator audit log. Path is configurable via audit_log_path.

All writes are atomic (tmp file + rename) through internal/safeio so a crash mid-flush can’t leave a corrupt file. Files are versioned (schemaVersion: 1); future bumps fail loudly rather than silently regenerating.

Operator-quid file (OPERATOR_QUID_FILE) is intentionally outside DATA_DIR. It carries the long-lived operator identity that may be shared across multiple nodes the same operator runs, and is typically read-only-mounted at /etc/quidnug/operator.quid.json. The operator quid is separate from node_key.json: the per-process NodeID identifies this specific instance for gossip dedup and fork detection, while the operator quid is the identity that accumulates trust grants. See README.md for the operator-vs-node split and the home-operator playbook for the provisioning flow.

  • Rate Limiting: Token bucket per IP (default 100 req/min)
  • Body Size Limit: Prevents oversized payloads (default 1MB)
  • Input Validation: Quid ID format, string sanitization

The ComputeRelationalTrust function computes transitive trust from an observer to a target:

func (node *QuidnugNode) ComputeRelationalTrust(
observer, target string,
maxDepth int,
) (float64, []string, error)
  1. BFS Traversal: Uses breadth-first search starting from the observer
  2. Multiplicative Decay: Path trust = product of all edge trust levels along the path
  3. Cycle Avoidance: Tracks visited nodes in each path to prevent infinite loops
  4. Best Path Selection: Returns the maximum trust found across all explored paths
  5. Depth Limiting: Respects maxDepth parameter (defaults to 5 if not specified)
Trust Graph:
A → B (0.8)
A → C (0.6)
B → D (0.7)
C → D (0.9)
Query: ComputeRelationalTrust("A", "D", 5)
Paths explored:
A → B → D: 0.8 × 0.7 = 0.56
A → C → D: 0.6 × 0.9 = 0.54
Result:
TrustLevel: 0.56 (maximum)
TrustPath: ["A", "B", "D"]
PathDepth: 2
ScenarioResult
Observer equals targetTrustLevel: 1.0, Path: [observer]
No path existsTrustLevel: 0.0, Path: empty
Direct trust onlyTrustLevel: direct edge value, Path: [observer, target]

The node maintains registries for tracking event streams associated with quids and titles.

// Metadata about each subject's event stream
EventStreamRegistry map[string]*EventStream // keyed by subjectId
// Actual event transactions for each subject
EventRegistry map[string][]EventTransaction // keyed by subjectId
type EventStream struct {
SubjectID string `json:"subjectId"` // Quid or asset ID
SubjectType string `json:"subjectType"` // "QUID" or "TITLE"
LatestSequence int64 `json:"latestSequence"` // Highest sequence number
EventCount int64 `json:"eventCount"` // Total events in stream
CreatedAt int64 `json:"createdAt"` // Unix timestamp of first event
UpdatedAt int64 `json:"updatedAt"` // Unix timestamp of last event
LatestEventID string `json:"latestEventId"` // Transaction ID of most recent event
}

When processing a block containing event transactions, updateEventStreamRegistry performs:

  1. Lock acquisition: Acquires write lock on EventStreamMutex
  2. Stream initialization: Creates new EventStream if subject has no prior events
  3. Event append: Adds the EventTransaction to EventRegistry[subjectId]
  4. Metadata update: Updates EventStreamRegistry with:
    • Incremented EventCount
    • Updated LatestSequence to the event’s sequence
    • Updated UpdatedAt timestamp
    • Updated LatestEventID
FunctionPurpose
GetEventStream(subjectId)Returns stream metadata, or false if no stream exists
GetStreamEvents(subjectId, limit, offset)Returns paginated events ordered by sequence (ascending)

Events are returned in sequence order to support chronological replay of a subject’s history.

Quidnug implements a novel consensus mechanism called Proof of Trust where block validation is subjective-each node validates blocks based on its own relational trust in the block’s validator.

Block validation is split into two distinct phases:

┌─────────────────────────────────────────────────────────────────┐
│ ValidateBlockCryptographic() │
│ • Hash verification │
│ • Signature verification │
│ • Chain linkage (prevHash, index) │
│ • Transaction format validation │
│ │
│ UNIVERSAL: All honest nodes agree on this │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ ValidateTrustProofTiered() │
│ • Compute relational trust: node → validator │
│ • Compare against domain threshold │
│ • Return acceptance tier │
│ │
│ SUBJECTIVE: Different nodes may have different results │
└─────────────────────────────────────────────────────────────────┘

This separation allows nodes to:

  1. Agree on cryptographic validity (objective)
  2. Disagree on whether to accept the block (subjective, based on trust)

The BlockAcceptance type defines four tiers:

type BlockAcceptance int
const (
BlockTrusted BlockAcceptance = iota // Integrate into main chain
BlockTentative // Store separately, don't build on
BlockUntrusted // Extract data, relay, don't store block
BlockInvalid // Reject entirely
)
Incoming Block
┌───────────────────────┐
│ ValidateBlockCrypto() │
└───────────────────────┘
┌─────┴─────┐
│ Valid? │
└─────┬─────┘
No │ Yes
┌───────────┴───────────┐
▼ ▼
BlockInvalid ┌────────────────────────┐
│ Extract trust edges │
│ (store as unverified) │
└────────────────────────┘
┌────────────────────────┐
│ ValidateTrustProof │
│ Tiered() │
└────────────────────────┘
┌───────────────────────┼───────────────────────┐
│ │ │
▼ ▼ ▼
trust >= threshold distrust < trust trust <= distrust
│ < threshold │
▼ │ ▼
BlockTrusted ▼ BlockUntrusted
│ BlockTentative │
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Add to main │ │ Store in │ │ Edges already │
│ chain │ │ TentativeBlocks │ │ extracted │
│ Process txs │ │ Don't build on │ │ Block discarded │
│ Promote edges │ └─────────────────┘ └─────────────────┘
│ to verified │
└─────────────────┘

The node maintains two separate trust edge registries:

// Edges from trusted validators (high-assurance decisions)
VerifiedTrustEdges map[string]map[string]TrustEdge
// Edges from all cryptographically valid blocks (discovery with discounting)
UnverifiedTrustRegistry map[string]map[string]TrustEdge
RegistrySourceUse Case
VerifiedTrustEdgesBlocks from trusted validatorsHigh-stakes decisions, authentication
UnverifiedTrustRegistryAll cryptographically valid blocksDiscovery, exploration, lower-stakes queries

When querying trust with includeUnverified=true, the algorithm can traverse unverified edges but applies appropriate discounting to the trust levels.

Every trust edge tracks its origin:

type TrustEdge struct {
Truster string // Who is trusting
Trustee string // Who is being trusted
TrustLevel float64 // Trust level 0.0-1.0
SourceBlock string // Block hash where this edge was recorded
ValidatorQuid string // Quid of validator who signed the block
Verified bool // True if from a trusted validator
Timestamp int64 // When recorded
}

This provenance enables:

  • Auditing trust relationships back to their source
  • Promoting edges when validator trust changes
  • Demoting edges if a validator becomes untrusted

Blocks from partially-trusted validators are stored separately:

TentativeBlocks map[string][]Block // keyed by trust domain
FunctionPurpose
StoreTentativeBlock(block)Add block to tentative storage
GetTentativeBlocks(domain)Retrieve tentative blocks for a domain
ReEvaluateTentativeBlocks(domain)Check if any can now be promoted

When trust relationships change (e.g., you establish trust in a new validator), call ReEvaluateTentativeBlocks() to check if any tentative blocks can now be promoted to the main chain:

// After establishing trust in a new validator
node.AddTrustTransaction(trustTx)
node.ReEvaluateTentativeBlocks("example.com")

During block generation, FilterTransactionsForBlock() ensures only trusted transactions are included:

func (node *QuidnugNode) FilterTransactionsForBlock(
txs []interface{},
domain string,
) []interface{}

For each pending transaction:

  1. Extract the creator quid (truster, creator, or first owner depending on type)
  2. Compute relational trust from node to creator
  3. Include only if trustLevel >= node.TransactionTrustThreshold

This prevents a node from propagating transactions it doesn’t trust, even if they’re cryptographically valid.

The EnhancedTrustResult provides additional provenance information:

type EnhancedTrustResult struct {
RelationalTrustResult
Confidence string // "high", "medium", "low"
UnverifiedHops int // Number of unverified edges traversed
VerificationGaps []VerificationGap // Details of unverified hops
}
type VerificationGap struct {
From string // Source quid of the gap
To string // Target quid of the gap
ValidatorQuid string // Validator who recorded this edge
ValidatorTrust float64 // Node's trust in that validator
}

Confidence levels are determined by:

  • High: All edges in path are verified
  • Medium: Some unverified edges, but validators have partial trust
  • Low: Significant unverified hops or low validator trust

The codebase uses granular mutexes following Go best practices:

MutexProtects
BlockchainMutexBlockchain slice
PendingTxsMutexPendingTxs slice
TrustDomainsMutexTrustDomains map
KnownNodesMutexKnownNodes map
TrustRegistryMutexTrustRegistry map
IdentityRegistryMutexIdentityRegistry map
TitleRegistryMutexTitleRegistry map
TentativeBlocksMutexTentativeBlocks map
UnverifiedRegistryMutexUnverifiedTrustRegistry map
EventStreamMutexEventStreamRegistry and EventRegistry maps

Always acquire read locks for queries and write locks for mutations.

Note: ComputeRelationalTrust acquires a read lock on TrustRegistryMutex via GetDirectTrustees() for each node visited during BFS traversal.