From f7fd7f079b54772c8af3bc271bee34d607a63f1d Mon Sep 17 00:00:00 2001 From: zimbora Date: Wed, 25 Sep 2024 23:49:01 +0100 Subject: [PATCH] Enhancement: - RPCClient: fork detection, highutxos detection. - UI: Alerts for fork detection and highutxos detection. Allows Autocombine utxos --- configure.ac | 18 +- src/Makefile.qt.include | 3 + src/bootstrap.cpp | 3 +- src/curl.cpp | 227 +++++++++++++++++-- src/curl.h | 7 +- src/fs.cpp | 82 +++++++ src/fs.h | 5 + src/guiinterface.h | 6 + src/logging.h | 2 + src/main.cpp | 14 ++ src/qt/clientmodel.cpp | 10 + src/qt/clientmodel.h | 5 + src/qt/pivx.cpp | 2 + src/qt/pivx.qrc | 2 + src/qt/pivx/forms/topbar.ui | 44 ++++ src/qt/pivx/pivxgui.cpp | 1 + src/qt/pivx/res/css/style_dark.css | 16 ++ src/qt/pivx/res/css/style_light.css | 16 ++ src/qt/pivx/res/img/ic-package-fork.svg | 7 + src/qt/pivx/res/img/ic-package-highutxos.svg | 7 + src/qt/pivx/topbar.cpp | 122 ++++++++++ src/qt/pivx/topbar.h | 6 + src/qt/walletmodel.h | 4 +- src/rpc/blockchain.cpp | 40 ++++ src/rpc/client.cpp | 1 + src/rpc/server.cpp | 1 + src/rpc/server.h | 1 + src/util.cpp | 17 ++ src/util.h | 3 + src/wallet/rpcwallet.cpp | 77 +++++++ src/wallet/wallet.cpp | 151 ++++++++++++ src/wallet/wallet.h | 10 +- 32 files changed, 884 insertions(+), 26 deletions(-) create mode 100644 src/qt/pivx/res/img/ic-package-fork.svg create mode 100644 src/qt/pivx/res/img/ic-package-highutxos.svg diff --git a/configure.ac b/configure.ac index 8f40bf8ba3..b8ac4690d9 100644 --- a/configure.ac +++ b/configure.ac @@ -19,6 +19,7 @@ BITCOIN_CLI_NAME=__decenomy__-cli BITCOIN_TX_NAME=__decenomy__-tx BOOTSTRAP_URL_DEFAULT=https://bootstraps.decenomy.net/ +DECENOMY_API_URL_DEFAULT=https://explorer.decenomy.net/api/v2/ dnl Unless the user specified ARFLAGS, force it to be cr AC_ARG_VAR(ARFLAGS, [Flags for the archiver, defaults to if not set]) @@ -122,6 +123,13 @@ AC_ARG_WITH([bootstrap-url], [BOOTSTRAP_URL="$withval"], [BOOTSTRAP_URL="$BOOTSTRAP_URL_DEFAULT"]) +# Allow the user to specify a different value for DECENOMY_API_URL +AC_ARG_WITH([api-url], + [AS_HELP_STRING([--with-api-url=URL], + [URL for decenomy api])], + [DECENOMY_API_URL="$withval"], + [DECENOMY_API_URL="$DECENOMY_API_URL_DEFAULT"]) + # Enable wallet AC_ARG_ENABLE([wallet], [AS_HELP_STRING([--disable-wallet], @@ -1411,6 +1419,7 @@ AC_DEFINE(CLIENT_VERSION_IS_RELEASE, _CLIENT_VERSION_IS_RELEASE, [Version is rel AC_DEFINE(COPYRIGHT_YEAR, _COPYRIGHT_YEAR, [Version is release]) AC_DEFINE_UNQUOTED(PARAMS_DIR, ["$params_path"], [Path to the zk params dir during unit tests on windows]) AC_DEFINE_UNQUOTED([BOOTSTRAP_URL], ["$BOOTSTRAP_URL"], [URL for bootstrap data]) +AC_DEFINE_UNQUOTED([DECENOMY_API_URL], ["$DECENOMY_API_URL"], [URL for decenomy api]) AC_SUBST(CLIENT_VERSION_MAJOR, _CLIENT_VERSION_MAJOR) AC_SUBST(CLIENT_VERSION_MINOR, _CLIENT_VERSION_MINOR) AC_SUBST(CLIENT_VERSION_REVISION, _CLIENT_VERSION_REVISION) @@ -1529,10 +1538,11 @@ esac echo echo "Options used to compile and link:" -echo " with wallet = $enable_wallet" -echo " with bootstrap = $enable_bootstrap" -echo " bootstrap url = $BOOTSTRAP_URL" -echo " with gui / qt = $bitcoin_enable_qt" +echo " with wallet = $enable_wallet" +echo " with bootstrap = $enable_bootstrap" +echo " bootstrap url = $BOOTSTRAP_URL" +echo " decenomy api url = $DECENOMY_API_URL" +echo " with gui / qt = $bitcoin_enable_qt" if test x$bitcoin_enable_qt != xno; then echo " with qtcharts = $use_qtcharts" fi diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 9466840178..9198c53b8a 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -301,6 +301,9 @@ RES_ICONS = \ qt/pivx/res/img/ic-check-peers-off.svg \ qt/pivx/res/img/ic-nav-dashboard.svg \ qt/pivx/res/img/ic-wallet-status-unlocked.svg \ + qt/pivx/res/img/ic-package-fork.svg \ + qt/pivx/res/img/ic-package-highutxos.svg \ + qt/pivx/res/img/ic-wallet-status-unlocked.svg \ qt/pivx/res/img/ic-check-peers.svg \ qt/pivx/res/img/ic-nav-master-active.svg \ qt/pivx/res/img/ic-watch-password-white.svg \ diff --git a/src/bootstrap.cpp b/src/bootstrap.cpp index fc41820480..757555c6c4 100644 --- a/src/bootstrap.cpp +++ b/src/bootstrap.cpp @@ -67,7 +67,8 @@ bool CBootstrap::DownloadAndApply() try { // Step 1: Download the bootstrap file - if (!CCurlWrapper::DownloadFile(url, fileName.string(), progressCallback)) { + CCurlWrapper client; + if (!client.DownloadFile(url, fileName.string(), progressCallback)) { LogPrintf( "CBootstrap::%s: Failed to download the bootstrap file: %s\n", __func__, diff --git a/src/curl.cpp b/src/curl.cpp index fbfea3d99e..059de8d3dc 100644 --- a/src/curl.cpp +++ b/src/curl.cpp @@ -6,9 +6,15 @@ #include "init.h" #include "logging.h" +#include "fs.h" + +#include +#include + +namespace fs = boost::filesystem; // Callback function to write downloaded data to a file -size_t writeCallback(void* data, size_t size, size_t nmemb, void* clientp) +size_t downloadWriteCallback(void* data, size_t size, size_t nmemb, void* clientp) { if(ShutdownRequested()) { LogPrintf( @@ -23,20 +29,112 @@ size_t writeCallback(void* data, size_t size, size_t nmemb, void* clientp) return total_size; } -bool CCurlWrapper::DownloadFile( - const std::string& url, - const std::string& filename, - curl_xferinfo_callback xferinfoCallback) -{ - try { - // Initializes libcurl - const auto curl = curl_easy_init(); - if (!curl) { - LogPrintf( - "CCurlWrapper::%s: Error initializing libcurl.\n", __func__); - return false; - } +// Callback function to append downloaded data from a request method +size_t requestWriteCallback(void* ptr, size_t size, size_t nmemb, std::string* data) { + data->append((char*)ptr, size * nmemb); + return size * nmemb; +} + +// Callback function to write received headers to a string +size_t headerCallback(void* buffer, size_t size, size_t nmemb, std::string* headers) { + size_t totalSize = size * nmemb; + headers->append((char*)buffer, totalSize); + return totalSize; +} + +std::string CalculateMD5(const std::string& filePath) { + std::ifstream file(filePath, std::ios::binary); + if (!file) { + LogPrintf("-MD5: Error opening file: %s\n",filePath); + return ""; + } + + MD5_CTX mdContext; + MD5_Init(&mdContext); + + const int bufferSize = 4096; + char buffer[bufferSize]; + while (file) { + file.read(buffer, bufferSize); + MD5_Update(&mdContext, buffer, file.gcount()); + } + file.close(); + + unsigned char digest[MD5_DIGEST_LENGTH]; + MD5_Final(digest, &mdContext); + char md5String[MD5_DIGEST_LENGTH * 2 + 1]; + for(int i = 0; i < MD5_DIGEST_LENGTH; i++) + sprintf(&md5String[i*2], "%02x", (unsigned int)digest[i]); + + return md5String; +} + +bool Base64Decode(const std::string &input, std::string &out) { + static constexpr unsigned char kDecodingTable[] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 64, 64, 64, 64, 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64 + }; + + size_t in_len = input.size(); + if (in_len % 4 != 0) + return false; + + size_t out_len = in_len / 4 * 3; + if (in_len >= 1 && input[in_len - 1] == '=') + out_len--; + if (in_len >= 2 && input[in_len - 2] == '=') + out_len--; + + out.resize(out_len); + + for (size_t i = 0, j = 0; i < in_len;) { + uint32_t a = input[i] == '=' + ? 0 & i++ + : kDecodingTable[static_cast(input[i++])]; + uint32_t b = input[i] == '=' + ? 0 & i++ + : kDecodingTable[static_cast(input[i++])]; + uint32_t c = input[i] == '=' + ? 0 & i++ + : kDecodingTable[static_cast(input[i++])]; + uint32_t d = input[i] == '=' + ? 0 & i++ + : kDecodingTable[static_cast(input[i++])]; + + uint32_t triple = + (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6); + + if (j < out_len) + out[j++] = (triple >> 2 * 8) & 0xFF; + if (j < out_len) + out[j++] = (triple >> 1 * 8) & 0xFF; + if (j < out_len) + out[j++] = (triple >> 0 * 8) & 0xFF; + } + + return true; +} + +CCurlWrapper::CCurlWrapper() { + curl_global_init(CURL_GLOBAL_ALL); + curl = curl_easy_init(); + if (!curl) { + LogPrintf("CCurlWrapper::%s: Error initializing libcurl.\n", __func__); + }else{ // Gets libcurl version information const auto info = curl_version_info(CURLVERSION_NOW); if (info) { @@ -53,14 +151,40 @@ bool CCurlWrapper::DownloadFile( LogPrintf( "CCurlWrapper::%s: Failed to retrieve libcurl version information.\n", __func__); - return false; } + } +} + +CCurlWrapper::~CCurlWrapper() { + if (curl) { + curl_easy_cleanup(curl); + } + curl_global_cleanup(); +} + +bool CCurlWrapper::DownloadFile( + const std::string& url, + const std::string& filename, + curl_xferinfo_callback xferinfoCallback) +{ + try { LogPrintf( "CCurlWrapper::%s: Downloading from %s\n", __func__, url); + if(fs::exists(filename)) + fs::remove(filename); + + // Try to set permissions + if(!GrantWritePermissions(fs::current_path())){ + LogPrintf( + "CCurlWrapper::%s: Couldn't grant permissions for folder: %s\n", + __func__,fs::current_path().string()); + return false; + } + // Creates and open the destination file std::ofstream outputFile(filename, std::ios::binary); if (!outputFile.is_open()) { @@ -82,7 +206,7 @@ bool CCurlWrapper::DownloadFile( #endif // Sets file releated parameters - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, downloadWriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &outputFile); // Sets progress function parameters @@ -95,13 +219,38 @@ bool CCurlWrapper::DownloadFile( curl_easy_setopt(curl, CURLOPT_XFERINFODATA, NULL); } + // Follow redirects + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + + // Set the callback function to receive headers + std::string headers; + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, headerCallback); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, &headers); + + const auto info = curl_version_info(CURLVERSION_NOW); + LogPrintf("curl: version: %s\n",info->version); + // Set User-Agent header + if (info) { + curl_easy_setopt(curl, CURLOPT_USERAGENT, info->version); + } + // HTTP call execution const auto res = curl_easy_perform(curl); - // Cleanup - curl_easy_cleanup(curl); outputFile.close(); + /* !! calculate md5hash if available + std::string hexMD5 = CalculateMD5(outputFileName); + std::cout << "calculated md5: " << hexMD5 << std::endl; + + // get md5 from header + std::string headerMD5 = "Z+Ye9ExZ/IUfkl/Rb5piiQ=="; + std::string decoded = ""; + Base64Decode(headerMD5,decoded); + std::string hex = StringToHex(decoded); + std::cout << "headerMD5: " << hex << std::endl; + */ + // Evaluation and return if (res != CURLE_OK) { if(!ShutdownRequested()) { @@ -122,3 +271,45 @@ bool CCurlWrapper::DownloadFile( return true; } + +std::string CCurlWrapper::Request(const std::string& url) { + + std::string response = ""; + try { + LogPrintf("CCurlWrapper::%s: request: %s\n", __func__, url); + if (curl) { + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); // Verify the peer's SSL certificate + curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, requestWriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + +#if defined(__APPLE__) + LogPrintf("CCurlWrapper::%s: apple ca path: %s\n", __func__, (const char*)APPLE_CA_PATH); + curl_easy_setopt(curl, CURLOPT_CAINFO, (const char*)APPLE_CA_PATH); +#endif + + // Set User-Agent header + curl_version_info_data *info = curl_version_info(CURLVERSION_NOW); + + if (info) { + curl_easy_setopt(curl, CURLOPT_USERAGENT, info->version); + }else{ + curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl x.xx"); + } + + CURLcode res = curl_easy_perform(curl); + if (res != CURLE_OK) { + LogPrintf("CCurlWrapper::%s: error: Failed to perform HTTP request: %s", __func__, curl_easy_strerror(res)); + } + } + } catch (const std::exception& e) { + LogPrintf( + "CCurlWrapper::%s: Error requesting url: %s\n", + __func__, e.what()); + return ""; + } + + + return response; +} \ No newline at end of file diff --git a/src/curl.h b/src/curl.h index f9b7bad3dd..f2a60a6e1a 100644 --- a/src/curl.h +++ b/src/curl.h @@ -13,10 +13,15 @@ class CCurlWrapper { public: - static bool DownloadFile( + CCurlWrapper(); + ~CCurlWrapper(); + bool DownloadFile( const std::string& url, const std::string& filename, curl_xferinfo_callback xferinfoCallback = nullptr); + std::string Request(const std::string& url); +private: + CURL* curl; }; #endif // CURL_H diff --git a/src/fs.cpp b/src/fs.cpp index d47732deca..8541bedec3 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -7,6 +7,13 @@ #include "fs.h" #include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif namespace fsbridge { @@ -21,3 +28,78 @@ FILE *freopen(const fs::path& p, const char *mode, FILE *stream) } } // fsbridge + +bool GrantWritePermissions(const fs::path& path) { + +#ifdef _WIN32 + try { + // Get the current permissions + fs::perms current_perms = fs::status(path).permissions(); + + // Add write permissions for owner, group, and others + fs::perms new_perms = current_perms + | fs::perms::owner_write + | fs::perms::group_write + | fs::perms::others_write; + + // Set the new permissions + fs::permissions(path, new_perms); + + LogPrintf("%s: write permissions granted to: %s \n", __func__, path); + return true; + } catch (const fs::filesystem_error& e) { + LogPrintf("%s: Error updating permissions: %s\n", __func__, e.what()); + return false; + } +#else + struct stat info; + if (stat(path.c_str(), &info) != 0) { + LogPrintf("%s: Error reading path stat\n", __func__); + return false; + } + + mode_t new_mode = info.st_mode | S_IWUSR | S_IWGRP | S_IWOTH; + if (chmod(path.c_str(), new_mode) != 0) { + LogPrintf("%s: Error granting permissions chmod: %d\n", __func__, new_mode); + } else { + LogPrintf("%s: write permissions granted to: %s \n", __func__, path); + return true; + } +#endif + return false; +} + +std::string File_SHA256(const std::string& path){ + + std::ifstream fp(path, std::ios::in | std::ios::binary); + + if (not fp.good()) { + LogPrintf("-SHA256::%s: Error: %s, Cannot open: %s\n",__func__, std::strerror(errno),path); + return ""; + } + + constexpr const std::size_t buffer_size { 1 << 12 }; + char buffer[buffer_size]; + + unsigned char hash[SHA256_DIGEST_LENGTH] = { 0 }; + + SHA256_CTX ctx; + SHA256_Init(&ctx); + + while (fp.good()) { + fp.read(buffer, buffer_size); + SHA256_Update(&ctx, buffer, fp.gcount()); + } + + SHA256_Final(hash, &ctx); + fp.close(); + + std::ostringstream os; + os << std::hex << std::setfill('0'); + + for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) { + os << std::setw(2) << static_cast(hash[i]); + } + +return os.str(); +} \ No newline at end of file diff --git a/src/fs.h b/src/fs.h index 933c180569..d1b5789b35 100644 --- a/src/fs.h +++ b/src/fs.h @@ -10,6 +10,8 @@ #include #include +#include "logging.h" + #define BOOST_FILESYSTEM_NO_DEPRECATED #include #include @@ -24,4 +26,7 @@ namespace fsbridge { FILE *freopen(const fs::path& p, const char *mode, FILE *stream); }; +std::string File_SHA256(const std::string& path); +bool GrantWritePermissions(const fs::path& path); + #endif diff --git a/src/guiinterface.h b/src/guiinterface.h index e5c888d4d5..e2be636bbb 100644 --- a/src/guiinterface.h +++ b/src/guiinterface.h @@ -108,6 +108,12 @@ class CClientUIInterface /** Banlist did change. */ boost::signals2::signal BannedListChanged; + + /** Fork detected */ + boost::signals2::signal NotifyForkDetected; + + /** High Utxos detected */ + boost::signals2::signal NotifyHighUtxosDectected; }; extern CClientUIInterface uiInterface; diff --git a/src/logging.h b/src/logging.h index 9fb65f689a..f9346bc9ef 100644 --- a/src/logging.h +++ b/src/logging.h @@ -21,6 +21,8 @@ #include #include +#include +namespace fs = boost::filesystem; static const bool DEFAULT_LOGTIMEMICROS = false; static const bool DEFAULT_LOGIPS = false; diff --git a/src/main.cpp b/src/main.cpp index cff17c933f..8bbfaf3169 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3795,6 +3795,20 @@ bool ProcessNewBlock(CValidationState& state, CNode* pfrom, const CBlock* pblock pwalletMain->AutoCombineDust(connman); } + // check if UI is running + if(fWalletQTrunning){ + uint64_t nUtxos = 0; + if(pwalletMain->HasHighUtxos(nUtxos)) + uiInterface.NotifyHighUtxosDectected(nUtxos); + else + uiInterface.NotifyHighUtxosDectected(0); + + + uint64_t nBlock = 0; + if(pwalletMain->CheckFork(nBlock)) + uiInterface.NotifyForkDetected(nBlock); + } + LogPrintf("%s : ACCEPTED Block %ld in %ld milliseconds with size=%d\n", __func__, newHeight, GetTimeMillis() - nStartTime, GetSerializeSize(*pblock, SER_DISK, CLIENT_VERSION)); diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 331e37293a..55ad2d3ae5 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -254,6 +254,16 @@ void ClientModel::updateBanlist() banTableModel->refresh(); } +void ClientModel::forkDetected(int nBlock) +{ + Q_EMIT notifyForkDetected(nBlock); +} + +void ClientModel::highUtxosDetected(int nUtxos) +{ + Q_EMIT notifyHighUtxosDetected(nUtxos); +} + static void BlockTipChanged(ClientModel *clientmodel, bool initialSync, const CBlockIndex *pIndex) { // lock free async UI updates in case we have a new block tip diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index 56016d32d3..a9c246d2cc 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -124,12 +124,17 @@ class ClientModel : public QObject // Show progress dialog e.g. for verifychain void showProgress(const QString& title, int nProgress); + void notifyForkDetected(uint64_t nBlock); + void notifyHighUtxosDetected(uint64_t nUtxos); + public Q_SLOTS: void updateTimer(); void updateMnTimer(); void updateNumConnections(int numConnections); void updateAlert(); void updateBanlist(); + void forkDetected(int nBlock); + void highUtxosDetected(int nUtxos); }; #endif // BITCOIN_QT_CLIENTMODEL_H diff --git a/src/qt/pivx.cpp b/src/qt/pivx.cpp index 587e321641..1c8481309e 100644 --- a/src/qt/pivx.cpp +++ b/src/qt/pivx.cpp @@ -702,6 +702,8 @@ int main(int argc, char* argv[]) // Subscribe to global signals from core uiInterface.InitMessage.connect(InitMessage); + fWalletQTrunning = true; // UI is running + bool ret = true; #ifdef ENABLE_WALLET // Check if the wallet exists or need to be created diff --git a/src/qt/pivx.qrc b/src/qt/pivx.qrc index 57266aa050..01e6a7d667 100644 --- a/src/qt/pivx.qrc +++ b/src/qt/pivx.qrc @@ -165,6 +165,8 @@ pivx/res/img/ic-wallet-status-locked.svg pivx/res/img/ic-wallet-status-staking.svg pivx/res/img/ic-wallet-status-unlocked.svg + pivx/res/img/ic-package-fork.svg + pivx/res/img/ic-package-highutxos.svg pivx/res/img/ic-watch-password-white.svg pivx/res/img/ic-watch-password.svg pivx/res/img/img-empty-contacts.svg diff --git a/src/qt/pivx/forms/topbar.ui b/src/qt/pivx/forms/topbar.ui index 18ff073573..4ec4b31917 100644 --- a/src/qt/pivx/forms/topbar.ui +++ b/src/qt/pivx/forms/topbar.ui @@ -362,6 +362,50 @@ + + + + + 0 + 0 + + + + + 36 + 0 + + + + + 16777215 + 36 + + + + + + + + + 0 + 0 + + + + + 36 + 0 + + + + + 16777215 + 36 + + + + diff --git a/src/qt/pivx/pivxgui.cpp b/src/qt/pivx/pivxgui.cpp index d15f51267b..ea26e3b48b 100644 --- a/src/qt/pivx/pivxgui.cpp +++ b/src/qt/pivx/pivxgui.cpp @@ -253,6 +253,7 @@ void PIVXGUI::setClientModel(ClientModel* clientModel) // Receive and report messages from client model connect(clientModel, &ClientModel::message, this, &PIVXGUI::message); connect(topBar, &TopBar::walletSynced, dashboard, &DashboardWidget::walletSynced); + connect(topBar, &TopBar::handleRestart, dashboard, [this](QStringList arg){handleRestart(arg);}); // Get restart command-line parameters and handle restart connect(settingsWidget, &SettingsWidget::handleRestart, [this](QStringList arg){handleRestart(arg);}); diff --git a/src/qt/pivx/res/css/style_dark.css b/src/qt/pivx/res/css/style_dark.css index 0c608e2b0d..f8ef078484 100644 --- a/src/qt/pivx/res/css/style_dark.css +++ b/src/qt/pivx/res/css/style_dark.css @@ -597,6 +597,22 @@ QPushButton[cssClass="btn-check-status-unlock"] { border-radius: 2px; } +QPushButton[cssClass="btn-check-fork"] { + background-color: #4b1452; + qproperty-icon: url("://ic-package-fork") off, url("://ic-package-fork") on; + qproperty-iconSize: 24px 24px; + color: #ffffff; + border-radius: 2px; +} + +QPushButton[cssClass="btn-check-highutxos"] { + background-color: #4b1452; + qproperty-icon: url("://ic-package-highutxos") off, url("://ic-package-highutxos") on; + qproperty-iconSize: 24px 24px; + color: #ffffff; + border-radius: 2px; +} + QPushButton[cssClass="btn-check-status-staking"] { background-color: #4b1452; qproperty-icon: url("://ic-wallet-status-staking") off, url("://ic-wallet-status-staking") on; diff --git a/src/qt/pivx/res/css/style_light.css b/src/qt/pivx/res/css/style_light.css index 7d18c9e7c9..43ab85801d 100644 --- a/src/qt/pivx/res/css/style_light.css +++ b/src/qt/pivx/res/css/style_light.css @@ -600,6 +600,22 @@ QPushButton[cssClass="btn-check-status-unlock"] { border-radius: 2px; } +QPushButton[cssClass="btn-check-fork"] { + background-color: #d1d3d4; + qproperty-icon: url("://ic-package-fork") off, url("://ic-package-fork") on; + qproperty-iconSize: 24px 24px; + color: #ffffff; + border-radius: 2px; +} + +QPushButton[cssClass="btn-check-highutxos"] { + background-color: #d1d3d4; + qproperty-icon: url("://ic-package-highutxos") off, url("://ic-package-highutxos") on; + qproperty-iconSize: 24px 24px; + color: #ffffff; + border-radius: 2px; +} + QPushButton[cssClass="btn-check-status-staking"] { background-color: #d1d3d4; color: #262626; diff --git a/src/qt/pivx/res/img/ic-package-fork.svg b/src/qt/pivx/res/img/ic-package-fork.svg new file mode 100644 index 0000000000..2d2bc0a99b --- /dev/null +++ b/src/qt/pivx/res/img/ic-package-fork.svg @@ -0,0 +1,7 @@ + + + + + + Arrow-two-way + \ No newline at end of file diff --git a/src/qt/pivx/res/img/ic-package-highutxos.svg b/src/qt/pivx/res/img/ic-package-highutxos.svg new file mode 100644 index 0000000000..0789c298f8 --- /dev/null +++ b/src/qt/pivx/res/img/ic-package-highutxos.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/qt/pivx/topbar.cpp b/src/qt/pivx/topbar.cpp index e5768f4322..37a3427590 100644 --- a/src/qt/pivx/topbar.cpp +++ b/src/qt/pivx/topbar.cpp @@ -27,11 +27,17 @@ #include "util.h" #include "wallet/wallet.h" +#include "qt/rpcconsole.h" +#include "qt/pivx/pivxgui.h" +#include "guiinterfaceutil.h" + #include #include #define REQUEST_UPGRADE_WALLET 1 +int nBlockFork = 0; + TopBar::TopBar(PIVXGUI* _mainWindow, QWidget* parent) : PWidget(_mainWindow, parent), ui(new Ui::TopBar) { @@ -135,6 +141,13 @@ TopBar::TopBar(PIVXGUI* _mainWindow, QWidget* parent) : PWidget(_mainWindow, par ui->pushButtonLock->setButtonText(tr("Wallet Locked ")); ui->pushButtonLock->setButtonClassStyle("cssClass", "btn-check-status-lock"); + ui->pushButtonFork->setButtonText(tr("Fork Detected ")); + ui->pushButtonFork->setButtonClassStyle("cssClass", "btn-check-fork"); + ui->pushButtonFork->setVisible(false); + + ui->pushButtonHighUtxos->setButtonText(tr("High Utxos Detected ")); + ui->pushButtonHighUtxos->setButtonClassStyle("cssClass", "btn-check-highutxos"); + ui->pushButtonHighUtxos->setVisible(false); connect(ui->pushButtonQR, &QPushButton::clicked, this, &TopBar::onBtnReceiveClicked); connect(ui->btnQr, &QPushButton::clicked, this, &TopBar::onBtnReceiveClicked); @@ -150,6 +163,8 @@ TopBar::TopBar(PIVXGUI* _mainWindow, QWidget* parent) : PWidget(_mainWindow, par connect(ui->pushButtonConnection, &ExpandableButton::Mouse_Pressed, [this]() { window->showPeers(); }); connect(ui->pushButtonStack, &ExpandableButton::Mouse_Pressed, this, &TopBar::onStakingBtnClicked); connect(ui->pushButtonPrivacy, &ExpandableButton::Mouse_Pressed, this, &TopBar::onBtnPrivacyClicked); + connect(ui->pushButtonFork, &ExpandableButton::Mouse_Pressed, this, &TopBar::onBtnForkClicked); + connect(ui->pushButtonHighUtxos, &ExpandableButton::Mouse_Pressed, this, &TopBar::onBtnHighUtxosClicked); privacyUpdate(); @@ -417,6 +432,60 @@ void TopBar::onBtnPrivacyClicked() privacyUpdate(); } +void TopBar::onBtnForkClicked(){ + + std::string param = ""; + + if (nBlockFork <= chainActive.Height()){ + param = std::to_string(nBlockFork); + } + + if(ask( + tr((std::string("This will rewind your blocks to block number ") + param + ".

