<!-- CURSOR_SUMMARY -->
> [!NOTE]
> **Medium Risk**
> Refactors wallet coin split/combine paths used by RPC; behavior should
be equivalent but touches core transaction construction and coin
selection.
>
> **Overview**
> Moves **split** and **combine** coin logic out of `WalletStateManager`
into a new **`FungibilityManager`**, wired on the state manager at
startup and used by the wallet RPC for `split_coins` / `combine_coins`.
>
> RPC handlers now resolve a fungible wallet via `get_fungible_wallet`
(standard or CAT only) and call the manager instead of
`wallet_state_manager.split_coins` / `combine_coins`. Invalid wallet
types raise **`Wallet {id} is not eligible for coin splitting`** (tests
updated for split and combine RPC paths). **`coin_num_limit`** is no
longer forwarded into the combine implementation; limits still apply via
`CombineCoins` request validation.
>
> Behavior of coin selection, fees, and transaction building is intended
to stay the same—this is primarily structural cleanup for LABS-490.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
28e52c2070. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
* Treat missing anchored peak header as not-synced in wallet timestamp lookup
Follow-up to the wallet timestamp backtracking bound. When
get_timestamp_for_height_from_peer runs in anchored mode (an
expected_header_hash supplied by new_peak_wallet) and the peer returns no
header block for the starting peak height, the loop previously decremented
the height without advancing the anchor and then validated lower heights
against the stale peak hash. Return None (peer treated as not synced)
instead, so the anchor chain is never extended from a missing block.
Non-anchored lookups (get_timestamp_for_height) are unchanged and still
backtrack past a missing peak height.
* Clarify anchored timestamp lookup comment
* Add DataLayer self-heal for blobless stores
Reset stores with missing committed Merkle blobs to a clean empty root before sync so already-corrupted subscriptions can recover instead of remaining falsely synced.
* Clear Merkle cache when resetting DataLayer stores
Invalidate cached Merkle blobs during store reset so recovery cannot keep serving stale in-memory blobs after the corresponding file has gone missing.
* Fix DataLayer reset helper formatting.
* [ASB-272] Pass remaining CLVM cost budget when building Offer caches
Offer.__post_init__ tracked a shrinking max_cost but never passed it into
compute_spend_hints_and_additions, and cost ValidationErrors were swallowed.
Propagate the remaining budget and re-raise cost limit failures.
Co-authored-by: Cursor <cursoragent@cursor.com>
* [ASB-272] Replace Offer max_cost spy test with conditions() cost check
Co-authored-by: Cursor <cursoragent@cursor.com>
* [ASB-272] Assert Offer rejects on remaining budget, not post-sum check
Co-authored-by: Cursor <cursoragent@cursor.com>
* [ASB-272] Leave Offer.conditions() unchanged
Co-authored-by: Cursor <cursoragent@cursor.com>
* [ASB-272] Use pytest.raises match= for Offer remaining cost test
Co-authored-by: Cursor <cursoragent@cursor.com>
* Cover Offer remapping of CLVM cost-exceeded ValueError
Lower MAX_BLOCK_COST_CLVM so a cheap first spend leaves too little budget for
the next puzzle run, hitting the ValueError→ValidationError path without mocks.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* Continue wallet subscriptions after a single peer fails
One bad peer in _process_new_subscriptions previously aborted the whole
batch, leaving later peers without the new PH/coin-id registrations.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use real peers in subscription peer-error test
Replace MagicMock peers with two full nodes and only fail the bad peer's register handlers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Narrow subscription peer-error handling to RPC failures
Only catch subscribe failures so local apply errors do not ban every peer, and guard peer.close so close exceptions cannot abort the batch.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Tighten subscription failure handling to peer/RPC errors
Catch ValueError/ProtocolError/OSError instead of Exception, and use log_exceptions when closing a failed peer.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Treat protocol Error and StreamableError as subscription peer failures
Normalize Error/unexpected subscribe responses to ValueError, and catch
StreamableError in the per-peer handler so corrupt payloads do not abort the batch.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Add unit tests for subscribe response normalization helper.
Cover Error, unexpected-type, None, and success paths without mocking peers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Drop happy-path unit test for subscribe response helper.
Rejection cases are enough for the coverage gap; success is already exercised elsewhere.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use a separate config for pooling information
* New PlotNFT drivers
* PlotNFT2 Wallet
* PlotNFT V2 RPCs and CLI
* Integrate v2 pooling protocol into farmer
* Comment by @cursor
* Fix test
* Comments by @cursor
* whoops
* Comments by @cursor
* Comments by @cursor
* Comments by @cursor
* Some tidying
* fix test for memo adjustment
* tweak additional memos again
* empirical testing with v1 pools
* Add expiration to `GetAuthRequest`
* Remove pyjwt dep
* Revert `get_login_link` to old behavior
* Add an extra derivation to auth key for v2
* unhardened
* Fix login link test
* Fix login link test again
* Fix network protocol data
* Comments by cursor
* Fix test
* Use correct pool url after redirect
* Fix test for coverage
* Upstream wallet fixes
* Add `REMARK` option to `launch`
* pre-commit
* chmod
* test coverage
* Add wallet name
* test coverage
* moar test coverage
* commentsby @cursor
* moar test coverage
* fix custody architecture namespace
* Comments by @matt-o-how
* Fix /GET farmer to omit the signature rather than None
* Fix protocol test
* fix test
* comments by @cursor
* diff minimizatino
* event dispatch
* Fix test deadlock
* Comments by @cursor
* test coverage
* Comments by @cursor
* Comments by @cursor
* Port `chia wallet notifications` to `@chia_command` framework
* Comments by @cursor
* Add parsing tests
* Fix tests
* Add context for AddressParamType
* Comments by @cursor
* Port `chia wallet vcs` to `@chia_command` framework
* Add context for AddressParamType
* Remove old CLI test
* [LABS-189] Port `chia wallet did` to `@chia_command`
* Fix tests
* Fix tests
* Comments by @cursor
* Comments by @cursor
* Comments by @cursor
* Port `chia wallet nft` to `@chia_command`
* test coverage
* remove executable bit
* Bad merge
* little bit more test coverage
The wallet sync path holds WalletStateManager.lock for the duration of
sync. When _add_coin_states discovers a DataLayer launcher and no
DataLayer wallet exists yet, it called
get_dl_wallet(create_if_not_found=True), which re-acquires the same
non-reentrant asyncio.Lock, deadlocking the sync task. Create the
wallet directly at the sync call site instead, leaving get_dl_wallet
unchanged for RPC callers.
Regression from #20320 (baf8bd05c0).
* [LABS-480] Remove interested_ph_cache and initialize coin cache in init
* [LABS-481] Remove unnecessary `server` field from WSM
* [LABS-486] Remove useless `add_transaction` from WSM
* [LABS-488] Extract signing functionality to its own module
* Test coverage