Skip to content

Stage 6 Release B: delete the BFT payout vote and the GHOST-03 ledger sweep - #750

Open
defenwycke wants to merge 13 commits into
mainfrom
feat/stage6-release-b
Open

Stage 6 Release B: delete the BFT payout vote and the GHOST-03 ledger sweep#750
defenwycke wants to merge 13 commits into
mainfrom
feat/stage6-release-b

Conversation

@defenwycke

@defenwycke defenwycke commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Stage 6, Release B: the deletion half. Removes the BFT vote from the payout path, makes the
share shard the only ledger a payout can be built from, and deletes the GHOST-03 unpaid-ledger
sweep. +730 / −5,042 across 18 files (measured against main after merging it in).

✅ The gate has fired and the precondition is met

PAYOUT_FROM_SHARD_HEIGHT = 964_100 fired at block 964,100, 2026-08-26 06:01:30 UTC, and the
fleet was observed paying from the shard for ~11 h before this was marked ready. Release A is
gate-reversible without a redeploy; this is not, because it deletes the machinery the gate falls
back to — so the three signals were measured before merging, on all 8 nodes.

Counts come from splitting journalctl -u ghost-pool at the gate timestamp — an 11 h
pre-window against the 10.8 h post-window. A 24 h count straddles the gate and averages the
transition into invisibility.

signal pre-gate (11 h) post-gate (10.8 h)
Payout consensus approved 42–47 0 on all 8 the vote this PR deletes has stopped
Paying from this node's own shard view (no vote 0 18–20 on all 8 the shard is paying
payout ledger checkpoint FINALISED 55–56 70–74 continued — the signal that matters

The last Payout consensus approved on vm1 was 05:51:59, 9.5 min before the gate; the first
post-gate FINALISED was 06:02:02, 32 s after it. Checkpoints carry approvals=6 needed=6
throughout, so the 18–21 Aug standoff has not recurred. Zero panics fleet-wide; all 8 on
ghost-pool 1.11.28.

⚠ The no vote rate is ~40–44/day, not the ~22/day this PR originally predicted. Present and
healthy on every node — the estimate was low, the behaviour is not wrong.

One unrelated defect this exposed, tracked separately

Splitting the fee-drift logs at the gate surfaced that payout_fee_estimate() is node-local and
has diverged 125x across the fleet (original_fees 25,030 on vm1 … 3,137,573 on vm6, while
available_fees is ~2.1–2.7M everywhere). Nodes that under-estimate route ~2–2.8M sats/block of
fees to the treasury and node pool instead of to miners; vm6 over-estimates and falls back to a
non-shard coinbase.

This is not caused by this PR and not fixed by it — it lives in template.rs, the gate merely
made the estimate node-local by removing the vote that used to collapse the fleet onto one value.
Worth stating here because a fix cannot rely on resurrecting the vote this PR deletes. Practical
impact is nil while the pool cannot win a block, but it must not ship public unfixed.

Why the vote goes

validate_proposal_split recomputed the miner split from the voter's own local_miner_work
and demanded exact equality. It never inspected a share. Its whole guarantee was "your arithmetic
matches mine", which is only as strong as the tables already agreeing — and when they did not,
that exactness converted divergence into a total payment halt.

Without it, divergence is benign: owed is signed and never clamped, so an over- or
under-payment leaves a residual the next block corrects (SHARE_SHARD.md §4.4, §4.6).
§8 settles the design question: "No consensus on the ledger. Each node pays from its own view."

Share validity is untouched. It is per-share and at receive time — GHOST-09 signature with
receiver and address binding, the PoW preimage check, the difficulty-tier commitment. Delete the
gate, keep the rule.

What is in it

commit what
379ad92c5 rehome fetch_coinbase_parts into coinbase_verifier — the shard's maturity settlement needs it
ca09d1cd9 delete the legacy BFT e2e tests, now the shard has its own
463445765 remove the BFT vote from the payout path
6bfc3bbd5 make the shard the only ledger; delete the vote's GHOST-02 validator
3f2bc9891 delete the GHOST-03 unpaid-ledger sweep (−870)
3c334ad88 count a pool win on every node, not just the submitter
aadbc6537 delete tip-time settlement — the shard settles at maturity (−1,813)

The sweep was already dead

shard_owns_evidence is true on all eight nodes, so the sweep branch has continued on every
tick since migration v56 landed on 18 Aug. This deletes machinery that was already being skipped.
The round-in-flight repair is kept — one request per tick, scoped to the current round, and
not what flooded.

