From the discussion in #36403, it is clear that there needs to be a way for users to rebroadcast a transaction. Currently, the way to do so requires getting the raw transaction, getting the hex, and using sendrawtransaction. This PR adds rebroadcastwallettx RPC that takes a txid to attempt to rebroadcast that single transaction. An action is also added to the GUI menu for each transaction to do the same.
wallet, rpc, gui: Allow users to manually rebroadcast a wallet transaction #36481
pull achow101 wants to merge 3 commits into bitcoin:master from achow101:manual-rebroadcast changing 9 files +135 −8-
achow101 commented at 9:41 PM on October 8, 2026: member
-
DrahtBot commented at 9:41 PM on October 8, 2026: contributor
<!--e57a25ab6845829454e8d69fc972939a-->
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
<!--006a51241073e994b41acfe9ec718e94-->
External sites
- Code coverage & benchmarks: https://corecheck.dev/bitcoin/bitcoin/pulls/36481
- Ralph review <img src=https://review.fish.foo/assets/bitcoin/bitcoin/36481.png alt="Ralph review status"> https://git.fish.foo/bitcoin/bitcoin/pulls/36481#author-ralph
<!--021abf342d371248e50ceaed478a90ca-->
Reviews
See the guideline and AI policy for information on the review process.
Type Reviewers Concept ACK polespinasa, mzumsande, murchandamus, jpk68, fametrano Approach ACK vasild If your review is incorrectly listed, please copy-paste <code><!--meta-tag:bot-skip--></code> into the comment that the bot should ignore.
<!--174a7506f384e20aa4161008e828411d-->
Conflicts
Reviewers, this pull request conflicts with the following ones:
- #36482 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36482.svg"></sub> (wallet: Only broadcast resubmitted transactions that were added to the mempool by achow101)
- #36380 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36380.svg"></sub> (wallet: extend -walletbroadcast to allow initial send only by vasild)
- #36330 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36330.svg"></sub> (wallet: don't resubmit transactions under -privatebroadcast by instagibbs)
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.
<!--5faf32d7da4f0f540f40219e4f7537a3-->
- achow101 force-pushed on Oct 8, 2026
- DrahtBot added the label CI failed on Oct 8, 2026
-
DrahtBot commented at 10:06 PM on October 8, 2026: contributor
<!--85328a0da195eb286784d51f73fa0af9-->
🚧 At least one of the CI tasks failed. <sub>Task
tidy: https://github.com/bitcoin/bitcoin/actions/runs/37848508934/job/113555303677</sub> <sub>LLM reason (✨ experimental): CI failed because clang-tidy flagged an ignored return value fromrebroadcastTransaction()inqt/transactionview.cpp:408(warnings treated as errors).</sub><details><summary>Hints</summary>
Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:
Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.
A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.
An intermittent issue.
Leave a comment here, if you need help tracking down a confusing failure.
</details>
-
polespinasa commented at 10:24 PM on October 8, 2026: member
concept ACK
-
mzumsande commented at 3:36 PM on October 9, 2026: contributor
Concept ACK
Whatever the solution of the existing issues with rebroadcast will be, giving the users the option to rebroadcast transactions manually is useful in all of these scenarios.
-
murchandamus commented at 7:39 PM on October 21, 2026: member
Concept ACK
-
jpk68 commented at 12:27 AM on October 22, 2026: contributor
Concept ACK
-
fametrano commented at 11:35 AM on October 25, 2026: contributor
Concept ACK
One issue should be fixed before merge:
rebroadcastwallettxfails with code -1 and an empty message when the transaction is out of the mempool and its feerate is above the wallet's-maxfeerate.I ran the RPC on regtest at 648450039a. Other mempool rejections keep their reason, such as "bad-txns-inputs-missingorspent" for a child whose parent expired. The empty message shows up when a user lowers
-maxfeerateto 20 sat/vB and restarts without mempool.dat. Our own 50 sat/vB child of an incoming payment then stays out of the mempool, and the RPC returns{"code": -1, "message": ""}. For the same transaction,sendrawtransactionwithmaxfeerate=0.0002returns -25 "Fee rate exceeds maximum configured by user (maxfeerate)".The cause is that
Chain::broadcastTransactionreturns only a bool, andBroadcastTransactionsets no error string forMAX_FEE_EXCEEDEDorMAX_FEE_RATE_EXCEEDED.sendrawtransactionfalls back toTransactionErrorStringin that case. The wallet's other callers only log the string or drop it, so this RPC is the first to show it to a user. The same fallback inside theChain::broadcastTransactionwrapper would fix it, and the GUI action will need that message once it shows the result. I can share the probe script. -
in src/interfaces/wallet.h:166 in 648450039a
162 | @@ -163,6 +163,12 @@ class Wallet 163 | //! Abandon transaction. 164 | virtual bool abandonTransaction(const Txid& txid) = 0; 165 | 166 | + //! Return whether transaction can be abandoned.
vasild commented at 10:41 AM on October 27, 2026://! Return whether transaction can be broadcast.
achow101 commented at 1:16 AM on November 5, 2026:Done
in src/interfaces/wallet.h:169 in 648450039a
162 | @@ -163,6 +163,12 @@ class Wallet 163 | //! Abandon transaction. 164 | virtual bool abandonTransaction(const Txid& txid) = 0; 165 | 166 | + //! Return whether transaction can be abandoned. 167 | + virtual bool transactionCanBeBroadcast(const Txid& txid) = 0; 168 | + 169 | + //! Abandon transaction.
vasild commented at 10:41 AM on October 27, 2026://! Rebroadcast a transaction.
achow101 commented at 1:16 AM on November 5, 2026:Done
in src/qt/transactionview.cpp:172 in 648450039a outdated
168 | @@ -169,6 +169,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa 169 | GUIUtil::ExceptionSafeConnect(bumpFeeAction, &QAction::triggered, this, &TransactionView::bumpFee); 170 | bumpFeeAction->setObjectName("bumpFeeAction"); 171 | abandonAction = contextMenu->addAction(tr("A&bandon transaction"), this, &TransactionView::abandonTx); 172 | + m_rebroadcast_action = contextMenu->addAction(tr("Re&broadcast transaction"), this, &TransactionView::rebroadcastTx);
vasild commented at 10:45 AM on October 27, 2026:What is the
&inRe&broadcast? It is also inA&bandon.
achow101 commented at 1:07 AM on November 5, 2026:It specifies the previous character as a shortcut key. With this menu open, the user can do
ctrl + eto trigger this action rather than needing to move the mouse to it and clicking.in src/qt/transactionview.cpp:400 in 648450039a
393 | @@ -392,6 +394,20 @@ void TransactionView::abandonTx() 394 | model->wallet().abandonTransaction(hash); 395 | } 396 | 397 | +void TransactionView::rebroadcastTx() 398 | +{ 399 | + if(!transactionView || !transactionView->selectionModel()) 400 | + return;
vasild commented at 10:48 AM on October 27, 2026:if (!transactionView || !transactionView->selectionModel()) return;also, the
returnshould be either on the same line asifor there should be{and}.
achow101 commented at 1:17 AM on November 5, 2026:Done
in src/qt/transactionview.cpp:407 in 648450039a
402 | + 403 | + // get the hash from the TxHashRole (QVariant / QString) 404 | + QString hashQStr = selection.at(0).data(TransactionTableModel::TxHashRole).toString(); 405 | + Txid hash = Txid::FromHex(hashQStr.toStdString()).value(); 406 | + 407 | + // Abandon the wallet transaction over the walletModel
vasild commented at 10:50 AM on October 27, 2026:// Rebroadcast the wallet transaction over the walletModel
achow101 commented at 1:17 AM on November 5, 2026:Done
in src/wallet/wallet.cpp:1839 in 648450039a
1838 | + auto it = mapWallet.find(txid); 1839 | + if (it == mapWallet.end()) { 1840 | + return util::Unexpected{WalletError{WalletErrorCode::InvalidParameter, strprintf(_("%s not in wallet"), txid.ToString())}}; 1841 | + } 1842 | + if (!TransactionCanBeBroadcast(it->second)) { 1843 | + return util::Unexpected{WalletError{WalletErrorCode::InvalidParameter, strprintf(_("%s is not eligible to be rebroadcast"), txid.ToString())}};
vasild commented at 1:38 PM on October 27, 2026:If the wallet broadcast is disabled (
-walletbroadcast=0), then the "is not eligible to be rebroadcast" will be misleading because it implies that there is something specific about this transaction that makes it not eligible.Consider extending
TransactionCanBeBroadcast()to return the reason why the transaction can not be broadcast:<details> <summary>[patch] TransactionCanBeBroadcast() with a reason</summary>
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 29460e3ba2..dcd571b684 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1832,51 +1832,64 @@ util::Expected<void, WalletError> CWallet::RebroadcastSingleTransaction(const Tx { LOCK(cs_wallet); auto it = mapWallet.find(txid); if (it == mapWallet.end()) { return util::Unexpected{WalletError{WalletErrorCode::InvalidParameter, strprintf(_("%s not in wallet"), txid.ToString())}}; } - if (!TransactionCanBeBroadcast(it->second)) { - return util::Unexpected{WalletError{WalletErrorCode::InvalidParameter, strprintf(_("%s is not eligible to be rebroadcast"), txid.ToString())}}; - } std::string err; + if (!TransactionCanBeBroadcast(it->second, err)) { + return util::Unexpected{WalletError{WalletErrorCode::InvalidParameter, strprintf(_("can not rebroadcast %s: %s"), txid.ToString(), err)}}; + } if (!SubmitTxMemoryPoolAndRelay(it->second, err, node::TxBroadcast::MEMPOOL_AND_BROADCAST_TO_ALL)) { return util::Unexpected{WalletError{WalletErrorCode::MiscError, Untranslated(err)}}; } return {}; } -bool CWallet::TransactionCanBeBroadcast(const Txid& txid) const +bool CWallet::TransactionCanBeBroadcast(const Txid& txid, std::string& reason) const { LOCK(cs_wallet); const CWalletTx* wtx = GetWalletTx(txid); - return wtx && TransactionCanBeBroadcast(*wtx); + return wtx && TransactionCanBeBroadcast(*wtx, reason); } -bool CWallet::TransactionCanBeBroadcast(const CWalletTx& wtx) const +bool CWallet::TransactionCanBeBroadcast(const CWalletTx& wtx, std::string& reason) const { AssertLockHeld(cs_wallet); // Can't relay if wallet is not broadcasting - if (!GetBroadcastTransactions()) return false; + if (!GetBroadcastTransactions()) { + reason = "the wallet is configured to not broadcast transactions"; + return false; + } // Don't relay abandoned transactions - if (wtx.isAbandoned()) return false; + if (wtx.isAbandoned()) { + reason = "the transaction is abandoned"; + return false; + } // Don't try to submit coinbase transactions. These would fail anyway but would // cause log spam. - if (wtx.IsCoinBase()) return false; + if (wtx.IsCoinBase()) { + reason = "the transaction is a coinbase transaction"; + return false; + } // Don't try to submit conflicted or confirmed transactions. - if (GetTxDepthInMainChain(wtx) != 0) return false; + if (GetTxDepthInMainChain(wtx) != 0) { + reason = "the transaction is already confirmed or is conflicted"; + return false; + } return true; } bool CWallet::SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, std::string& err_string, node::TxBroadcast broadcast_method) const { AssertLockHeld(cs_wallet); - if (!TransactionCanBeBroadcast(wtx)) return false; + std::string reason; + if (!TransactionCanBeBroadcast(wtx, reason)) return false; const char* what{""}; switch (broadcast_method) { case node::TxBroadcast::MEMPOOL_AND_BROADCAST_TO_ALL: what = "to mempool and for broadcast to peers"; break; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 8d7c72f4d7..0b64d62571 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -443,13 +443,13 @@ private: // Update last block processed in memory only void SetLastBlockProcessedInMem(int block_height, uint256 block_hash) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Update mempool conflicts for TRUC sibling transactions void UpdateTrucSiblingConflicts(const CWalletTx& parent_wtx, const Txid& child_txid, bool add_conflict) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool TransactionCanBeBroadcast(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool TransactionCanBeBroadcast(const CWalletTx& wtx, std::string& reason) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); public: /** * Main wallet lock. * This lock protects all the fields added by CWallet. */ @@ -635,13 +635,13 @@ public: void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) override; /** Set the next time this wallet should resend transactions to 12-36 hours from now, ~1 day on average. */ void SetNextResend() { m_next_resend = GetDefaultNextResend(); } /** Return true if all conditions for periodically resending transactions are met. */ bool ShouldResend() const; void ResubmitWalletTransactions(node::TxBroadcast broadcast_method, bool force); - bool TransactionCanBeBroadcast(const Txid& txid) const; + bool TransactionCanBeBroadcast(const Txid& txid, std::string& reason) const; util::Expected<void, WalletError> RebroadcastSingleTransaction(const Txid& txid); OutputType TransactionChangeType(const std::optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const; /** Fetch the inputs and sign with SIGHASH_ALL. */ bool SignTransaction(CMutableTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 78139f635d..1759680b02 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -283,13 +283,17 @@ public: bool transactionCanBeAbandoned(const Txid& txid) override { return m_wallet->TransactionCanBeAbandoned(txid); } bool abandonTransaction(const Txid& txid) override { LOCK(m_wallet->cs_wallet); return m_wallet->AbandonTransaction(txid); } - bool transactionCanBeBroadcast(const Txid& txid) override { return m_wallet->TransactionCanBeBroadcast(txid); } + bool transactionCanBeBroadcast(const Txid& txid) override + { + std::string reason; + return m_wallet->TransactionCanBeBroadcast(txid, reason); + } util::Expected<void, WalletError> rebroadcastTransaction(const Txid& txid) override { LOCK(m_wallet->cs_wallet); return m_wallet->RebroadcastSingleTransaction(txid); } bool transactionCanBeBumped(const Txid& txid) override</details>
achow101 commented at 1:17 AM on November 5, 2026:Added a commit for this
in src/wallet/rpc/transactions.cpp:1115 in 648450039a outdated
1110 | + HelpExampleRpc("rebroadcastwallettx", "\"txid\"") 1111 | + }, 1112 | + [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue 1113 | + { 1114 | + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); 1115 | + if (!wallet) return UniValue::VNULL;
vasild commented at 1:43 PM on October 27, 2026:Should we not throw here?
UniValue::VNULLis for "If rebroadcast is successfully initiated".- if (!wallet) return UniValue::VNULL; + if (!wallet) { + throw JSONRPCError(RPC_WALLET_ERROR, "no wallet"); + }
achow101 commented at 1:18 AM on November 5, 2026:GetWalletForJSONRPCRequestnever returns nullptr, this should really be aAssumereturn a reference. But this is the pattern that we currently use for all of the wallet RPCs.in src/wallet/rpc/transactions.cpp:1103 in 648450039a
1094 | @@ -1095,4 +1095,32 @@ RPCMethod abortrescan() 1095 | }, 1096 | }; 1097 | } 1098 | + 1099 | +RPCMethod rebroadcastwallettx() 1100 | +{ 1101 | + return RPCMethod{"rebroadcastwallettx", 1102 | + "Rebroadcast the specified wallet transaction.\n" 1103 | + "Note that rebroadcasting a transaction may reveal that your node is involved as either a sender or receiver in the transaction.",
vasild commented at 1:45 PM on October 27, 2026:I think it would be useful to add the sentence
Use the listtransactions RPC to see the wallet transactionshere.
achow101 commented at 1:18 AM on November 5, 2026:Done
vasild commented at 2:03 PM on October 27, 2026: contributorApproach ACK 648450039ab1c93f5e0001e0829f094ca41d2bae
achow101 force-pushed on Nov 5, 2026achow101 force-pushed on Nov 5, 2026DrahtBot removed the label CI failed on Nov 5, 2026in src/interfaces/wallet.h:170 in 96c0e43277 outdated
162 | @@ -163,6 +163,12 @@ class Wallet 163 | //! Abandon transaction. 164 | virtual bool abandonTransaction(const Txid& txid) = 0; 165 | 166 | + //! Return whether transaction can be broadcast. 167 | + virtual bool transactionCanBeBroadcast(const Txid& txid) = 0; 168 | + 169 | + //! Rebroadcast a transaction. 170 | + virtual util::Expected<void, wallet::WalletError> rebroadcastTransaction(const Txid& txid) = 0;
ryanofsky commented at 1:35 PM on November 5, 2026:In commit "gui: Add rebroadcast tx menu action" (96c0e432770d5396294034618d35260510211bff)
Could this (and
RebroadcastSingleTransaction) returnutil::Result<void>instead ofutil::Expected<void, wallet::WalletError>? That would make the interface say what it returns: an error message, with no codes callers are expected to handle.WalletErrorcan hold any of six codes, but this method only returns two,InvalidParameterandMiscError, and callers can't tell that from the declaration. None of them handles the codes differently either: the GUI shows the message, and the RPC only uses the code to pick an RPC error number. TheWalletErrorCodedocumentation asks for a specific code only "when callers need to handle the condition differently", andGenericErrorotherwise (https://github.com/bitcoin/bitcoin/blob/0e38ad26af922abdc0fc36c9379e17efd2a72b80/src/wallet/types.h#L63-L65).util::Resultis meant for this case, functions reporting error strings to end users, anddisplayAddress()above already returnsutil::Result<void>.TransactionCanBeBroadcastcould return it too, instead ofutil::Expected<void, bilingual_str>. The RPC could then throwRPC_WALLET_ERRORfor failures. It would also stop "Wallet broadcast is disabled" from being reported as an invalid parameter. If the RPC should report an unknown txid the waygettransactiondoes (RPC_INVALID_ADDRESS_OR_KEY), it could checkmapWalletitself before calling, without a wallet-wide code. Sincerebroadcastwallettxis new, no client depends on its error codes yet.Using
util::Expected<void, bilingual_str>instead ofutil::Result<void>would be fine too. My main concern is the signature saying the function can return error codes that it never returns and no caller handles.
achow101 commented at 8:36 PM on November 5, 2026:My understanding is that for the QML GUI, they generally want error codes as some errors may be handled differently. While the GUI as I have implemented it currently does not use these codes, that does not preclude the future GUI work from using them either, and I'd prefer to avoid needing another interface change at that time as well. cc @pseudoramdom
Even for the new RPC, I think it makes sense to get the codes so that we can give reasonable RPC error codes. I think it is incorrect to return RPC_WALLET_ERROR for things that are clearly the user providing a transaction that cannot be broadcast, hence using InvalidParameter so that it can return RPC_INVALID_PARAMETER.
Furthermore, I agree that it is strange that "Wallet broadcast is disabled" is InvalidParameter, but I don't think the solution is to drop the error codes from the wallet, but rather that
TransactionCanBeBroadcastshould useutil::Expected<void, WalletError>as well, which I've done.achow101 force-pushed on Nov 5, 2026DrahtBot added the label Needs rebase on Nov 6, 2026achow101 force-pushed on Nov 6, 2026DrahtBot added the label CI failed on Nov 6, 2026DrahtBot removed the label Needs rebase on Nov 6, 2026in src/wallet/wallet.cpp:1853 in eba7a572f8
1852 | +bool CWallet::TransactionCanBeBroadcast(const Txid& txid) const 1853 | +{ 1854 | + LOCK(cs_wallet); 1855 | + const CWalletTx* wtx = GetWalletTx(txid); 1856 | + return wtx && TransactionCanBeBroadcast(*wtx); 1857 | +}
polespinasa commented at 8:36 AM on November 6, 2026:in eba7a572f8832fa355f943f005708e4f20a1b5eb wallet, rpc: Add rebroadcastwallettx
Why do we need two separate functions? This first one only adds one check, and because of the second one being private, they are never called independently outside the class.
Can't we add the if
wtxcheck inside the private function and make it take a txid? A caller knowing theCWalletTxcan always know atxid.<details> <summary>suggested diff</summary>
$ git diff diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index cf0c45a1b4..c420be2f5f 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1835,7 +1835,7 @@ util::Expected<void, WalletError> CWallet::RebroadcastSingleTransaction(const Tx if (it == mapWallet.end()) { return util::Unexpected{WalletError{WalletErrorCode::InvalidParameter, strprintf(_("%s not in wallet"), txid.ToString())}}; } - if (!TransactionCanBeBroadcast(it->second)) { + if (!TransactionCanBeBroadcast(it->first)) { return util::Unexpected{WalletError{WalletErrorCode::InvalidParameter, strprintf(_("%s is not eligible to be rebroadcast"), txid.ToString())}}; } std::string err; @@ -1849,21 +1849,17 @@ bool CWallet::TransactionCanBeBroadcast(const Txid& txid) const { LOCK(cs_wallet); const CWalletTx* wtx = GetWalletTx(txid); - return wtx && TransactionCanBeBroadcast(*wtx); -} - -bool CWallet::TransactionCanBeBroadcast(const CWalletTx& wtx) const -{ - AssertLockHeld(cs_wallet); + // Transaction is not in the wallet + if (!wtx) return false; // Can't relay if wallet is not broadcasting if (!GetBroadcastTransactions()) return false; // Don't relay abandoned transactions - if (wtx.isAbandoned()) return false; + if (wtx->isAbandoned()) return false; // Don't try to submit coinbase transactions. These would fail anyway but would // cause log spam. - if (wtx.IsCoinBase()) return false; + if (wtx->IsCoinBase()) return false; // Don't try to submit conflicted or confirmed transactions. - if (GetTxDepthInMainChain(wtx) != 0) return false; + if (GetTxDepthInMainChain(*wtx) != 0) return false; return true; } @@ -1873,7 +1869,7 @@ bool CWallet::SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, { AssertLockHeld(cs_wallet); - if (!TransactionCanBeBroadcast(wtx)) return false; + if (!TransactionCanBeBroadcast(wtx.GetHash())) return false; const char* what{""}; switch (broadcast_method) { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 0633e928e1..c64e53e0f5 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -446,8 +446,6 @@ private: //! Update mempool conflicts for TRUC sibling transactions void UpdateTrucSiblingConflicts(const CWalletTx& parent_wtx, const Txid& child_txid, bool add_conflict) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool TransactionCanBeBroadcast(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - public: /** * Main wallet lock.</details>
achow101 commented at 11:22 PM on November 6, 2026:To avoid needing to do another
mapWalletlookup for callers that already have aCWalletTx.in src/wallet/wallet.cpp:1866 in eba7a572f8
1863 | @@ -1843,6 +1864,16 @@ bool CWallet::SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, 1864 | if (wtx.IsCoinBase()) return false; 1865 | // Don't try to submit conflicted or confirmed transactions. 1866 | if (GetTxDepthInMainChain(wtx) != 0) return false;
polespinasa commented at 8:43 AM on November 6, 2026:in eba7a572f8832fa355f943f005708e4f20a1b5eb wallet, rpc: Add rebroadcastwallettx
Why not use
wtx.isBlockConflicted()?Also random and unrelated question. Why
IsUnconfirmed()checks if there is any mempool conflict? That existance of a mempool conflict doesn't change the unconfirmed state...
achow101 commented at 11:29 PM on November 6, 2026:Why not use
wtx.isBlockConflicted()?This is moved code.
Also random and unrelated question. Why
IsUnconfirmed()checks if there is any mempool conflict? That existance of a mempool conflict doesn't change the unconfirmed state...Same reason it checks
isAbandoned()andisConflicted- unconfirmed in this context means that this transaction is unconfirmed, and we want/expect it to become confirmed. If the transaction is abandoned or has a conflict already in the mempool, we either do not want it to become confirmed, or it cannot become confirmed.in src/wallet/wallet.cpp:1837 in eba7a572f8 outdated
1836 | - AssertLockHeld(cs_wallet); 1837 | + LOCK(cs_wallet); 1838 | + auto it = mapWallet.find(txid); 1839 | + if (it == mapWallet.end()) { 1840 | + return util::Unexpected{WalletError{WalletErrorCode::InvalidParameter, strprintf(_("%s not in wallet"), txid.ToString())}}; 1841 | + }
polespinasa commented at 9:43 AM on November 6, 2026:in eba7a572f8832fa355f943f005708e4f20a1b5eb wallet, rpc: Add rebroadcastwallettx
small nit, feel free to ignore
This check is duplicated, it is already done inside
TransactionCanBeBroadcast. Not criticla and i guess you need the find function to callSubmitTxMemoryPoolAndRelay.I tried to refactor it so
TransactionCanBeBroadcastreturns a CWalletTx instead of a Void in the next commit, but I think is not worth the work.Mentioning in case you come with a better idea :)
in src/wallet/rpc/transactions.cpp:1088 in eba7a572f8 outdated
1083 | +RPCMethod rebroadcastwallettx() 1084 | +{ 1085 | + return RPCMethod{"rebroadcastwallettx", 1086 | + "Rebroadcast the specified wallet transaction.\n" 1087 | + "Note that rebroadcasting a transaction may reveal that your node is involved as either a sender or receiver in the transaction." 1088 | + "Use listrawtransactions to see the wallet's transactions",
polespinasa commented at 10:41 AM on November 6, 2026:in eba7a572f8832fa355f943f005708e4f20a1b5eb wallet, rpc: Add rebroadcastwallettx
nit: missing empty space or line jump after "receiver in the transaction."
$ ./build/bin/bitcoin-cli -regtest rebroadcastwallettx error code: -1 error message: rebroadcastwallettx "txid" Rebroadcast the specified wallet transaction. Note that rebroadcasting a transaction may reveal that your node is involved as either a sender or receiver in the transaction.Use listrawtransactions to see the wallet's transactions
achow101 commented at 11:40 PM on November 6, 2026:DOe
in src/wallet/wallet.cpp:1863 in 592e7465b4 outdated
1862 | +util::Expected<void, WalletError> CWallet::TransactionCanBeBroadcast(const CWalletTx& wtx) const 1863 | { 1864 | AssertLockHeld(cs_wallet); 1865 | // Can't relay if wallet is not broadcasting 1866 | - if (!GetBroadcastTransactions()) return false; 1867 | + if (!GetBroadcastTransactions()) return util::Unexpected{WalletError{WalletErrorCode::MiscError, _("Wallet broadcast is disabled")}};;
polespinasa commented at 10:46 AM on November 6, 2026:in 592e7465b475fa6a6e5af846e694b84990505878 wallet: Return specific error messages from TransactionCanBeBroadcast
nit: there are two
;here? I am surprised this does compile with one or two.<details> <summary>fun fact</summary>
Am I the only one who did not know that you can put as many semicolons as you want and still being correct? This does compile:
$ git diff diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 5de55db7fa..c104bbe409 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1860,7 +1860,7 @@ util::Expected<void, WalletError> CWallet::TransactionCanBeBroadcast(const CWall { AssertLockHeld(cs_wallet); // Can't relay if wallet is not broadcasting - if (!GetBroadcastTransactions()) return util::Unexpected{WalletError{WalletErrorCode::MiscError, _("Wallet broadcast is disabled")}};; + if (!GetBroadcastTransactions()) return util::Unexpected{WalletError{WalletErrorCode::MiscError, _("Wallet broadcast is disabled")}};;;;;;;;;;;;;;;;;;; // Don't relay abandoned transactions if (wtx.isAbandoned()) return util::Unexpected{WalletError{WalletErrorCode::InvalidParameter, strprintf(_("%s is not eligible to be rebroadcast, tx is abandoned"), wtx.GetHash().ToString())}}; // Don't try to submit coinbase transactions. These would fail anyway but would @@ -1869,7 +1869,7 @@ util::Expected<void, WalletError> CWallet::TransactionCanBeBroadcast(const CWall // Don't try to submit conflicted or confirmed transactions. if (GetTxDepthInMainChain(wtx) > 0) return util::Unexpected{WalletError{WalletErrorCode::InvalidParameter, strprintf(_("%s is not eligible to be rebroadcast, tx is confirmed"), wtx.GetHash().ToString())}}; if (GetTxDepthInMainChain(wtx) < 0) return util::Unexpected{WalletError{WalletErrorCode::InvalidParameter, strprintf(_("%s is not eligible to be rebroadcast, tx is conflicted"), wtx.GetHash().ToString())}}; - return {}; + return {};;;;;;;;;;;;;;; }</details>
achow101 commented at 11:41 PM on November 6, 2026:Fixed
polespinasa commented at 10:52 AM on November 6, 2026: memberThis will need a release note.
Also might be worth taking a look at ralph comments, specially the GUI crash, the blocks sync and the empty error messages :)
achow101 commented at 11:37 PM on November 6, 2026: memberAlso might be worth taking a look at ralph comments, specially the GUI crash, the blocks sync and the empty error messages :)
None of these are relevant or things we care about at all.
wallet, rpc: Add rebroadcastwallettx 2545bca85bachow101 force-pushed on Nov 6, 2026wallet: Return specific error messages from TransactionCanBeBroadcast 2ff30311cfgui: Add rebroadcast tx menu action e27218f7adachow101 force-pushed on Nov 6, 2026DrahtBot removed the label CI failed on Nov 7, 2026
github-metadata-mirror
This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-11-08 19:51 UTC
This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me