diff --git a/configure.ac b/configure.ac index 8f1c84605788..cf82907c421e 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ AC_PREREQ([2.69]) dnl Don't forget to push a corresponding tag when updating any of _CLIENT_VERSION_* numbers define(_CLIENT_VERSION_MAJOR, 23) define(_CLIENT_VERSION_MINOR, 1) -define(_CLIENT_VERSION_BUILD, 2) +define(_CLIENT_VERSION_BUILD, 3) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2026) define(_COPYRIGHT_HOLDERS,[The %s developers]) diff --git a/contrib/flatpak/org.dash.dash-core.metainfo.xml b/contrib/flatpak/org.dash.dash-core.metainfo.xml index 56961fd0486e..0c55086cbe41 100644 --- a/contrib/flatpak/org.dash.dash-core.metainfo.xml +++ b/contrib/flatpak/org.dash.dash-core.metainfo.xml @@ -21,6 +21,7 @@ + diff --git a/doc/man/dash-cli.1 b/doc/man/dash-cli.1 index 7100c3010e7f..9cc231e532ee 100644 --- a/doc/man/dash-cli.1 +++ b/doc/man/dash-cli.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH DASH-CLI "1" "March 2026" "dash-cli v23.1.2" "User Commands" +.TH DASH-CLI "1" "May 2026" "dash-cli v23.1.3" "User Commands" .SH NAME -dash-cli \- manual page for dash-cli v23.1.2 +dash-cli \- manual page for dash-cli v23.1.3 .SH SYNOPSIS .B dash-cli [\fI\,options\/\fR] \fI\, \/\fR[\fI\,params\/\fR] \fI\,Send command to Dash Core\/\fR @@ -15,7 +15,7 @@ dash-cli \- manual page for dash-cli v23.1.2 .B dash-cli [\fI\,options\/\fR] \fI\,help Get help for a command\/\fR .SH DESCRIPTION -Dash Core RPC client version v23.1.2 +Dash Core RPC client version v23.1.3 .SH OPTIONS .HP \-? diff --git a/doc/man/dash-qt.1 b/doc/man/dash-qt.1 index 0616bce70872..54655be0285a 100644 --- a/doc/man/dash-qt.1 +++ b/doc/man/dash-qt.1 @@ -1,12 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH DASH-QT "1" "March 2026" "dash-qt v23.1.2" "User Commands" +.TH DASH-QT "1" "May 2026" "dash-qt v23.1.3" "User Commands" .SH NAME -dash-qt \- manual page for dash-qt v23.1.2 +dash-qt \- manual page for dash-qt v23.1.3 .SH SYNOPSIS .B dash-qt [\fI\,command-line options\/\fR] [\fI\,URI\/\fR] .SH DESCRIPTION -Dash Core version v23.1.2 +Dash Core version v23.1.3 .PP Optional URI is a Dash address in BIP21 URI format. .SH OPTIONS diff --git a/doc/man/dash-tx.1 b/doc/man/dash-tx.1 index 3c151d797021..8f5973ad28d5 100644 --- a/doc/man/dash-tx.1 +++ b/doc/man/dash-tx.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH DASH-TX "1" "March 2026" "dash-tx v23.1.2" "User Commands" +.TH DASH-TX "1" "May 2026" "dash-tx v23.1.3" "User Commands" .SH NAME -dash-tx \- manual page for dash-tx v23.1.2 +dash-tx \- manual page for dash-tx v23.1.3 .SH SYNOPSIS .B dash-tx [\fI\,options\/\fR] \fI\, \/\fR[\fI\,commands\/\fR] \fI\,Update hex-encoded dash transaction\/\fR @@ -9,7 +9,7 @@ dash-tx \- manual page for dash-tx v23.1.2 .B dash-tx [\fI\,options\/\fR] \fI\,-create \/\fR[\fI\,commands\/\fR] \fI\,Create hex-encoded dash transaction\/\fR .SH DESCRIPTION -Dash Core dash\-tx utility version v23.1.2 +Dash Core dash\-tx utility version v23.1.3 .SH OPTIONS .HP \-? diff --git a/doc/man/dash-util.1 b/doc/man/dash-util.1 index 239e3896be7e..f65c79eae172 100644 --- a/doc/man/dash-util.1 +++ b/doc/man/dash-util.1 @@ -1,12 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH DASH-UTIL "1" "March 2026" "dash-util v23.1.2" "User Commands" +.TH DASH-UTIL "1" "May 2026" "dash-util v23.1.3" "User Commands" .SH NAME -dash-util \- manual page for dash-util v23.1.2 +dash-util \- manual page for dash-util v23.1.3 .SH SYNOPSIS .B dash-util [\fI\,options\/\fR] [\fI\,commands\/\fR] \fI\,Do stuff\/\fR .SH DESCRIPTION -Dash Core dash\-util utility version v23.1.2 +Dash Core dash\-util utility version v23.1.3 .SH OPTIONS .HP \-? diff --git a/doc/man/dash-wallet.1 b/doc/man/dash-wallet.1 index 853f59ee8ae4..127882e90910 100644 --- a/doc/man/dash-wallet.1 +++ b/doc/man/dash-wallet.1 @@ -1,9 +1,9 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH DASH-WALLET "1" "March 2026" "dash-wallet v23.1.2" "User Commands" +.TH DASH-WALLET "1" "May 2026" "dash-wallet v23.1.3" "User Commands" .SH NAME -dash-wallet \- manual page for dash-wallet v23.1.2 +dash-wallet \- manual page for dash-wallet v23.1.3 .SH DESCRIPTION -Dash Core dash\-wallet version v23.1.2 +Dash Core dash\-wallet version v23.1.3 .PP dash\-wallet is an offline tool for creating and interacting with Dash Core wallet files. By default dash\-wallet will act on wallets in the default mainnet wallet directory in the datadir. diff --git a/doc/man/dashd.1 b/doc/man/dashd.1 index f7cdfeb6e61a..9f7b1d8cec1f 100644 --- a/doc/man/dashd.1 +++ b/doc/man/dashd.1 @@ -1,12 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH DASHD "1" "March 2026" "dashd v23.1.2" "User Commands" +.TH DASHD "1" "May 2026" "dashd v23.1.3" "User Commands" .SH NAME -dashd \- manual page for dashd v23.1.2 +dashd \- manual page for dashd v23.1.3 .SH SYNOPSIS .B dashd [\fI\,options\/\fR] \fI\,Start Dash Core\/\fR .SH DESCRIPTION -Dash Core version v23.1.2 +Dash Core version v23.1.3 .SH OPTIONS .HP \-? diff --git a/doc/release-notes.md b/doc/release-notes.md index 6ec5ed5c8a41..9f1ff3363f48 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -1,6 +1,6 @@ -# Dash Core version v23.1.2 +# Dash Core version v23.1.3 -This is a new patch version release, bringing GUI improvements, new features, bugfixes, and performance optimizations. +This is a new patch version release, bringing GUI improvements and bugfixes. This release is **optional** for all nodes, although recommended. Please report bugs using the issue tracker at GitHub: @@ -27,38 +27,16 @@ require a reindex. ## GUI changes -- Introduced a framework for sourcing and applying data with dedicated feeds, used by the Masternode and Proposal list views for improved data flow and separation of concerns (dash#7146). -- Added a new "Proposal Information" widget to the Information tab with an interactive donut chart showing proposal budget allocation (dash#7159). -- Added distinct widgets for Dash-specific reporting in the Debug window, including dedicated Information and Network tabs (dash#7118). -- Added support for reporting `OP_RETURN` payloads as Data Transactions in the transaction list (dash#7144). -- Added Tahoe styled icons for macOS with runtime styling for each network type (mainnet, testnet, devnet, regtest), updated bundle icon, and added mask-based tray icon with generation scripts (dash#7180). -- Filter preferences in the masternode list are now persisted across sessions (dash#7148). -- Fixed overview page font double scaling, recalculated minimum width correctly, fixed `SERVICE` and `STATUS` column sorting, and fixed common types filtering in masternode list (dash#7147). -- Fixed `labelError` styling by moving it from `proposalcreate.ui` into `general.css` for consistency (dash#7145). -- Fixed banned masternodes incorrectly returning status=0 instead of their actual ban status (dash#7157). +- Restored the Send button when using an external signer (dash#7271). ## Bug Fixes -- Fixed MN update notifications where the old and new masternode lists were swapped, causing incorrect change detection (dash#7154). -- Reject identity elements in BLS deserialization and key generation to prevent invalid keys from being accepted (dash#7193). -- Fixed quorum labels not being correctly reseated when new quorum types are inserted (dash#7191). -- Skip collecting block txids during IBD to prevent unbounded memory growth in `ChainLockSigner` (dash#7208). -- Serialize `TrySignChainTip` to prevent concurrent signing races that could split signing shares across different block hashes (dash#7209). -- Properly skip evodb repair when reindexing to prevent unnecessary repair attempts (dash#7222). +- Kept relaying InstantSend lock inventory messages to non-masternode peers that request recovered signatures (dash#7293). +- Reverted an improper dual-way connection attempt avoidance change that could break recovered-signature handshakes, and added coverage for the symmetric `QSENDRECSIGS` handshake under spork 21 (dash#7289). +- Fixed intermittent incorrect `CheckQueue` logging for invalid blocks (dash#7312). +- Fixed `listaddressbalances` RPC help so the documented result matches returned address balances (dash#7279). -## Miscellaneous - -- Renamed `bitcoin-util` manpage and test references to `dash-util` (dash#7221). - -## Interfaces - -- Consolidated masternode counts into a single struct and exposed chainlock, InstantSend, credit pool, and quorum statistics through the node interface (dash#7160). - -## Performance Improvements - -- Replaced two heavy `HashMap` constructions with linear lookups in hot paths where the maps were rarely used, reducing overhead (dash#7176). - -# v23.1.2 Change log +# v23.1.3 Change log See detailed [set of changes][set-of-changes]. @@ -66,10 +44,8 @@ See detailed [set of changes][set-of-changes]. Thanks to everyone who directly contributed to this release: -- Kittywhiskers Van Gogh - Konstantin Akimov - PastaPastaPasta -- UdjinM6 As well as everyone that submitted issues, reviewed pull requests and helped debug the release candidates. @@ -78,6 +54,7 @@ debug the release candidates. These releases are considered obsolete. Old release notes can be found here: +- [v23.1.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.1.2.md) released Mar/12/2026 - [v23.1.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.1.0.md) released Feb/15/2026 - [v23.0.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.0.2.md) released Dec/4/2025 - [v23.0.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.0.0.md) released Nov/10/2025 @@ -92,4 +69,4 @@ These releases are considered obsolete. Old release notes can be found here: - [v21.0.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-21.0.0.md) released Jul/25/2024 - [v20.1.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-20.1.1.md) released April/3/2024 -[set-of-changes]: https://github.com/dashpay/dash/compare/v23.1.0...dashpay:v23.1.2 +[set-of-changes]: https://github.com/dashpay/dash/compare/v23.1.2...dashpay:v23.1.3 diff --git a/doc/release-notes/dash/release-notes-23.1.2.md b/doc/release-notes/dash/release-notes-23.1.2.md new file mode 100644 index 000000000000..6ec5ed5c8a41 --- /dev/null +++ b/doc/release-notes/dash/release-notes-23.1.2.md @@ -0,0 +1,95 @@ +# Dash Core version v23.1.2 + +This is a new patch version release, bringing GUI improvements, new features, bugfixes, and performance optimizations. +This release is **optional** for all nodes, although recommended. + +Please report bugs using the issue tracker at GitHub: + + + +# Upgrading and downgrading + +## How to Upgrade + +If you are running an older version, shut it down. Wait until it has completely +shut down (which might take a few minutes for older versions), then run the +installer (on Windows) or just copy over /Applications/Dash-Qt (on Mac) or +dashd/dash-qt (on Linux). + +## Downgrade warning + +### Downgrade to a version < v23.0.0 + +Downgrading to a version older than v23.0.0 is not supported, and will +require a reindex. + +# Release Notes + +## GUI changes + +- Introduced a framework for sourcing and applying data with dedicated feeds, used by the Masternode and Proposal list views for improved data flow and separation of concerns (dash#7146). +- Added a new "Proposal Information" widget to the Information tab with an interactive donut chart showing proposal budget allocation (dash#7159). +- Added distinct widgets for Dash-specific reporting in the Debug window, including dedicated Information and Network tabs (dash#7118). +- Added support for reporting `OP_RETURN` payloads as Data Transactions in the transaction list (dash#7144). +- Added Tahoe styled icons for macOS with runtime styling for each network type (mainnet, testnet, devnet, regtest), updated bundle icon, and added mask-based tray icon with generation scripts (dash#7180). +- Filter preferences in the masternode list are now persisted across sessions (dash#7148). +- Fixed overview page font double scaling, recalculated minimum width correctly, fixed `SERVICE` and `STATUS` column sorting, and fixed common types filtering in masternode list (dash#7147). +- Fixed `labelError` styling by moving it from `proposalcreate.ui` into `general.css` for consistency (dash#7145). +- Fixed banned masternodes incorrectly returning status=0 instead of their actual ban status (dash#7157). + +## Bug Fixes + +- Fixed MN update notifications where the old and new masternode lists were swapped, causing incorrect change detection (dash#7154). +- Reject identity elements in BLS deserialization and key generation to prevent invalid keys from being accepted (dash#7193). +- Fixed quorum labels not being correctly reseated when new quorum types are inserted (dash#7191). +- Skip collecting block txids during IBD to prevent unbounded memory growth in `ChainLockSigner` (dash#7208). +- Serialize `TrySignChainTip` to prevent concurrent signing races that could split signing shares across different block hashes (dash#7209). +- Properly skip evodb repair when reindexing to prevent unnecessary repair attempts (dash#7222). + +## Miscellaneous + +- Renamed `bitcoin-util` manpage and test references to `dash-util` (dash#7221). + +## Interfaces + +- Consolidated masternode counts into a single struct and exposed chainlock, InstantSend, credit pool, and quorum statistics through the node interface (dash#7160). + +## Performance Improvements + +- Replaced two heavy `HashMap` constructions with linear lookups in hot paths where the maps were rarely used, reducing overhead (dash#7176). + +# v23.1.2 Change log + +See detailed [set of changes][set-of-changes]. + +# Credits + +Thanks to everyone who directly contributed to this release: + +- Kittywhiskers Van Gogh +- Konstantin Akimov +- PastaPastaPasta +- UdjinM6 + +As well as everyone that submitted issues, reviewed pull requests and helped +debug the release candidates. + +# Older releases + +These releases are considered obsolete. Old release notes can be found here: + +- [v23.1.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.1.0.md) released Feb/15/2026 +- [v23.0.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.0.2.md) released Dec/4/2025 +- [v23.0.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.0.0.md) released Nov/10/2025 +- [v22.1.3](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-22.1.3.md) released Jul/15/2025 +- [v22.1.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-22.1.2.md) released Apr/15/2025 +- [v22.1.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-22.1.1.md) released Feb/17/2025 +- [v22.1.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-22.1.0.md) released Feb/10/2025 +- [v22.0.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-22.0.0.md) released Dec/12/2024 +- [v21.1.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-21.1.1.md) released Oct/22/2024 +- [v21.1.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-21.1.0.md) released Aug/8/2024 +- [v21.0.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-21.0.2.md) released Aug/1/2024 +- [v21.0.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-21.0.0.md) released Jul/25/2024 +- [v20.1.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-20.1.1.md) released April/3/2024 + +[set-of-changes]: https://github.com/dashpay/dash/compare/v23.1.0...dashpay:v23.1.2 diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 47caba4066c2..88afa4dd87ce 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -218,8 +218,8 @@ class CMainParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_V24].nFalloffCoeff = 5; // this corresponds to 10 periods consensus.vDeployments[Consensus::DEPLOYMENT_V24].useEHF = true; - consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000b567e2d53a06de194061"); // 2429859 - consensus.defaultAssumeValid = uint256S("0x00000000000000018fb7d55a2d7ab5f3d1369cf0d7eef25db727bf8c9ca7d4b2"); // 2429859 + consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000b9040746437784aaec47"); // 2471728 + consensus.defaultAssumeValid = uint256S("0x000000000000001a19ad7270422a00f86123ea94e0b295a3a796d6861bd7b032"); // 2471728 /** * The message start string is designed to be unlikely to occur in normal data. @@ -332,6 +332,7 @@ class CMainParams : public CChainParams { {2216986, uint256S("0x0000000000000010b1135dc743f27f6fc8a138c6420a9d963fc676f96c2048f4")}, {2361500, uint256S("0x0000000000000009ba1e8f47851d036bb618a4f6565eb3c32d1f647d450ff195")}, {2421800, uint256S("0x000000000000000718ed026ebd644a8b70b42d4cbd7b25304c066c9bf15f85b7")}, + {2471728, uint256S("0x000000000000001a19ad7270422a00f86123ea94e0b295a3a796d6861bd7b032")}, } }; @@ -339,12 +340,12 @@ class CMainParams : public CChainParams { // TODO to be specified in a future patch. }; - // getchaintxstats 17280 000000000000000718ed026ebd644a8b70b42d4cbd7b25304c066c9bf15f85b7 + // getchaintxstats 17280 000000000000001a19ad7270422a00f86123ea94e0b295a3a796d6861bd7b032 chainTxData = ChainTxData{ - 1770962602, // * UNIX timestamp of last known number of transactions (Block 2421800) - 64859329, // * total number of transactions between genesis and that timestamp + 1778832687, // * UNIX timestamp of last known number of transactions (Block 2471728) + 69379403, // * total number of transactions between genesis and that timestamp // (the tx=... number in the ChainStateFlushed debug.log lines) - 0.9523581589072819, // * estimated number of transactions per second after that timestamp + 0.1476929741159368, // * estimated number of transactions per second after that timestamp }; } }; diff --git a/src/llmq/utils.cpp b/src/llmq/utils.cpp index 8106d893fbeb..2a42e2c8350c 100644 --- a/src/llmq/utils.cpp +++ b/src/llmq/utils.cpp @@ -799,13 +799,28 @@ bool EnsureQuorumConnections(const Consensus::LLMQParams& llmqParams, CConnman& util_params.m_base_index->GetBlockHash().ToString()); Uint256HashSet connections; + Uint256HashSet relayMembers; if (isMember) { connections = GetQuorumConnections(llmqParams, sporkman, util_params, myProTxHash, /*onlyOutbound=*/true); + // If all-members-connected is enabled for this quorum type, leverage the full-mesh + // connections for low-latency recovered sig propagation by treating all members as + // relay members (instead of the ring-based subset). This ensures peers will send + // QSENDRECSIGS to each other across the full mesh and set m_wants_recsigs widely. + if (IsAllMembersConnectedEnabled(llmqParams.type, sporkman)) { + for (const auto& dmn : members) { + if (dmn->proTxHash != myProTxHash) { + relayMembers.emplace(dmn->proTxHash); + } + } + } else { + relayMembers = GetQuorumRelayMembers(llmqParams, util_params, myProTxHash, true); + } } else { auto cindexes = CalcDeterministicWatchConnections(llmqParams.type, util_params.m_base_index, members.size(), 1); for (auto idx : cindexes) { connections.emplace(members[idx]->proTxHash); } + relayMembers = connections; } if (!connections.empty()) { if (!connman.HasMasternodeQuorumNodes(llmqParams.type, util_params.m_base_index->GetBlockHash()) && @@ -824,7 +839,9 @@ bool EnsureQuorumConnections(const Consensus::LLMQParams& llmqParams, CConnman& LogPrint(BCLog::NET_NETCONN, debugMsg.c_str()); /* Continued */ } connman.SetMasternodeQuorumNodes(llmqParams.type, util_params.m_base_index->GetBlockHash(), connections); - connman.SetMasternodeQuorumRelayMembers(llmqParams.type, util_params.m_base_index->GetBlockHash(), connections); + } + if (!relayMembers.empty()) { + connman.SetMasternodeQuorumRelayMembers(llmqParams.type, util_params.m_base_index->GetBlockHash(), relayMembers); } return true; } diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 195514d49a88..eeff7d1f5d85 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1185,6 +1185,16 @@ static uint16_t GetHeadersLimit(const CNode& pfrom, bool compressed) return MAX_HEADERS_UNCOMPRESSED_RESULT; } +// Returns true when peer is a verified masternode that has opted in to receive recsigs. +// Such peers participate in the signing flow that populates creatingInstantSendLocks, so +// they can reconstruct an ISDLOCK locally from the recsig and don't need the ISDLOCK inv. +// Non-MN peers (e.g. nodes running with -watchquorums) also opt in to recsigs via +// QSENDRECSIGS but still need ISDLOCK invs because they don't run the signing flow. +static bool PeerReconstructsISLockFromRecsig(const CNode& pnode, const Peer& peer) +{ + return peer.m_wants_recsigs && !pnode.GetVerifiedProRegTxHash().IsNull(); +} + static void PushInv(Peer& peer, const CInv& inv) { auto inv_relay = peer.GetInvRelay(); @@ -1196,13 +1206,6 @@ static void PushInv(Peer& peer, const CInv& inv) return; } - // Skip ISDLOCK inv announcements for peers that want recsigs, as they can reconstruct - // the islock from the recsig - if (inv.type == MSG_ISDLOCK && peer.m_wants_recsigs) { - LogPrint(BCLog::NET, "%s -- skipping ISDLOCK inv (peer wants recsigs): %s peer=%d\n", __func__, inv.ToString(), peer.m_id); - return; - } - LOCK(inv_relay->m_tx_inventory_mutex); if (inv_relay->m_tx_inventory_known_filter.contains(inv.hash)) { LogPrint(BCLog::NET, "%s -- skipping known inv: %s peer=%d\n", __func__, inv.ToString(), peer.m_id); @@ -2518,6 +2521,11 @@ void PeerManagerImpl::RelayInvFiltered(const CInv& inv, const CTransaction& rela return; } } // LOCK(tx_relay->m_bloom_filter_mutex) + if (inv.type == MSG_ISDLOCK && PeerReconstructsISLockFromRecsig(*pnode, *peer)) { + LogPrint(BCLog::NET, "%s -- skipping ISDLOCK inv (peer wants recsigs): %s peer=%d\n", + __func__, inv.ToString(), peer->m_id); + return; + } PushInv(*peer, inv); }); } @@ -2543,6 +2551,11 @@ void PeerManagerImpl::RelayInvFiltered(const CInv& inv, const uint256& relatedTx return; } } // LOCK(tx_relay->m_bloom_filter_mutex) + if (inv.type == MSG_ISDLOCK && PeerReconstructsISLockFromRecsig(*pnode, *peer)) { + LogPrint(BCLog::NET, "%s -- skipping ISDLOCK inv (peer wants recsigs): %s peer=%d\n", + __func__, inv.ToString(), peer->m_id); + return; + } PushInv(*peer, inv); }); } @@ -6317,9 +6330,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (islock == nullptr) continue; uint256 isLockHash{::SerializeHash(*islock)}; tx_relay->m_tx_inventory_known_filter.insert(isLockHash); - // Skip ISDLOCK inv announcements for peers that want recsigs, as they can reconstruct - // the islock from the recsig - if (!peer->m_wants_recsigs) { + if (!PeerReconstructsISLockFromRecsig(*pto, *peer)) { queueAndMaybePushInv(CInv(MSG_ISDLOCK, isLockHash)); } } diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 8890cd10b6a1..c09783e2ab08 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -500,6 +500,80 @@ bool SendCoinsDialog::send(const QList& recipients, QString& return true; } +void SendCoinsDialog::presentPSBT(PartiallySignedTransaction& psbtx) +{ + // Serialize the PSBT + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str()); + QMessageBox msgBox; + msgBox.setText("Unsigned Transaction"); + msgBox.setInformativeText("The PSBT has been copied to the clipboard. You can also save it."); + msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard); + msgBox.setDefaultButton(QMessageBox::Discard); + switch (msgBox.exec()) { + case QMessageBox::Save: { + QString selectedFilter; + QString fileNameSuggestion = ""; + bool first = true; + for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) { + if (!first) { + fileNameSuggestion.append(" - "); + } + QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label; + QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount); + fileNameSuggestion.append(labelOrAddress + "-" + amount); + first = false; + } + fileNameSuggestion.append(".psbt"); + QString filename = GUIUtil::getSaveFileName(this, + tr("Save Transaction Data"), fileNameSuggestion, + //: Expanded name of the binary PSBT file format. See: BIP 174. + tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selectedFilter); + if (filename.isEmpty()) { + return; + } + std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary}; + out << ssTx.str(); + out.close(); + Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk", CClientUIInterface::MSG_INFORMATION); + break; + } + case QMessageBox::Discard: + break; + default: + assert(false); + } // msgBox.exec() +} + +bool SendCoinsDialog::signWithExternalSigner(PartiallySignedTransaction& psbtx, CMutableTransaction& mtx, bool& complete) { + TransactionError err; + try { + err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/true, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete); + } catch (const std::runtime_error& e) { + QMessageBox::critical(nullptr, tr("Sign failed"), e.what()); + return false; + } + if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) { + //: "External signer" means using devices such as hardware wallets. + QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found"); + return false; + } + if (err == TransactionError::EXTERNAL_SIGNER_FAILED) { + //: "External signer" means using devices such as hardware wallets. + QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure"); + return false; + } + if (err != TransactionError::OK) { + tfm::format(std::cerr, "Failed to sign PSBT"); + processSendCoinsReturn(WalletModel::TransactionCreationFailed); + return false; + } + // fillPSBT does not always properly finalize + complete = FinalizeAndExtractPSBT(psbtx, mtx); + return true; +} + void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked) { if(!model || !model->getOptionsModel()) @@ -510,7 +584,9 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked) assert(m_current_transaction); const QString confirmation = tr("Confirm send coins"); - auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, !model->wallet().privateKeysDisabled(), model->getOptionsModel()->getEnablePSBTControls(), this); + const bool enable_send{!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner()}; + const bool always_show_unsigned{model->getOptionsModel()->getEnablePSBTControls()}; + auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, this); confirmationDialog->setAttribute(Qt::WA_DeleteOnClose); // TODO: Replace QDialog::exec() with safer QDialog::show(). const auto retval = static_cast(confirmationDialog->exec()); @@ -523,102 +599,53 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked) bool send_failure = false; if (retval == QMessageBox::Save) { + // "Create Unsigned" clicked CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())}; PartiallySignedTransaction psbtx(mtx); bool complete = false; - // Always fill without signing first. This prevents an external signer - // from being called prematurely and is not expensive. - TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, nullptr, psbtx, complete); + // Fill without signing + TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete); assert(!complete); assert(err == TransactionError::OK); + + // Copy PSBT to clipboard and offer to save + presentPSBT(psbtx); + } else { + // "Send" clicked + assert(!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner()); + bool broadcast = true; if (model->wallet().hasExternalSigner()) { - try { - err = model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, nullptr, psbtx, complete); - } catch (const std::runtime_error& e) { - QMessageBox::critical(nullptr, tr("Sign failed"), e.what()); - send_failure = true; - return; - } - if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) { - //: "External signer" means using devices such as hardware wallets. - QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found"); - send_failure = true; - return; - } - if (err == TransactionError::EXTERNAL_SIGNER_FAILED) { - //: "External signer" means using devices such as hardware wallets. - QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure"); - send_failure = true; - return; - } - if (err != TransactionError::OK) { - tfm::format(std::cerr, "Failed to sign PSBT"); - processSendCoinsReturn(WalletModel::TransactionCreationFailed); - send_failure = true; - return; + CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())}; + PartiallySignedTransaction psbtx(mtx); + bool complete = false; + // Always fill without signing first. This prevents an external signer + // from being called prematurely and is not expensive. + TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete); + assert(!complete); + assert(err == TransactionError::OK); + send_failure = !signWithExternalSigner(psbtx, mtx, complete); + // Don't broadcast when user rejects it on the device or there's a failure: + broadcast = complete && !send_failure; + if (!send_failure) { + // A transaction signed with an external signer is not always complete, + // e.g. in a multisig wallet. + if (complete) { + // Prepare transaction for broadcast transaction if complete + const CTransactionRef tx = MakeTransactionRef(mtx); + m_current_transaction->setWtx(tx); + } else { + presentPSBT(psbtx); + } } - // fillPSBT does not always properly finalize - complete = FinalizeAndExtractPSBT(psbtx, mtx); } - // Broadcast transaction if complete (even with an external signer this - // is not always the case, e.g. in a multisig wallet). - if (complete) { - const CTransactionRef tx = MakeTransactionRef(mtx); - m_current_transaction->setWtx(tx); + // Broadcast the transaction, unless an external signer was used and it + // failed, or more signatures are needed. + if (broadcast) { + // now send the prepared transaction model->sendCoins(*m_current_transaction, m_coin_control->IsUsingCoinJoin()); - return; - } - - // Copy PSBT to clipboard and offer to save - assert(!complete); - // Serialize the PSBT - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << psbtx; - GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str()); - QMessageBox msgBox; - msgBox.setText("Unsigned Transaction"); - msgBox.setInformativeText("The PSBT has been copied to the clipboard. You can also save it."); - msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard); - msgBox.setDefaultButton(QMessageBox::Discard); - switch (msgBox.exec()) { - case QMessageBox::Save: { - QString selectedFilter; - QString fileNameSuggestion = ""; - bool first = true; - for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) { - if (!first) { - fileNameSuggestion.append(" - "); - } - QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label; - QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount); - fileNameSuggestion.append(labelOrAddress + "-" + amount); - first = false; - } - fileNameSuggestion.append(".psbt"); - QString filename = GUIUtil::getSaveFileName(this, - tr("Save Transaction Data"), fileNameSuggestion, - //: Expanded name of the binary PSBT file format. See: BIP 174. - tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selectedFilter); - if (filename.isEmpty()) { - return; - } - std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary}; - out << ssTx.str(); - out.close(); - Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk", CClientUIInterface::MSG_INFORMATION); - break; + Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash()); } - case QMessageBox::Discard: - break; - default: - assert(false); - } // msgBox.exec() - } else { - assert(!model->wallet().privateKeysDisabled()); - // now send the prepared transaction - model->sendCoins(*m_current_transaction, m_coin_control->IsUsingCoinJoin()); - Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash()); } if (!send_failure) { accept(); diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index d9d5857a9dcb..90e1eff6ce6e 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -72,6 +72,8 @@ public Q_SLOTS: bool fFeeMinimized{true}; bool fKeepChangeAddress; + // Copy PSBT to clipboard and offer to save it. + void presentPSBT(PartiallySignedTransaction& psbt); // Process WalletModel::SendCoinsReturn and generate a pair consisting // of a message and message flags for use in Q_EMIT message(). // Additional parameter msgArg can be used via .arg(msgArg). @@ -79,6 +81,15 @@ public Q_SLOTS: void minimizeFeeSection(bool fMinimize); // Format confirmation message bool PrepareSendText(QString& question_string, QString& informative_text, QString& detailed_text); + /* Sign PSBT using external signer. + * + * @param[in,out] psbtx the PSBT to sign + * @param[in,out] mtx needed to attempt to finalize + * @param[in,out] complete whether the PSBT is complete (a successfully signed multisig transaction may not be complete) + * + * @returns false if any failure occurred, which may include the user rejection of a transaction on the device. + */ + bool signWithExternalSigner(PartiallySignedTransaction& psbt, CMutableTransaction& mtx, bool& complete); void updateFeeMinimizedLabel(); void updateCoinControlState(); @@ -119,6 +130,8 @@ class SendConfirmationDialog : public QMessageBox public: SendConfirmationDialog(const QString &title, const QString &text, int secDelay = 0, QWidget *parent = nullptr); SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text = "", const QString& detailed_text = "", int secDelay = SEND_CONFIRM_DELAY, bool enable_send = true, bool always_show_unsigned = true, QWidget* parent = nullptr); + /* Returns QMessageBox::Cancel, QMessageBox::Yes when "Send" is + clicked and QMessageBox::Save when "Create Unsigned" is clicked. */ int exec() override; private Q_SLOTS: diff --git a/src/validation.cpp b/src/validation.cpp index 1ae451117ad9..e9641a545224 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2429,8 +2429,8 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // in multiple threads). Preallocate the vector size so a new allocation // doesn't invalidate pointers into the vector, and keep txsdata in scope // for as long as `control`. - CCheckQueueControl control(fScriptChecks && g_parallel_script_checks ? &scriptcheckqueue : nullptr); std::vector txsdata(block.vtx.size()); + CCheckQueueControl control(fScriptChecks && g_parallel_script_checks ? &scriptcheckqueue : nullptr); std::vector prevheights; CAmount nFees = 0; diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp index 51fd86088526..f5ae196b9daf 100644 --- a/src/wallet/rpc/coins.cpp +++ b/src/wallet/rpc/coins.cpp @@ -162,6 +162,55 @@ RPCHelpMan getreceivedbylabel() }; } +RPCHelpMan listaddressbalances() +{ + return RPCHelpMan{"listaddressbalances", + "\nLists addresses of this wallet and their balances\n", + { + {"minamount", RPCArg::Type::NUM, RPCArg::Default{0}, "Minimum balance in " + CURRENCY_UNIT + " an address should have to be shown in the list"}, + }, + RPCResult{ + RPCResult::Type::OBJ_DYN, "", "Balances of addresses", + { + {RPCResult::Type::STR_AMOUNT, "address", "the amount in " + CURRENCY_UNIT}, + } + }, + RPCExamples{ + HelpExampleCli("listaddressbalances", "") + + HelpExampleCli("listaddressbalances", "10") + + HelpExampleRpc("listaddressbalances", "") + + HelpExampleRpc("listaddressbalances", "10") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + const std::shared_ptr pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return UniValue::VNULL; + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK(pwallet->cs_wallet); + + CAmount nMinAmount = 0; + if (!request.params[0].isNull()) + nMinAmount = AmountFromValue(request.params[0]); + + if (nMinAmount < 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); + + UniValue jsonBalances(UniValue::VOBJ); + std::map balances = GetAddressBalances(*pwallet); + for (auto& balance : balances) + if (balance.second >= nMinAmount) + jsonBalances.pushKV(EncodeDestination(balance.first), ValueFromAmount(balance.second)); + + return jsonBalances; +}, + }; +} + + RPCHelpMan getbalance() { return RPCHelpMan{"getbalance", diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index 542fd4838382..8201ecafdacc 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -42,51 +42,6 @@ bool HaveKey(const SigningProvider& wallet, const CKey& key) return wallet.HaveKey(key.GetPubKey().GetID()) || wallet.HaveKey(key2.GetPubKey().GetID()); } -static RPCHelpMan listaddressbalances() -{ - return RPCHelpMan{"listaddressbalances", - "\nLists addresses of this wallet and their balances\n", - { - {"minamount", RPCArg::Type::NUM, RPCArg::Default{0}, "Minimum balance in " + CURRENCY_UNIT + " an address should have to be shown in the list"}, - }, - RPCResult{ - RPCResult::Type::ARR, "", "", - { - {RPCResult::Type::STR_AMOUNT, "amount", "The Dash address and the amount in " + CURRENCY_UNIT}, - } - }, - RPCExamples{ - HelpExampleCli("listaddressbalances", "") - + HelpExampleCli("listaddressbalances", "10") - + HelpExampleRpc("listaddressbalances", "") - + HelpExampleRpc("listaddressbalances", "10") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - const std::shared_ptr pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return UniValue::VNULL; - - LOCK(pwallet->cs_wallet); - - CAmount nMinAmount = 0; - if (!request.params[0].isNull()) - nMinAmount = AmountFromValue(request.params[0]); - - if (nMinAmount < 0) - throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); - - UniValue jsonBalances(UniValue::VOBJ); - std::map balances = GetAddressBalances(*pwallet); - for (auto& balance : balances) - if (balance.second >= nMinAmount) - jsonBalances.pushKV(EncodeDestination(balance.first), ValueFromAmount(balance.second)); - - return jsonBalances; -}, - }; -} - - static RPCHelpMan setcoinjoinrounds() { return RPCHelpMan{"setcoinjoinrounds", @@ -1138,6 +1093,7 @@ RPCHelpMan importelectrumwallet(); // coins RPCHelpMan getreceivedbyaddress(); RPCHelpMan getreceivedbylabel(); +RPCHelpMan listaddressbalances(); RPCHelpMan getbalance(); RPCHelpMan getunconfirmedbalance(); RPCHelpMan lockunspent(); diff --git a/test/functional/create_cache.py b/test/functional/create_cache.py index 1108a8e3544b..2179bb7815a7 100755 --- a/test/functional/create_cache.py +++ b/test/functional/create_cache.py @@ -16,6 +16,7 @@ class CreateCache(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 0 + self.uses_wallet = True def setup_network(self): pass diff --git a/test/functional/feature_llmq_signing.py b/test/functional/feature_llmq_signing.py index bfe9094340bc..61db6c20942d 100755 --- a/test/functional/feature_llmq_signing.py +++ b/test/functional/feature_llmq_signing.py @@ -41,6 +41,7 @@ def run_test(self): if self.options.spork21: assert self.mninfo[0].get_node(self).getconnectioncount() == self.llmq_size + self.assert_qsendrecsigs_symmetric() id = "0000000000000000000000000000000000000000000000000000000000000001" msgHash = "0000000000000000000000000000000000000000000000000000000000000002" @@ -200,5 +201,25 @@ def assert_sigs_nochange(hasrecsigs, isconflicting1, isconflicting2, timeout): self.bump_mocktime(2) wait_for_sigs(True, False, True, 2) + def assert_qsendrecsigs_symmetric(self): + # If only one direction's QSENDRECSIGS arrives, the receiving side keeps + # m_wants_recsigs=false and silently drops half of all recsig pushes. + self.log.info("Assert QSENDRECSIGS was exchanged in both directions on every MN-MN edge") + mn_protxs = {mn.proTxHash for mn in self.mninfo} + + def all_symmetric(): + for mn in self.mninfo: + for p in mn.get_node(self).getpeerinfo(): + if p.get("verified_proregtx_hash", "") not in mn_protxs: + continue + if p.get("bytessent_per_msg", {}).get("qsendrecsigs", 0) == 0: + return False + if p.get("bytesrecv_per_msg", {}).get("qsendrecsigs", 0) == 0: + return False + return True + + self.wait_until(all_symmetric, timeout=10) + + if __name__ == '__main__': LLMQSigningTest().main() diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index a10d04f0312d..301804a12da2 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -144,6 +144,7 @@ def __init__(self): self._requires_wallet = False # Disable ThreadOpenConnections by default, so that adding entries to # addrman will not result in automatic connections to them. + self.uses_wallet = False self.disable_autoconnect = True self.set_test_params() assert self.wallet_names is None or len(self.wallet_names) <= self.num_nodes @@ -577,6 +578,7 @@ def get_bin_from_version(version, bin_name, bin_default): use_valgrind=self.options.valgrind, descriptors=self.options.descriptors, v2transport=self.options.v2transport, + uses_wallet=self.uses_wallet, ) self.nodes.append(test_node_i) if not test_node_i.version_is_at_least(160000): @@ -955,7 +957,7 @@ def _initialize_chain(self): cache_node_dir, chain=self.chain, extra_conf=["bind=127.0.0.1"], - extra_args=['-disablewallet', f"-mocktime={TIME_GENESIS_BLOCK}"], + extra_args=[f"-mocktime={TIME_GENESIS_BLOCK}"], extra_args_from_options=self.extra_args_from_options, rpchost=None, timewait=self.rpc_timeout, @@ -966,6 +968,7 @@ def _initialize_chain(self): coverage_dir=None, cwd=self.options.tmpdir, descriptors=self.options.descriptors, + uses_wallet=self.uses_wallet, )) self.start_node(CACHE_NODE_ID) cache_node = self.nodes[CACHE_NODE_ID] diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 52f785c14ce5..1d4c2c817255 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -69,7 +69,7 @@ class TestNode(): To make things easier for the test writer, any unrecognised messages will be dispatched to the RPC connection.""" - def __init__(self, i, datadir, extra_args_from_options, *, chain, rpchost, timewait, timeout_factor, bitcoind, bitcoin_cli, mocktime, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None, descriptors=False, v2transport=False): + def __init__(self, i, datadir, extra_args_from_options, *, chain, rpchost, timewait, timeout_factor, bitcoind, bitcoin_cli, mocktime, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None, descriptors=False, v2transport=False, uses_wallet=False): """ Kwargs: start_perf (bool): If True, begin profiling the node with `perf` as soon as @@ -114,7 +114,7 @@ def __init__(self, i, datadir, extra_args_from_options, *, chain, rpchost, timew if self.mocktime != 0: self.args.append(f"-mocktime={mocktime}") - if self.descriptors is None: + if uses_wallet is not None and not uses_wallet and self.descriptors is None: self.args.append("-disablewallet") # Use valgrind, expect for previous release binaries diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index e94bd47150dd..e6f06a9f2f17 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -922,6 +922,8 @@ def _get_uncovered_rpc_commands(self): covered_cmds = set({'generate'}) # TODO: implement functional tests for voteraw covered_cmds.add('voteraw') + # TODO: implement functional tests for importelectrumwallet + covered_cmds.add('importelectrumwallet') # TODO: implement functional tests for getmerkleblocks covered_cmds.add('getmerkleblocks') diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py index b039f147aa3b..5454a43406c9 100755 --- a/test/functional/wallet_balance.py +++ b/test/functional/wallet_balance.py @@ -109,6 +109,15 @@ def run_test(self): assert_equal(self.nodes[0].getbalance("*", 1, True), 500) assert_equal(self.nodes[1].getbalance(minconf=0, include_watchonly=True), 500) + self.log.info("Test listaddressbalances") + address_balances = {} + address_balances['yYdShjQSptFKitYLksFEUSwHe4hnbar5rf'] = Decimal('500.00000000') + if not self.options.descriptors: + address_balances['yVg3NBUHNEhgDceqwVUjsZHreC5PBHnUo9'] = Decimal('500.00000000') + assert_equal(address_balances, self.nodes[0].listaddressbalances()) + assert_equal(address_balances, self.nodes[0].listaddressbalances(500)) + assert_equal({}, self.nodes[0].listaddressbalances(501)) + # Send 490 BTC from 0 to 1 and 960 BTC from 1 to 0. txs = create_transactions(self.nodes[0], self.nodes[1].getnewaddress(), 490 , [Decimal('0.01')]) self.nodes[0].sendrawtransaction(txs[0]['hex']) diff --git a/test/functional/wallet_transactiontime_rescan.py b/test/functional/wallet_transactiontime_rescan.py index 268e623cd90d..e7c57fb774ea 100755 --- a/test/functional/wallet_transactiontime_rescan.py +++ b/test/functional/wallet_transactiontime_rescan.py @@ -136,6 +136,10 @@ def run_test(self): restorewo_wallet.importaddress(wo2, rescan=False) restorewo_wallet.importaddress(wo3, rescan=False) + self.log.info('Testing abortrescan when no rescan is in progress') + assert_equal(restorewo_wallet.getwalletinfo()['scanning'], False) + assert_equal(restorewo_wallet.abortrescan(), False) + # check user has 0 balance and no transactions assert_equal(restorewo_wallet.getbalance(), 0) assert_equal(len(restorewo_wallet.listtransactions()), 0)