⚠ The LedgerRequest / LedgerResponse enum variants are kept, inert, for one release.
ConvergencePayload is serde_json externally-tagged, so removing a variant makes a message
from a pre-Stage-6 peer fail to decode as a whole envelope, not just that field. No node
emits them any more, but an operator on an older binary still could. Drop them when no fleet
binary can.

blocks_found had to be fixed here

won_blocks had one writer — settle_paid_block, which runs on the node that submitted the
block. Seven of eight nodes reported 0 for a block the pool genuinely won, and which node
reported 1 was an accident of timing.

The shard's maturity settlement now records it too. That path is chain-derived, so every node
reaches it from the same coinbase at the same depth, and it needs no reorg reversal — a block
COINBASE_MATURITY deep is not coming back. INSERT OR IGNORE keeps both writers idempotent
against each other. Cost is a ~16-hour lag, which is the honest number: it counts blocks whose
coins exist and are spendable.

This is a precondition for deleting settlement.rs, not a tidy-up — that file is the
chain-observed settler, the only path by which a non-submitting node learned of a win.

Tip-time settlement goes with it

SettlementObserver settled the legacy ledger from an observed coinbase at tip. It existed
because the seven non-winning nodes would otherwise still owe work the pool had paid and, being
the majority, would carry that into the next BFT proposal and pay it twice. No vote, no majority
view to poison — and ShareShard::settle_matured reaches the same conclusion from the same
evidence, on every node, at COINBASE_MATURITY instead of at tip.

That depth is why this is a deletion rather than a port: the tip-time settler needed
reverse_settlement and a deferred-settlement table because a reorg could undo what it booked.
A block 100 deep cannot be.

Deleting the observer while keeping settle_paid_block was not an option — the reverser
lived in the deleted file, so the pair would have left settlements a reorg could never undo plus
a won_blocks row inflating the public blocks_found. They go together or not at all.

Kept on purpose: proposal_sync (it answers peers, and a pre-Stage-6 node needs those
answers); settle_block_atomic / reverse_settlement / the deferred-settlement accessors and
their tests, because settled_blocks and deferred_settlements must stay replayable — the same
reasoning that retained the sbc_* tables; and OBSERVED_SETTLEMENT_HEIGHT as a tombstone, since
old logs and the #606 ordering are only interpretable with it.

crates/ghost-reconciliation/src/settlement.rs is an unrelated module (Ghost Pay L2) and is
not in scope for any of this.

Verification

No live block can validate this — the pool wins one roughly every 161 years. So:

  • 485 unit tests pass; the whole suite is green after rebasing onto 0abf2ea5a
  • the commitment built from a locally constructed proposal is byte-identical to the one the BFT
    path produced from the same BlockFoundData — this is the load-bearing test, because it proves
    the change is a route change, not an amount change
  • set_local_payout_caches_the_proposal_it_publishes pins the settlement precondition:
    approved_payout is set and the proposal is retrievable, which is what makes
    payout_snapshotsettle_paid_block fire
  • the new blocks_found test was mutation-checked — it reads 0 with the fix removed
  • regtest e2e proves a shard-built, unvoted coinbase is accepted by real ghostd

Rollback

Once merged and deployed there is no gate to fall back to. The rollback is the .bak binary,
which is why this must not ride the same release as a cutover.

…etion can reach it

Stage 6 Release B deletes `settlement.rs` — the legacy tip-observing settlement. But
the SHARD's maturity settlement calls into that file: `shard.rs:1855`,
`crate::settlement::fetch_coinbase_parts(rpc, &hash)`.

Deleting it naively would have broken the shard's settlement — the money path the
deletion exists to preserve. Its own doc comment says why it is a free function:

    BOTH ledgers' settlements read coinbases the same way ... it carries the
    internal-vs-display hash-order fix below, and a fresh implementation would
    re-hit that trap.

That trap is real and documented: `BlockEvent` hashes arrive in internal order
despite the parser claiming otherwise (observed on vm5, 2026-08-01 — every
settlement probe got "Block not found" for a block that existed). Rewriting rather
than moving would have re-earned it.

Moved to `coinbase_verifier.rs`, next to the `CoinbaseOutput` it returns, rather than
into `shard.rs`: what it encodes is how to READ a coinbase, not how to settle one.
After the deletion the shard is its only caller.

