fix(history): keep wallet-created sends visible
Replay cached outgoing viewtransaction entries during transaction refresh so shielded sends created from the wallet remain in the History tab after send tracking is cleared. Keep incomplete tracked sends retryable, preserve cached send timestamp/confirmation metadata, and emit a send placeholder from gettransaction metadata when viewtransaction enrichment is not yet available. Add regression coverage for cached sends, retryable empty entries, placeholder sends, and send txid cleanup behavior.
This commit is contained in:
@@ -1000,12 +1000,81 @@ void testNetworkRefreshRpcCollectors()
|
||||
EXPECT_EQ(transactionResult.blockHeight, 321);
|
||||
EXPECT_EQ(transactionResult.newViewTxEntries.size(), static_cast<size_t>(1));
|
||||
EXPECT_EQ(transactionResult.newViewTxEntries.count("pending-send"), static_cast<size_t>(1));
|
||||
EXPECT_EQ(transactionResult.newViewTxEntries.at("pending-send").timestamp, static_cast<int64_t>(500));
|
||||
EXPECT_EQ(transactionResult.newViewTxEntries.at("pending-send").confirmations, 1);
|
||||
EXPECT_EQ(transactionResult.transactions.size(), static_cast<size_t>(4));
|
||||
EXPECT_EQ(transactionResult.transactions.front().txid, std::string("pending-send"));
|
||||
EXPECT_EQ(transactionResult.transactions.front().type, std::string("send"));
|
||||
EXPECT_NEAR(transactionResult.transactions.front().amount, -0.40, 0.00000001);
|
||||
EXPECT_EQ(transactionResult.transactions.front().timestamp, static_cast<int64_t>(500));
|
||||
EXPECT_EQ(transactionResult.transactions[1].txid, std::string("transparent-a"));
|
||||
|
||||
Refresh::TransactionRefreshSnapshot cachedOnlySnapshot;
|
||||
auto cachedOnlyEntry = cachedEntry;
|
||||
cachedOnlyEntry.timestamp = 450;
|
||||
cachedOnlyEntry.confirmations = 6;
|
||||
cachedOnlySnapshot.viewTxCache["cached-only-send"] = cachedOnlyEntry;
|
||||
cachedOnlySnapshot.fullyEnrichedTxids = {"cached-only-send"};
|
||||
MockRefreshRpc cachedOnlyRpc;
|
||||
cachedOnlyRpc.addResponse("listtransactions", json::array());
|
||||
auto cachedOnlyResult = Refresh::collectTransactionRefreshResult(cachedOnlyRpc, cachedOnlySnapshot, 322, 4);
|
||||
EXPECT_TRUE(cachedOnlyRpc.methodNames() == std::vector<std::string>({"listtransactions"}));
|
||||
EXPECT_EQ(cachedOnlyResult.transactions.size(), static_cast<size_t>(1));
|
||||
EXPECT_EQ(cachedOnlyResult.transactions[0].txid, std::string("cached-only-send"));
|
||||
EXPECT_EQ(cachedOnlyResult.transactions[0].type, std::string("send"));
|
||||
EXPECT_NEAR(cachedOnlyResult.transactions[0].amount, -0.20, 0.00000001);
|
||||
EXPECT_EQ(cachedOnlyResult.transactions[0].timestamp, static_cast<int64_t>(450));
|
||||
EXPECT_EQ(cachedOnlyResult.transactions[0].confirmations, 6);
|
||||
|
||||
Refresh::TransactionRefreshSnapshot retrySnapshot;
|
||||
retrySnapshot.sendTxids = {"retry-send"};
|
||||
retrySnapshot.viewTxCache["retry-send"] = Refresh::TransactionViewCacheEntry{};
|
||||
retrySnapshot.fullyEnrichedTxids = {"retry-send"};
|
||||
MockRefreshRpc retryRpc;
|
||||
retryRpc.addResponse("listtransactions", json::array());
|
||||
retryRpc.addResponse("z_viewtransaction", json{
|
||||
{"spends", json::array({json{{"address", "zs-retry-from"}}})},
|
||||
{"outputs", json::array({
|
||||
json{{"outgoing", true}, {"address", "zs-retry-dest"}, {"value", 0.55}}
|
||||
})}
|
||||
});
|
||||
retryRpc.addResponse("gettransaction", json{{"time", 650}, {"confirmations", 2}});
|
||||
auto retryResult = Refresh::collectTransactionRefreshResult(retryRpc, retrySnapshot, 323, 4);
|
||||
EXPECT_TRUE(retryRpc.methodNames() == std::vector<std::string>({
|
||||
"listtransactions", "z_viewtransaction", "gettransaction"
|
||||
}));
|
||||
EXPECT_EQ(retryResult.transactions.size(), static_cast<size_t>(1));
|
||||
EXPECT_EQ(retryResult.transactions[0].txid, std::string("retry-send"));
|
||||
EXPECT_NEAR(retryResult.transactions[0].amount, -0.55, 0.00000001);
|
||||
EXPECT_EQ(retryResult.newViewTxEntries.count("retry-send"), static_cast<size_t>(1));
|
||||
EXPECT_EQ(retryResult.newViewTxEntries.at("retry-send").timestamp, static_cast<int64_t>(650));
|
||||
EXPECT_EQ(retryResult.newViewTxEntries.at("retry-send").confirmations, 2);
|
||||
|
||||
Refresh::TransactionRefreshSnapshot placeholderSnapshot;
|
||||
placeholderSnapshot.sendTxids = {"placeholder-send"};
|
||||
MockRefreshRpc placeholderRpc;
|
||||
placeholderRpc.addResponse("listtransactions", json::array());
|
||||
placeholderRpc.addResponse("z_viewtransaction", json{{"spends", json::array()}, {"outputs", json::array()}});
|
||||
placeholderRpc.addResponse("gettransaction", json{
|
||||
{"time", 700},
|
||||
{"confirmations", 0},
|
||||
{"amount", -0.33},
|
||||
{"details", json::array({
|
||||
json{{"category", "send"}, {"address", "zs-placeholder-dest"}, {"amount", -0.33}}
|
||||
})}
|
||||
});
|
||||
auto placeholderResult = Refresh::collectTransactionRefreshResult(placeholderRpc, placeholderSnapshot, 324, 4);
|
||||
EXPECT_TRUE(placeholderRpc.methodNames() == std::vector<std::string>({
|
||||
"listtransactions", "z_viewtransaction", "gettransaction"
|
||||
}));
|
||||
EXPECT_EQ(placeholderResult.transactions.size(), static_cast<size_t>(1));
|
||||
EXPECT_EQ(placeholderResult.transactions[0].txid, std::string("placeholder-send"));
|
||||
EXPECT_EQ(placeholderResult.transactions[0].type, std::string("send"));
|
||||
EXPECT_NEAR(placeholderResult.transactions[0].amount, -0.33, 0.00000001);
|
||||
EXPECT_EQ(placeholderResult.transactions[0].timestamp, static_cast<int64_t>(700));
|
||||
EXPECT_EQ(placeholderResult.transactions[0].confirmations, 0);
|
||||
EXPECT_EQ(placeholderResult.transactions[0].address, std::string("zs-placeholder-dest"));
|
||||
EXPECT_EQ(placeholderResult.newViewTxEntries.count("placeholder-send"), static_cast<size_t>(0));
|
||||
}
|
||||
|
||||
void testNetworkRefreshResultModels()
|
||||
@@ -1198,9 +1267,10 @@ void testNetworkRefreshResultModels()
|
||||
pendingTx.timestamp = 1001;
|
||||
transactionResult.transactions = {pendingTx, confirmedTx};
|
||||
transactionResult.newViewTxEntries["shielded-send"] = viewEntry;
|
||||
transactionResult.newViewTxEntries["pending-empty"] = Refresh::TransactionViewCacheEntry{};
|
||||
|
||||
Refresh::TransactionViewCache viewCache;
|
||||
std::unordered_set<std::string> sendTxids{"shielded-send", "pending"};
|
||||
std::unordered_set<std::string> sendTxids{"shielded-send", "pending", "pending-empty"};
|
||||
std::vector<dragonx::TransactionInfo> confirmedCache;
|
||||
std::unordered_set<std::string> confirmedIds;
|
||||
int confirmedBlock = -1;
|
||||
@@ -1217,9 +1287,10 @@ void testNetworkRefreshResultModels()
|
||||
EXPECT_EQ(state.transactions.size(), static_cast<size_t>(2));
|
||||
EXPECT_EQ(state.last_tx_update, static_cast<int64_t>(4567));
|
||||
EXPECT_EQ(lastTxBlock, 222);
|
||||
EXPECT_EQ(viewCache.size(), static_cast<size_t>(1));
|
||||
EXPECT_EQ(viewCache.size(), static_cast<size_t>(2));
|
||||
EXPECT_EQ(sendTxids.count("shielded-send"), static_cast<size_t>(0));
|
||||
EXPECT_EQ(sendTxids.count("pending"), static_cast<size_t>(1));
|
||||
EXPECT_EQ(sendTxids.count("pending-empty"), static_cast<size_t>(1));
|
||||
EXPECT_EQ(confirmedCache.size(), static_cast<size_t>(1));
|
||||
EXPECT_EQ(confirmedCache[0].txid, std::string("confirmed"));
|
||||
EXPECT_EQ(confirmedIds.count("confirmed"), static_cast<size_t>(1));
|
||||
|
||||
Reference in New Issue
Block a user