wallet: Make CWalletTx "comment" and "to" member variables
What changed, and why it matters
This commit is a straightforward internal cleanup in Bitcoin Core's wallet code. It moves two optional user-provided text fields, 'comment' and 'to', out of a generic key/value map and into explicit named member variables on the wallet transaction object. The change does not alter what data is stored or how users interact with it; it only makes the code clearer and less error-prone. There is no indication this fixes a security vulnerability.
No security action required. Treat as a normal code-quality refactor during review.
Security signals we found
No security-relevant behavior change observed
Refactor only: same data stored and exposed via same RPC keys
Backward-compatible serialization preserved
No input validation, parsing, or privilege changes
Evidence from the diff
The patch refactors CWalletTx so that ‘comment’ and ‘to’ (renamed m_comment and m_comment_to) are std::optional
Changed components
src/wallet/transaction.hsrc/wallet/wallet.cppsrc/wallet/interfaces.cppsrc/wallet/rpc/transactions.cppsrc/wallet/feebumper.cppsrc/qt/transactiondesc.cppsrc/qt/transactionrecord.cppsrc/interfaces/wallet.hInspect captured patch +30 / −15
diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h
index 7f36692c..ce1c69b0 100644
--- a/src/interfaces/wallet.h
+++ b/src/interfaces/wallet.h
@@ -393,6 +393,8 @@ struct WalletTx
int64_t time;
std::optional<std::string> from; // Deprecated
std::optional<std::string> message; // Deprecated
+ std::optional<std::string> comment;
+ std::optional<std::string> comment_to;
std::map<std::string, std::string> value_map;
bool is_coinbase;
diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp
index d16e2808..4d1be8c7 100644
--- a/src/qt/transactiondesc.cpp
+++ b/src/qt/transactiondesc.cpp
@@ -155,10 +155,9 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
//
// To
//
- if (wtx.value_map.contains("to") && !wtx.value_map["to"].empty())
- {
+ if (wtx.comment_to) {
// Online transaction
- std::string strAddress = wtx.value_map["to"];
+ std::string strAddress = *wtx.comment_to;
strHTML += "<b>" + tr("To") + ":</b> ";
CTxDestination dest = DecodeDestination(strAddress);
std::string name;
@@ -210,8 +209,7 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
if (toSelf && all_from_me)
continue;
- if (!wtx.value_map.contains("to") || wtx.value_map["to"].empty())
- {
+ if (!wtx.comment_to) {
// Offline transaction
CTxDestination address;
if (ExtractDestination(txout.scriptPubKey, address))
@@ -274,8 +272,9 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
if (wtx.message) {
strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(*wtx.message, true) + "<br>";
}
- if (wtx.value_map.count("comment") && !wtx.value_map["comment"].empty())
- strHTML += "<br><b>" + tr("Comment") + ":</b><br>" + GUIUtil::HtmlEscape(wtx.value_map["comment"], true) + "<br>";
+ if (wtx.comment) {
+ strHTML += "<br><b>" + tr("Comment") + ":</b><br>" + GUIUtil::HtmlEscape(*wtx.comment, true) + "<br>";
+ }
strHTML += "<b>" + tr("Transaction ID") + ":</b> " + rec->getTxHash() + "<br>";
strHTML += "<b>" + tr("Transaction total size") + ":</b> " + QString::number(wtx.tx->ComputeTotalSize()) + " bytes<br>";
diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp
index a06736bb..1f323590 100644
--- a/src/qt/transactionrecord.cpp
+++ b/src/qt/transactionrecord.cpp
@@ -77,7 +77,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const interface
{
// Sent to IP, or other non-address transaction like OP_EVAL
sub.type = TransactionRecord::SendToOther;
- sub.address = mapValue["to"];
+ sub.address = wtx.comment_to.value_or("");
}
CAmount nValue = txout.nValue;
diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp
index 5ccaf896..84eb022e 100644
--- a/src/wallet/feebumper.cpp
+++ b/src/wallet/feebumper.cpp
@@ -370,9 +370,7 @@ Result CommitTransaction(CWallet& wallet, const Txid& txid, CMutableTransaction&
// commit/broadcast the tx
CTransactionRef tx = MakeTransactionRef(std::move(mtx));
- std::optional<std::string> comment = oldWtx.mapValue.contains("comment") ? std::optional(oldWtx.mapValue.at("comment")) : std::nullopt;
- std::optional<std::string> comment_to = oldWtx.mapValue.contains("to") ? std::optional(oldWtx.mapValue.at("to")) : std::nullopt;
- wallet.CommitTransaction(tx, oldWtx.vOrderForm, oldWtx.GetHash(), comment, comment_to);
+ wallet.CommitTransaction(tx, oldWtx.vOrderForm, oldWtx.GetHash(), oldWtx.m_comment, oldWtx.m_comment_to);
// mark the original tx as bumped
bumped_txid = tx->GetHash();
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index 49271648..d95e0574 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -81,6 +81,8 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx)
result.time = wtx.GetTxTime();
result.from = wtx.m_from;
result.message = wtx.m_message;
+ result.comment = wtx.m_comment;
+ result.comment_to = wtx.m_comment_to;
result.value_map = wtx.mapValue;
result.is_coinbase = wtx.IsCoinBase();
return result;
diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
index 77af932e..8d7b8019 100644
--- a/src/wallet/rpc/transactions.cpp
+++ b/src/wallet/rpc/transactions.cpp
@@ -62,6 +62,9 @@ static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue
entry.pushKV("bip125-replaceable", rbfStatus);
}
+ if (wtx.m_comment) entry.pushKV("comment", *wtx.m_comment);
+ if (wtx.m_comment_to) entry.pushKV("to", *wtx.m_comment_to);
+
for (const std::pair<const std::string, std::string>& item : wtx.mapValue)
entry.pushKV(item.first, item.second);
}
diff --git a/src/wallet/transaction.h b/src/wallet/transaction.h
index 32236aaf..5ea686b7 100644
--- a/src/wallet/transaction.h
+++ b/src/wallet/transaction.h
@@ -199,14 +199,15 @@ public:
// These fields are kept to avoid losing metadata.
std::optional<std::string> m_from;
std::optional<std::string> m_message;
+ // Comment strings provided by the user
+ std::optional<std::string> m_comment;
+ std::optional<std::string> m_comment_to;
/**
* Key/value map with information about the transaction.
*
* The following keys can be read and written through the map and are
* serialized in the wallet database:
*
- * "comment", "to" - comment strings provided to sendtoaddress,
- * and sendmany wallet RPCs
* "replaces_txid" - txid (as HexStr) of transaction replaced by
* bumpfee on transaction created by bumpfee
* "replaced_by_txid" - txid (as HexStr) of transaction created by
@@ -223,6 +224,8 @@ public:
* 2014 (removed in commit 93a18a3)
* "from", "message" - obsolete fields that could be set in UI prior to
* 2011 (removed in commit 4d9b223)
+ * "comment", "to" - comment strings provided to sendtoaddress,
+ * and sendmany wallet RPCs
*/
mapValue_t mapValue;
std::vector<std::pair<std::string, std::string> > vOrderForm;
@@ -292,6 +295,8 @@ public:
mapValue_t mapValueCopy = mapValue;
if (m_from) mapValueCopy["from"] = *m_from;
if (m_message) mapValueCopy["message"] = *m_message;
+ if (m_comment) mapValueCopy["comment"] = *m_comment;
+ if (m_comment_to) mapValueCopy["to"] = *m_comment_to;
mapValueCopy["fromaccount"] = "";
if (nOrderPos != -1) {
@@ -330,6 +335,8 @@ public:
else if (key == "timesmart") nTimeSmart = LocaleIndependentAtoi<int64_t>(value);
else if (key == "from") m_from = value;
else if (key == "message") m_message = value;
+ else if (key == "comment") m_comment = value;
+ else if (key == "to") m_comment_to = value;
}
mapValue.erase("fromaccount");
@@ -338,6 +345,8 @@ public:
mapValue.erase("timesmart");
mapValue.erase("from");
mapValue.erase("message");
+ mapValue.erase("comment");
+ mapValue.erase("to");
}
void SetTx(CTransactionRef arg)
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index b9980428..095bb285 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -744,6 +744,8 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran
if (!copyFrom->IsEquivalentTo(*copyTo)) continue;
copyTo->m_from = copyFrom->m_from;
copyTo->m_message = copyFrom->m_message;
+ copyTo->m_comment = copyFrom->m_comment;
+ copyTo->m_comment_to = copyFrom->m_comment_to;
copyTo->mapValue = copyFrom->mapValue;
copyTo->vOrderForm = copyFrom->vOrderForm;
// nTimeReceived not copied on purpose
@@ -2343,8 +2345,8 @@ void CWallet::CommitTransaction(
CHECK_NONFATAL(wtx.mapValue.empty());
CHECK_NONFATAL(wtx.vOrderForm.empty());
if (replaces_txid) wtx.mapValue["replaces_txid"] = replaces_txid->ToString();
- if (comment) wtx.mapValue["comment"] = *comment;
- if (comment_to) wtx.mapValue["to"] = *comment_to;
+ if (comment) wtx.m_comment = comment;
+ if (comment_to) wtx.m_comment_to = comment_to;
wtx.vOrderForm = std::move(orderForm);
return true;
});
Why this scored 19/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.