Skip to content

Add an explicit pool-fee-first coinbase output policy - #105

Closed
RobertClarke wants to merge 2 commits into
mainfrom
pool-fee-first-output-policy
Closed

Add an explicit pool-fee-first coinbase output policy#105
RobertClarke wants to merge 2 commits into
mainfrom
pool-fee-first-output-policy

Conversation

@RobertClarke

@RobertClarke RobertClarke commented Jul 31, 2026

Copy link
Copy Markdown
Member

Closes #86.

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.
  • cargo test --locked --workspace --all-targets: 216 passed. python -m unittest discover -s tests: 788 passed (3 docker-dependent skips run in CI).

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


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


Note

Medium Risk
Changes coinbase txids, manifest hashes, and CTV parent vouts when switching policies, but only prospectively at job construction; settlement and audit verification paths are heavily tested. Misconfiguration is rejected at startup rather than silently demoting the fee.

Overview
Adds PRISM_COINBASE_OUTPUT_POLICY (canonical default, or pool-fee-first) so operators can fix pool-fee placement in coinbase outputs instead of relying on lexicographic sort alone.

Under pool-fee-first** with a positive fee, settlement reserves a direct coinbase slot for the fee (never CTV fanout), pins it at **vout 0** via CoinbaseBuildRequest.pinned_first_output, and fails job construction if the output budget cannot hold that slot. **select_settlement_mode_with_pinned_direct` runs before amount-priority direct selection; canonical behavior stays unchanged when the policy is default or omitted from serialized artifacts.

The chosen policy is recorded on PayoutPolicy / payout manifests (omitted for canonical to preserve bytes), enforced in verify_audit_bundle, and surfaced on audit verification reports. Coordinator, compose, self-check, and PRISM.md wire validation and env passthrough.

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

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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3a131a6dab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/qbit-prism/src/lib.rs
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>
@djh58

djh58 commented Jul 31, 2026

Copy link
Copy Markdown
Member

Retargeted to 1.x.x as #106 (cherry-picked with authorship preserved; main hasn't moved since 1.x.x branched, and a plain base edit would conflict with the 1.x.x profiled audit-build phases). See the retarget notes on #106 for the conflict resolution and the one test adaptation.

@djh58 djh58 closed this Jul 31, 2026
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.

Add an explicit pool-fee-first coinbase output policy

2 participants