Skip to content

refactor!: answer the #2428 review threads that were still live in code - #2574

Open
zancas wants to merge 4 commits into
devfrom
pr2428_review_followups
Open

refactor!: answer the #2428 review threads that were still live in code#2574
zancas wants to merge 4 commits into
devfrom
pr2428_review_followups

Conversation

@zancas

@zancas zancas commented Jul 28, 2026

Copy link
Copy Markdown
Member

Your review of #2428 left thirteen threads unresolved when the PR merged. This PR is the collected answer. It changes the code where a comment still described it, and it accounts for every other thread below, so each can be resolved from here.

Changed in this PR

get_migration_heights returns the heights directly (thread). Agreed, and done. The helper wrapped an infallible call yet returned Result<Option<_>>, and all five callers unwrapped the None case with the same ok_or(WalletError::NoSyncData). The function now returns the heights directly and maps the None case to NoSyncData itself, which deleted the identical ok_or at all five call sites. The wrapper survives for one reason, which its doc now states: it pins the wallet's own min_confirmations and types the no-sync-data case once.

The confirmation-wait errors drop their Split names (thread). Agreed. The function is generic over migration transactions, yet its errors said "split". This PR renames the two shared variants to MigrationTransactionFailed and MigrationConfirmationTimeout; SplitDidNotConverge keeps its name, being genuinely split-only. The rename is breaking on the public MigrationError enum.

The duplicated comment is gone (thread). The doc on broadcast_due_parts_selected carried its two paragraphs twice, verbatim. Fixed.

The ironwood balance comment now answers your question (thread). You asked why compact block scanning would fail to find positions for ironwood. It does not fail: scanning finds ironwood positions, and pepper-sync carries ironwood located trees and a nullifier-to-position map. A note lacks a position only until a scan locates its block in the commitment tree, and the wallet records its own outputs at broadcast, before any scan, so those notes hold no position and cannot be witnessed. The comment in shielded_spendable_balance now states this reason instead of asserting the conclusion.

Resolved by work that landed since the review

The COIN constant (thread). Agreed: in this codebase "coin" names a transparent output, and the constant collided with that sense. The local constant is gone. params.rs now imports zcash_protocol::value::COIN, so the workspace defines no such name of its own.

The dust_floor field (thread). The field is renamed, though not to denom_floor. Your name has the virtue of saying what the field does: it floors the denomination ladder. We chose max_residual_value instead, because ZIP 318 names the quantity MAX_RESIDUAL_VALUE and the reference crate exports it under that name; a field that mirrors the specification reads without translation.

An error when sync data is missing (thread). Done as you suggested. WalletError::NoSyncData now exists, and every planning and broadcast path returns it when last_known_chain_height is None. This PR folds the same mapping into get_migration_heights, so the migration height reads are typed as well.

Visibility of migration_note_values (thread). The function is pub(crate) now.

Visibility of build_note_split_transaction (thread). The function is pub(crate) now.

Protection before NU6.3 activation (thread). Later work added the protection you asked about. The start path refuses with IronwoodEraTooYoung until the first window boundary after NU6.3 activation, and the scheduler floors every anchor at activation, so a pre-activation release fails once with a typed error instead of striking the build error repeatedly.

Checkpoint count versus bucket modulus (thread). The code landed where your third comment arrived. Raising the checkpoint retention would have guaranteed every boundary a witness, but, as you said, it would store extra data forever for the sake of a single migration. Instead, refresh_part_witnesses caches each part's witness while its boundary checkpoint is still retained, and a part whose boundary is pruned takes the MissedBoundary skip and is re-assigned.

Kept as it is

The SyncDomain re-export (thread). Removing the re-export would give SyncDomain a single canonical path and spare the reader one indirection. We kept it nonetheless: wallet/traits.rs and sync/spend.rs both import crate::SyncDomain through it, and because it is pub(crate) it adds nothing to the crate's public surface.

Tracked onward

The dust-floor economics (thread). Later work resolved most of this in your direction. The floor is no longer local policy: it is ZIP 318's MAX_RESIDUAL_VALUE (0.01 ZEC), imported from the reference crate. Notes at or below sweep_min, and any pool too small to fund the smallest denomination, now stay in the pre-Ironwood pool as residual — recorded in the plan and covered by the consent hash — rather than folding into fees. One fee-fold remains: when a pool funds at least one denomination, the sizing surplus below the smallest denomination becomes the final split transaction's fee. That surplus is bounded below 0.01 ZEC per migration, but your underlying point stands — the user should not pay value away silently — so #2573 tracks whether the surplus should become a residual V2 change note instead.

🤖 Generated with Claude Code

Oscar's review of #2428 left thirteen unresolved threads; most were
resolved by later work, and three still described the code. This commit
lands those three, and sharpens one comment a fourth thread questioned.

`get_migration_heights` now returns the heights directly, mapping the
no-sync-data case to the typed `WalletError::NoSyncData` instead of
returning `Result<Option<_>>` for five callers to unwrap identically
(#2428 (comment)).

The confirmation-wait errors rename to `MigrationTransactionFailed` and
`MigrationConfirmationTimeout`: `await_migration_confirmations` serves
both the note-splitting rounds and the parts-broadcast path, so the
`Split*` names misreported part failures
(#2428 (comment)).
`SplitDidNotConverge` keeps its name because it is genuinely split-only.

The doc comment on `broadcast_due_parts_selected` sheds its duplicated
paragraphs
(#2428 (comment)),
and the ironwood comment in `shielded_spendable_balance` now states why
a note can lack a position instead of asserting the conclusion
(#2428 (comment)).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@zancas
zancas marked this pull request as draft July 28, 2026 18:09
@zancas
zancas marked this pull request as ready for review July 28, 2026 18:23
@zancas
zancas requested a review from Oscar-Pepper July 28, 2026 18:24
…tract

This implements the two suggestions from the #2574 review.

MigrationError::MigrationTransactionFailed and
MigrationError::MigrationConfirmationTimeout repeated the enum's name at
every use site; they become TransactionFailed and ConfirmationTimeout.
The display strings are unchanged, so no user-facing text moves. The
rename is breaking on the public MigrationError enum, exactly as the base
PR's rename was.

A unit test pins the contract #2574 gave get_migration_heights: a wallet
that has never synced returns the typed WalletError::NoSyncData from the
wrapper itself, so every migration site inherits the typed refusal from
one place and the promise in the wrapper's doc comment is enforced rather
than asserted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Oscar-Pepper
Oscar-Pepper previously approved these changes Jul 29, 2026

@Oscar-Pepper Oscar-Pepper left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

not merging until #2582 (review) is resolved

refactor!: de-stutter the migration errors and pin the NoSyncData contract
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