diff --git a/src/DynexCNCore/Auth.cpp b/src/DynexCNCore/Auth.cpp index df4af036..70cd756b 100644 --- a/src/DynexCNCore/Auth.cpp +++ b/src/DynexCNCore/Auth.cpp @@ -52,78 +52,86 @@ using namespace Logging; namespace DynexCN { - // mallob block authentication endpoints - static const std::vector mallob_endpoints{ "https://networkv2.dynexcoin.org", "https://node.dynexcoin.org", "https://node2.dynexcoin.org", "https://network.dynexcoin.org" }; - static const int mallob_timeout = 20; - - // curl return value function - static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) { - ((std::string*)userp)->append((char*)contents, size * nmemb); - return size * nmemb; - } +static std::vector mallob_endpoints{ "https://networkv2.dynexcoin.org", "https://node.dynexcoin.org", "https://node2.dynexcoin.org", "https://network.dynexcoin.org" }; +static const int mallob_timeout = 3; +static const bool update_default_endpoint = false; + +// curl return value function +static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) { + ((std::string*)userp)->append((char*)contents, size * nmemb); + return size * nmemb; +} - // block authentication function, uses all auth endpoints - bool AuthBlock(uint32_t height, uint32_t nonce, ILogger& log) { +bool AuthBlock(uint32_t height, uint32_t nonce, ILogger& log) { static LoggerRef logger(log, "mallob"); + static CURL *curl = curl_easy_init(); // curl_easy_cleanup(curl) + static struct curl_slist* slist = curl_slist_append(NULL, "Accept: application/json"); // curl_slist_free_all + + if (!curl) { + logger(ERROR) << "Curl init error"; + return false; + } + + if (!height || height == UINT32_MAX) return false; - struct curl_slist *list = NULL; - list = curl_slist_append(list, "Accept: application/json"); - list = curl_slist_append(list, "Content-type: application/json"); + //curl_easy_reset(curl); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, mallob_timeout); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, mallob_timeout); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + + curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); + curl_easy_setopt(curl, CURLOPT_MAXAGE_CONN, 1800L); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 60L); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 60L); std::stringstream ss; ss << std::hex << std::setfill('0') << std::setw(8) << __builtin_bswap32(nonce); - for (const auto& endpoint: mallob_endpoints) { + for (size_t i = 0; i < mallob_endpoints.size(); ++i) { - std::string url = endpoint + "/api/v2/node?method=verify_block&height=" + std::to_string(height) + "&nonce=" + ss.str(); + std::string url = mallob_endpoints[i] + "/api/v2/node?method=verify_block&height=" + std::to_string(height) + "&nonce=" + ss.str(); logger(DEBUGGING) << "Mallob request: " << url; - CURL *curl = curl_easy_init(); - - if (curl) { - auto t1 = std::chrono::high_resolution_clock::now(); - std::string readBuffer; - curl_easy_setopt(curl, CURLOPT_URL, url.c_str() ); - curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, mallob_timeout); - curl_easy_setopt(curl, CURLOPT_TIMEOUT, mallob_timeout); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - auto res = curl_easy_perform(curl); - curl_easy_cleanup(curl); - // measure response time: - auto t2 = std::chrono::high_resolution_clock::now(); - int resp = std::chrono::duration_cast(t2 - t1).count(); - // parse result: - if (res != CURLE_OK) { - logger(ERROR) << "Mallob curl error: " << curl_easy_strerror(res) << "[" << resp << "ms]"; - } else { - logger(INFO) << "Authentication response received [" << resp << "ms] [" << endpoint << "]"; - logger(DEBUGGING) << "Mallob answer: " << readBuffer; - try { - std::stringstream stream(readBuffer); - JsonValue json; - stream >> json; - if (json.contains("status")) { - if (json("status").getBool()) { - logger(DEBUGGING) << "Block " << height << " authorized"; - return true; - } else { - logger(WARNING) << "Block " << height << " not authorized"; - return false; - } + std::string readBuffer; + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + + auto t1 = std::chrono::high_resolution_clock::now(); + auto res = curl_easy_perform(curl); + auto t2 = std::chrono::high_resolution_clock::now(); + int resp = std::chrono::duration_cast(t2 - t1).count(); + if (res != CURLE_OK) { + logger(ERROR) << "Mallob curl error: " << curl_easy_strerror(res); + } else { + //logger(INFO) << "Authentication response received [" << resp << "ms] [" << mallob_endpoints[i] << "]"; + logger(DEBUGGING) << "Mallob answer: " << readBuffer; + try { + std::stringstream stream(readBuffer); + JsonValue json; + stream >> json; + if (json.contains("status")) { + if (update_default_endpoint && i) { + logger(WARNING) << "Switching default endpoint to " << mallob_endpoints[i]; + mallob_endpoints[0].swap(mallob_endpoints[i]); // update first entry + } + if (json("status").getBool()) { + logger(INFO) << "Block " << height << " authorized [" << mallob_endpoints[i] << "] [" << resp << "ms]"; + return true; + } else { + logger(WARNING) << "Block " << height << " not authorized [" << mallob_endpoints[i] << "] [" << resp << "ms]"; + return false; } - } - catch(const std::exception &e) { - logger(ERROR) << "Mallob json parse error: " << readBuffer; } } - logger(ERROR) << "Block " << height << " authorization error"; + catch(const std::exception &e) { + logger(ERROR) << "Mallob json parse error: " << readBuffer; + } } + logger(ERROR) << "Block " << height << " authorization error [" << mallob_endpoints[i] << "] [" << resp << "ms]"; } return false; } } - diff --git a/src/DynexCNCore/Blockchain.cpp b/src/DynexCNCore/Blockchain.cpp index 6e115b85..5770806f 100644 --- a/src/DynexCNCore/Blockchain.cpp +++ b/src/DynexCNCore/Blockchain.cpp @@ -57,8 +57,6 @@ using namespace Logging; using namespace Common; -std::unordered_map PrivacyMemBC; - namespace { std::string appendPath(const std::string& path, const std::string& fileName) { @@ -2090,20 +2088,20 @@ bool Blockchain::pushBlock(const Block& blockData, block_verification_context& b bool Blockchain::check_non_privacy(const Transaction& tx) { // purge?: - const Crypto::Hash _txhash = getObjectHash(tx); + const Crypto::Hash txhash = getObjectHash(tx); if (PrivacyMemBC.size()>1000) { logger(DEBUGGING) << "DEBUG (Blockchain.cpp): memory purged "; PrivacyMemBC.clear(); } else { // check only once - auto it = PrivacyMemBC.find(_txhash); + auto it = PrivacyMemBC.find(txhash); if (it != PrivacyMemBC.end()) { - logger(DEBUGGING) << "DEBUG (Blockchain.cpp): using check_non_privacy memory for transaction " << getObjectHash(tx); + logger(DEBUGGING) << "DEBUG (Blockchain.cpp): using check_non_privacy memory for transaction " << txhash; return it->second; } } - logger(DEBUGGING) << "DEBUG (Blockchain.cpp): check_non_privacy invoked for transaction " << getObjectHash(tx); + logger(DEBUGGING) << "DEBUG (Blockchain.cpp): check_non_privacy invoked for transaction " << txhash; DynexCN::TransactionPrefix transaction = *static_cast(&tx); std::vector txExtraFields; @@ -2130,8 +2128,8 @@ bool Blockchain::check_non_privacy(const Transaction& tx) { // enforce only non-privacy tx: if (amount.empty() || to_address.empty()) { { - logger(ERROR) << "Transaction " << getObjectHash(tx) << " rejected: privacy transaction"; - PrivacyMemBC.insert({_txhash, false}); + logger(ERROR) << "Transaction " << txhash << " rejected: privacy transaction"; + PrivacyMemBC.insert({txhash, false}); return false; } } @@ -2145,14 +2143,14 @@ bool Blockchain::check_non_privacy(const Transaction& tx) { if (!Crypto::generate_key_derivation(address.viewPublicKey, tx_key, derivation)) { logger(ERROR) << "Failed to generate key derivation from transaction"; - PrivacyMemBC.insert({_txhash, false}); + PrivacyMemBC.insert({txhash, false}); return false; } // look for outputs uint64_t received(0); size_t keyIndex(0); - std::vector outputs; + //std::vector outputs; try { for (const TransactionOutput& o : transaction.outputs) { if (o.target.type() == typeid(KeyOutput)) { @@ -2161,7 +2159,7 @@ bool Blockchain::check_non_privacy(const Transaction& tx) { derive_public_key(derivation, keyIndex, address.spendPublicKey, pubkey); if (pubkey == out_key.key) { received += o.amount; - outputs.push_back(o); + //outputs.push_back(o); } } ++keyIndex; @@ -2170,18 +2168,18 @@ bool Blockchain::check_non_privacy(const Transaction& tx) { catch (...) { logger(ERROR) << "Failed to parse transaction outputs"; - PrivacyMemBC.insert({_txhash, false}); + PrivacyMemBC.insert({txhash, false}); return false; } if ((uint64_t)amount[i] != received) { logger(ERROR) << "Error: transaction addresses & output amount mismatch"; - PrivacyMemBC.insert({_txhash, false}); + PrivacyMemBC.insert({txhash, false}); return false; } } - logger(DEBUGGING) << "PASSED non_privacy transaction validation: " << getObjectHash(tx); - PrivacyMemBC.insert({_txhash, true}); + logger(DEBUGGING) << "PASSED non_privacy transaction validation: " << txhash; + PrivacyMemBC.insert({txhash, true}); return true; } diff --git a/src/DynexCNCore/Blockchain.h b/src/DynexCNCore/Blockchain.h index c1030873..a1b06d1e 100644 --- a/src/DynexCNCore/Blockchain.h +++ b/src/DynexCNCore/Blockchain.h @@ -250,9 +250,13 @@ namespace DynexCN { bool is_tx_spendtime_unlocked(uint64_t unlock_time); bool is_tx_spendtime_unlocked(uint64_t unlock_time, uint32_t height); bool check_tx_inputs_keyimages_domain(const Crypto::KeyImage& keyImage); + // non-privacy functions: + bool check_non_privacy(const Transaction& tx); private: + std::unordered_map PrivacyMemBC; + struct MultisignatureOutputUsage { TransactionIndex transactionIndex; uint16_t outputIndex; @@ -382,8 +386,6 @@ namespace DynexCN { bool checkTransactionInputs(const Transaction& tx, const Crypto::Hash& tx_prefix_hash, uint32_t* pmax_used_block_height = NULL); bool checkTransactionInputs(const Transaction& tx, uint32_t* pmax_used_block_height = NULL); const TransactionEntry& transactionByIndex(TransactionIndex index); - // non-privacy functions: - bool check_non_privacy(const Transaction& tx); bool pushBlock(const Block& blockData, block_verification_context& bvc); bool pushBlock(const Block& blockData, const std::vector& transactions, block_verification_context& bvc); bool pushBlock(BlockEntry& block); diff --git a/src/DynexCNCore/Core.cpp b/src/DynexCNCore/Core.cpp index ab171ee4..5fc39f8f 100644 --- a/src/DynexCNCore/Core.cpp +++ b/src/DynexCNCore/Core.cpp @@ -1216,106 +1216,6 @@ bool core::getPaymentId(const Transaction& transaction, Crypto::Hash& paymentId) return getPaymentIdFromTransactionExtraNonce(extraNonce.nonce, paymentId); } -bool core::check_non_privacy(const Transaction& tx) { - - // purge?: - const Crypto::Hash _txhash = getObjectHash(tx); - if (PrivacyMem.size()>1000) { - logger(DEBUGGING) << "DEBUG (Core.cpp): memory purged "; - PrivacyMem.clear(); - } else { - // check only once - auto it = PrivacyMem.find(_txhash); - if (it != PrivacyMem.end()) { - logger(DEBUGGING) << "DEBUG (Core.cpp): using check_non_privacy memory for transaction " << getObjectHash(tx); - return it->second; - } - } - - logger(DEBUGGING) << "DEBUG (Core.cpp): check_non_privacy invoked for transaction " << getObjectHash(tx); - - DynexCN::TransactionPrefix transaction = *static_cast(&tx); - - std::vector txExtraFields; - parseTransactionExtra(tx.extra, txExtraFields); - AccountPublicAddress from_address; - Crypto::SecretKey tx_key; - std::vector to_address; - std::vector amount; - - for (const TransactionExtraField& field : txExtraFields) { - if (typeid(TransactionExtraFromAddress) == field.type()) { - from_address = boost::get(field).address; - } else if (typeid(TransactionExtraToAddress) == field.type()) { - to_address.push_back(boost::get(field).address); - } else if (typeid(TransactionExtraAmount) == field.type()) { - std::vector amount_vec = boost::get(field).amount; - int64_t amtint = getAmountInt64(amount_vec); - amount.push_back(amtint); - } else if (typeid(TransactionExtraTxkey) == field.type()) { - tx_key = boost::get(field).tx_key; - } - } - - // enforce only non-privacy tx: - if (amount.empty() || to_address.empty()) { - { - logger(ERROR) << "Transaction " << getObjectHash(tx) << " rejected: privacy transaction"; - PrivacyMem.insert({_txhash, false}); - return false; - } - } - - // check for all destination address(es): - for (uint32_t i=0; i outputs; - try { - for (const TransactionOutput& o : transaction.outputs) { - if (o.target.type() == typeid(KeyOutput)) { - const KeyOutput out_key = boost::get(o.target); - Crypto::PublicKey pubkey; - derive_public_key(derivation, keyIndex, address.spendPublicKey, pubkey); - if (pubkey == out_key.key) { - received += o.amount; - outputs.push_back(o); - } - } - ++keyIndex; - } - } - catch (...) - { - logger(ERROR) << "Failed to parse transaction outputs"; - PrivacyMem.insert({_txhash, false}); - return false; - } - - if ((uint64_t)amount[i] != received) { - logger(ERROR) << "Error: transaction addresses & output amount mismatch"; - PrivacyMem.insert({_txhash, false}); - return false; - } - - } - logger(DEBUGGING) << "PASSED non_privacy transaction validation: " << getObjectHash(tx); - PrivacyMem.insert({_txhash, true}); - return true; -} - bool core::handleIncomingTransaction(const Transaction& tx, const Crypto::Hash& txHash, size_t blobSize, tx_verification_context& tvc, bool keptByBlock, uint32_t height) { if (!check_tx_syntax(tx)) { logger(INFO) << "WRONG TRANSACTION BLOB, Failed to check tx " << txHash << " syntax, rejected"; @@ -1348,7 +1248,7 @@ bool core::handleIncomingTransaction(const Transaction& tx, const Crypto::Hash& return false; } - if (!check_non_privacy(tx)) { + if (!m_blockchain.check_non_privacy(tx)) { logger(ERROR) << "Transaction verification failed: incorrect non-privacy data " << txHash << ", rejected"; tvc.m_verification_failed = true; return false; diff --git a/src/DynexCNCore/Core.h b/src/DynexCNCore/Core.h index 90fe5ee3..b06e7dfe 100644 --- a/src/DynexCNCore/Core.h +++ b/src/DynexCNCore/Core.h @@ -128,9 +128,6 @@ namespace DynexCN { static bool getPaymentId(const Transaction& transaction, Crypto::Hash& paymentId); - // non-privacy functions: - bool check_non_privacy(const Transaction& tx); - bool have_block(const Crypto::Hash& id) override; std::vector buildSparseChain() override; std::vector buildSparseChain(const Crypto::Hash& startBlockId) override; diff --git a/src/P2p/NetNode.cpp b/src/P2p/NetNode.cpp index 4b281f99..22d31a29 100644 --- a/src/P2p/NetNode.cpp +++ b/src/P2p/NetNode.cpp @@ -89,7 +89,7 @@ size_t get_random_index_with_fixed_probability(size_t max_index) { void addPortMapping(Logging::LoggerRef& logger, uint32_t port) { // Add UPnP port mapping - logger(INFO) << "Attempting to add IGD port mapping."; + logger(INFO) << "Attempting to add IGD port mapping for TCP " << std::to_string(port) << "."; int result; UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, 2, &result); UPNPUrls urls; @@ -595,7 +595,7 @@ namespace DynexCN if(m_external_port) logger(INFO) << "External port defined as " << m_external_port; - addPortMapping(logger, m_listeningPort); + addPortMapping(logger, m_external_port?m_external_port:m_listeningPort); return true; } @@ -721,31 +721,17 @@ namespace DynexCN return false; } - std::stringstream ss; - ss << CN_PROJECT_VERSION; - std::string lvs; - ss >> lvs; - // only allow current version - if (rsp.node_data.node_version!=lvs) { + if (rsp.node_data.node_version != CN_PROJECT_VERSION) { logger(Logging::DEBUGGING) << context << "COMMAND_HANDSHAKE Failed, wrong daemon version! (" << rsp.node_data.node_version << "), closing connection."; return false; } // --- - // Check for latest Daemon version (unused from here) - std::string remote_version = boost::replace_all_copy(rsp.node_data.node_version, ".", ""); - std::string remote_version_str = rsp.node_data.node_version; - remote_version_str.erase(std::remove(remote_version_str.begin(), remote_version_str.end(), '\n'), remote_version_str.end()); - - auto local_version = boost::replace_all_copy(lvs, ".", ""); - auto remote_ip = Common::ipAddressToString(context.m_remote_ip); - std::string min_version = "200"; - // Check if is trusted node if (std::find(std::begin(DynexCN::TRUSTED_NODES), std::end(DynexCN::TRUSTED_NODES), Common::ipAddressToString(context.m_remote_ip)) != std::end(DynexCN::TRUSTED_NODES)) { - logger(Logging::DEBUGGING, Logging::BRIGHT_BLUE) << context << "CONNECTING TO TRUSTED NODE! - Skip version check!"; + logger(Logging::DEBUGGING, Logging::BRIGHT_BLUE) << context << "CONNECTING TO TRUSTED NODE!"; } if (!handle_remote_peerlist(rsp.local_peerlist, rsp.node_data.local_time, context)) { @@ -1115,7 +1101,7 @@ namespace DynexCN if(!fix_time_delta(peerlist_, local_time, delta)) return false; logger(Logging::TRACE) << context << "REMOTE PEERLIST: TIME_DELTA: " << delta << ", remote peerlist size=" << peerlist_.size(); - logger(Logging::TRACE) << context << "REMOTE PEERLIST: " << print_peerlist_to_string(peerlist_); + //logger(Logging::TRACE) << context << "REMOTE PEERLIST: " << print_peerlist_to_string(peerlist_); return m_peerlist.merge_peerlist(peerlist_); } //----------------------------------------------------------------------------------- @@ -1303,9 +1289,18 @@ namespace DynexCN //fill response rsp.local_time = time(NULL); - m_peerlist.get_peerlist_head(rsp.local_peerlist); + std::list local_peerlist_new; + m_peerlist.get_peerlist_head(local_peerlist_new); + size_t cnt = local_peerlist_new.size(); + //only include out peers we did not already send + for (auto &pe : local_peerlist_new) + { + if (!context.sent_addresses.insert(pe.adr).second) + continue; + rsp.local_peerlist.push_back(std::move(pe)); + } m_payload_handler.get_payload_sync_data(rsp.payload_data); - logger(Logging::TRACE) << context << "COMMAND_TIMED_SYNC"; + logger(Logging::TRACE) << context << "COMMAND_TIMED_SYNC (peers: " << rsp.local_peerlist.size() << "/" << cnt << ")"; return 1; } //----------------------------------------------------------------------------------- @@ -1321,56 +1316,19 @@ namespace DynexCN } if (arg.node_data.network_id != m_network_id) { - add_host_fail(context.m_remote_ip); + //add_host_fail(context.m_remote_ip); logger(Logging::INFO) << context << "WRONG NETWORK AGENT CONNECTED! id=" << arg.node_data.network_id; context.m_state = DynexCNConnectionContext::state_shutdown; - block_host(context.m_remote_ip, 24 * 60 * 60); // 24h + block_host(context.m_remote_ip, 1 * 60 * 60); // 1h return 1; } + // Check if is trusted node + if (std::find(std::begin(DynexCN::TRUSTED_NODES), std::end(DynexCN::TRUSTED_NODES), Common::ipAddressToString(context.m_remote_ip)) != std::end(DynexCN::TRUSTED_NODES)) + { + logger(Logging::DEBUGGING, Logging::BRIGHT_BLUE) << context << "INCOMMING TRUSTED NODE DETECTED!"; + } - // By DYNEX - //Check for latest Daemon version - std::string remote_version = boost::replace_all_copy(arg.node_data.node_version, ".", ""); - - std::string remote_version_str = arg.node_data.node_version; - remote_version_str.erase(std::remove(remote_version_str.begin(), remote_version_str.end(), '\n'), remote_version_str.end()); - - std::stringstream ss; - ss << CN_PROJECT_VERSION; - std::string lvs; - ss >> lvs; - - auto local_version = boost::replace_all_copy(lvs, ".", ""); - auto remote_ip = Common::ipAddressToString(context.m_remote_ip); - std::string min_version = "200"; - - // Check if is trusted node - if (std::find(std::begin(DynexCN::TRUSTED_NODES), std::end(DynexCN::TRUSTED_NODES), Common::ipAddressToString(context.m_remote_ip)) != std::end(DynexCN::TRUSTED_NODES)) - { - logger(Logging::DEBUGGING, Logging::BRIGHT_BLUE) << context << "INCOMMING TRUSTED NODE DETECTED! - Skip version check!"; - } - // Else Continue checking versions - /*else - { - if(local_version == remote_version) - { - logger(Logging::DEBUGGING, Logging::BRIGHT_GREEN) << context << "GREAT!! Imcomming peer " << remote_ip << " are using same version!! (" << remote_version_str << ")"; - } - else if(remote_version > min_version) - { - logger(Logging::DEBUGGING, Logging::BRIGHT_GREEN) << context << "GREAT!! Imcomming peer " << remote_ip << " are using accepted version!! (" << remote_version_str << ")"; - } - else if((local_version > remote_version) && (remote_version < min_version)) - { - logger(DEBUGGING, Logging::BRIGHT_RED) << context << "Daemon version on imcomming peer " << remote_ip << " is not up to date! (" << remote_version_str << ") Closing connection..."; - context.m_state = DynexCNConnectionContext::state_shutdown; - return 1; - } - }*/ - - // Continue - if(!context.m_is_income) { add_host_fail(context.m_remote_ip); logger(Logging::DEBUGGING) << context << "COMMAND_HANDSHAKE came not from incoming connection"; @@ -1411,6 +1369,9 @@ namespace DynexCN //fill response m_peerlist.get_peerlist_head(rsp.local_peerlist); + for (const auto &e : rsp.local_peerlist) { + context.sent_addresses.insert(e.adr); + } get_local_node_data(rsp.node_data); m_payload_handler.get_payload_sync_data(rsp.payload_data); diff --git a/src/P2p/NetNode.h b/src/P2p/NetNode.h index 0d176780..8bcb320f 100644 --- a/src/P2p/NetNode.h +++ b/src/P2p/NetNode.h @@ -105,7 +105,8 @@ namespace DynexCN System::Context* context; PeerIdType peerId; System::TcpConnection connection; - + std::set sent_addresses; + P2pConnectionContext(System::Dispatcher& dispatcher, Logging::ILogger& log, System::TcpConnection&& conn) : context(nullptr), peerId(0), diff --git a/src/version.h.in b/src/version.h.in index 454ede53..d4fa0395 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -23,4 +23,4 @@ #define CN_CURRENCY_DISPLAY_NAME "DYNEX" #define CN_CURRENCY_TICKER "DNX" -#define CN_WALLET_REV ".3 (non-privacy)" +#define CN_WALLET_REV ".5 (non-privacy)"