Skip to content

Add an explicit pool-fee-first coinbase output policy (1.x.x) - #106

Merged
djh58 merged 3 commits into
1.x.xfrom
pool-fee-first-output-policy-1.x
Jul 31, 2026
Merged

Add an explicit pool-fee-first coinbase output policy (1.x.x)#106
djh58 merged 3 commits into
1.x.xfrom
pool-fee-first-output-policy-1.x

Conversation

@djh58

@djh58 djh58 commented Jul 31, 2026

Copy link
Copy Markdown
Member

Closes #86. Replaces #105, retargeted from main to 1.x.x (the active release line that mainnet deploys track).

Retarget notes (vs #105)

Both commits are cherry-picked from #105 with authorship preserved, plus one adaptation commit:

  • Conflict resolution in crates/qbit-prism/src/lib.rs: 1.x.x wraps the audit-bundle build steps of build_audit_bundle_with_ctv_settlement_options in profile_audit_build_phase closures. The resolution keeps the profiling structure and swaps select_settlement_mode for select_settlement_mode_with_pinned_direct (plus the pin derivation) inside the payout-derivation phase closure.
  • Test adaptation: test_build_audit_bundle_passes_coinbase_output_policy_to_cli_payload mocked subprocess.run, but the 1.x.x coordinator drives qbit-prism-build-audit-bundle through the Popen-based job-build worker. The test now uses the fake_audit_bundle_popen harness that the sibling pool-fee payload test already uses.
  • All other hunks applied cleanly; remaining select_settlement_mode call sites on 1.x.x are canonical-wrapper unit tests, and all eight pinned_first_output wiring sites map 1:1.
  • Suites on this branch: cargo test --locked --workspace --all-targets 221 passed / 0 failed; python -m unittest discover -s tests 1357 tests OK (3 docker-dependent skips).

What

Adds PRISM_COINBASE_OUTPUT_POLICY=canonical|pool-fee-first (default canonical, unknown values rejected at startup and at the Rust CLI boundary). Under pool-fee-first, when the configured pool fee is positive, settlement:

  1. reserves one direct coinbase settlement slot for the pool-fee output (it consumes a max_direct_coinbase_outputs slot and is counted in the output-budget walk),
  2. never routes the fee entitlement through CTV fanout,
  3. emits the fee output at coinbase vout 0, even when the fee is below the direct payout floor or when amount-priority selection would otherwise exclude it,
  4. keeps canonical (order_key, recipient_id, p2mr_program_hex) ordering for the direct miner and CTV covenant outputs after it,
  5. keeps the zero-value witness commitment as the final output, and
  6. fails job construction (SettlementModeSelection) instead of silently demoting the fee when the budget cannot hold the reserved slot.

How

  • qbit-pool-builder: CoinbaseBuildRequest.pinned_first_output (serde-default None) rotates the matching entitlement to vout 0 after the canonical sort. Allocation amounts are unchanged by construction — pro-rata split and largest-remainder tie-breaks are account-key-based, not position-based. Missing/ambiguous pins are typed BuilderErrors.
  • qbit-prism settlement: select_settlement_mode_with_pinned_direct reserves the pinned slot ahead of amount-priority selection; the existing select_settlement_mode delegates with no pin, so canonical behavior is bit-identical.
  • Policy commitment: CoinbaseOutputPolicy lives on PayoutPolicy and is copied into PayoutPolicyManifest, which is hashed into the on-chain audit commitment leaf (witness nonce) and carried in the signed audit bundle; the field is omitted for canonical so all existing artifacts, hashes, and golden vectors keep their bytes. Existing blocks and persisted CTV artifacts continue to verify under their original ordering; the policy applies prospectively at job construction only.
  • Verification: verify_audit_bundle re-derives the pin from the recomputed manifest, plus an explicit coinbase_output_policy check (fee at vout 0, direct, absent from every fanout chunk). A canonically-built bundle stamped pool-fee-first fails with AuditMismatch("coinbase_output_policy"); downgrading a pool-fee-first bundle to canonical breaks the audit commitment leaves. AuditVerificationReport now reports the policy.
  • CTV manifests: parent vouts are resolved from the built manifest by account key, so covenant parent_coinbase_vout values reflect the reordered coinbase and the manifest set still verifies (covered by tests asserting the shifted vouts).
  • Coordinator: prism_payout_policy() validates the env var (SystemExit on unknown values, and on pool-fee-first without an enabled pool fee) and forwards coinbase_output_policy in the qbit-prism-build-audit-bundle payload only when non-canonical. scripts/prism-self-check.py gains a coinbase.output_policy check.

Tests

  • Rust: builder pin unit tests (vout 0, canonical tail, unchanged amounts, shuffled-input determinism, missing/ambiguous pin); settlement pinned-slot tests (sub-floor pin, slot consumption, never-fanout at the cap, budget-overflow failure, input-order independence); bundle-level tests for plain and hybrid CTV paths (sub-floor fee at vout 0, shifted covenant parent vouts, direct-slot exhaustion, budget failure with a canonical control, zero-fee vacuous case, serialization stability for canonical, unknown-value rejection, verifier rejection of mis-ordered and downgraded bundles); CLI end-to-end pool-fee-first build+verify; 5k-miner scale test asserting byte-identical settlement artifacts across reversed share order with the fee pinned at vout 0.
  • Python: coordinator config tests (default omitted, explicit canonical omitted, pool-fee-first included in the CLI payload, unknown values rejected, pool-fee-first without a fee rejected); self-check tests; compose profile passthrough assertion.

Docs

.env.example, compose.yaml (coordinator env passthrough), and a new "Coinbase Output Ordering" subsection in PRISM.md documenting the canonical rule, the new policy, its audit commitment, and the prospective txid/manifest-hash/parent-vout impact.

🤖 Generated with Claude Code


Note

Medium Risk
Changes coinbase txids, manifest hashes, and CTV parent vouts when switching to pool-fee-first, and alters settlement slot accounting on the critical payout path; canonical default and byte-stable serialization limit blast radius.

Overview
Introduces PRISM_COINBASE_OUTPUT_POLICY (canonical default, or pool-fee-first) so operators can fix where the pool fee sits in the coinbase instead of relying on lexicographic (order_key, recipient_id, p2mr_program_hex) ordering.

With pool-fee-first and a positive fee, settlement reserves a direct coinbase slot for the fee (never CTV fanout), pins it at vout 0 even below the direct floor or when amount-priority would skip it, and fails job construction if the output budget cannot hold that slot. Other outputs stay canonically ordered after the fee; witness commitment remains last. The policy is recorded on payout policy manifests and audit bundles (omitted for canonical to preserve existing artifact bytes); verifiers enforce vout 0 and direct-only fee routing.

Implementation adds pinned_first_output on coinbase build requests (rotate matched entitlement to vout 0 after sort), select_settlement_mode_with_pinned_direct for CTV hybrid paths, and coordinator/self-check validation (pool-fee-first requires an enabled pool fee). Docs, compose, and .env.example document the knob.

Reviewed by Cursor Bugbot for commit f699dc0. Bugbot is set up for automated code reviews on this repo. Configure here.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

RobertClarke and others added 3 commits July 31, 2026 13:17
PRISM_COINBASE_OUTPUT_POLICY selects the coinbase output ordering rule:
canonical (compatibility default) keeps the historical lexicographic
(order_key, recipient_id, p2mr_program_hex) ordering, while pool-fee-first
reserves one direct coinbase settlement slot for a positive pool fee, keeps
the fee entitlement out of CTV fanout chunks, and emits the fee output at
vout 0 with canonical ordering preserved after it and the witness commitment
still last. Job construction fails instead of demoting the fee when the
settlement output budget cannot hold the reserved slot.

The selected policy is committed in the payout policy manifest (and thereby
the on-chain audit commitment and signed audit bundle) and enforced plus
reported by verification tooling, so independent verifiers and indexers can
recover the intended ordering without operator-local environment
configuration. Canonical artifacts keep their historical bytes; the policy
applies prospectively at job construction only.

Closes #86

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
apply_payout_policy now fails with PoolFeeFirstRequiresPoolFee when the
coinbase output policy is pool-fee-first but no pool fee policy is
configured, instead of silently building a canonical-ordered artifact
whose committed manifest claims pool-fee-first. This closes the Rust
CLI/library gap behind the coordinator-level check; verification
inherits the same rejection, so stamped bundles no longer verify. A
configured zero-bps fee remains valid: its output only appears when
dust sweeps make it positive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
test_build_audit_bundle_passes_coinbase_output_policy_to_cli_payload
mocked subprocess.run, but the 1.x.x coordinator drives
qbit-prism-build-audit-bundle through the Popen-based job-build worker,
so the mock never engaged and the test spawned the real CLI. Reuse the
fake_audit_bundle_popen harness that the sibling pool-fee payload test
already uses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@djh58
djh58 merged commit 373a881 into 1.x.x Jul 31, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants