The normative contract for workspace crate layering, one-way dependency direction, storage engine confinement, and composition boundaries.
Owners:
Cargo.toml,crates/*/Cargo.toml,bin/bitcoin-rs/Cargo.toml- Workspace dependency gate in
bin/bitcoin-rs/tests/gates/g17_dependency_direction.rs
┌─────────────────────────────────────────────────────────────────────────┐
│ Layer 4: Compose │
│ bitcoin-rs-node, bitcoin-rs │
│ - Lifecycle orchestration, runtime assembly, config, cache allocation │
└────────────────────────────────────┬────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Layer 3: Surface │
│ bitcoin-rs-rpc │
│ - Protocol boundaries and RPC dispatch │
└────────────────────────────────────┬────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Layer 2: Services │
│ bitcoin-rs-chain, bitcoin-rs-utxo, bitcoin-rs-p2p, │
│ bitcoin-rs-mempool, bitcoin-rs-index, bitcoin-rs-mining │
│ - Domain capabilities, index query runtimes, network protocol state │
└────────────────────────────────────┬────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Layer 1: Storage │
│ bitcoin-rs-storage │
│ - Storage abstractions (KvStore), exclusive owner of engine deps │
└────────────────────────────────────┬────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Layer 0: Core │
│ bitcoin-rs-primitives, bitcoin-rs-script, bitcoin-rs-consensus │
│ - Protocol types, script validation, consensus rules; zero storage/IO │
└─────────────────────────────────────────────────────────────────────────┘
- Every workspace crate is assigned to an approved layer (0 to 4).
- A crate may depend only on crates in the same layer or a strictly lower layer.
Edges pointing upward or across forbidden boundaries fail the
g17_dependency_directiongate. - Crate layer assignments:
- Layer 0 (Core):
bitcoin-rs-primitives,bitcoin-rs-script,bitcoin-rs-consensus. Pure protocol types, consensus verification, and script interpreter logic. Layer 0 crates have zero dependencies on storage, network, or filesystem I/O. - Layer 1 (Storage):
bitcoin-rs-storage. Key-value storage abstractions, batching primitives, and backend engine drivers. - Layer 2 (Services):
bitcoin-rs-chain,bitcoin-rs-utxo,bitcoin-rs-p2p,bitcoin-rs-mempool,bitcoin-rs-index,bitcoin-rs-mining. Domain services and capability runtimes.chainandutxosit in Layer 2 because they depend onstoragefor block index records, undo storage, and UTXO snapshots.chainalso depends onconsensusfor BIP9 parameters and the BIP113 locktime cutoff.miningsits in Layer 2 because it depends onmempoolfor candidate selection andchainfor candidate header/work/time context. - Layer 3 (Surface):
bitcoin-rs-rpc. External wire protocols and RPC handlers. - Layer 4 (Compose):
bitcoin-rs-node,bitcoin-rs. Daemon assembly, subsystem lifecycle coordination, and CLI binary entry points.
- Layer 0 (Core):
- Explicit non-goal: Layer numbers do not justify speculative new crates or thin wrapper layers. A boundary exists only when it isolates external dependencies, enforces safety/consensus boundaries, or separates independent runtime lifecycles.
bitcoin-rs-storageis the sole crate in the workspace permitted to depend on underlying storage engine crates (fjall,redb,rust-rocksdb,signet-libmdbx).- No crate outside
bitcoin-rs-storagemay name a storage engine dependency in[dependencies],[build-dependencies], or[dev-dependencies]. - All higher layers interact with persistent state through the
KvStorefacade and storage abstractions exported bybitcoin-rs-storage.
- Backend feature forwarding (
fjall,redb,rocksdb,mdbx) is strictly confined to:- Operator-facing entry points (
bitcoin-rs-node,bitcoin-rs) that expose backend selection to operators and packaging scripts. - Services-tier adapter crates (Layer 2) whose features exist solely so
-ppackage builds propagate backend selection intobitcoin-rs-storage.
- Operator-facing entry points (
- Crates in Layer 0 (Core) and Layer 3 (Surface / RPC) must never define or forward storage backend features.
bitcoin-rs-rpcmust have zero non-test dependency edges onbitcoin-rs-storageand zero dependencies on storage engine crates.- RPC consumes node capabilities (chain, mempool, index, mining, p2p, utxo)
exclusively through capability query handles and domain context interfaces
(
Context,ContextHandles), never through direct database access. bitcoin-rs-rpcdefines and forwards zero backend features. (The bench-only dev-dependency used for offlinetxoutprooffixtures is isolated to test scope and documented incrates/rpc/Cargo.toml).
bitcoin-rs-node(Layer 4) is the assembly and lifecycle orchestration layer. It wires together storage backends, consensus validators, mempool gateway, P2P listeners, index reconciliation workers, and RPC services into an executable node runtime.- Domain mechanics belong to domain crates: consensus rules in consensus/script,
mempool admission and mutation sequencing in mempool, connection lifecycle in
p2p, template assembly and the mining control contract in mining, and index
schemas in their owning crates.
bitcoin-rs-miningownsCandidate,BlockTemplate,MiningInfo, andMiningControl. RPC maps those types onto BIP22/BIP23 JSON and does not cache templates or long-poll. bitcoin-rs-nodeowns runtime startup/shutdown sequencing, configuration resolution and validation (UserConfiglayers →NodeConfig), the mining generation coordinator keyed by(applied_tip_hash, mempool_sequence), watch-only coinbase payout configuration (MiningConfig::payout_script), and process-level cache budgeting (dbcachedistribution across chainstate and txindex namespaces). Thebitcoin-rsbinary owns argv, environment, and TOML parsing. Applied-tip mutation is owned by the chainstate facade (ARCH-07), not by a public field bag of subsystem handles.UserConfig::overlayapplies a later layer field-wise: a set field replaces the earlier value; an unset field leaves it. Nested override structs merge the same way, includingChainstateJournalOverridesandMiningOverrides. Proof:crates/node/src/config.rstestsuser_config_overlay_lets_set_fields_winandmining_payout_overlay_lets_the_later_address_win.
- Any change to workspace crate layer assignments, introduction of new workspace
crates, or addition of cross-crate dependencies requires:
- Updating the
approved_layertable or engine crate assertions inbin/bitcoin-rs/tests/gates/g17_dependency_direction.rs. - Updating this normative contract (
docs/contracts/architecture.md) with the rationale and invariant justification. - Passing the
g17_dependency_directiongate test.
- Updating the
- Speculative or circular dependency edges that violate the one-way flow are rejected by automated gate enforcement in CI.
bitcoin_rs_node::Chainstateis the in-process owner of applied-tip mutation.NodeState,BlockSync, mining, and RPC chain-control hold or clone that facade; they do not assemble a transition from independent locks.Chainstate::begin_transitionis the only public constructor of aChainTransition. Reorg planning that must abort without mutating takeslock_transitionfirst and promotes it withbegin_transition_lockedonly after the authoritative plan matches the preloaded plan.- Snapshot reads (
Chainstate::snapshot) copy the independently published header tip and a coherent applied-tip / chain-tx-count pair. They do not take the transition lock and cannot mutate chainstate.ChainEventPublishercells remain a separate coherent snapshot of the applied tip for index consumers (EVT-01). Chainstate::validate_blockdry-runs the apply path's pre-write consensus gates underlock_transition. It does not take mempool generation and does not persist. BIP22 proposal omits proof-of-work; every other pre-write gate is the same function commit runs. Owner:crates/node/src/apply.rs.- Authoritative apply still lives in
crates/nodebecause it composes chain, consensus, utxo, and storage.Chainstatedoes not hold or import RPC, ZMQ, TxIndex, mining, or P2P admission types. Apply publishes the tip and returns aConnectOutcomeorDisconnectOutcome. Capture flags (Chainstate::capturing) are set at construction so apply can producerawtxand canonical block bytes without holding the consumers. - The composition root (
NodeState,BlockSync, reorg, mining) dispatchesChainFollowerswhile theChainTransitionis still held, then callsfinish. Convenience methods that finish before returning (Chainstate::apply_block,disconnect_block) do not dispatch followers. RPCBlockLog, hash/raw ZMQ, TxIndex wake, sequenceC/D, mining generation, and admission run from that dispatch. Mempool eviction stays inside apply. Consumer failure cannot invalidate chainstate. Issue #77 owns the durable event journal; this is dependency direction, not a second event contract. Do not push cross-store ordering intoutxoorstorage.
- Node slimming and extraction (#217): Peer connection session and lease
ownership has moved to
PeerTable/P2pServiceincrates/p2p(#215, #217, #218). BIP9/softfork lookups, P2P chain serving, txindex status projection, mempool mutation consumers, and block-body access live with their owner crates (#272). Applied-tip mutation goes through theChainstate/ChainTransitionfacade (ARCH-07). Derived consumers live inChainFollowers/ChainEffectsand are dispatched after commit while theChainTransitionis still held;Chainstatedoes not hold them.crates/nodestill carries leftover domain mechanics: UTXO undo persistence and disconnect markers (apply.rs), the P2P download scheduler (sync.rs), and direct backend construction and cache share dispatch (state.rs). Relocating those intocrates/utxo,crates/storage, andcrates/p2premains tracked under #217 (open). A dedicatedcrates/chainstatewaits until journal, checkpoint, andChainEventPublisheralso leave node.crates/nodeis the composition layer, but is not yet fully slim.
bin/bitcoin-rs/tests/gates/g17_dependency_direction.rs:workspace_dependency_direction_is_one_way: parsescargo metadata --no-deps, validates every internal workspace dependency edge against the approved layer table, verifiesbitcoin-rs-storageexclusively owns storage engine dependencies, confirmsbitcoin-rs-rpchas no dependency on storage and forwards no backend features, and verifies backend feature forwarding is confined to operator tiers and service adapters.
- Manifest enforcement:
- Root
Cargo.toml: workspace member list and package versions. crates/storage/Cargo.toml: engine dependency definitions.crates/rpc/Cargo.toml: zero storage backend dependencies or features.crates/node/Cargo.tomlandbin/bitcoin-rs/Cargo.toml: confined operator-tier backend feature flags.
- Root
crates/node/src/apply.rstestssnapshot_reads_applied_tip_without_taking_a_transition,chain_transition_connect_and_finish_publish_the_new_tip,proposal_rejects_excess_coinbase_without_persisting,proposal_omits_proof_of_work: the facade copies published tips without reserving generation, connect/finish throughChainTransitionis the mutation path, and BIP22 proposal reuses the apply gates without persistence.crates/node/src/apply.rstestsapply_block_publishes_rawtx_bytes_in_block_order,connected_sequence_event_observes_the_published_applied_tip,connect_and_disconnect_wake_the_mining_generation,follower_dispatch_holds_the_chain_transition,with_zmq_publisher_swaps_handle: apply returns a committed outcome;ChainFollowersconsume it after the tip is published and while the transition is still held; ZMQ publishers are configured outside apply.crates/node/src/chain_effects.rstestsnoop_asks_for_no_payloads,connect_then_disconnect_rewinds_the_rpc_log_and_emits_in_order,disconnect_does_not_pop_a_different_tail: post-commit RPC/ZMQ work is owned byChainEffects, not by apply.crates/node/src/config.rstestuser_config_overlay_lets_set_fields_win: laterUserConfiglayers win on set fields, including nestedChainstateJournalOverrides(ARCH-05).crates/node/src/config.rstestmining_payout_overlay_lets_the_later_address_winandcrates/node/tests/config_layered.rstestmining_payout_address_decodes_after_all_layers: watch-only mining payout is decoded once after overlay, against the resolved network (ARCH-05).