Skip to content

Commit

Permalink
node performance update
Browse files Browse the repository at this point in the history
- reduced check_non_privacy() cpu usage;
- use check_non_privacy() from  blockchain in core (single cache);
- optimized peerlist processing (send only new ones);
- reduced host blocking to 1h on wrong network;
- significantly improved block auth response time;
- fixed upnp mapping with external port specified;
- removed some unused code;
  • Loading branch information
dynexcoin committed Dec 20, 2023
1 parent 8b2aacc commit d30c774
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 245 deletions.
122 changes: 65 additions & 57 deletions src/DynexCNCore/Auth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,78 +52,86 @@ using namespace Logging;

namespace DynexCN {

// mallob block authentication endpoints
static const std::vector<std::string> 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<std::string> 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<std::chrono::milliseconds>(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<std::chrono::milliseconds>(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;
}

}

28 changes: 13 additions & 15 deletions src/DynexCNCore/Blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@
using namespace Logging;
using namespace Common;

std::unordered_map<Crypto::Hash, bool> PrivacyMemBC;

namespace {

std::string appendPath(const std::string& path, const std::string& fileName) {
Expand Down Expand Up @@ -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<const TransactionPrefix*>(&tx);

std::vector<TransactionExtraField> txExtraFields;
Expand All @@ -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;
}
}
Expand All @@ -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<TransactionOutput> outputs;
//std::vector<TransactionOutput> outputs;
try {
for (const TransactionOutput& o : transaction.outputs) {
if (o.target.type() == typeid(KeyOutput)) {
Expand All @@ -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;
Expand All @@ -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;
}

Expand Down
6 changes: 4 additions & 2 deletions src/DynexCNCore/Blockchain.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Crypto::Hash, bool> PrivacyMemBC;

struct MultisignatureOutputUsage {
TransactionIndex transactionIndex;
uint16_t outputIndex;
Expand Down Expand Up @@ -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<Transaction>& transactions, block_verification_context& bvc);
bool pushBlock(BlockEntry& block);
Expand Down
102 changes: 1 addition & 101 deletions src/DynexCNCore/Core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<const TransactionPrefix*>(&tx);

std::vector<TransactionExtraField> txExtraFields;
parseTransactionExtra(tx.extra, txExtraFields);
AccountPublicAddress from_address;
Crypto::SecretKey tx_key;
std::vector<AccountPublicAddress> to_address;
std::vector<int64_t> amount;

for (const TransactionExtraField& field : txExtraFields) {
if (typeid(TransactionExtraFromAddress) == field.type()) {
from_address = boost::get<TransactionExtraFromAddress>(field).address;
} else if (typeid(TransactionExtraToAddress) == field.type()) {
to_address.push_back(boost::get<TransactionExtraToAddress>(field).address);
} else if (typeid(TransactionExtraAmount) == field.type()) {
std::vector<uint8_t> amount_vec = boost::get<TransactionExtraAmount>(field).amount;
int64_t amtint = getAmountInt64(amount_vec);
amount.push_back(amtint);
} else if (typeid(TransactionExtraTxkey) == field.type()) {
tx_key = boost::get<TransactionExtraTxkey>(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<to_address.size(); i++) {
AccountPublicAddress address = to_address[i];

// obtain key derivation
Crypto::KeyDerivation derivation;
if (!Crypto::generate_key_derivation(address.viewPublicKey, tx_key, derivation))
{
logger(ERROR) << "Failed to generate key derivation from transaction";
PrivacyMem.insert({_txhash, false});
return false;
}

// look for outputs
uint64_t received(0);
size_t keyIndex(0);
std::vector<TransactionOutput> outputs;
try {
for (const TransactionOutput& o : transaction.outputs) {
if (o.target.type() == typeid(KeyOutput)) {
const KeyOutput out_key = boost::get<KeyOutput>(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";
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 0 additions & 3 deletions src/DynexCNCore/Core.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Crypto::Hash> buildSparseChain() override;
std::vector<Crypto::Hash> buildSparseChain(const Crypto::Hash& startBlockId) override;
Expand Down
Loading

0 comments on commit d30c774

Please sign in to comment.