⚠ This is the rehome the build plan's rule predicted — "find what the shard inherited
BEFORE deleting, or the delete takes working machinery with it" — and it is NOT
`apply_settlement`, which an earlier survey wrongly listed as a survivor.
`apply_settlement`'s only non-test callers are `settle_paid_block` and
`settlement.rs`, both of which the deletion removes, so it goes with them.

Also removes the legacy `select_ledger_miner_work` call from the payout checkpoint
(`main.rs`). Its gate `CHECKPOINT_FROM_SHARD_HEIGHT` (963,388) passed on 2026-08-21,
so that arm has been unreachable in production since; it now abstains loudly instead
of computing a root from a source that is being removed. The gate itself stays —
collapsing gates is a later release.

No behaviour change: `shard::` 33 tests and `coinbase_verifier::` 11 tests green.
… its own

Removes `payout_ledger_e2e.rs` (931 lines) and `fleet_rehearsal.rs` (511), both of
which exercise the mesh-ratified payout path that Stage 6 Release B deletes.

⛔ Deleted only because the replacement exists on this branch:
`regtest_shard_coinbase_e2e.rs` proves a real block, real proof-of-work, real
`submitblock`, coinbase built from the shard's `owed` with NO vote — verified by the
chain advancing rather than by a green tick — and
`regtest_shard_coinbase_control.rs` proves the fixture cannot arm a coinbase below
the gate, so the positive test is not passing on its own permissiveness.

Deleting these before that existed would have removed the only end-to-end proof that
a mined block pays the miners it should. That was Release B's stated prerequisite.

⚠ `fleet_rehearsal` was FAILING at the point of deletion — it reconciles all 8 nodes
and then ratifies 0 of 8 where it asserts 8. It is not being deleted to hide that:
it had been failing undetected because the file skipped (wrong credentials read as
"no regtest node"), and what it tests — BFT ratification of a mesh payout — is the
machinery this release removes. Fixing it would have been work on something being
deleted. Recorded here so the failure is not mistaken later for something this
release caused.

Both files also held the last non-`main.rs` consumers of `make_proposal_validator`,
`settle_paid_block` and `select_ledger_miner_work`, so the remaining surface is now
`main.rs` alone.
Scoped under #411 (2026-08-23). The gate reads like leftover caution and is not:
aggregated mode already works — the translator mints real per-downstream extranonces
locally with no upstream round trip. What is missing is that the per-share TLV carries
only the worker NAME, not the payout address, so payout derivation has nothing
per-miner to key on and every miner in the aggregate collapses onto one channel
address.

Anyone who finds this gate, assumes it is stale and removes it causes a money bug that
pays the wrong people. The reason now lives next to the check.

Making aggregation safe means moving payout derivation to the per-share TLV for every
SV1 miner — a money-path change needing a height gate and a fleet roll — and it is
deferred deliberately: the pool runs ~4 miners against a cap of 1,000.
…se B, part 1)

`SHARE_SHARD.md` §8: "No consensus on the ledger. Each node pays from its own view."
This is that, in code.

`handle_block_found` now commits the payout from this node's own shard view
unconditionally. Gone with the vote:

- the `PAYOUT_FROM_SHARD_HEIGHT` conditional. It HAD to go — you cannot delete a
  branch and keep the conditional that selects it. The gate did its job: it let the
  no-vote path run on the live fleet and be observed before the machinery was
  removed. The constant is now inert and is retired by the tip-keyed gate collapse,
  a later release.
- the single-operator bypass (`is_single_operator`, `is_single_operator_mode`) and
  its two tests. That bypass existed so a pool with one operator did not need a
  quorum of its own nodes agreeing with themselves. With no vote at all, mode no
  longer changes what happens to a payout.
- the GHOST-02 proposal validator wiring in `main.rs`.
- `PayoutHandler`'s `vote_handler` and `mining_mode` fields, and both constructor
  parameters. This is the structural result worth noting: **`payout.rs` no longer
  imports `VoteHandler`.** The dependency is deleted, not merely unused.

⚠ **The vote handler itself STAYS.** Elder revocation proposes BFT revocation votes
(`main.rs`), a different proposal type the payout validator never inspected. Deleting
the voting layer was considered and rejected on 2026-08-19.

What the removed vote actually did, since the name oversells it:
`validate_proposal_split` recomputed the miner split from the VOTER's own table and
demanded an exact match. It never inspected a share. Its whole guarantee was "your
arithmetic matches mine" — only ever as strong as the two tables already agreeing,
and when they did not it converted divergence into a total payment HALT (nothing
finalised 18-21 Aug 2026). Share validity is untouched and lives where it always
did: per-share at receive time, plus §6 sampling.

Also deletes `regtest_shard_coinbase_control.rs`. It asserted the fixture could NOT
arm a coinbase below the gate — a premise that existed only while the branch did. It
failed honestly the moment the branch went, which is the correct behaviour for a
control, and its companion e2e now states the argument for a world with no gate.

Verified: `ghost-pool` lib 512 passed / 0 failed; clippy and rustdoc clean under
`-D warnings`; and `regtest_shard_coinbase_e2e` still mines a real block accepted by
a real ghostd — chain 793 -> 794 — on code where the BFT path no longer exists.
…HOST-02 validator

Two changes that belong together, because the second is only safe once the first
removes its last caller.

**The shard is now mandatory.** `create_proposal`'s `None` arm — the legacy fallback
that recomputed the split from `data.miner_work`, i.e. the unpaid share ledger — is
replaced by a hard error. A node without a running, genesis-installed shard can no
longer construct a payout and must say so, rather than silently paying from a source
that is being deleted.

⚠ This makes `pool.share_shard` plus installed genesis a HARD REQUIREMENT for taking
part in payouts. It is the same position `CHECKPOINT_FROM_SHARD_HEIGHT` already took
for the checkpoint, which abstains loudly instead of falling back — a silent fall
back to a second source is exactly the mixed-source divergence that gate exists to
end.

**`make_proposal_validator` and the six GHOST-02 vote tests are deleted.** The
validator recomputed a peer's miner split from the voter's own table and demanded an
exact match. With no payout proposal broadcast, nothing reaches it.

⚠ GHOST-02's protection is NOT lost. `payout_checkpoint.rs` has its own
`payouts_agree` (13 references) doing the equivalent comparison on the checkpoint
path, which is the surviving consensus mechanism. Only the vote's copy goes.

`ghost02_creator` is reinstated as `regtest_creator` — it never had anything to do
with GHOST-02, and the #726 shard address-validation tests still need it. The
compiler caught that; the name had disguised it.

`create_proposal_fee_split_anchors_to_cutoff_not_block_timestamp` moves onto the
shard source. What it tests — that the fee split anchors to the CUTOFF rather than
the block timestamp — is unaffected by where the balances came from.

Verified: `ghost-pool` lib 506 passed / 0 failed; clippy and rustdoc clean under
`-D warnings`; `regtest_shard_coinbase_e2e` still mines a block accepted by a real
ghostd, chain 794 -> 795.
Three documents disagreed: #608 said the deletion happens after the release,
SHARE_SHARD_BUILD.md scheduled it for "first September release", and the work has
been running before v1 since step 3 shipped.

Ratifying what is actually happening, because we are committed to it: the gate is
armed at 964,100 and Release B is five commits deep.

Two reasons the ordering is right, not merely convenient:

- A public release should not carry a dead payout path plus a GHOST-03 sweep doing
  3,392-6,507 writes per node per day into a 5.3 GB database for a ledger nothing
  reads any more.
- The release exists so a stranger can run the ninth node. That person should never
  meet the legacy machinery, and every piece of it left in is surface they can hit
  and we would have to explain.

#608 updated to match in a separate comment.
The sweep repaired the LEGACY unpaid ledger. Migration v56 gave the shard
ownership of `shares`, which left nothing for it to repair — so it was
switched off at runtime on 2026-08-18 and has been a no-op on every node
since. This deletes the machinery it was already skipping.

`main.rs` keeps the round-in-flight repair, which is a different thing: one
request per tick, scoped to the current round, and not what flooded. What
goes is the bucket rotation, the sweep cursor, the span anchoring and the
fan-out cap — all unreachable behind `if shard_owns_evidence { continue }`.

`convergence.rs` loses `build_ledger_request`, `ledger_request_bytes`,
`handle_ledger_request`, `apply_ledger_response` and `accept_proof`, plus
the five tests that exercised only those.

The `LedgerRequest`/`LedgerResponse` variants are KEPT, inert, for one
release. `ConvergencePayload` is serialised with `serde_json` as an
externally-tagged enum, so removing a variant makes a message from a
pre-Stage-6 peer fail to decode as a whole envelope. No node emits them any
more, but an operator on an older binary still could.

