feat(wallet): persist history and surface pending sends
Add an encrypted SQLite transaction history cache with cached tip metadata and per-address shielded scan progress so startup and full refreshes avoid re-scanning every z-address while still invalidating on wallet/address/rescan changes. Improve wallet history loading by paging transparent transactions, preserving cached shielded and sent rows, keeping recent/unconfirmed activity visible, and classifying mining-address receives. Show z_sendmany opid sends immediately in History and Overview, pin pending rows through refreshes, and apply optimistic address/balance debits until opids resolve. Add timestamped RPC console tracing by source/method without logging params or results, reduce redundant refresh/RPC calls, and cache Explorer recent block summaries in SQLite. Expand focused tests for transaction cache encryption, scan-progress persistence/invalidation, history preservation, operation-status parsing, pending send visibility, and Explorer/RPC refresh behavior.
This commit is contained in:
@@ -10,13 +10,76 @@
|
||||
#include "../util/base64.h"
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <atomic>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include "../util/logger.h"
|
||||
|
||||
namespace dragonx {
|
||||
namespace rpc {
|
||||
|
||||
namespace {
|
||||
|
||||
std::mutex g_trace_mutex;
|
||||
RPCClient::TraceCallback g_trace_callback;
|
||||
std::atomic_bool g_trace_enabled{false};
|
||||
thread_local std::string g_trace_source;
|
||||
|
||||
void emitRpcTrace(const std::string& method)
|
||||
{
|
||||
if (!g_trace_enabled.load(std::memory_order_relaxed)) return;
|
||||
|
||||
RPCClient::TraceCallback callback;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_trace_mutex);
|
||||
callback = g_trace_callback;
|
||||
}
|
||||
if (!callback) return;
|
||||
|
||||
std::string source = g_trace_source.empty() ? std::string("App") : g_trace_source;
|
||||
callback(source, method);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RPCClient::TraceScope::TraceScope(std::string source)
|
||||
: previous_(RPCClient::currentTraceSource())
|
||||
{
|
||||
RPCClient::setTraceSource(std::move(source));
|
||||
}
|
||||
|
||||
RPCClient::TraceScope::~TraceScope()
|
||||
{
|
||||
RPCClient::setTraceSource(std::move(previous_));
|
||||
}
|
||||
|
||||
void RPCClient::setTraceCallback(TraceCallback callback)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_trace_mutex);
|
||||
g_trace_callback = std::move(callback);
|
||||
}
|
||||
|
||||
void RPCClient::setTraceEnabled(bool enabled)
|
||||
{
|
||||
g_trace_enabled.store(enabled, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
bool RPCClient::isTraceEnabled()
|
||||
{
|
||||
return g_trace_enabled.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
std::string RPCClient::currentTraceSource()
|
||||
{
|
||||
return g_trace_source;
|
||||
}
|
||||
|
||||
void RPCClient::setTraceSource(std::string source)
|
||||
{
|
||||
g_trace_source = std::move(source);
|
||||
}
|
||||
|
||||
// Callback for libcurl to write response data
|
||||
static size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* userp) {
|
||||
size_t totalSize = size * nmemb;
|
||||
@@ -71,6 +134,7 @@ bool RPCClient::connect(const std::string& host, const std::string& port,
|
||||
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
|
||||
host_ = host;
|
||||
port_ = port;
|
||||
last_connect_info_ = json();
|
||||
|
||||
// Create Basic auth header with proper base64 encoding
|
||||
std::string credentials = user + ":" + password;
|
||||
@@ -116,6 +180,7 @@ bool RPCClient::connect(const std::string& host, const std::string& port,
|
||||
warming_up_ = false;
|
||||
warmup_status_.clear();
|
||||
last_connect_error_.clear();
|
||||
last_connect_info_ = result;
|
||||
DEBUG_LOGF("Connected to dragonxd v%d\n", result["version"].get<int>());
|
||||
return true;
|
||||
}
|
||||
@@ -146,15 +211,23 @@ bool RPCClient::connect(const std::string& host, const std::string& port,
|
||||
connected_ = false;
|
||||
warming_up_ = false;
|
||||
warmup_status_.clear();
|
||||
last_connect_info_ = json();
|
||||
return false;
|
||||
}
|
||||
|
||||
json RPCClient::getLastConnectInfo() const
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
|
||||
return last_connect_info_;
|
||||
}
|
||||
|
||||
void RPCClient::disconnect()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
|
||||
connected_ = false;
|
||||
warming_up_ = false;
|
||||
warmup_status_.clear();
|
||||
last_connect_info_ = json();
|
||||
if (impl_->curl) {
|
||||
curl_easy_cleanup(impl_->curl);
|
||||
impl_->curl = nullptr;
|
||||
@@ -182,6 +255,8 @@ json RPCClient::call(const std::string& method, const json& params)
|
||||
throw std::runtime_error("Not connected");
|
||||
}
|
||||
|
||||
emitRpcTrace(method);
|
||||
|
||||
json payload = makePayload(method, params);
|
||||
std::string body = payload.dump();
|
||||
std::string response_data;
|
||||
@@ -235,6 +310,8 @@ json RPCClient::call(const std::string& method, const json& params, long timeout
|
||||
throw std::runtime_error("Not connected");
|
||||
}
|
||||
|
||||
emitRpcTrace(method);
|
||||
|
||||
// Temporarily override timeout
|
||||
long prevTimeout = 30L;
|
||||
curl_easy_setopt(impl_->curl, CURLOPT_TIMEOUT, timeoutSec);
|
||||
@@ -294,6 +371,8 @@ std::string RPCClient::callRaw(const std::string& method, const json& params)
|
||||
throw std::runtime_error("Not connected");
|
||||
}
|
||||
|
||||
emitRpcTrace(method);
|
||||
|
||||
json payload = makePayload(method, params);
|
||||
std::string body = payload.dump();
|
||||
std::string response_data;
|
||||
|
||||
Reference in New Issue
Block a user