Skip to content

wallet rpc: utxo state filtering and per-utxo state in account_utxos - #2128

Merged
nullPointerEnjoyer merged 4 commits into
masterfrom
feature/wallet-utxo-state-rpc
Oct 2, 2026
Merged

nullPointerEnjoyer merged 4 commits into
masterfrom
feature/wallet-utxo-state-rpc

Conversation

@nullPointerEnjoyer

Copy link
Copy Markdown
Collaborator

Summary

Part 3 of the series: #2126 (mempool local-origin orphans) → #2127 (wallet chain-aware repush) → this PR.

Adds optional utxo_states and with_locked parameters to the account_utxos RPC (defaulting to all states / unlocked, i.e. the previous behavior), and includes the state of the creating transaction (Confirmed / InMempool / Inactive / Conflicted / Abandoned) in every returned UtxoInfo.

With this, callers can obtain a trustworthy spendable view — account_utxos with utxo_states=["Confirmed"] whose coin sum equals account_balance called with the same values — instead of having to diff the full listing against the balance to spot outputs of dropped transactions. This pairs with #2127's reconcile pass, which now deterministically marks dropped transactions Abandoned in the wallet state.

Changes

  • output_cache: utxos_with_states (the existing utxos now delegates to it), attaching the creating transaction's state to each result;
  • account/wallet/runtime_wallet/read: get_utxos_with_states and get_multisig_utxos_with_states threading the state through;
  • wallet-rpc-lib: account_utxos gains the optional utxo_types, utxo_states, with_locked parameters; UtxoInfo carries the state; standalone_multisig_utxos responses carry it too;
  • wallet-rpc-daemon: RPC.md regenerated;
  • e2e tests in wallet-rpc-lib/tests/basic.rs.

Compatibility note

The new state field is required on the wire: a client deserializing responses from a daemon that predates it needs to update first. Wallet client and daemon are versioned and released together, and existing callers that omit the new parameters see no behavior change.

Tests

  • e2e (wallet-rpc-lib/tests/basic.rs): state filtering, per-UTXO state reporting, spendable-view/balance equivalence;
  • full wallet, wallet-controller, node-comm, wallet-rpc-lib suites green; fmt and clippy clean (--all-features --all-targets).

Add optional utxo_states and with_locked parameters to the
account_utxos RPC (defaulting to all states / unlocked, i.e. the
previous behavior), and include the state of the creating transaction
(Confirmed / InMempool / Inactive / Conflicted / Abandoned) in every
returned UtxoInfo.

With this, callers can obtain a trustworthy spendable view
(account_utxos with utxo_states=[Confirmed]) whose coin sum equals
account_balance called with the same filter, instead of having to
diff the full listing against the balance to spot outputs of dropped
transactions.

- output_cache: utxos_with_states (the existing utxos now delegates to
  it), attaching the creating transaction's state to each result;
- account/wallet/runtime_wallet/read: get_utxos_with_states and
  get_multisig_utxos_with_states threading the state through;
- wallet-rpc-lib: account_utxos gains the optional parameters; UtxoInfo
  carries the state; standalone_multisig_utxos responses carry it too;
- wallet-rpc-daemon docs regenerated.

No behavior change for existing callers that omit the new parameters.
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

🔍 OpenCodeReview found 2 issue(s) in this PR.

  • ✅ Successfully posted inline: 0 comment(s)
  • 📋 Routed to summary by policy: 2 comment(s)

performance · low

📄 wallet/src/account/mod.rs (L2158-L2161)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category performance)

The backward-compatible wrapper eagerly builds the full Vec of triples (with cloned UtxoState per entry) just to map it back down to pairs, adding an extra allocation per call on every existing caller. Since the underlying scan already builds the Vec, an in-place truncate-style rewrite (e.g., map a mutable Vec of tuples and drain the state field, or a parallel iterator adapter) could avoid the second allocation. Low priority, but applies identically in get_utxos and the OutputCache::utxos wrapper.

💡 Suggested Change

Before:

