wallet: Make CWalletTx "from" and "message" member variables
What changed, and why it matters
This is a small code cleanup in Bitcoin Core's wallet. It moves two very old, rarely-used pieces of transaction metadata—'from' and 'message'—out of a generic key/value map and into explicit named fields. The change preserves the same data and behavior, just makes the code clearer. It is not a security fix and does not appear to introduce a meaningful vulnerability.
No security action required. Treat as normal code maintenance. Reviewers may optionally verify that the serialization/deserialization round-trip preserves 'from'/'message' values exactly as before.
Security signals we found
No security-relevant change: pure refactor of deprecated metadata fields
HTML escaping of 'from' and 'message' remains unchanged in Qt UI
Backward-compatible serialization preserves existing wallet data
No new network, RPC, or consensus behavior
Evidence from the diff
The commit refactors CWalletTx so that the obsolete ‘from’ and ‘message’ mapValue entries are represented as std::optional
Changed components
src/wallet/transaction.hsrc/wallet/interfaces.cppsrc/wallet/wallet.cppsrc/qt/transactiondesc.cppsrc/qt/transactionrecord.cppsrc/interfaces/wallet.hInspect captured patch +30 / −14
diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h
index 4d965ec5..7f36692c 100644
--- a/src/interfaces/wallet.h
+++ b/src/interfaces/wallet.h
@@ -391,6 +391,8 @@ struct WalletTx
CAmount debit;
CAmount change;
int64_t time;
+ std::optional<std::string> from; // Deprecated
+ std::optional<std::string> message; // Deprecated
std::map<std::string, std::string> value_map;
bool is_coinbase;
diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp
index 79c19115..d16e2808 100644
--- a/src/qt/transactiondesc.cpp
+++ b/src/qt/transactiondesc.cpp
@@ -123,11 +123,9 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
if (wtx.is_coinbase)
{
strHTML += "<b>" + tr("Source") + ":</b> " + tr("Generated") + "<br>";
- }
- else if (wtx.value_map.contains("from") && !wtx.value_map["from"].empty())
- {
+ } else if (wtx.from) {
// Online transaction
- strHTML += "<b>" + tr("From") + ":</b> " + GUIUtil::HtmlEscape(wtx.value_map["from"]) + "<br>";
+ strHTML += "<b>" + tr("From") + ":</b> " + GUIUtil::HtmlEscape(*wtx.from) + "<br>";
}
else
{
@@ -273,9 +271,10 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
//
// Message
//
- if (wtx.value_map.contains("message") && !wtx.value_map["message"].empty())
- strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(wtx.value_map["message"], true) + "<br>";
- if (wtx.value_map.contains("comment") && !wtx.value_map["comment"].empty())
+ 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>";
strHTML += "<b>" + tr("Transaction ID") + ":</b> " + rec->getTxHash() + "<br>";
diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp
index 1143bc35..a06736bb 100644
--- a/src/qt/transactionrecord.cpp
+++ b/src/qt/transactionrecord.cpp
@@ -112,7 +112,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const interface
{
// Received by IP connection (deprecated features), or a multisignature or other non-simple transaction
sub.type = TransactionRecord::RecvFromOther;
- sub.address = mapValue["from"];
+ sub.address = wtx.from.value_or("");
}
if (wtx.is_coinbase)
{
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index 800e52a4..49271648 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -79,6 +79,8 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx)
result.debit = CachedTxGetDebit(wallet, wtx, /*avoid_reuse=*/true);
result.change = CachedTxGetChange(wallet, wtx);
result.time = wtx.GetTxTime();
+ result.from = wtx.m_from;
+ result.message = wtx.m_message;
result.value_map = wtx.mapValue;
result.is_coinbase = wtx.IsCoinBase();
return result;
diff --git a/src/wallet/transaction.h b/src/wallet/transaction.h
index 29baf669..32236aaf 100644
--- a/src/wallet/transaction.h
+++ b/src/wallet/transaction.h
@@ -194,6 +194,11 @@ public:
class CWalletTx
{
public:
+ // "from" and "message" are obsolete fields that could be set in
+ // the UI prior to 2011 (removed in commit 4d9b223)
+ // These fields are kept to avoid losing metadata.
+ std::optional<std::string> m_from;
+ std::optional<std::string> m_message;
/**
* Key/value map with information about the transaction.
*
@@ -206,8 +211,6 @@ public:
* bumpfee on transaction created by bumpfee
* "replaced_by_txid" - txid (as HexStr) of transaction created by
* bumpfee on transaction replaced by bumpfee
- * "from", "message" - obsolete fields that could be set in UI prior to
- * 2011 (removed in commit 4d9b223)
*
* The following keys are serialized in the wallet database, but shouldn't
* be read or written through the map (they will be temporarily added and
@@ -218,6 +221,8 @@ public:
* "timesmart" - serialized nTimeSmart value
* "spent" - serialized vfSpent value that existed prior to
* 2014 (removed in commit 93a18a3)
+ * "from", "message" - obsolete fields that could be set in UI prior to
+ * 2011 (removed in commit 4d9b223)
*/
mapValue_t mapValue;
std::vector<std::pair<std::string, std::string> > vOrderForm;
@@ -285,6 +290,8 @@ public:
void Serialize(Stream& s) const
{
mapValue_t mapValueCopy = mapValue;
+ if (m_from) mapValueCopy["from"] = *m_from;
+ if (m_message) mapValueCopy["message"] = *m_message;
mapValueCopy["fromaccount"] = "";
if (nOrderPos != -1) {
@@ -318,15 +325,19 @@ public:
m_state = TxStateInterpretSerialized({serialized_block_hash, serializedIndex});
- const auto it_op = mapValue.find("n");
- nOrderPos = (it_op != mapValue.end()) ? LocaleIndependentAtoi<int64_t>(it_op->second) : -1;
- const auto it_ts = mapValue.find("timesmart");
- nTimeSmart = (it_ts != mapValue.end()) ? static_cast<unsigned int>(LocaleIndependentAtoi<int64_t>(it_ts->second)) : 0;
+ for (const auto& [key, value] : mapValue) {
+ if (key == "n") nOrderPos = LocaleIndependentAtoi<int64_t>(value);
+ else if (key == "timesmart") nTimeSmart = LocaleIndependentAtoi<int64_t>(value);
+ else if (key == "from") m_from = value;
+ else if (key == "message") m_message = value;
+ }
mapValue.erase("fromaccount");
mapValue.erase("spent");
mapValue.erase("n");
mapValue.erase("timesmart");
+ mapValue.erase("from");
+ mapValue.erase("message");
}
void SetTx(CTransactionRef arg)
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 5f529a52..b9980428 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -742,6 +742,8 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran
if (copyFrom == copyTo) continue;
assert(copyFrom && "Oldest wallet transaction in range assumed to have been found.");
if (!copyFrom->IsEquivalentTo(*copyTo)) continue;
+ copyTo->m_from = copyFrom->m_from;
+ copyTo->m_message = copyFrom->m_message;
copyTo->mapValue = copyFrom->mapValue;
copyTo->vOrderForm = copyFrom->vOrderForm;
// nTimeReceived not copied on purpose
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.