Also drops `count_unpaid_shares_in` and `unpaid_share_hashes_in`, which had
no other caller, and corrects the Component A sweep's comment — it mirrored
the deleted sweep by design and is a separate subsystem.

Net -870 lines.
`blocks_found` (public API, `/api/v1/pool`) counts `won_blocks`, and the
only writer was `settle_paid_block` — which runs on the node that SUBMITTED
the block. Seven of eight nodes therefore reported 0 for a block the pool
genuinely won, and which node reported 1 was an accident of timing.

The shard's maturity settlement is chain-derived: every node reaches it from
the same coinbase at the same depth. Recording the win there makes the
number agree fleet-wide, and needs no reorg reversal — a block
COINBASE_MATURITY deep is not coming back, which is the reversal machinery
the shard design exists to not need. `INSERT OR IGNORE` keeps this and the
submitter's own writer idempotent against each other.

The cost is a ~16-hour lag before a win appears. That is the honest number:
it counts blocks whose coins exist and are spendable.

This matters now because deleting `settlement.rs` (the chain-OBSERVED
legacy settler) removes the only other path by which a non-submitting node
learned of a win. Landing it first keeps that deletion semantically neutral.
Removes `SettlementObserver` (911 lines), its regtest rehearsal, the
block-submitted `settle_paid_block` call site, and the four payout functions
only it reached. Net -1,813.

The observer settled the LEGACY unpaid ledger from an observed coinbase at
tip. It existed for a specific reason, recorded on `OBSERVED_SETTLEMENT_HEIGHT`:
the seven non-winning nodes would otherwise still owe work the pool had paid
and, being the majority, would carry that view into the next BFT proposal and
pay it twice.

Release B removes the vote, so there is no majority view to poison, and the
shard is the only ledger a payout is built from. `ShareShard::settle_matured`
reaches the same conclusion from the same evidence — the coinbase — on every
node. The difference is depth: at COINBASE_MATURITY rather than at tip.

That depth is why this is a deletion and not a port. The tip-time settler
needed `reverse_settlement` and a deferred-settlement table because a reorg
could undo what it had booked. A block 100 deep cannot be undone, so the
replacement carries none of that machinery.

Deleting the tip-settler and keeping `settle_paid_block` was not an option:
the reverser lived in the file being deleted, so the pair would have left
settlements a reorg could never undo, plus a `won_blocks` row inflating the
public `blocks_found`. They go together or not at all.

The ~100-block window in which `owed` still counts work already paid is
deliberate, not a gap. `owed` is signed and never clamped, so a second
payment inside the window leaves a residual the next block corrects
(SHARE_SHARD.md §4.4, §4.6).

KEPT on purpose:
- `proposal_sync` — it answers peers as well as asking, and a node on a
  pre-Stage-6 binary needs those answers. Retire it when none can ask.
- `settle_block_atomic`, `reverse_settlement`, the deferred-settlement
  accessors and their tests. `settled_blocks` and `deferred_settlements`
  must stay replayable, same reasoning as the retained `sbc_*` tables.
- `OBSERVED_SETTLEMENT_HEIGHT`, as a tombstone. Old logs and the ordering
  assertion on #606 are only interpretable with it. Nothing reads it.
The soak signal for the cutover: once per epoch it scanned the legacy unpaid
ledger and compared it to the shard's balances, naming the worst-drifting
addresses. It did its job — it is how convergence was validated, and how the
-355e12 growth was attributed.

It cannot survive Release B, for two reasons.

It would lie. Nothing settles the legacy ledger any more, so the first block
the pool wins makes the shard's `settled` rise while the legacy unpaid set
stays where it is. The drift jumps by the paid amount and the warning fires
for ever, describing a divergence that is the intended behaviour rather than
a fault.

And it costs. Each call runs `get_top_unpaid_addresses` — the 2.76M-row,
~1.6 s scan its own doc comment calls "the very load this design exists to
delete". Removing a recurring full scan of the unpaid ledger is a small
direct win against the working set behind #690.

Also removes an orphaned doc comment left where the function had been. It had
silently reattached itself to the next function, which is legal, so rustdoc
stayed green and only reading caught it.
#608 §B asks for all three mining modes proven end-to-end on a chain and
records that none had been. PublicPool has since been covered by
`regtest_shard_coinbase_e2e`; this adds the two that had no chain-level
proof at all.