self.get_multisig_utxos_with_states(utxo_types, median_time, utxo_states, with_locked)
    .into_iter()
    .map(|(outpoint, output, _state)| (outpoint, output))
    .collect()

After:

let mut result: Vec<_> = self.get_multisig_utxos_with_states(
    utxo_types,
    median_time,
    utxo_states,
    with_locked,
)
.into_iter()
.map(|(outpoint, output, _)| (outpoint, output))
.collect();
result

maintainability · low

📄 wallet/wallet-rpc-lib/src/rpc/server_impl.rs (L528-L531)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category maintainability)

utxo_types is collected into an owned Vec and then re-iterated via &...iter().map(...).collect::<Vec<_>>() just to call try_into on the reference — a double allocation. Per the TryFrom<&Vec<UtxoType>> impl (types/src/utxo_types.rs), only the empty case fails, so building the intermediate Vec<UtxoType> and referencing it is unnecessary; convert the collected vec directly. Also consider handling the error the same explicit way utxo_states does below for consistency.

💡 Suggested Change

Before:

        let utxo_types =
            (&utxo_types.unwrap_or_default().iter().map(UtxoType::from).collect::<Vec<_>>())
                .try_into()
                .unwrap_or(UtxoTypes::ALL);

After:

        let utxo_type_filter: Vec<UtxoType> =
            utxo_types.unwrap_or_default().iter().map(UtxoType::from).collect();
        let utxo_types = match (&utxo_type_filter).try_into() {
            Ok(types) => types,
            Err(BitFlagError::Empty) => UtxoTypes::ALL,
        };

Comment thread wallet/wallet-rpc-client/src/handles_client/mod.rs Outdated
The account_utxos path was changed to fall back to UtxoStates::ALL for
an omitted/empty filter (matching the RPC's server-side default and
docs), but get_balance and get_multisig_utxos still fell back to
Confirmed-only - an inconsistency with the daemon RPCs they mirror.
Unify all three and document the pre-existing Confirmed fallback in the
comment, so the default change is explicit.
The security review caught the previous commit over-generalizing: the
standalone_multisig_utxos RPC's server-side default for an omitted or
empty utxo_states filter is Confirmed-only (unlike account_utxos, which
defaults to all states), and the regenerated RPC.md makes no all-states
claim for it. Restore the Confirmed fallback with a comment documenting
the asymmetry, so remote multisig callers cannot suddenly observe
unconfirmed UTXOs.
Comment thread wallet/wallet-rpc-lib/tests/basic.rs
The PoS block reward is tracked as block data (RewardAdded event), not
as a utxo, so account_utxos never lists a LockThenTransfer utxo and the
balance-invariant test can only exercise Transfer outputs. Pin the
boundary with an assertion so a future wallet change that starts
listing them forces the invariant test to be extended.
@nullPointerEnjoyer

Copy link
Copy Markdown
Collaborator Author

OpenCodeReview cycle summary

Cycle Head Findings Resolution
1 76bf68dc5 1 (bug-med: handles-client empty-utxo_states default changed silently) 2e8fadefd — unified the fallbacks with the server-side defaults and documented the pre-PR Confirmed default
2 2e8fadefd 0 —
3 412a76640 1 (test-med: balance invariant untested for LockThenTransfer) 549811a70 — see below
4 549811a70 0 —

On the cycle-3 finding: PoS block rewards are tracked by the wallet as block data (the RewardAdded event), not as utxos, so account_utxos never lists a LockThenTransfer utxo and the end-to-end invariant test can only exercise Transfer outputs. The design boundary is now pinned by an assertion (account_utxos with utxo_types=[LockThenTransfer] must return an empty set) so that a future wallet change which starts listing them forces the invariant test to be extended.

@nullPointerEnjoyer
nullPointerEnjoyer merged commit 34bb825 into master Oct 2, 2026
21 checks passed
@nullPointerEnjoyer
nullPointerEnjoyer deleted the feature/wallet-utxo-state-rpc branch October 2, 2026 14:40
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