From 1b52c85fb69e646b4400c1fa87248044ae4be3c7 Mon Sep 17 00:00:00 2001 From: pasta Date: Thu, 9 Apr 2026 11:19:26 -0500 Subject: [PATCH 1/6] Merge #7271: Merge bitcoin-core/gui#555: Restore Send button when using external signer 3af06ee0e3deaf75d36eed166b0fc3011a293386 Merge bitcoin-core/gui#555: Restore Send button when using external signer (Hennadii Stepanov) Pull request description: ## Issue being fixed or feature implemented Fixes bitcoin-core/gui#551 For the simplest use case of a wallet with one external signer and "PSBT Controls" disabled in settings (the default), the send dialog will behave the same as when using a wallet with private keys. I.e. there's no "Create Unsigned" button. When PSBT controls are turned on, you can now actually make a PSBT with signing it; before this PR that button would trigger a sign event and would broadcast the transaction. In case a multisig, the Send button will sign on the device, and then fall back to presenting a PSBT (same behavior as before #441). This PR starts with two refactoring commits to move some stuff into a helper function for improved readability in general, and to make the main commit easier to review. ## What was done? Backport bitcoin-core/gui#555 ## How Has This Been Tested? ## Breaking Changes N/A ## Checklist: - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ ACKs for top commit: UdjinM6: utACK 3af06ee0e3deaf75d36eed166b0fc3011a293386 Tree-SHA512: ec0cb2a2daad54d30b5ec9b82e0fb3b59f1bcfd049b036dc52875923b197ceee66aa2b15374c3c83ac894e5c7343b37d1e97e361dc573786db54c9d46f1785b1 (cherry picked from commit bcc009260e768db8bead52cd0c6f9114886a5b9a) --- src/qt/sendcoinsdialog.cpp | 199 +++++++++++++++++++++---------------- src/qt/sendcoinsdialog.h | 13 +++ 2 files changed, 126 insertions(+), 86 deletions(-) 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: From 5bc0bd501098ffd472c62ae425626cee4485e9fd Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 28 Apr 2026 09:08:18 -0500 Subject: [PATCH 2/6] Merge #7293: fix: keep sending ISDLOCK invs to non-MN peers with watchquorums f6bb02d666b0d40b0a290b72d40b912bcee32e18 fix: keep sending ISDLOCK invs to non-MN peers that want recsigs (Konstantin Akimov) Pull request description: ## Issue being fixed or feature implemented PR #6994 made masternodes skip ISDLOCK inv announcements to any peer with m_wants_recsigs set, on the premise that such peers can reconstruct the ISLOCK from the recsig. It works for MN peers but it does not work for quorum observers running with -watchquorums: those nodes also opt in to recsigs via QSENDRECSIGS but they don't have a signer worker running, so they cannot reconstruct an ISDLOCK from a recsig and they still need the inv. nodes[0] runs with -watchquorums and had progressively sent QSENDRECSIGS to all four MN peers; by the third call every MN saw nodes[0].m_wants_recsigs=true and skipped the inv to it. ## What was done? The ISDLOCK is skipped now only on the peer being verified masternode. Move the policy from PushInv (called for every inv type) to the three sites that actually relay MSG_ISDLOCK and have CNode in scope. ## How Has This Been Tested? Run functional test interface_dash_zmq.py multiple times. This PR drops failure rate from 50% to 0. ## Breaking Changes N/A ## Checklist: - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone ACKs for top commit: PastaPastaPasta: utACK f6bb02d666b0d40b0a290b72d40b912bcee32e18 Tree-SHA512: 14be2cfaad6dd0cb359bedca9e65e176b3d52de109f1c9e606892ee284f8079860d01117d538efb19bcf695e5da0cae747f08dcb98c209624ca28d6cf7d1e34d (cherry picked from commit 67683f37826f82ad727b55ac2e38da5fd3204033) --- src/net_processing.cpp | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) 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)); } } From 5e0af0b98047cb3ffe7fd4d5ce4f38641d5e2fd2 Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 28 Apr 2026 09:42:16 -0500 Subject: [PATCH 3/6] Merge #7279: test: RPC coverage linter + related fixes for listaddressbalances c27ba879e4f1dc67181224536b49cf006a700db2 fix: field name in listaddresses (Konstantin Akimov) 53cd79259174f77af0305d56eb25f108136508a0 fix: pass uses_wallet correctly to fix coverage bug (Konstantin Akimov) ff4302f89b66f4d9f580fe4007c017a7307ba702 Merge bitcoin/bitcoin#33064: test: fix RPC coverage check (Konstantin Akimov) 2b3cdfbe5f34bca4bba4e088cec2596216059045 refactor: move listaddressbalances to wallet/rpc/coins.cpp where it belongs to (Konstantin Akimov) 4b1a3353ca580d6f677de661f999cf87c7bad929 test: add coverage for RPC listaddressbalances (Konstantin Akimov) f6c66c807da329066d0d8c0ae94e743b020c854c test: add exception for importelectrumwallet RPC coverage (Konstantin Akimov) afa9b912dfcd4b24caeb8135533c86d50e3bc598 fix: RPC doc for listaddressbalances (Konstantin Akimov) Pull request description: ## Issue being fixed or feature implemented - listaddressbalances returns results that is not matched with its doc and it causes failure on debug builds such as: ``` $ listaddressbalances Internal bug detected: std::any_of(m_results.m_results.begin(), m_results.m_results.end(), [&ret](const RPCResult& res) { return res.MatchesType(ret); }) rpc/util.cpp:530 (HandleRequest) Dash Core v23.1.2-320-ge9b93e3b87be-dirty Please report this issue here: https://github.com/dashpay/dash/issues (code -1) ``` Further investigation discovered the bug, that exclude _all_ wallet RPCs from coverage linter. ## What was done? - fixes doc for RPC `listaddressbalances` - implementation of `listaddressbalances` moved between files to where it is supposed to be - adds functional tests for `listaddressbalances` - fixes coverage linter to add all wallet RPCs - backport bitcoin#33064 (fixes coverage linter and add test for `abortrescan` RPC) ## Backport candidate only this commit is considerable useful for backporting: [fix: RPC doc for listaddressbalances](https://github.com/dashpay/dash/commit/afa9b912dfcd4b24caeb8135533c86d50e3bc598) ## How Has This Been Tested? See updates in functional tests ## Breaking Changes N/A ## Checklist: - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ ACKs for top commit: PastaPastaPasta: utACK c27ba879e4f1dc67181224536b49cf006a700db2 Tree-SHA512: 538077f3d4c1d177a819c6cc1a87f5e9b529b39b3ef5b341d4b091c23df776948225a568e0f529c374e9bc94731e74e6e2f025d8de101129accf265c09e304f4 (cherry picked from commit 2b831c1a2de35ed1a816d61b382f487cc44d0228) --- src/wallet/rpc/coins.cpp | 45 ++++++++++++++++++ src/wallet/rpc/wallet.cpp | 46 +------------------ test/functional/create_cache.py | 1 + .../test_framework/test_framework.py | 5 +- test/functional/test_framework/test_node.py | 4 +- test/functional/test_runner.py | 2 + test/functional/wallet_balance.py | 9 ++++ .../wallet_transactiontime_rescan.py | 4 ++ 8 files changed, 68 insertions(+), 48 deletions(-) diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp index 51fd86088526..8c62e2c73872 100644 --- a/src/wallet/rpc/coins.cpp +++ b/src/wallet/rpc/coins.cpp @@ -162,6 +162,51 @@ 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; + + 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/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) From 4616073819a0e2d469a13e86a7d9c156df8bfbad Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 5 May 2026 08:59:19 -0500 Subject: [PATCH 4/6] Merge #7289: revert: "fix: avoid improper dual way connection attempts" + tests c1cdb751e4b3576c884a733209d56bbf98524baa test: assert QSENDRECSIGS handshake is symmetric under spork21 (Konstantin Akimov) 0e33aa2a9d78024c34fb0f7c2aa1044c7d9f3f48 revert: "fix: avoid improper dual way connection attempts" (Konstantin Akimov) Pull request description: ## Issue being fixed or feature implemented https://github.com/dashpay/dash/issues/7255 Intermittent failure at `feature_llmq_signing.py --spork21` File "feature_llmq_signing.py", line 111, in run_test wait_for_sigs(True, False, True, 15) File "feature_llmq_signing.py", line 60, in wait_for_sigs self.wait_until(lambda: check_sigs(hasrecsigs, isconflicting1, isconflicting2), timeout = timeout) AssertionError: Predicate '' not true after seconds ## What was done? This reverts a57a8119a081bfa75f044e75b9c5eb073f0b58d5 from PR #6967. `relayMembers` and `connections` in EnsureQuorumConnections are not interchangeable. * `connections`: who this node should connect TO. For each pair (A, B) only the deterministic-outbound side is listed, so the pair results in one TCP connection. * `relayMembers`: who this node should ask to push recovered sigs. For every already-connected MN in the set we send QSENDRECSIGS=true, and the peer then flips m_wants_recsigs=true on its side. For the handshake to happen in both directions, the set must list every other quorum member -- not just the outbound half. After a57a811, only the outbound half is listed, so on the inbound half of each pair m_wants_recsigs stays false. RelayRecoveredSig only pushes QSIGREC to peers with m_wants_recsigs=true, so half of all proactive recovered-sig pushes are silently dropped. This only triggers with spork21 on (IsAllMembersConnectedEnabled returns true). In this case both the path that uses the half-mesh outbound subset and the path that relies on proactive QSIGREC. ## How Has This Been Tested? This fixes the functional test `feature_llmq_signing.py --spork21`, which times out in wait_for_sigs. It reduced amount of failures on my localhost from ~50% to almost 0. Added new functional test in feature_llmq_signing.py for --spork21 case; which fails as expected without this patch. ## Breaking Changes N/A ## Checklist: - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone ACKs for top commit: UdjinM6: utACK c1cdb751e4b3576c884a733209d56bbf98524baa Tree-SHA512: 81730edf32317f3ac81a35ef72dbe556b03120cc91eba3b809af252ea2909d04561efa8bcbbb8bf73d4cf91508b74f9869c5e7946d904fd9c9b21401bae36381 (cherry picked from commit 8c42e829b349fce11403637ca2165d6f73b209dc) --- src/llmq/utils.cpp | 19 ++++++++++++++++++- test/functional/feature_llmq_signing.py | 21 +++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) 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/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() From d08fdd772730e2ace1dcd6aedfc4e9cb05b4a85a Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 12 May 2026 09:05:02 -0500 Subject: [PATCH 5/6] Merge #7312: fix: intermittent incorrect logging of CheckQueue for invalid blocks 76e6645cd43e73847e1444bb1aef788f58e8c0cf fix: intermittent incorrect logging of CheckQueue for invalid blocks (Konstantin Akimov) Pull request description: ## Issue being fixed or feature implemented ConnectBlock may return no-named failure instead proper error code such as: 2024-09-23T13:13:10Z ERROR: ConnectBlock: CheckQueue failed ## What was done? This PR fixes this intermittent failure ## How Has This Been Tested? N/A ## Breaking Changes N/A ## Checklist: - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone ACKs for top commit: UdjinM6: utACK 76e6645cd43e73847e1444bb1aef788f58e8c0cf Tree-SHA512: 5d2b2d40ae65086082dc81c4d0faf9dc2895db1e97cfe1d47857efc6ec5050ea930ca501eb8ef56ebc70ff7e0a14622b69979e63a8d3c1d6703875da06121e8b (cherry picked from commit f4f0b492ba8fff146866677dd15dd00bbdd2f3d1) --- src/validation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From 3026972a0f7f984d737c5d2036ddd89b212b1211 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Fri, 15 May 2026 02:22:34 -0500 Subject: [PATCH 6/6] chore: prepare v23.1.3 release --- configure.ac | 2 +- .../flatpak/org.dash.dash-core.metainfo.xml | 1 + doc/man/dash-cli.1 | 6 +- doc/man/dash-qt.1 | 6 +- doc/man/dash-tx.1 | 6 +- doc/man/dash-util.1 | 6 +- doc/man/dash-wallet.1 | 6 +- doc/man/dashd.1 | 6 +- doc/release-notes.md | 43 ++------- .../dash/release-notes-23.1.2.md | 95 +++++++++++++++++++ src/chainparams.cpp | 13 +-- src/wallet/rpc/coins.cpp | 4 + 12 files changed, 136 insertions(+), 58 deletions(-) create mode 100644 doc/release-notes/dash/release-notes-23.1.2.md 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/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp index 8c62e2c73872..f5ae196b9daf 100644 --- a/src/wallet/rpc/coins.cpp +++ b/src/wallet/rpc/coins.cpp @@ -186,6 +186,10 @@ RPCHelpMan listaddressbalances() 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;