The mode logic already has good unit coverage, but a unit test asserts what
WE think the coinbase says. A coinbase can be internally consistent, hash
correctly, and still be rejected — wrong subsidy for the height, a malformed
witness commitment, an output script the chain will not take. Only a real
`submitblock` settles it. Every payment claim here is read back from
`getblock`, not from the `PayoutProposal` we built.

Both gates are armed via env so the modes are proven on the SHIPPING fee
model: regtest heights are ~500 and `COINBASE_FEE_SPLIT_HEIGHT` is 959,290,
so without the override this would have tested the superseded pre-gate split.

The regtest chain is one shared mutable resource and both tests mine on it.
Run in parallel — cargo's default — one advances the tip while the other is
still building on the height it read, and `submitblock` answers
"inconclusive": well-formed but unattachable. That reads exactly like a bad
coinbase and is not one, so a mutex covers fetch-template THROUGH submit
rather than a note asking for --test-threads=1.
@defenwycke
defenwycke force-pushed the feat/stage6-release-b branch from a7d8a3d to a833be2 Compare August 24, 2026 22:38
defenwycke added a commit that referenced this pull request Aug 25, 2026
…770)

Seven e2e tests have been reporting green without ever executing. Nothing in CI started a
regtest `ghostd`, and each target opens with a skip-and-return, so they were compiled, run,
printed SKIP and passed by doing nothing. `GHOST_REGTEST_REQUIRED=1` makes an unreachable node a
FAILURE rather than a skip; this makes that requirement satisfiable.

`ghostd` is not built from this workspace — it lives in the Bitcoin Core port — so it comes from
the published release tarball, which ships it beside the pool binaries. Pinned to a version
rather than tracking `latest`: CI should not change behaviour because someone cut a release.

⚠ The targets run ONE AT A TIME, in their own step, separate from the bulk `cargo test`. cargo
runs test binaries in parallel and these share a single chain: `empty_template_e2e` parks a
fee-paying transaction in the mempool on purpose, while `regtest_shard_coinbase_e2e` requires an
empty one and drains it. Concurrently they sabotage each other. The sequencing is load-bearing.

Two traps found by proving the recipe on a BLANK chain before writing any of it — neither would
have shown up against the long-lived docker node, which has state the tests were leaning on
without saying so:

- **Priming with a second wallet breaks `empty_template_e2e`.** It resolves wallet RPCs on the
  base url, which only works while exactly one wallet is loaded. The chain is primed with the
  same wallet the tests use.
- **The `ghost-cli` in the release tarball is the POOL ADMIN cli**, not a Core-style RPC client.
  It shares a name with the Core-style ghost-cli deployed at `/opt/ghost/bin/ghost-cli` on the
  fleet and takes entirely different arguments, so RPC is driven with curl.

Two known-red tests are marked `#[ignore]` with their reasons rather than fixed, because #750
deletes both files outright: `fleet_rehearsal`'s fixture backdates its proposal past
`PRE_GATE_FRESHNESS_SECS` so 0 of 8 nodes can ever ratify, and `payout_ledger_e2e`'s
block-acceptance test builds a coinbase-only block against a populated mempool. Ignored rather
than left silently skipping, because a test that fails in CI is worse than one visibly not
running. Block-acceptance coverage does not depend on the latter — `regtest_shard_coinbase_e2e`
submits a real block on the shard path and drains the mempool first.

Verified end to end against a fresh chain from the v1.11.28 tarball: `payout_ledger_e2e` 4 passed
+ 1 ignored, `empty_template_e2e` passed, `regtest_shard_coinbase_e2e` passed with a real
`submitblock` accepted by ghostd. `check-workflow-scalars` and `check-inlined-copies` clean.
# Conflicts:
#	bins/ghost-pool/tests/fleet_rehearsal.rs
#	bins/ghost-pool/tests/payout_ledger_e2e.rs
…se B deletes

`payout_ledger_e2e` and `fleet_rehearsal` are deleted by this release, but
`payout_ledger_e2e` was still named in the regtest sequence, so CI would have
tried to run a target that no longer exists.

`regtest_mining_modes_e2e` arrived with this release and was in no sequence at
all — `common::require_regtest()` returns `None` when `GHOST_REGTEST_REQUIRED`
is unset, so it would have SKIPPED and passed without a node ever starting. That
is the defect #770 closed for the other targets, reintroduced by a new file.

It runs first, on a clean mempool, because `empty_template_e2e` deliberately
parks a transaction.
@defenwycke
defenwycke marked this pull request as ready for review August 26, 2026 17:55
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.

1 participant