bump to latest main#29
Merged
Merged
Conversation
The payment-output split already routed Orchard-pool payments into the Ironwood view, but the change output is computed in `single_pool_output_ balance`, which counted it in the Orchard bundle even when Ironwood is active. The builder routes Orchard-pool change into the Ironwood bundle, so the proposal's per-bundle action counts could diverge from the built transaction (each bundle is padded to its own action floor), defeating the consistency this PR is meant to provide. Thread an `orchard_change_to_ironwood` flag through `ChangeStrategy::compute_balance` (carrying the same routing decision the caller already computes for payments) and route the Orchard-pool change count to the Ironwood action count when it is set. Adds `orchard_change_routes_to_ironwood_bundle`, asserting that toggling the flag moves the change between bundles and changes the fee. Also: document the `ironwood`/`orchard_change_to_ironwood` parameters in the `compute_balance` rustdoc, correct the `NetFlows` Ironwood-fields comment, and restore the dropped note that the Orchard view still uses a placeholder `BundleVersion`.
Pin the expected fees in the Ironwood fee tests instead of asserting only that one is larger, so a change to the ZIP 317 per-bundle action floor is caught. Also note in the `compute_balance` docs why `orchard_change_to_ironwood` is a boolean (it carries the builder's already-computed routing decision).
…pletion zcash_client_backend: Wire Ironwood anchors into wallet transaction building
This stack now sits on a tx-builder branch that replaced the crate-wide `ANY_ORCHARD_BUNDLE_VERSION` constant with a height-derived `orchard_bundle_version_for_height` helper, and added an Orchard/Ironwood routing view (and a routing flag) to `ChangeStrategy::compute_balance`. Point the proposal's Orchard bundle view at the height-derived version, which resolves the placeholder TODO left in the "Thread a separate Ironwood bundle view" commit. Update the zip317 fee tests to the current `ShieldedPool` constructor name, the concrete `orchard_v2` bundle version (the value the old constant carried), and the current `compute_balance` signature. Also drop the `any(zcash_unstable = "nu6.3", zcash_unstable = "nu7")` compile-time gate around the proposal path's `orchard_outputs_are_ironwood` decision. The build path in `wallet.rs` gates Ironwood routing purely on runtime NU6.3 activation, so the compile-time gate made the fee estimate diverge from the built transaction in stable builds, and `nu6.3` is not a recognized `zcash_unstable` value, so it warned on every build. The proposal path now mirrors the builder via `orchard_outputs_to_ironwood`.
zcash_client_backend: Account for the Ironwood bundle in fee and change calculation
Ironwood note commitments are Orchard-shaped, so this persists them using the same `shardtree` machinery as Orchard, under a separate `ironwood` table prefix. Adds a migration that creates the `ironwood_tree_shards`, `ironwood_tree_cap`, `ironwood_tree_checkpoints`, and `ironwood_tree_checkpoint_marks_removed` tables, the `ironwood_commitment_tree_size` and `ironwood_action_count` columns on `blocks`, and the `v_ironwood_shard_scan_ranges`, `v_ironwood_shard_unscanned_ranges`, and `v_ironwood_shards_scan_state` views, each mirroring the Orchard equivalents but keyed on NU6.3 (Ironwood) activation. When a wallet has already scanned NU6.3-era blocks, the migration requeues them from activation so they are rescanned with Ironwood tree state enabled. Wires an `ironwood_tree` accessor over the `ironwood` prefix and overrides `WalletCommitmentTrees::with_ironwood_tree_mut` in both `WalletDb` implementations to provide the persisted tree, replacing the default that reported no Ironwood tree. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: fdecroix <fdecroix@eryx.co>
Ironwood note commitments are Orchard-shaped, so this persists them using the same `shardtree` machinery as Orchard, under a separate `ironwood` table prefix. Adds a migration that creates the `ironwood_tree_shards`, `ironwood_tree_cap`, `ironwood_tree_checkpoints`, and `ironwood_tree_checkpoint_marks_removed` tables, the `ironwood_commitment_tree_size` and `ironwood_action_count` columns on `blocks`, and the `v_ironwood_shard_scan_ranges`, `v_ironwood_shard_unscanned_ranges`, and `v_ironwood_shards_scan_state` views, each mirroring the Orchard equivalents but keyed on NU6.3 (Ironwood) activation. When a wallet has already scanned NU6.3-era blocks, the migration requeues them from activation so they are rescanned with Ironwood tree state enabled. Wires an `ironwood_tree` accessor over the `ironwood` prefix and overrides `WalletCommitmentTrees::with_ironwood_tree_mut` in both `WalletDb` implementations to provide the persisted tree, replacing the default that reported no Ironwood tree. The Ironwood shard store and tree are exposed as `IronwoodShardStore` / `IronwoodCommitmentTree` aliases so Ironwood usage is self-documenting at call sites. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… propose_transaction
…XO spending functionality
…or of batched query `gather_account_transparent_inputs` issued one `get_spendable_transparent_outputs` query per source address. Now that `InputSource` exposes the batched `get_spendable_transparent_outputs_for_addresses`, the per-address loop is unnecessary: inline a single batched call (filtered to `NonCoinbaseOnly`, then account-redacted) at the sole call site in `propose_transaction` and delete the function. This matches the batched approach already used for shielding and gives general transfers the same single-query behavior for wallets with many transparent addresses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ry builder Factor the SQL query body of get_spendable_transparent_outputs_for_addresses out into a new spendable_transparent_outputs_query helper, parameterized over the address-predicate and ORDER BY fragments, and factor the coinbase-filter encoding out into coinbase_filter_encoding. This is a pure refactor with no behavior change: get_spendable_transparent_outputs_for_addresses now builds its query via these helpers instead of inlining the SQL and match directly. This is preparatory: a following commit adds a new select_spendable_transparent_outputs query that reuses the same query body with a different address predicate and ordering (by account, ordered by value descending, for the value-bounded transparent gather). Also tightens the redeem-script row read to use a single row.get::<_, Option<Vec<u8>>>(...) call instead of a separate row.get(...)? followed by an Option check. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ent input gather Add InputSource::select_spendable_transparent_outputs, a value-bounded counterpart to get_spendable_transparent_outputs[_for_addresses]: rather than returning every spendable transparent output for an account, it returns only enough (ordered by descending value) to cover a requested TargetValue, optionally padded by a caller-supplied estimated_additional_fees headroom. This is intended to scale to wallets with large numbers of transparent addresses and UTXOs, which would otherwise need to materialize their entire UTXO set just to build a small transfer. The SQLite implementation backs the value-descending ordering with a new idx_transparent_received_outputs_value_zat index (added by a new migration), and reuses the spendable_transparent_outputs_query/ coinbase_filter_encoding helpers introduced in the preceding commit. GreedyInputSelector::propose_transaction now uses this method (instead of gathering every account transparent receiver via get_transparent_receivers and querying get_spendable_transparent_outputs_for_addresses) when the TransparentSpendPolicy requires transparent inputs, bounded initially by the requested payment total and re-gathered if a subsequent ChangeError::InsufficientFunds reveals the bound was too low. This also lets GreedyInputSelector's trait bound on DbT simplify from WalletRead + InputSource<...> down to just InputSource. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e-aware InputSource::select_spendable_transparent_outputs now takes a concrete fee_rule: &StandardFeeRule parameter in place of the previous estimated_additional_fees: Option<Zatoshis> static headroom. The SQLite gather recomputes the cumulative ZIP 317 marginal fee cost of the transparent inputs examined so far at each step, maintaining a running total of input sizes so the recomputation stays O(1) per candidate UTXO, and stops once the post-fee accumulated value meets the requested TargetValue. This removes the need for callers to guess a fee headroom up front and avoids the previously-common second round trip to correct an under-estimated one. Using a concrete fee rule (rather than a generic FeeRuleT bounded by FeeRuleT::Error: Into<Self::Error>) keeps the change localized: no other InputSelector/ChangeStrategy signatures need updating, since the gather's fee estimate is a heuristic bound decoupled from the caller's actual change strategy (real fee/balance correctness is still enforced by that change strategy, and the existing retry-on-InsufficientFunds fallback is unchanged). Adds SqliteClientError::FeeRuleError to carry FeeRule errors from the gather (zip317::FeeError doesn't implement std::error::Error, so it's wrapped). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… re-gather succeeds without new shielded notes GreedyInputSelector::propose_transaction's retry loop tracked progress solely via growth in the shielded note total (prior_available vs. new_available), even though the same loop can also grow the transparent input set via a re-gather when the initial (fee-only) estimate proves insufficient. For an account with no spendable shielded notes at all (or none beyond what's already excluded), shielded-side "progress" can never be made, so the loop bailed out with InsufficientFunds immediately after the first failed balance computation, even when the freshly re-gathered transparent inputs would have been sufficient on the very next iteration. Track whether the transparent gather grew this iteration (transparent_gather_grew) and treat that as valid progress alongside shielded note growth, so the loop retries compute_balance with the enlarged transparent input set instead of failing prematurely. Adds a regression test (propose_t2shielded_requires_transparent_regather) that funds a transparent-only account with many small UTXOs and pays a shielded recipient, forcing the initial transparent-only fee estimate (which cannot account for the additional shielded action) to undershoot and require exactly this re-gather-without-shielded-progress path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…iew findings - Move two changelog entries that described this PR's new work out of already-released, dated version sections (zcash_client_backend's [0.22.0] and zcash_client_sqlite's [0.20.0]) into the correct Unreleased/PLANNED sections. Released changelog sections must remain immutable; both frozen sections now match main exactly. - Reword changelog entries and doc comments that described this PR's own discarded intermediate designs (an unbounded gather, an Option<Zatoshis> static-fee-headroom parameter) as though they were prior shipped behavior. Since select_spendable_transparent_outputs is entirely new and unreleased, changelog/doc text should describe only the final capability, not the path taken to arrive at it. - Remove a doc comment's direct reference to this repo's internal AGENTS.md conventions file, meaningless to external readers of the public rustdoc. - Improve NonEmptyBTreeSet's doc comment (was "A quick and easy non-empty BTreeSet") and document its previously-undocumented public fields; drop a private implementation note about possibly moving it to another crate someday. - Document the spend_policy parameter on InputSelector::propose_transaction, which controls the same privacy behavior the method's doc already describes but was never mentioned. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…n derivation When gap-limit generation derives a transparent address that already exists as a standalone (Foreign) import, store_address_range now upgrades the existing addresses row in place to its derived form instead of inserting a duplicate row for the same receiver (which the UNIQUE index on cached_transparent_receiver_address rejects). The row id is preserved, so UTXOs already attached to the imported receiver become spendable. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… duplicate receivers The add_transparent_receiver_address_index migration already collapses a transparent receiver duplicated as both a derived and an imported record. It now also carries the minimum exposed_at_height across the merged records onto the surviving canonical record, so an earlier exposure recorded against the imported record is not lost. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ss upgrade Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ivation A transparent receiver duplicated across accounts was previously an unconditional migration abort. Resolve it instead when derivation definitively identifies the correct record: if exactly one record reproduces the receiver when derived from its own account's viewing key at its recorded child index, that record is retained, and the received outputs of the other records are repointed to it along with their account attribution (the reason cross-account merges were previously unsafe). No account is privileged in this determination: an address genuinely derived under the ZIP 32 account index 0x7FFFFFFF used for the zcashd legacy account wins over another account's imported record just as any other verified derivation would. Cross-account duplicates for which no unique record verifies still abort. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ount on derivation The runtime reconcile-on-derive path in store_address_range only matched a Foreign row in the deriving account, so deriving an address that had been imported standalone under a *different* account fell through to the INSERT and failed hard on the receiver-uniqueness index (the upsert's conflict target does not cover it), wedging gap-limit generation and anything that drives it. Deriving the address is itself proof that the deriving account owns it, so the lookup now matches the Foreign row regardless of account and the upgrade moves the row's account attribution — and that of any outputs received at the address — to the deriving account, mirroring the migration's derivation-based cross-account resolution. Retargeting cannot violate the address-tuple constraint: a row already held by the deriving account at that (scope, child index) would be this same receiver, which the uniqueness index makes impossible. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ges from rebase onto main Rebasing onto main (which has moved substantially, including the ShieldedProtocol -> ShieldedPool rename and Ironwood fee/change wiring) surfaced two required adaptations beyond conflict resolution: - Update the remaining ShieldedProtocol::Sapling references (added by this branch's own tests) to ShieldedPool::Sapling, avoiding deprecated-item usage now that ShieldedProtocol is a deprecated alias for ShieldedPool. - FeeRule::fee_required gained a new ironwood_action_count parameter; pass 0 from the transparent-only gather in select_spendable_transparent_outputs, matching the existing 0 passed for sapling_input_count/sapling_output_count/orchard_action_count. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…utxos_to_txos, for the value_zat index migration fix_transparent_received_outputs rebuilds transparent_received_outputs via DROP TABLE + ALTER TABLE ... RENAME TO, which destroys any index created on the old table. add_transparent_value_index previously depended only on utxos_to_txos (the migration that first creates the table), which does not guarantee it runs after that rebuild; depending on schemerz's topological ordering among otherwise-independent migrations, the index could have been created before the rebuild and then silently lost, with no indication that anything had gone wrong. Depend on fix_transparent_received_outputs directly instead, which transitively depends on utxos_to_txos, guaranteeing the index is always created on the table that will actually persist. Also corrects the migration's own doc comment, which incorrectly asserted that the index "remains valid across the rebuild" so long as column names were preserved -- SQLite indexes do not survive a DROP TABLE regardless of the recreated table's schema. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…solution - Decode key scopes via KeyScope::decode and match on the enum rather than raw integer codes, and replace the address-record tuple with a bespoke struct. - Verify ephemeral-scope records by derivation: the ephemeral scope has a genuine derivation relationship to the account (AccountPubKey::derive_ephemeral_ivk), so a cross-account duplicate whose ephemeral record reproduces the receiver is resolved in its favor rather than aborting. Only Foreign records remain unverifiable, having no derivation relationship to their account. - Fix a stale child-index reference in a test comment. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…blocks_rows Per review: rather than exposing a caller-supplied floor (an API with an unsound-use surface), put_blocks_rows now determines internally when nullifier-map insertion is skippable, and put_blocks — and therefore every consumer — benefits automatically. This also fixes the long-standing missed optimization upstream: nullifiers were inserted unconditionally even when the scanned range extended the contiguous fully-scanned frontier, where the map can never be consulted (it exists to detect spends observed before the corresponding note's block is scanned, which cannot occur below a contiguous frontier). The derivation requires knowing the fully-scanned height at the low-level layer, so LowLevelWalletRead gains block_fully_scanned_height (the traits are unreleased, so this is not a breaking change). Frontier-extending batches retain the trailing NULLIFIER_MAP_RETENTION_BLOCKS window, keeping the map aligned with an equal prune_tracked_nullifiers pruning depth; out-of-order ranges (after a gap, recent-first, chain-tip pre-scans) continue to track the nullifiers of every block, pinned by unit tests covering exactly the after-a-gap counter-example from review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…er-tracking-floor zcash_client_backend: add a nullifier-tracking floor to `put_blocks_rows`
…iki-e/install-action-2.83.0 build(deps): bump taiki-e/install-action from 2.82.11 to 2.83.0
…l-anchors pczt: Preserve absent shielded anchors across roles
…onwood After NU6.3 the Orchard turnstile forbids value from entering the Orchard pool, so a caller that names Orchard as its fallback change pool must not produce Orchard change for a transaction that spends no Orchard notes. `select_change_pool` promotes such change to Ironwood, but nothing covered that promotion through a change strategy, so a regression in the wiring between `MultiOutputChangeStrategy` and `select_change_pool` would not have been caught. Add a test that shields a transparent UTXO with an Orchard fallback and checks the proposed change pool either side of NU6.3 activation: Orchard before, Ironwood after. The fee is identical at both heights, as the change output is charged to the Ironwood bundle rather than the Orchard one and each pads to the same two-action floor. Add `ChangeValue::ironwood`, parallel to the existing `sapling` and `orchard` constructors, so the expected change value can be named. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…l-test zcash_client_backend: test that an Orchard change fallback is promoted to Ironwood after NU6.3
`Step::from_parts` previously accepted a non-optional `BlockHeight` and reinterpreted the value `0` as "no anchor" — leaking the protobuf `anchorHeight` scalar-default sentinel into the domain constructor and treating genesis, which can never be a valid anchor, as a magic value. Let `Step::from_parts` take `anchor_height: Option<BlockHeight>` like the field it populates, and require a concrete anchor for any step that produces a shielded bundle — one that spends shielded notes, pays to a shielded pool, or returns shielded change. Every shielded-tree lookup such a step performs, including the dummy spends that pad an output-only bundle, is bound to that anchor, so an anchor selected at proposal construction time is what keeps a routed-output transaction indistinguishable from one that spends real notes. Only a purely transparent step may carry `None`. Proposals are ephemeral, so a `0` anchor on a shielded-bundle step is malformed rather than a legacy deferral: the protobuf decoder rejects it at the parse boundary with a new `ProposalDecodingError::MissingShieldedAnchor`, and `Step::from_parts` enforces the same invariant for direct callers with a new `ProposalError::MissingShieldedAnchor`. Both are covered by tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…elding-anchor fix: preserve shielding proposal anchors
Prune the `unpublished` entries for zcash_protocol, zcash_address, zcash_keys, zcash_primitives, zcash_proofs, zcash_transparent, and zcash_history, which have since been published to crates.io, and refresh the corresponding publisher records. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…0.22-stack Release zip321 0.9.0-rc.1, pczt 0.8.0-rc.1, zcash_client_backend 0.24.0-rc.1, zcash_client_sqlite 0.22.0-rc.1
`propose_send_max` never assigned an output pool to the payment when the recipient was a bare transparent address or a unified address having no shielded receiver, so every such proposal failed validation with `ProposalError::PaymentPoolsMismatch`. Assign `PoolType::Transparent` to the payment for recipients that are paid directly via a transparent output, excluding TEX recipients, whose payment is delivered by the ephemeral second step. The unified-address arm of the fee computation now shares the same predicate, so the fee's transparent-output accounting and the payment pool assignment cannot drift apart. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…parent-inputs The `Address::Tex` arm of the send-max fee computation was not gated on the `transparent-inputs` feature, so in builds without that feature a send-max proposal to a TEX recipient deducted the second-step fee from the payment but constructed no ephemeral step, failing proposal validation with a misleading `PaymentPoolsMismatch` error. Gate the arm and reject such recipients up front with `GreedyInputSelectorError::UnsupportedTexAddress`, the same error that `propose_transfer` reports for this condition. Expressing this error requires widening `ProposeSendMaxErrT`'s note-selection error type from `BalanceError` to `GreedyInputSelectorError`; balance errors encountered during send-max input selection are now wrapped in `GreedyInputSelectorError::Balance`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…alue When a wallet's spendable balance exactly equaled the required fee, `propose_send_max` constructed a proposal paying zero to a shielded recipient, consuming the entire balance as fees (transparent recipients were rejected, but with an unrelated `ZeroValuedTransparentOutput` error). Report `InsufficientFunds` instead whenever the fee leaves nothing for the recipient, with `required` reflecting the minimum total needed to deliver a nonzero amount. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…available `propose_send_max` decided whether an Orchard receiver preempts Sapling delivery using `ZcashAddress::can_receive_as`, which reflects the receivers present in the address rather than the receivers this build can pay. In a build without the `orchard` feature, a payment to a unified address having both Sapling and Orchard receivers was assigned no output pool at all, failing proposal validation with `PaymentPoolsMismatch`. Give Orchard receivers delivery precedence only when the `orchard` feature is enabled, so that such payments fall back to the Sapling receiver. Additionally, reject a unified address for which no payment pool can be assigned (one containing no receiver the build is able to pay) with `GreedyInputSelectorError::UnsupportedAddress` rather than letting it fail proposal validation with the same misleading error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…awarely Route the Ironwood action count in `propose_send_max` through `transactional_action_count` with the bundle version in effect at the target height, as every other action-count computation site does, instead of calling `num_actions` directly with hardcoded `Flags::ENABLED`. The result is unchanged for all current bundle versions (every Ironwood version's default flags are fully enabled), but the count now follows the version's action-count policy rather than silently bypassing it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the `Builder::new_with_orchard_pool_bundle_type` override constructor with separate `orchard_bundle_type` and `ironwood_bundle_type` fields on `BuildConfig::Standard`, selecting the transactional bundle type independently for each Orchard protocol value pool directly in the build configuration. This mirrors the existing per-pool anchor fields and keeps the build config as the single source of truth, instead of threading an override past it; the `BundleType::Coinbase` assertion disappears along with the override channel it guarded. `zcash_client_backend` sets both fields from its existing single parameter, so its API is unchanged.
…panic The sum of the two per-transaction fees for a ZIP 320 send-max pair was computed with an `expect`, which is reachable when a caller-supplied fee rule produces fees near the maximum monetary amount. Report the overflow as `GreedyInputSelectorError::Balance(BalanceError::Overflow)` instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every existing send-max test funds the wallet within a single shielded pool. Add a two-pool-tester test that funds one note in each of the Sapling and Orchard pools and sends the maximum to an external recipient, verifying that both notes are spent in a single step, that the ZIP 317 fee reflects both padded shielded bundles, and that the built transaction mines and leaves the wallet empty. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ipient zcash_client_backend: Fix `propose_send_max_transfer` transparent-recipient and edge-case handling
…le-type Allow per-pool Orchard protocol bundle types
dorianvp
merged commit Jul 13, 2026
d6289bc
into
feature/op-return-zingolib-compat
47 of 49 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.