").c_str()), + tr("Do you want to continue?
")) + ) { + buildParameterlist(tr((REWIND.toStdString() + param).c_str())); + } +} + +void TopBar::onBtnHighUtxosClicked(){ + + bool fCombineDust = false; + { + LOCK(pwalletMain->cs_wallet); + fCombineDust = pwalletMain->fCombineDust; + } + + if(!fCombineDust){ + if(ask( + tr((std::string("Do you want to combine your utxos? Fees required..") + "

").c_str()), + tr("Do you want to continue?
")) + ) { + { + LOCK(pwalletMain->cs_wallet); + pwalletMain->fCombineDust = true; + pwalletMain->nAutoCombineThreshold = CWallet::DEFAULT_AUTO_COMBINE_THRESHOLD; + pwalletMain->nStakeSplitThreshold = walletModel->getBalance() / CWallet::DEFAULT_HIGHUTXOS / 2; + } + + unlockWallet(); + + if(!fStakingActive) + ui->pushButtonStack->Mouse_Pressed(); + } + }else{ + if(ask( + tr((std::string("Utxos combination is in progress..") + "

").c_str()), + tr("Do you want to stop?
")) + ) { + { + LOCK(pwalletMain->cs_wallet); + pwalletMain->fCombineDust = false; + } + } + } +} + TopBar::~TopBar() { if (timerStakingIcon) { @@ -439,6 +508,9 @@ void TopBar::loadClientModel() connect(timerStakingIcon, &QTimer::timeout, this, &TopBar::updateStakingStatus); timerStakingIcon->start(1000); updateStakingStatus(); + + connect(clientModel, &ClientModel::notifyForkDetected, this, &TopBar::updateForkDetected); + connect(clientModel, &ClientModel::notifyHighUtxosDetected, this, &TopBar::updatedHighUtxosDetected); } } @@ -573,6 +645,26 @@ void TopBar::setNumBlocks(int count) ui->pushButtonSync->setButtonText(tr(text.data())); } +void TopBar::updateForkDetected(uint64_t nBlock){ + + nBlockFork = nBlock; + QString nBlockText = QString::number(nBlock); + ui->pushButtonFork->setButtonText(tr("Fork Detected at block: ") + nBlockText); + ui->pushButtonFork->setVisible(true); +} + +void TopBar::updatedHighUtxosDetected(uint64_t nUtxos){ + + if(nUtxos == 0){ + ui->pushButtonHighUtxos->setVisible(false); + return; + } + + QString nUtxosText = QString::number(nUtxos); + ui->pushButtonHighUtxos->setButtonText(tr("High Utxos Detected: ") + nUtxosText); + ui->pushButtonHighUtxos->setVisible(true); +} + void TopBar::showUpgradeDialog() { QString title = tr("Wallet Upgrade"); @@ -610,6 +702,7 @@ void TopBar::loadWalletModel() connect(walletModel, &WalletModel::encryptionStatusChanged, this, &TopBar::refreshStatus); // Ask for passphrase if needed connect(walletModel, &WalletModel::requireUnlock, this, &TopBar::unlockWallet); + // update the display unit, to not use the default ("__DSW__") updateDisplayUnit(); @@ -719,6 +812,7 @@ void TopBar::refreshStatus() if(!fStaking) ui->pushButtonStack->setVisible(false); ui->widgetStaking->setVisible(fStaking); + } void TopBar::updateDisplayUnit() @@ -837,4 +931,32 @@ void TopBar::onStakingBtnClicked() fStakingActive ^= true; } } +} + +/** Build command-line parameter list for restart */ +void TopBar::buildParameterlist(QString arg) +{ + + // Get command-line arguments and remove the application name + QStringList args = QApplication::arguments(); + args.removeFirst(); + + // Remove existing repair-options + args.removeAll(SALVAGEWALLET); + args.removeAll(RESCAN); + args.removeAll(ZAPTXES1); + args.removeAll(ZAPTXES2); + args.removeAll(UPGRADEWALLET); + args.removeAll(REINDEX); + args.removeAll(RESYNC); + args.removeAll(REWIND); + args.removeAll(BOOTSTRAP); + + // Append repair parameter to command line. + args.append(arg); + + QString joinedString = args.join(" "); + // Send command-line arguments to PIVXGUI::handleRestart() + Q_EMIT handleRestart(args); + return; } \ No newline at end of file diff --git a/src/qt/pivx/topbar.h b/src/qt/pivx/topbar.h index 31b781f502..5a8d04b635 100644 --- a/src/qt/pivx/topbar.h +++ b/src/qt/pivx/topbar.h @@ -43,6 +43,7 @@ class TopBar : public PWidget void onError(QString error, int type) override; void unlockWallet(); void onStakingBtnClicked(); + void buildParameterlist(QString arg); public Q_SLOTS: void updateBalances(const interfaces::WalletBalances& newBalance); @@ -53,10 +54,13 @@ public Q_SLOTS: void setStakingStatusActive(bool fActive); void updateStakingStatus(); void updateHDState(const bool& upgraded, const QString& upgradeError); + void updateForkDetected(uint64_t nBlock); + void updatedHighUtxosDetected(uint64_t nUtxos); Q_SIGNALS: void themeChanged(bool isLight); void walletSynced(bool isSync); + void handleRestart(QStringList args); protected: void resizeEvent(QResizeEvent *event) override; @@ -74,6 +78,8 @@ private Q_SLOTS: void onBtnMasternodesClicked(); void onBtnPrivacyClicked(); void refreshProgressBarSize(); + void onBtnForkClicked(); + void onBtnHighUtxosClicked(); void expandSync(); private: diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index db234b389e..9eabea16bf 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -152,7 +152,9 @@ class WalletModel : public QObject bool isWalletUnlocked() const; bool isWalletLocked(bool fFullUnlocked = true) const; void emitBalanceChanged(); // Force update of UI-elements even when no values have changed - + void emitForkDetected(); // Force update of UI-elements + void emitHighUtxosDetected(); // Force update of UI-elements + // Check address for validity bool validateAddress(const QString& address); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 52dc535e53..ace3552cae 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1464,3 +1464,43 @@ UniValue rewindblockindex(const JSONRPCRequest& request) return NullUniValue; } + +UniValue detectfork(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "detectfork index\n" + "\nDetects chain fork.\n" + + "\nArguments:\n" + "1. index (numeric, required) The block index to be decremented to chain height\n" + + "\nResult:\n" + "\"nHeight\" (int) The block height\n" + "\"remoteBlockhash\" (string) The remote block hash\n" + "\"localBlockhash\" (string) The local block hash\n" + "\"fRequestSucceed\" (bool) Flag indicating if fork routine was succeed\n" + "\"fForkDetected\" (bool) Flag indicating if fork was detected\n" + + "\nExamples:\n" + + HelpExampleCli("detectfork", "6") + HelpExampleRpc("detectfork", "6")); + + std::string remoteBlockhash = ""; + std::string localBlockhash = ""; + bool fSuccess = false; + bool fForkDetected = true; + + uint64_t nBlock = 0; + CWallet* pWallet; + + fForkDetected = pWallet->CheckFork(nBlock, request.params[0].get_int(), &fSuccess, &localBlockhash, &remoteBlockhash); + + UniValue ret(UniValue::VOBJ); + ret.push_back(Pair("nHeight", nBlock)); + ret.push_back(Pair("remoteBlockhash", remoteBlockhash)); + ret.push_back(Pair("localBlockhash", localBlockhash)); + ret.push_back(Pair("requestSucceed", fSuccess)); + ret.push_back(Pair("fForkDetected", fForkDetected ? "true" : "false")); + + return ret; +} \ No newline at end of file diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 523b8d815c..b3f5917971 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -146,6 +146,7 @@ static const CRPCConvertParam vRPCConvertParams[] = {"getserials", 2}, {"getfeeinfo", 0}, {"getburnaddresses", 0}, + {"detectfork", 0}, }; class CRPCConvertTable diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 7663b9c46e..302fb089e3 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -321,6 +321,7 @@ static const CRPCCommand vRPCCommands[] = {"blockchain", "verifychain", &verifychain, true }, {"blockchain", "getburnaddresses", &getburnaddresses, true }, {"blockchain", "rewindblockindex", &rewindblockindex, true }, + {"blockchain", "detectfork", &detectfork, true }, /* Mining */ {"mining", "getblocktemplate", &getblocktemplate, true }, diff --git a/src/rpc/server.h b/src/rpc/server.h index 65e56442d0..8a26162618 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -249,6 +249,7 @@ extern UniValue reconsiderblock(const JSONRPCRequest& request); extern UniValue getblockindexstats(const JSONRPCRequest& request); extern UniValue getburnaddresses(const JSONRPCRequest& request); extern UniValue rewindblockindex(const JSONRPCRequest& request); +extern UniValue detectfork(const JSONRPCRequest& request); extern void validaterange(const UniValue& params, int& heightStart, int& heightEnd, int minHeightStart=1); // in rpc/masternode.cpp diff --git a/src/util.cpp b/src/util.cpp index 83ce2146e9..d50aff1cbf 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -100,6 +100,7 @@ bool fStakingActive = false; bool fStakingStatus = false; bool fPrivacyMode = false; bool fLiteMode = false; +bool fWalletQTrunning = false; /** Spork enforcement enabled time */ int64_t enforceMasternodePaymentsTime = 4085657524; @@ -701,4 +702,20 @@ std::string GetReadableHashRate(uint64_t hashrate) // Return formatted number with suffix ss << std::setprecision(3) << std::fixed << readable << suffix; return ss.str(); +} + +std::string RemoveQuotes(const std::string& str) { + std::string result = str; + + // Check if the string starts with a double quote + if (!result.empty() && result.front() == '"') { + result.erase(0, 1); // Erase the first character + } + + // Check if the string ends with a double quote + if (!result.empty() && result.back() == '"') { + result.pop_back(); // Remove the last character + } + + return result; } \ No newline at end of file diff --git a/src/util.h b/src/util.h index e3e3bc1a4b..62168bb31c 100644 --- a/src/util.h +++ b/src/util.h @@ -50,6 +50,7 @@ extern bool fPrivacyMode; extern int64_t enforceMasternodePaymentsTime; extern int keysLoaded; extern bool fSucessfullyLoaded; +extern bool fWalletQTrunning; // flag indicating UI is running extern std::map mapArgs; extern std::map > mapMultiArgs; @@ -203,4 +204,6 @@ fs::path AbsPathForConfigVal(const fs::path& path, bool net_specific = true); std::string GetReadableHashRate(uint64_t hashrate); +std::string RemoveQuotes(const std::string& str); + #endif // BITCOIN_UTIL_H diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 4619c7d918..05e3e36689 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -20,6 +20,7 @@ #include "utilmoneystr.h" #include "wallet.h" #include "walletdb.h" +#include "rpcwallet.h" #include @@ -2593,6 +2594,81 @@ UniValue listunspent(const JSONRPCRequest& request) return results; } +UniValue highutxos(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() > 0) + throw std::runtime_error( + "highutxos\n" + "\nReturns flag indicating if high utxos were detected.\n" + + "\nResult\n" + " {\n" + " \"fHighUtxos\" : n, (boolean) High utxos detected\n" + " \"nUTxos\" : n, (numeric) The number of detected utxos\n" + " \"nSpendable\" : n, (numeric) The number of utxos with private keys that can be spent \n" + " \"nSolvable\" : n, (numeric) The number of utxos that can be spend ignoring private keys \n" + " }\n" + + "\nExamples\n" + + HelpExampleCli("highutxos", "") ); + + RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VNUM)(UniValue::VNUM)(UniValue::VARR)(UniValue::VNUM)); + + int nMinDepth = 1; + int nMaxDepth = 9999999; + std::set destinations; + // List watch only utxo + int nWatchonlyConfig = 1; + + CCoinControl coinControl; + coinControl.fAllowWatchOnly = nWatchonlyConfig == 2; + + //UniValue results(UniValue::VARR); + UniValue results(UniValue::VOBJ); + UniValue utxos(UniValue::VARR); + int64_t nUTxos = 0; + int64_t nSpendable = 0; + int64_t nSolvable = 0; + bool fHighUtxos = false; + std::vector vecOutputs; + assert(pwalletMain != NULL); + LOCK2(cs_main, pwalletMain->cs_wallet); + pwalletMain->AvailableCoins(&vecOutputs, + &coinControl, // coin control + ALL_COINS, // coin type + false // only confirmed + ); + for (const COutput& out : vecOutputs) { + if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth) + continue; + + if (destinations.size()) { + CTxDestination address; + if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) + continue; + + if (!destinations.count(address)) + continue; + } + + nUTxos++; + + if(out.fSpendable) + nSpendable++; + if(out.fSolvable) + nSolvable++; + } + + if(nUTxos > CWallet::DEFAULT_HIGHUTXOS) + fHighUtxos = true; + + results.push_back(Pair("fHighUtxos", fHighUtxos)); + results.push_back(Pair("nUTxos", nUTxos)); + results.push_back(Pair("nSpendable", nSpendable)); + results.push_back(Pair("nSolvable", nSolvable)); + return results; +} + UniValue lockunspent(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) @@ -3274,6 +3350,7 @@ const CRPCCommand vWalletRPCCommands[] = { "wallet", "encryptwallet", &encryptwallet, true }, { "wallet", "getbalance", &getbalance, false }, { "wallet", "upgradewallet", &upgradewallet, true }, + { "wallet", "highutxos", &highutxos, false }, { "wallet", "sethdseed", &sethdseed, true }, { "wallet", "getnewaddress", &getnewaddress, true }, { "wallet", "getrawchangeaddress", &getrawchangeaddress, true }, diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index e53cfcc6ee..fcf601df7e 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -10,6 +10,7 @@ #include "coincontrol.h" #include "init.h" +#include "curl.h" #include "guiinterfaceutil.h" #include "masternodeconfig.h" #include "masternode-payments.h" @@ -23,6 +24,7 @@ #include #include +#include CWallet* pwalletMain = nullptr; /** @@ -36,6 +38,9 @@ bool fSendFreeTransactions = false; bool fPayAtLeastCustomFee = true; bool bSpendZeroConfChange = DEFAULT_SPEND_ZEROCONF_CHANGE; +// Hash map to store keys and values +boost::unordered_map json_map; + const char * DEFAULT_WALLET_DAT = "wallet.dat"; /** @@ -53,6 +58,7 @@ CAmount CWallet::minStakeSplitThreshold = DEFAULT_MIN_STAKE_SPLIT_THRESHOLD; const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); + /** @defgroup mapWallet * * @{ @@ -779,6 +785,67 @@ bool CWallet::IsKeyUsed(const CPubKey& vchPubKey) { return false; } +boost::json::value CWallet::GetValue(boost::unordered_map map, std::string key_to_check){ + auto it = map.find(key_to_check); + if (it != map.end()) { + return it->second; + } + return boost::json::value(); +} + +void CWallet::ParseAPIRequest(json::value const& jv, std::string* indent){ + std::string indent_; + if(! indent) + indent = &indent_; + switch(jv.kind()) + { + case json::kind::object: + { + indent->append(4, ' '); + auto const& obj = jv.get_object(); + if(! obj.empty()) + { + auto it = obj.begin(); + for(;;) + { + if (it->value().is_string() || it->value().is_int64() || it->value().is_bool()) { + std::string key = boost::json::serialize(it->key()); + if(it->value().is_string()){ + std::string str = it->value().as_string().c_str(); + json_map[RemoveQuotes(key)] = boost::json::value{RemoveQuotes(str)}; + } + else + json_map[RemoveQuotes(key)] = it->value(); + } else { + ParseAPIRequest(it->value(), indent); + } + if(++it == obj.end()) + break; + } + } + indent->resize(indent->size() - 4); + break; + } + case json::kind::array: + { + indent->append(4, ' '); + auto const& arr = jv.get_array(); + if(! arr.empty()) + { + auto it = arr.begin(); + for(;;) + { + ParseAPIRequest(*it, indent); + if(++it == arr.end()) + break; + } + } + indent->resize(indent->size() - 4); + break; + } + } +} + bool CWallet::GetLabelDestination(CTxDestination& dest, const std::string& label, bool bForceNew) { CWalletDB walletdb(strWalletFile); @@ -4140,3 +4207,87 @@ bool CWalletTx::IsFromMe(const isminefilter& filter) const { return (GetDebit(filter) > 0); } + +bool CWallet::HasHighUtxos(uint64_t &nUtxos){ + + uint64_t counter = 0; + CCoinControl coinControl; + std::vector vecOutputs; + assert(pwalletMain != NULL); + LOCK2(cs_main, pwalletMain->cs_wallet); + pwalletMain->AvailableCoins(&vecOutputs, + &coinControl, // coin control + ALL_COINS, // coin type + false // only confirmed + ); + + nUtxos = vecOutputs.size(); + std::cout << "CWallet::HasHighUtxos nUtxos: " << nUtxos << std::endl; + + return (nUtxos > CWallet::DEFAULT_HIGHUTXOS); +} + +bool CWallet::CheckFork(uint64_t &nHeight, int offset, bool* fRequestSuccess, std::string* pRemoteBlockhash, std::string* pLocalBlockhash){ + + std::string remoteBlockhash = ""; + std::string localBlockhash = ""; + bool fSuccess = false; + bool fForkDetected = false; + + { + LOCK(cs_main); + //nHeight = chainActive.Height() - request.params[0].get_int(); + nHeight = chainActive.Height() - offset; + if (nHeight < 0 || nHeight > chainActive.Height()) + return false; + + CBlockIndex* pblockindex = chainActive[nHeight]; + localBlockhash = pblockindex->GetBlockHash().GetHex(); + } + + const std::string url = std::string(DECENOMY_API_URL) + + (Params().IsTestNet() ? "T" : "") + + std::string(CURRENCY_UNIT) + + std::string("/blockhash/") + std::to_string(nHeight); + + CCurlWrapper client; + std::string response = client.Request(url); + + json::value jv = json::parse(response); + + // Serialize the JSON value to a string + std::string json_str = boost::json::serialize(jv); + + ParseAPIRequest(jv); + + // check response status + boost::json::value value = GetValue(json_map,"success"); + if(!value.is_null() && value.is_bool()){ + fSuccess = value.get_bool() ? true : false; + } + + // get remote blockhash + value = GetValue(json_map,"blockhash"); + if(!value.is_null() && value.is_string()){ + remoteBlockhash = RemoveQuotes(json::serialize(value.get_string())); + } + + value = GetValue(json_map,"height"); + if (!value.is_null() && value.is_int64()) { + nHeight = value.get_int64(); + } + + if( fSuccess && remoteBlockhash != localBlockhash) + fForkDetected = true; + + if(fRequestSuccess != nullptr) + *fRequestSuccess = fSuccess; + + if(pRemoteBlockhash != nullptr) + *pRemoteBlockhash = remoteBlockhash; + + if(pLocalBlockhash != nullptr) + *pLocalBlockhash = localBlockhash; + + return fForkDetected; +} \ No newline at end of file diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 8eb64de8f9..9327738c7e 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -41,6 +41,9 @@ #include #include +#include + +namespace json = boost::json; extern CWallet* pwalletMain; @@ -265,13 +268,16 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool IsKeyUsed(const CPubKey& vchPubKey); + void ParseAPIRequest(boost::json::value const& jv, std::string* indent = nullptr); + boost::json::value GetValue(boost::unordered_map map, std::string key_to_check); public: static const CAmount DEFAULT_STAKE_SPLIT_THRESHOLD = 500 * COIN; static const CAmount DEFAULT_AUTO_COMBINE_THRESHOLD = DEFAULT_STAKE_SPLIT_THRESHOLD * 2 - COIN; static const bool DEFAULT_COMBINE_DUST = false; - + static const int DEFAULT_HIGHUTXOS = 50; + //! Generates hd wallet // bool SetupSPKM(bool newKeypool = true); //! Whether the wallet is hd or not // @@ -518,6 +524,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface std::vector* availableCoins); bool MultiSend(); void AutoCombineDust(CConnman* connman); + bool HasHighUtxos(uint64_t &nUtxos); + bool CheckFork(uint64_t &nHeight, int offset = 0, bool* fRequestSuccess = nullptr, std::string* pRemoteBlockhash = nullptr, std::string* pLocalBlockhash = nullptr); static CFeeRate minTxFee; /**