Problem
The interfaces::Wallet receive-request API uses raw serialized strings, coupling GUI code to the wallet’s internal storage format. The GUI serializes/deserializes RecentRequestEntry objects itself, and crashes if deserialization fails on malformed data. The GUI also owns ID assignment, which should be the wallet’s responsibility.
Root Cause
getAddressReceiveRequests() returns vector<string> of opaque serialized blobs, and setAddressReceiveRequest() accepts the same — forcing the GUI to own the serialization format (RecentRequestEntry + SendCoinsRecipient SERIALIZE_METHODS in Qt code). The SpanReader deserialization in RecentRequestsTableModel::addNewRequest(const std::string&) throws on malformed data with no error handling.
Solution
Replace the two raw-string methods with three typed methods on interfaces::Wallet:
getReceiveRequests()→ returnsvector<WalletReceiveRequest>with typed fieldsaddReceiveRequest()→ wallet assigns ID + timestamp, serializes internally, returns assigned IDeraseReceiveRequest()→ deletes by destination + ID
Serialization moves to wallet/interfaces.cpp using local RecipientData/RequestEntryData structs that are byte-identical to the Qt-side SendCoinsRecipient/RecentRequestEntry format, but have no Qt dependency. Malformed entries are logged and skipped instead of crashing. DB format is unchanged for full backward/forward compatibility.
Testing
- Updated existing GUI test (
src/qt/test/wallettests.cpp) to verify the new typed API:- Round-trip: add request via GUI →
getReceiveRequests()returns correct typed fields - Erase: remove request →
getReceiveRequests()returns empty
- Round-trip: add request via GUI →
- Existing wallet-layer test (
LoadReceiveRequestsinwallet_tests.cpp) unchanged — CWallet internals not modified - Verify:
cmake --build build && build/bin/test_bitcoin-qt
Before / After
- Before: GUI deserializes raw strings from wallet.
SpanReaderthrows on malformed data → GUI crash. GUI owns ID assignment. Single methodsetAddressReceiveRequestoverloaded for both save (non-empty value) and delete (empty value). - After: Wallet returns typed
WalletReceiveRequeststructs. Malformed entries are logged and skipped. Wallet assigns IDs and timestamps. SeparateaddReceiveRequest/eraseReceiveRequestmethods.
Edge Cases Handled
- Malformed/corrupted DB entries: caught with try/catch, logged via
LogWarning, skipped - ID=0 entries: skipped on load (matches previous GUI behavior)
- Invalid destination address on add: returns
nullopt - ID collision with existing entries: scans all existing IDs to find max, assigns max+1
- Empty label/message/zero amount: handled correctly (no special-casing needed)
- Thread safety: all methods hold
LOCK(m_wallet->cs_wallet) - Legacy BIP70 fields (
sPaymentRequest,authenticatedMerchant): preserved in serialization format for DB compatibility
Closes #34629