Move the per-transaction position computation from CustomAppend into
DB::WriteTxs, so the DB layer receives the whole block instead of a
pre-built vector of positions. This is a non-functional refactor.
The hashed txindex entries cannot be found by older nodes. Record sync
progress under a new locator key so a downgraded node will not rely on
entries indexed by upgraded nodes, and instead continue syncing from the
legacy locator.
`Range` iterators point to the view, so iterators saved from temporary views dangle.
Point them to the underlying container, use the range's getter for element access, and remove `operator->`, which returned elements by value and could not support arrow expressions.
Co-authored-by: Daniel Pfeifer <daniel@pfeifer-mail.de>
Document the `mutated` output flag on the `ComputeMerkleRoot` declaration and explain in the inner loop why the mutation check runs at every tree level even after a duplicate is found.
Add a direct regression test for the duplicate-subtree construction described in the code comments for CVE-2012-2459: `[1,2,3,4,5,6]` and `[1,2,3,4,5,6,5,6]` produce the same root.
The test also verifies that mutation detection checks equal pairs before the final pair of a tree level.
The existing `merkle_test` already exercises this behavior indirectly through random duplications and old-vs-new comparisons.
The new test pins it down explicitly through the `ComputeMerkleRoot` API.
Both would fail under a refactor that stops the outer reduction once mutation is detected.
Co-authored-by: Hodlinator <172445034+hodlinator@users.noreply.github.com>
d055a3ab10 test: verify disallowed RPC clients are rejected upon `accept()` (winterrdog)
Pull request description:
this is a follow-up PR from a suggestion in this [comment](https://github.com/bitcoin/bitcoin/pull/35592#pullrequestreview-4823271031)
it adds a unit test that confirms that clients not permitted by
`-rpcallowip` are rejected immediately after `accept()`, before any
request bytes are read from the socket
specifically, the test checks that: no request is ever dispatched to the
server's request handler, the connection is closed without any response
to the client, no `HTTPRemoteClient` is ever registered for it, and the
client's request bytes are left completely unread in the socket's
receive buffer
ACKs for top commit:
achow101:
ACK d055a3ab10
pinheadmz:
ACK d055a3ab10
w0xlt:
reACK d055a3ab10
Tree-SHA512: 5b99eb8a795e302333549f3ea5c76118c380402e2ebac8256db0a06ac0b4c0739b0b94471aa2f278214e65574e84f321b416e2543b361bf4fd8eb6854768377a
PSBTv2 permits outputs to be added before inputs.
`UpdatePSBTOutput()` traverses output scripts with a signature creator for input index 0, so ECDSA signing or a miniscript relative-timelock check can access a missing input and abort.
Construct a standalone transaction with one input for metadata traversal.
Continue taking the output from the unsigned PSBT transaction so scripts and key origins remain associated with the correct output while traversal no longer depends on the PSBT input list.
`MutableTransactionSignatureCreator` continues to require callers to provide a valid input index.
Co-authored-by: Ava Chow <github@achow101.com>
Add a relative-timelock case that consults the transaction checker during output metadata traversal.
Use a valid input because the pre-fix missing-input path is only reliably diagnosed under a sanitizer.
Document the scripts and key origins collected for common output descriptors before changing zero-input handling.
Use a valid input for ECDSA-backed outputs because their zero-input paths currently abort.
f280f5eb47 wallet: rpc: deprecate removeprunedfunds (David Gumberg)
e5b7785447 test: wallet: resend: avoid internal behavior via removeprunedfunds (David Gumberg)
Pull request description:
Originally added in https://github.com/bitcoin/bitcoin/pull/7558 as a companion to `importprunedfunds`, this RPC has no known helpful use while being both dangerous and a maintenance burden.
Despite what the name says, it allows the deletion of arbitrary transactions, and `importprunedfunds` does not allow the importing of transactions not belonging to the user, and `listtransactions` does not list transactions not belonging to the wallet, so this RPC can only be used to delete transactions actually belonging to the wallet, and in the unlikely event that transactions not belonging to the wallet are present, they cause no harm except for occupying a few bytes on the users disk.
ACKs for top commit:
achow101:
ACK f280f5eb47
polespinasa:
ACK f280f5eb47
pablomartin4btc:
reACK f280f5eb47
Tree-SHA512: ed9c30c50be514d637999b4c8f3fa9b9b1446a5553e3974703638b45d8f55f1291f5cfb82dd2ead6d0743e424e9e3b1edbd56fc04dae3dcdee4d175e2a1ce061
4e8c4bc794 test: classify SOCKS5 peers via getpeerinfo addrbind (Henry Romp)
Pull request description:
p2p_private_broadcast.py classifies each SOCKS5 connection by scanning the node's debug log for `trying v. connection (...) to <addr>:<port>`, then attaches a fake peer for that type. The helper returned the first match in the whole log, so when a feeler selected a clearnet address that private broadcast had used earlier in the run (in the CI failure, `[50::1]:8333`, about 10 seconds apart), the feeler was labelled private-broadcast, was given the `NoRelayP2PInterface`, and disconnected as a feeler rather than with the expected "connected in vain" message.
Instead of relying on the debug log, identify the connection via the SOCKS5 proxy client socket's source address, which equals the node's `addrbind` for that peer, and read `connection_type` from getpeerinfo. The proxy replies to the SOCKS5 request before invoking `destinations_factory`, so the node has already registered the peer by the time classification runs. This also stops treating debug.log contents as a stable test interface. Dropping the log scrape removes a full re-read of debug.log per SOCKS5 connection; `p2p_private_broadcast.py` goes from ~23s to ~14s locally.
Fixes#35843
Tested with:
`build/test/functional/test_runner.py p2p_private_broadcast.py p2p_private_broadcast_retry_v1.py --timeout-factor=2`, and against the forced-feeler repro from the issue, which no longer mislabels the feeler.
ACKs for top commit:
jeanpablojp:
tACK 4e8c4bc794
andrewtoth:
ACK 4e8c4bc794
mzumsande:
Code Review ACK 4e8c4bc794
Tree-SHA512: ce2db418787d7ecf518bd49b37d7d664748fee5991a2924522dfaf42b27d90ca001caa0611011310636b453d3aada1061d086f20bb866eb66605645935f55c74
While this is usually used where the types are not enforced strictly,
adding the description is both useful to the developer implementing a
client and for potentially using the openrpc output as a basis for
documentation.
```diff
diff interim_dump.json new_dump.json
465c465,466
< }
---
> },
> "description": "A key-value pair. The key (string) is the bitcoin address,\nthe value (float or string) is the amount in BTC"
777c778,779
< }
---
> },
> "description": "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in BTC"
903c905,906
< }
---
> },
> "description": "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in BTC"
12915c12918,12919
< }
---
> },
> "description": "A key-value pair. The key (string) is the bitcoin address,\nthe value (float or string) is the amount in BTC"
13692c13696,13697
< }
---
> },
> "description": "A key-value pair. The key (string) is the bitcoin address,\nthe value (float or string) is the amount in BTC"
14002c14007,14008
< }
---
> },
> "description": "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in BTC"
14254c14260,14261
< }
---
> },
> "description": "The bitcoin address is the key, the numeric amount (can be string) in BTC is the value"
16046c16053,16054
< }
---
> },
> "description": "A key-value pair. The key (string) is the bitcoin address,\nthe value (float or string) is the amount in BTC"
```
Instead of filling them in with empty object and array args be a bit
more friendly to the consumer by giving type hints, while retaining type
flexibility:
Keep the empty object, but fill the array with the hinted at types by
recursing through them. Add an additional argument to the openrpc
functions (`in_skip_type_check`) to keep track of when a loosely typed
argument is under evaluation. Note that the array is evaluated stricter
than before: If it contains items, they need to match the nested
objects.
The change from oneOf->anyOf and removing additionalProperties: false
should still convey to the reader that this is a loosely typed object
and prevent collisions between these loosely-typed schemas.
This was initially motivated by testing the dump of the schema against
open-rpc-generator, which crashed with:
```
open-rpc-generator generate -t client -l rust -n bitcoin_client -d ./openrpc.gen.json -o ./generated
There was error at generator runtime:
TypeError: Cannot convert undefined or null to object
```
The changes here fix this crash, but I think creating a friendlier
output is helpful on its own.
This patch changes the schema exported from CreateTxDoc as follows:
```diff
diff -U6 <(jq '.methods[] | select(.name == "createrawtransaction")' dump.json) \
<(jq '.methods[] | select(.name == "createrawtransaction")' new_dump.json)
@@ -37,13 +37,43 @@
{
"name": "outputs",
"required": true,
"schema": {
"oneOf": [
{
- "type": "array"
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "object",
+ "additionalProperties": {
+ "oneOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ }
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "string",
+ "pattern": "^[0-9a-fA-F]+$",
+ "description": "A key-value pair. The key must be \"data\", the value is hex-encoded data that becomes a part of an OP_RETURN output"
+ }
+ },
+ "required": [
+ "data"
+ ]
+ }
+ ]
+ }
},
{
"type": "object"
}
]
},
```
e2bf51543a build, msvc: Disable default features of the `sqlite3` package (Hennadii Stepanov)
a31425610a build, msvc: Update vcpkg manifest baseline (Hennadii Stepanov)
Pull request description:
This change updates the vcpkg manifest baseline from the ["2025.08.27 Release"](https://github.com/microsoft/vcpkg/releases/tag/2025.08.27) to the ["2026-07-29 Release"](https://github.com/microsoft/vcpkg/releases/tag/2026.07.29), with the following package changes:
- boost: 1.88.0 --> 1.91.0
- sqlite3: 3.50.4 --> 3.53.4
- zeromq: 4.3.5#2 --> 4.3.5#3
- qtbase: 6.9.1 --> 6.11.1#1
- qttools: 6.9.1 --> 6.11.1
- libqrencode: 4.1.1#2 --> 4.1.1#3
The previous update was made in https://github.com/bitcoin/bitcoin/pull/33408.
---
Additionally, the default features of the `sqlite3` package have been disabled. See the commit message for more details.
ACKs for top commit:
fanquake:
ACK e2bf51543a
Tree-SHA512: dc486afa3e4900ac44c2e51d4aaf1aa8bb4542cb8ef1e0ebb6444a171ea31171564e35f1fcc28469c2890437f23879d4468695033b1583e40709e0f717731ee7
OriginPubkeyProvider::GetPubKey() derives its sub-provider straight into
the output provider and then prepends its origin to the entry it finds
there. The sub-providers insert with emplace(), so if the same key was
already expanded by another key expression the insert is a no-op and the
entry that gets the origin prepended is the one that already has it.
musig() is where this shows up, because it expands its participants into
the provider it is given, so two musig() expressions in the same key
expression list end up expanding a shared participant twice. A
participant origin of m/86h/1h/0h then comes out as
m/86h/1h/0h/86h/1h/0h in the input and output taproot BIP32 derivation
maps of a PSBT, so a signer that follows them derives the wrong key.
When the two expressions declare different origins for the participant,
the fingerprint of one is combined with the path of both.
Derive into a temporary provider and apply the origin there. Merge the
rest into the output, then insert the clean origin separately. This
avoids stacking paths and makes an explicit origin replace an implicit
one already present for the same key.
The miniscript duplicate key check compares two key expressions by
deriving each of them at index 0, and it did that with an empty signing
provider. Any expression with a hardened step could therefore not be
derived and came back empty, so two of them compared equal and the
descriptor was rejected with "contains duplicate public keys" even
though the keys were different. musig() makes this easy to run into,
since one participant on a hardened path keeps the whole aggregate key
from resolving, but plain key expressions are affected just the same.
Derive with the signing provider that is filled while parsing, or with
the one we are inferring from, since that is where the private keys for
the hardened steps are. If both keys still cannot be derived, compare
the key expressions instead, so that two different expressions are not
mistaken for one another.
A duplicate written two different ways can still be missed if either
spelling cannot be derived. The private key that is missing for the
comparison is also needed to expand the descriptor, so deriveaddresses
and importdescriptors both refuse it. Once the needed private keys are
available, both expressions resolve and the duplicate is caught again.
156f2c6c49 kernel: add `btck_set_mock_time` for testing time-dependent paths (stringintech)
Pull request description:
Some kernel paths read the current time (e.g. header validation's future-time check, and the `btck_SynchronizationState` carried by `btck_NotifyBlockTip` / `btck_NotifyHeaderTip` callbacks), which makes them awkward to exercise deterministically in tests. This PR exposes `btck_set_mock_time` as a wrapper over the existing `SetMockTime`, mirroring what the node has via the `setmocktime` RPC.
Prior IRC discussion: https://gnusha.org/bitcoin-kernel/2026-06-04.log
ACKs for top commit:
josibake:
ACK 156f2c6c49
purpleKarrot:
ACK 156f2c6c49
achow101:
ACK 156f2c6c49
janb84:
ACK 156f2c6c49
sedited:
ACK 156f2c6c49
Tree-SHA512: da61faab0477fdc0de0e16420f228923d57733c42ef91b8947ef576e43abc8a1b78b9c1fa2ae60550ed5fd545f4d1c714dad0191d4442d4063112031ef7d1027
4ca182ca40 doc: clarify alternate_wtxids is empty when only one witness variant (pablomartin4btc)
fa48b5d28e test: assert listsinceblock "removed" reports current canonical wtxid (pablomartin4btc)
9b96ee1288 wallet, test: add unit test for variant txid validation in CWalletTx deserializer (pablomartin4btc)
9de6543cb5 wallet: post-#35501 cleanup in CWalletTx (pablomartin4btc)
Pull request description:
Follow-up cleanups and clarifications after #35501 was merged.
Commit breakdown:
1. _post-[#35501](https://github.com/bitcoin/bitcoin/pull/35501) cleanup in_ `CWalletTx`
- Rename `arg_state` → `new_state` in `Update()` for consistency
- Simplify `RecomputeCanonical()` using `std::ranges::min_element` with a projection lambda (14 lines → 3 lines)
- Add variant txid validation in the `CWalletTx` deserialise constructor: throws `std::runtime_error` if any variant's txid doesn't match the canonical txid deserialized from the stream
- Move `Init()` to `private` and extend it to clear `m_txs` and reset `m_canonical_wtxid`, so a full re-deserialise via `Unserialize()` starts from a clean state
All [suggested](https://github.com/bitcoin/bitcoin/pull/35501#pullrequestreview-4854519083) by ajtowns.
2. _add unit test for variant txid validation in_ `CWalletTx` _deserializer_
3. _assert_ `listsinceblock` "removed" _reports current canonical wtxid_
Documents that removed entries reflect the wallet's current `CWalletTx` state, not a snapshot of the detached block. A future followup could improve this (requires per-block tracking of which witness variant was included).
[Suggested](https://github.com/bitcoin/bitcoin/pull/35501#discussion_r3632044472) by w0xlt.
4. _clarify_ `alternate_wtxids` _is empty when only one witness variant_
[Suggested](https://github.com/bitcoin/bitcoin/pull/35501#discussion_r3632113003) by polespinasa.
ACKs for top commit:
jeanpablojp:
re-ACK 4ca182ca40
achow101:
ACK 4ca182ca40
polespinasa:
ACK 4ca182ca40
Tree-SHA512: 64eadeb11372d904c79edbfd264c4d8dc1b4fe4ce5e3acc301bfeba9e556efb5dce2c684f0e58c7b74e2c687cc7dd77389970662b3fd629ed034a97bcfdfb71c
99497b38f6 cmake: Unconditionally add .rc files to sources (Daniel Pfeifer)
654a4cf5e6 cmake: Use builtin support for .manifest files (Daniel Pfeifer)
8f695379f3 cmake: Unconditionally set WIN32_EXECUTABLE target property (Daniel Pfeifer)
Pull request description:
Remove some redundant logic from the CMake code:
* The `WIN32_EXECUTABLE` target property only has an effect when building for `WIN32`. Checking `WIN32` is redundant.
* CMake has builtin support for `.rc` and `.manifest` files. Both may be added to sources unconditionally. They only have an effect when building for `WIN32`.
ACKs for top commit:
151henry151:
re-ACK 99497b38f6
hebasto:
ACK 99497b38f6.
Tree-SHA512: 3d1a6a86feefff58e06d2e692809349f8e5d50e7ded821da775420a6bbfd54baf7dfdba957a0c7e9fc61b87455addfe67a1cc48dbfe4a8e3055f69bb027109c0
When bitcoin-gui is built against OpenBSD's system Qt packages (which
have GLib support), shutdown emits "GLib-CRITICAL **:
g_main_context_pop_thread_default: assertion 'stack != NULL' failed"
messages on stderr, which the test framework treats as a failure.
Set `QT_NO_GLIB=1` so Qt falls back to its poll-based event dispatcher,
which avoids the GLib thread-default context entirely.
e8691056c0 test: Unroll `&&` conditions in macros (rustaceanrob)
Pull request description:
Picked from #35713. Given that I think this is a strict debugging improvement, I opened as a separate pull:
Using `&&` in `BOOST_CHECK` is problematic as failures will not indicate which condition failed. By unrolling these checks, the user knows exactly which expression is the failing case.
As an example, here is a line that would be particularly hard to debug if it failed:
```
src/test/net_tests.cpp
BOOST_CHECK((*ret)[1] && (*ret)[1]->m_type == "headers" && std::ranges::equal((*ret)[1]->m_recv, MakeByteSpan(msg_data_2)));
```
If any one of these conditions fail, the whole expression fails, with no values printed or indication as to which condition failed.
This is also required when using test macros that support value decomposition, which requires `&&` and `||` are `delete`. Examples include `BOOST_TEST`, doctest, Catch2, etc.
ref: https://catch2-temp.readthedocs.io/en/latest/assertions.html#other-limitations
ref: https://fekir.info/post/decomposing-an-expression/
ACKs for top commit:
maflcko:
re-ACK e8691056c0🌽
ismaelsadeeq:
reACK e8691056c0
sedited:
ACK e8691056c0
Tree-SHA512: 9eb74cecd47ee4fdc3f53beb7d50d5056d543303d023c68b8d47cbe52d37f1156488c8b943faf68dd52c192c43626037bbf08172e7cc24753e0f6070db6e3ab2
0390338692 test: check MuSig import private key warnings (woltx)
5e62fbf09c wallet: check descriptor private key completeness on import (woltx)
cd8d01bf47 descriptors: require complete MuSig private keys (woltx)
Pull request description:
`importdescriptors` currently checks whether all private keys are present by expanding the descriptor and verifying that every expanded origin pubkey has a private key.
This is wrong for MuSig descriptors because expansion includes the synthetic aggregate pubkey. There is no individual private key for that aggregate pubkey, so importing a fully private MuSig descriptor such as `rawtr(musig(A_priv,B_priv))` incorrectly returns:
```
Not all private keys provided. Some wallet functionality may return unexpected errors
```
This PR fixes the issue by making descriptor private-key completeness account for MuSig participant keys, and by having `importdescriptors` use `Descriptor::HavePrivateKeys()` instead of duplicating its own manual completeness check.
The functional test covers both cases:
- `rawtr(musig(A_priv,B_priv))` imports without warnings.
- `rawtr(musig(A_priv,B_pub))` still warns that not all private keys were provided.
ACKs for top commit:
achow101:
ACK 0390338692
theStack:
Code-review ACK 0390338692
Tree-SHA512: a55fb084c63f725a0991556acdfb822f3a5a669f745a00b9f0bf0996b639986cdf5e2be2e8d3d0a2ee3fe5744355f20b40df576601792ef3db698e606629ad52
6304789a18 Wallet, refactor: Remove orphaned EraseWatchOnly function (vicjuma)
Pull request description:
**Summary**
This is a follow-up PR for #28710 by achow101 where `bool LegacyScriptPubKeyMan::RemoveWatchOnly(const CScript &dest)` which was the only remaining caller for the function `EraseWatchOnly` was removed. This PR is just a cleanup to remove the orphaned function.
ACKs for top commit:
jeanpablojp:
ACK 6304789a18
pablomartin4btc:
ACK 6304789a18
Tree-SHA512: 48546b3fa9155d6f9549ce8e77d9fe34cbfc738e6814ac9cd256b85668d0fa325e51404f962d491c6d4989baefd03815c680578bfcc73741cf9c4e65b141f92e
a7b0b5084a doc: fix dead link in txrequest.h (cyb3ralbert)
Pull request description:
The link is dead. Both the file and the whole `blockchainbib/` section return 404. The host certificate expired on 2026-08-07, so the site looks abandoned rather than moved.
It was a local copy of Miller et al. 2015, *Discovering Bitcoin's Public Topology and Influential Nodes*. The [`blockchainbib` bibliography](https://github.com/kernoelpanic/blockchainbib) that distributed it points to `cs.umd.edu/projects/coinscope/coinscope.pdf`, where the original is still accessible.
No other occurrences in the tree.
ACKs for top commit:
l0rinc:
ACK a7b0b5084a
sedited:
ACK a7b0b5084a
Tree-SHA512: 518b3c703417f544c7aa96d94db4cb92a669c0223eaefecd9847378061273258c45939376e47d616651e78eb090110a30ef9dc786506e1ae5d4ac7cd20cd0dcd
When there is only one known witness variant for a transaction,
alternate_wtxids is an empty array, analogous to walletconflicts and
mempoolconflicts.
Suggested-by: polespinasa
When a block is detached, listsinceblock "removed" entries reflect the
wallet's current CWalletTx rather than a snapshot of the variant that
was actually in the detached block. Add assertions to make this
behaviour explicit. A future followup could improve listsinceblock to
track and report the specific witness variant that was in the
disconnected block (requires per-block tracking of which witness variant was included).
Co-authored-by: w0xlt <94266259+w0xlt@users.noreply.github.com>
- Rename arg_state to new_state in Update() declaration to match implementation
- Replace RecomputeCanonical manual loop with std::ranges::min_element
- Add variant txid validation in the deserialize constructor
- Make Init() private and have it clear all members including m_txs
Co-authored-by: Anthony Towns <aj@erisian.com.au>
At the current builtin baseline, the only default feature is `json1`,
which enables SQLite's JSON functionality. Bitcoin Core does not use
it, and the depends build explicitly omits it with `-DSQLITE_OMIT_JSON`.
ea59f17220 test: cover v0.14.3 wallet migration (w0xlt)
18b8afd093 test: support v0.14.x in dumb_sync_blocks (w0xlt)
Pull request description:
This PR adds test coverage for migrating legacy Bitcoin Core wallets from v0.14.3 (released in 2017) to the descriptor wallet format. The test validates that users can safely upgrade their wallets while preserving all funds, transaction history, and addresses.
This test was originally developed on top of #32977, as it was requested in reviews.
However, since it also increases test coverage, it can be merged independently.
The test covers two wallet migration scenarios:
* Non-HD Wallet Migration - Tests migration of non-HD wallets (created with `-usehd=0`)
* Single Chain HD Wallet Migration - Tests migration of HD wallets from v0.14.3 (`VERSION_HD_BASE`)
The node v0.14.3 cannot be synced using the normal test framework helpers because it does not have the `syncwithvalidationinterfacequeue` RPC, so the test uses `dumb_sync_blocks` to submit blocks from the ancient node to the modern node before migrating the wallet.
Each scenario uses its own dedicated old/new node pair, which keeps the setup isolated and makes this testing best managed in a separate file rather than in the existing migration test files.
On the Windows cross-built CI job, this test is excluded from the main functional test runner and re-run sequentially in an ASCII-only tmpdir, because the v0.14.3 binary cannot handle non-ASCII characters in the temporary directory path.
ACKs for top commit:
furszy:
utACK ea59f17220
pablomartin4btc:
reACK ea59f17220
Tree-SHA512: 35ef7173e10fe52f20db0d6d1f144c5a1343ff406d294ab7d0e346d79546797f3f145f2893d99bb7b57d71aa14580690ab80eae148ec94b91498eb0938b2d15e
089c883c55 test: Add coverage for listreceivedby* excluding "send" addresses (pablomartin4btc)
873c054805 wallet: Exclude non-owned addresses from listreceivedby* (pablomartin4btc)
Pull request description:
Fixes#16159.
`listreceivedbyaddress`/`listreceivedbylabel` with `include_empty=true` walk the entire address book and return every entry that has no matching `mapTally` record — including addresses with a "send" purpose (foreign addresses that got a label via `setlabel`, the GUI, or `addmultisigaddress`) that this wallet never received funds to and doesn't own.
This excludes those via `IsMine()` rather than the address book's `purpose` field, since `purpose` is set inconsistently across several code paths and `IsMine()` is the same check `mapTally` itself is already built from.
Picks up prior work by kouloumos in #25973 and BrandonOdiwuor in #30972, both closed for
inactivity:
- [#25973](https://github.com/bitcoin/bitcoin/pull/25973) filtered on `purpose == "send"` directly. ryanofsky pointed out purpose "is set pretty haphazardly in code" and [suggested](https://github.com/bitcoin/bitcoin/pull/25973#discussion_r1269477246) `IsMine()` instead.
- [#30972](https://github.com/bitcoin/bitcoin/pull/30972) implemented that, then furszy pointed out `IsMine()` only needs to run for addresses missing from `mapTally`, not every one. rkrux further suggested dropping the redundant re-lock in favor of `EXCLUSIVE_LOCKS_REQUIRED` directly on the lambda — matching the existing pattern in `wallet/interfaces.cpp` — and simplifying the branching.
This PR carries that final approach forward on current master. The regression test is a small, standalone addition rather than reviving the test-file "split into subtests" refactor from the earlier PRs, which achow101 [flagged](https://github.com/bitcoin/bitcoin/pull/30972#issuecomment-3688186614) on #30972 as unrelated stylistic churn.
ACKs for top commit:
polespinasa:
lgtm re-ACK 089c883c55
jeanpablojp:
ACK 089c883c55
achow101:
ACK 089c883c55
Tree-SHA512: d45488c93b9294258faaab5d1891ca5e8c4b8d0d4feb298403c7c3f20d6aa08989d548cddd25ccd47a1ed969e4a309ee68ad1541c6121fed39ed534c78c256e7