wallet: Replace CWalletTx's vOrderForm with specific fields
What changed, and why it matters
This commit is a straightforward internal cleanup in Bitcoin Core's wallet code. It replaces a single mixed-purpose data field called vOrderForm with two clearer fields: one for BIP 21 payment messages and one for BIP 70 payment requests. The change preserves existing wallet data and behavior, and does not appear to fix or introduce a security vulnerability.
No security action required. Treat as normal code-quality refactor during review; verify serialization round-trip tests cover the new fields if desired.
Security signals we found
No security-relevant keywords in commit title or message
Pure data-structure refactor with behavior-preserving serialization shim
No input validation, parsing, or memory-unsafe changes observed
No privilege, authentication, or network boundary changes
Evidence from the diff
The patch refactors CWalletTx by removing the std::vector
Changed components
src/wallet/transaction.hsrc/wallet/wallet.cppsrc/wallet/feebumper.cppsrc/wallet/interfaces.cppsrc/qt/transactiondesc.cppsrc/interfaces/wallet.hInspect captured patch +51 / −49
diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h
index 3c1f70fb..326361aa 100644
--- a/src/interfaces/wallet.h
+++ b/src/interfaces/wallet.h
@@ -59,8 +59,6 @@ struct WalletTxOut;
struct WalletTxStatus;
struct WalletMigrationResult;
-using WalletOrderForm = std::vector<std::pair<std::string, std::string>>;
-
//! Interface for accessing a wallet.
class Wallet
{
@@ -195,7 +193,8 @@ public:
//! Get transaction details.
virtual WalletTx getWalletTxDetails(const Txid& txid,
WalletTxStatus& tx_status,
- WalletOrderForm& order_form,
+ std::vector<std::string>& messages,
+ std::vector<std::string>& payment_requests,
bool& in_mempool,
int& num_blocks) = 0;
diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp
index 4d1be8c7..6ea0dbbf 100644
--- a/src/qt/transactiondesc.cpp
+++ b/src/qt/transactiondesc.cpp
@@ -98,9 +98,10 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
{
int numBlocks;
interfaces::WalletTxStatus status;
- interfaces::WalletOrderForm orderForm;
bool inMempool;
- interfaces::WalletTx wtx = wallet.getWalletTxDetails(rec->hash, status, orderForm, inMempool, numBlocks);
+ std::vector<std::string> messages;
+ std::vector<std::string> payment_requests;
+ interfaces::WalletTx wtx = wallet.getWalletTxDetails(rec->hash, status, messages, payment_requests, inMempool, numBlocks);
QString strHTML;
@@ -282,24 +283,19 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
strHTML += "<b>" + tr("Output index") + ":</b> " + QString::number(rec->getOutputIndex()) + "<br>";
// Message from normal bitcoin:URI (bitcoin:123...?message=example)
- for (const std::pair<std::string, std::string>& r : orderForm) {
- if (r.first == "Message")
- strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(r.second, true) + "<br>";
-
- //
- // PaymentRequest info:
- //
- if (r.first == "PaymentRequest")
- {
- QString merchant;
- if (!GetPaymentRequestMerchant(r.second, merchant)) {
- merchant.clear();
- } else {
- merchant = tr("%1 (Certificate was not verified)").arg(merchant);
- }
- if (!merchant.isNull()) {
- strHTML += "<b>" + tr("Merchant") + ":</b> " + GUIUtil::HtmlEscape(merchant) + "<br>";
- }
+ for (const std::string& msg : messages) {
+ strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(msg, true) + "<br>";
+ }
+ // BIP 70 Payment Requests
+ for (const std::string& req : payment_requests) {
+ QString merchant;
+ if (!GetPaymentRequestMerchant(req, merchant)) {
+ merchant.clear();
+ } else {
+ merchant = tr("%1 (Certificate was not verified)").arg(merchant);
+ }
+ if (!merchant.isNull()) {
+ strHTML += "<b>" + tr("Merchant") + ":</b> " + GUIUtil::HtmlEscape(merchant) + "<br>";
}
}
diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp
index 9fff706b..19157e2e 100644
--- a/src/wallet/feebumper.cpp
+++ b/src/wallet/feebumper.cpp
@@ -370,13 +370,7 @@ Result CommitTransaction(CWallet& wallet, const Txid& txid, CMutableTransaction&
// commit/broadcast the tx
CTransactionRef tx = MakeTransactionRef(std::move(mtx));
- std::vector<std::string> messages;
- std::vector<std::string> payment_requests;
- for (const auto& [type, data] : oldWtx.vOrderForm) {
- if (type == "Message") messages.emplace_back(data);
- else if (type == "PaymentRequest") payment_requests.emplace_back(data);
- }
- wallet.CommitTransaction(tx, oldWtx.GetHash(), oldWtx.m_comment, oldWtx.m_comment_to, messages, payment_requests);
+ wallet.CommitTransaction(tx, oldWtx.GetHash(), oldWtx.m_comment, oldWtx.m_comment_to, oldWtx.m_messages, oldWtx.m_payment_requests);
// mark the original tx as bumped
bumped_txid = tx->GetHash();
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index 524abac6..622ccbab 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -44,7 +44,6 @@ using interfaces::WalletAddress;
using interfaces::WalletBalances;
using interfaces::WalletLoader;
using interfaces::WalletMigrationResult;
-using interfaces::WalletOrderForm;
using interfaces::WalletTx;
using interfaces::WalletTxOut;
using interfaces::WalletTxStatus;
@@ -349,7 +348,8 @@ public:
}
WalletTx getWalletTxDetails(const Txid& txid,
WalletTxStatus& tx_status,
- WalletOrderForm& order_form,
+ std::vector<std::string>& messages,
+ std::vector<std::string>& payment_requests,
bool& in_mempool,
int& num_blocks) override
{
@@ -358,7 +358,8 @@ public:
if (mi != m_wallet->mapWallet.end()) {
num_blocks = m_wallet->GetLastBlockHeight();
in_mempool = mi->second.InMempool();
- order_form = mi->second.vOrderForm;
+ messages = mi->second.m_messages;
+ payment_requests = mi->second.m_payment_requests;
tx_status = MakeWalletTxStatus(*m_wallet, mi->second);
return MakeWalletTx(*m_wallet, mi->second);
}
diff --git a/src/wallet/transaction.h b/src/wallet/transaction.h
index 21c0e267..6159bf12 100644
--- a/src/wallet/transaction.h
+++ b/src/wallet/transaction.h
@@ -201,7 +201,10 @@ public:
std::optional<std::string> m_comment_to;
std::optional<Txid> m_replaces_txid;
std::optional<Txid> m_replaced_by_txid;
- std::vector<std::pair<std::string, std::string> > vOrderForm;
+ // BIP 21 URI Messages
+ std::vector<std::string> m_messages;
+ // BIP 70 Payment Request (deprecated, field kept to preserve metadata from old wallets)
+ std::vector<std::string> m_payment_requests;
unsigned int nTimeReceived; //!< time received by this node
/**
* Stable timestamp that never changes, and reflects the order a transaction
@@ -238,7 +241,6 @@ public:
void Init()
{
- vOrderForm.clear();
nTimeReceived = 0;
nTimeSmart = 0;
fChangeCached = false;
@@ -275,13 +277,22 @@ public:
if (nOrderPos != -1) string_values["n"] = util::ToString(nOrderPos);
if (nTimeSmart) string_values["timesmart"] = strprintf("%u", nTimeSmart);
+ std::vector<std::pair<std::string, std::string>> msgs_reqs;
+ msgs_reqs.reserve(m_messages.size() + m_payment_requests.size());
+ for (const std::string& msg : m_messages) {
+ msgs_reqs.emplace_back("Message", msg);
+ }
+ for (const std::string& req : m_payment_requests) {
+ msgs_reqs.emplace_back("PaymentRequest", req);
+ }
+
std::vector<uint8_t> dummy_vector1; // Used to be vMerkleBranch
std::vector<uint8_t> dummy_vector2; // Used to be vtxPrev
bool dummy_bool = false; // Used to be fFromMe, and fSpent
uint32_t dummy_int = 0; // Used to be fTimeReceivedIsTxTime
uint256 serializedHash = TxStateSerializedBlockHash(m_state);
int serializedIndex = TxStateSerializedIndex(m_state);
- s << TX_WITH_WITNESS(tx) << serializedHash << dummy_vector1 << serializedIndex << dummy_vector2 << string_values << vOrderForm << dummy_int << nTimeReceived << dummy_bool << dummy_bool;
+ s << TX_WITH_WITNESS(tx) << serializedHash << dummy_vector1 << serializedIndex << dummy_vector2 << string_values << msgs_reqs << dummy_int << nTimeReceived << dummy_bool << dummy_bool;
}
template<typename Stream>
@@ -296,7 +307,8 @@ public:
uint256 serialized_block_hash;
int serializedIndex;
std::map<std::string, std::string> string_values;
- s >> TX_WITH_WITNESS(tx) >> serialized_block_hash >> dummy_vector1 >> serializedIndex >> dummy_vector2 >> string_values >> vOrderForm >> dummy_int >> nTimeReceived >> dummy_bool >> dummy_bool;
+ std::vector<std::pair<std::string, std::string>> msgs_reqs;
+ s >> TX_WITH_WITNESS(tx) >> serialized_block_hash >> dummy_vector1 >> serializedIndex >> dummy_vector2 >> string_values >> msgs_reqs >> dummy_int >> nTimeReceived >> dummy_bool >> dummy_bool;
m_state = TxStateInterpretSerialized({serialized_block_hash, serializedIndex});
@@ -315,6 +327,14 @@ public:
throw std::runtime_error("Unexpected value in CWalletTx strings value map");
}
}
+
+ for (const auto& [type, data] : msgs_reqs) {
+ if (type == "Message") m_messages.emplace_back(data);
+ else if (type == "PaymentRequest") m_payment_requests.emplace_back(data);
+ else {
+ throw std::runtime_error("Unknown type in CWalletTx messages and requests vector");
+ }
+ }
}
void SetTx(CTransactionRef arg)
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index ed224a05..7d5c04e3 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -748,7 +748,8 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran
copyTo->m_comment_to = copyFrom->m_comment_to;
copyTo->m_replaces_txid = copyFrom->m_replaces_txid;
copyTo->m_replaced_by_txid = copyFrom->m_replaced_by_txid;
- copyTo->vOrderForm = copyFrom->vOrderForm;
+ copyTo->m_messages = copyFrom->m_messages;
+ copyTo->m_payment_requests = copyFrom->m_payment_requests;
// nTimeReceived not copied on purpose
copyTo->nTimeSmart = copyFrom->nTimeSmart;
// nOrderPos not copied on purpose
@@ -2344,20 +2345,11 @@ void CWallet::CommitTransaction(
// Add tx to wallet, because if it has change it's also ours,
// otherwise just for transaction history.
CWalletTx* wtx = AddToWallet(tx, TxStateInactive{}, [&](CWalletTx& wtx, bool new_tx) {
- CHECK_NONFATAL(wtx.vOrderForm.empty());
if (replaces_txid) wtx.m_replaces_txid = replaces_txid;
if (comment) wtx.m_comment = comment;
if (comment_to) wtx.m_comment_to = comment_to;
- if (!messages.empty()) {
- for (const std::string& msg : messages) {
- wtx.vOrderForm.emplace_back("Message", msg);
- }
- }
- if (!payment_requests.empty()) {
- for (const std::string& msg : payment_requests) {
- wtx.vOrderForm.emplace_back("PaymentRequest", msg);
- }
- }
+ if (!messages.empty()) wtx.m_messages = messages;
+ if (!payment_requests.empty()) wtx.m_payment_requests = payment_requests;
return true;
});
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.