mirror of https://github.com/oxen-io/oxen-core.git
2522 lines
107 KiB
C++
2522 lines
107 KiB
C++
// Copyright (c) 2014-2019, The Monero Project
|
|
// Copyright (c) 2018, The Loki Project
|
|
//
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without modification, are
|
|
// permitted provided that the following conditions are met:
|
|
//
|
|
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
|
// conditions and the following disclaimer.
|
|
//
|
|
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
|
// of conditions and the following disclaimer in the documentation and/or other
|
|
// materials provided with the distribution.
|
|
//
|
|
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
|
// used to endorse or promote products derived from this software without specific
|
|
// prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
|
|
|
|
#include <boost/algorithm/string.hpp>
|
|
|
|
#include "epee/string_tools.h"
|
|
|
|
#include <unordered_set>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
#include <oxenc/base32z.h>
|
|
|
|
extern "C" {
|
|
#include <sodium.h>
|
|
#ifdef ENABLE_SYSTEMD
|
|
# include <systemd/sd-daemon.h>
|
|
#endif
|
|
}
|
|
|
|
#include <sqlite3.h>
|
|
|
|
#include "cryptonote_core.h"
|
|
#include "uptime_proof.h"
|
|
#include "common/file.h"
|
|
#include "common/sha256sum.h"
|
|
#include "common/threadpool.h"
|
|
#include "common/command_line.h"
|
|
#include "common/hex.h"
|
|
#include "common/base58.h"
|
|
#include "epee/warnings.h"
|
|
#include "crypto/crypto.h"
|
|
#include "cryptonote_config.h"
|
|
#include "cryptonote_basic/hardfork.h"
|
|
#include <csignal>
|
|
#include "checkpoints/checkpoints.h"
|
|
#include "ringct/rctTypes.h"
|
|
#include "blockchain_db/blockchain_db.h"
|
|
#include "blockchain_db/sqlite/db_sqlite.h"
|
|
#include "ringct/rctSigs.h"
|
|
#include "common/notify.h"
|
|
#include "version.h"
|
|
#include "epee/memwipe.h"
|
|
#include "common/i18n.h"
|
|
#include "epee/net/local_ip.h"
|
|
#include "logging/oxen_logger.h"
|
|
#include <fmt/std.h>
|
|
#include <fmt/color.h>
|
|
#include <oxenmq/fmt.h>
|
|
|
|
DISABLE_VS_WARNINGS(4355)
|
|
|
|
#define BAD_SEMANTICS_TXES_MAX_SIZE 100
|
|
|
|
// basically at least how many bytes the block itself serializes to without the miner tx
|
|
#define BLOCK_SIZE_SANITY_LEEWAY 100
|
|
|
|
namespace cryptonote
|
|
{
|
|
|
|
static auto logcat = oxen::log::Cat("cn");
|
|
static auto omqlogcat = oxen::log::Cat("omq");
|
|
|
|
const command_line::arg_descriptor<bool, false> arg_testnet_on = {
|
|
"testnet"
|
|
, "Run on testnet. The wallet must be launched with --testnet flag."
|
|
, false
|
|
};
|
|
const command_line::arg_descriptor<bool, false> arg_devnet_on = {
|
|
"devnet"
|
|
, "Run on devnet. The wallet must be launched with --devnet flag."
|
|
, false
|
|
};
|
|
const command_line::arg_descriptor<bool> arg_regtest_on = {
|
|
"regtest"
|
|
, "Run in a regression testing mode."
|
|
, false
|
|
};
|
|
const command_line::arg_descriptor<bool> arg_keep_fakechain = {
|
|
"keep-fakechain"
|
|
, "Don't delete any existing database when in fakechain mode."
|
|
, false
|
|
};
|
|
const command_line::arg_descriptor<difficulty_type> arg_fixed_difficulty = {
|
|
"fixed-difficulty"
|
|
, "Fixed difficulty used for testing."
|
|
, 0
|
|
};
|
|
const command_line::arg_descriptor<bool> arg_dev_allow_local = {
|
|
"dev-allow-local-ips"
|
|
, "Allow a local IPs for local and received service node public IP (for local testing only)"
|
|
, false
|
|
};
|
|
const command_line::arg_descriptor<std::string, false, true, 2> arg_data_dir = {
|
|
"data-dir"
|
|
, "Specify data directory"
|
|
, tools::get_default_data_dir().u8string()
|
|
, {{ &arg_testnet_on, &arg_devnet_on }}
|
|
, [](std::array<bool, 2> testnet_devnet, bool defaulted, std::string val)->std::string {
|
|
if (testnet_devnet[0])
|
|
return (fs::u8path(val) / "testnet").u8string();
|
|
else if (testnet_devnet[1])
|
|
return (fs::u8path(val) / "devnet").u8string();
|
|
return val;
|
|
}
|
|
};
|
|
const command_line::arg_descriptor<bool> arg_offline = {
|
|
"offline"
|
|
, "Do not listen for peers, nor connect to any"
|
|
};
|
|
const command_line::arg_descriptor<size_t> arg_block_download_max_size = {
|
|
"block-download-max-size"
|
|
, "Set maximum size of block download queue in bytes (0 for default)"
|
|
, 0
|
|
};
|
|
|
|
static const command_line::arg_descriptor<bool> arg_test_drop_download = {
|
|
"test-drop-download"
|
|
, "For net tests: in download, discard ALL blocks instead checking/saving them (very fast)"
|
|
};
|
|
static const command_line::arg_descriptor<uint64_t> arg_test_drop_download_height = {
|
|
"test-drop-download-height"
|
|
, "Like test-drop-download but discards only after around certain height"
|
|
, 0
|
|
};
|
|
static const command_line::arg_descriptor<uint64_t> arg_fast_block_sync = {
|
|
"fast-block-sync"
|
|
, "Sync up most of the way by using embedded, known block hashes."
|
|
, 1
|
|
};
|
|
static const command_line::arg_descriptor<uint64_t> arg_prep_blocks_threads = {
|
|
"prep-blocks-threads"
|
|
, "Max number of threads to use when preparing block hashes in groups."
|
|
, 4
|
|
};
|
|
static const command_line::arg_descriptor<uint64_t> arg_show_time_stats = {
|
|
"show-time-stats"
|
|
, "Show time-stats when processing blocks/txs and disk synchronization."
|
|
, 0
|
|
};
|
|
static const command_line::arg_descriptor<size_t> arg_block_sync_size = {
|
|
"block-sync-size"
|
|
, "How many blocks to sync at once during chain synchronization (0 = adaptive)."
|
|
, 0
|
|
};
|
|
static const command_line::arg_descriptor<bool> arg_pad_transactions = {
|
|
"pad-transactions"
|
|
, "Pad relayed transactions to help defend against traffic volume analysis"
|
|
, false
|
|
};
|
|
static const command_line::arg_descriptor<size_t> arg_max_txpool_weight = {
|
|
"max-txpool-weight"
|
|
, "Set maximum txpool weight in bytes."
|
|
, DEFAULT_MEMPOOL_MAX_WEIGHT
|
|
};
|
|
static const command_line::arg_descriptor<bool> arg_service_node = {
|
|
"service-node"
|
|
, "Run as a service node, option 'service-node-public-ip' must be set"
|
|
};
|
|
static const command_line::arg_descriptor<std::string> arg_public_ip = {
|
|
"service-node-public-ip"
|
|
, "Public IP address on which this service node's services (such as the Loki "
|
|
"storage server) are accessible. This IP address will be advertised to the "
|
|
"network via the service node uptime proofs. Required if operating as a "
|
|
"service node."
|
|
};
|
|
static const command_line::arg_descriptor<uint16_t> arg_storage_server_port = {
|
|
"storage-server-port", "Deprecated option, ignored.", 0};
|
|
static const command_line::arg_descriptor<uint16_t, false, true, 2> arg_quorumnet_port = {
|
|
"quorumnet-port"
|
|
, "The port on which this service node listen for direct connections from other "
|
|
"service nodes for quorum messages. The port must be publicly reachable "
|
|
"on the `--service-node-public-ip' address and binds to the p2p IP address."
|
|
" Only applies when running as a service node."
|
|
, config::QNET_DEFAULT_PORT
|
|
, {{ &cryptonote::arg_testnet_on, &cryptonote::arg_devnet_on }}
|
|
, [](std::array<bool, 2> testnet_devnet, bool defaulted, uint16_t val) -> uint16_t {
|
|
return defaulted && testnet_devnet[0] ? config::testnet::QNET_DEFAULT_PORT :
|
|
defaulted && testnet_devnet[1] ? config::devnet::QNET_DEFAULT_PORT :
|
|
val;
|
|
}
|
|
};
|
|
static const command_line::arg_descriptor<bool> arg_omq_quorumnet_public{
|
|
"lmq-public-quorumnet",
|
|
"Allow the curve-enabled quorumnet address (for a Service Node) to be used for public RPC commands as if passed to --lmq-curve-public. "
|
|
"Note that even without this option the quorumnet port can be used for RPC commands by --lmq-admin and --lmq-user pubkeys.",
|
|
false};
|
|
static const command_line::arg_descriptor<std::string> arg_block_notify = {
|
|
"block-notify"
|
|
, "Run a program for each new block, '%s' will be replaced by the block hash"
|
|
, ""
|
|
};
|
|
static const command_line::arg_descriptor<bool> arg_prune_blockchain = {
|
|
"prune-blockchain"
|
|
, "Prune blockchain"
|
|
, false
|
|
};
|
|
static const command_line::arg_descriptor<std::string> arg_reorg_notify = {
|
|
"reorg-notify"
|
|
, "Run a program for each reorg, '%s' will be replaced by the split height, "
|
|
"'%h' will be replaced by the new blockchain height, and '%n' will be "
|
|
"replaced by the number of new blocks in the new chain"
|
|
, ""
|
|
};
|
|
static const command_line::arg_descriptor<bool> arg_keep_alt_blocks = {
|
|
"keep-alt-blocks"
|
|
, "Keep alternative blocks on restart"
|
|
, false
|
|
};
|
|
|
|
static const command_line::arg_descriptor<uint64_t> arg_store_quorum_history = {
|
|
"store-quorum-history",
|
|
"Store the service node quorum history for the last N blocks to allow historic quorum lookups "
|
|
"(e.g. by a block explorer). Specify the number of blocks of history to store, or 1 to store "
|
|
"the entire history. Requires considerably more memory and block chain storage.",
|
|
0};
|
|
|
|
// Loads stubs that fail if invoked. The stubs are replaced in the cryptonote_protocol/quorumnet.cpp glue code.
|
|
[[noreturn]] static void need_core_init(std::string_view stub_name) {
|
|
throw std::logic_error("Internal error: core callback initialization was not performed for "s + std::string(stub_name));
|
|
}
|
|
|
|
void (*long_poll_trigger)(tx_memory_pool& pool) = [](tx_memory_pool&) { need_core_init("long_poll_trigger"sv); };
|
|
quorumnet_new_proc *quorumnet_new = [](core&) -> void* { need_core_init("quorumnet_new"sv); };
|
|
quorumnet_init_proc *quorumnet_init = [](core&, void*) { need_core_init("quorumnet_init"sv); };
|
|
quorumnet_delete_proc *quorumnet_delete = [](void*&) { need_core_init("quorumnet_delete"sv); };
|
|
quorumnet_relay_obligation_votes_proc *quorumnet_relay_obligation_votes = [](void*, const std::vector<service_nodes::quorum_vote_t>&) { need_core_init("quorumnet_relay_obligation_votes"sv); };
|
|
quorumnet_send_blink_proc *quorumnet_send_blink = [](core&, const std::string&) -> std::future<std::pair<blink_result, std::string>> { need_core_init("quorumnet_send_blink"sv); };
|
|
quorumnet_pulse_relay_message_to_quorum_proc *quorumnet_pulse_relay_message_to_quorum = [](void *, pulse::message const &, service_nodes::quorum const &, bool) -> void { need_core_init("quorumnet_pulse_relay_message_to_quorum"sv); };
|
|
|
|
//-----------------------------------------------------------------------------------------------
|
|
core::core()
|
|
: m_mempool(m_blockchain_storage)
|
|
, m_service_node_list(m_blockchain_storage)
|
|
, m_blockchain_storage(m_mempool, m_service_node_list)
|
|
, m_quorum_cop(*this)
|
|
, m_miner(this, [this](const cryptonote::block &b, uint64_t height, unsigned int threads, crypto::hash &hash) {
|
|
hash = cryptonote::get_block_longhash_w_blockchain(m_nettype, &m_blockchain_storage, b, height, threads);
|
|
return true;
|
|
})
|
|
, m_pprotocol(&m_protocol_stub)
|
|
, m_starter_message_showed(false)
|
|
, m_target_blockchain_height(0)
|
|
, m_last_json_checkpoints_update(0)
|
|
, m_nettype(network_type::UNDEFINED)
|
|
, m_last_storage_server_ping(0)
|
|
, m_last_lokinet_ping(0)
|
|
, m_pad_transactions(false)
|
|
, ss_version{0}
|
|
, lokinet_version{0}
|
|
{
|
|
m_checkpoints_updating.clear();
|
|
}
|
|
void core::set_cryptonote_protocol(i_cryptonote_protocol* pprotocol)
|
|
{
|
|
if(pprotocol)
|
|
m_pprotocol = pprotocol;
|
|
else
|
|
m_pprotocol = &m_protocol_stub;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::update_checkpoints_from_json_file()
|
|
{
|
|
if (m_checkpoints_updating.test_and_set()) return true;
|
|
|
|
// load json checkpoints every 10min and verify them with respect to what blocks we already have
|
|
bool res = true;
|
|
if (time(NULL) - m_last_json_checkpoints_update >= 600)
|
|
{
|
|
res = m_blockchain_storage.update_checkpoints_from_json_file(m_checkpoints_path);
|
|
m_last_json_checkpoints_update = time(NULL);
|
|
}
|
|
m_checkpoints_updating.clear();
|
|
|
|
// if anything fishy happened getting new checkpoints, bring down the house
|
|
if (!res)
|
|
{
|
|
graceful_exit();
|
|
}
|
|
return res;
|
|
}
|
|
//-----------------------------------------------------------------------------------
|
|
void core::stop()
|
|
{
|
|
m_miner.stop();
|
|
m_blockchain_storage.cancel();
|
|
}
|
|
//-----------------------------------------------------------------------------------
|
|
void core::init_options(boost::program_options::options_description& desc)
|
|
{
|
|
command_line::add_arg(desc, arg_data_dir);
|
|
|
|
command_line::add_arg(desc, arg_test_drop_download);
|
|
command_line::add_arg(desc, arg_test_drop_download_height);
|
|
|
|
command_line::add_arg(desc, arg_testnet_on);
|
|
command_line::add_arg(desc, arg_devnet_on);
|
|
command_line::add_arg(desc, arg_regtest_on);
|
|
command_line::add_arg(desc, arg_keep_fakechain);
|
|
command_line::add_arg(desc, arg_fixed_difficulty);
|
|
command_line::add_arg(desc, arg_dev_allow_local);
|
|
command_line::add_arg(desc, arg_prep_blocks_threads);
|
|
command_line::add_arg(desc, arg_fast_block_sync);
|
|
command_line::add_arg(desc, arg_show_time_stats);
|
|
command_line::add_arg(desc, arg_block_sync_size);
|
|
command_line::add_arg(desc, arg_offline);
|
|
command_line::add_arg(desc, arg_block_download_max_size);
|
|
command_line::add_arg(desc, arg_max_txpool_weight);
|
|
command_line::add_arg(desc, arg_service_node);
|
|
command_line::add_arg(desc, arg_public_ip);
|
|
command_line::add_arg(desc, arg_storage_server_port);
|
|
command_line::add_arg(desc, arg_quorumnet_port);
|
|
|
|
command_line::add_arg(desc, arg_pad_transactions);
|
|
command_line::add_arg(desc, arg_block_notify);
|
|
#if 0 // TODO(oxen): Pruning not supported because of Service Node List
|
|
command_line::add_arg(desc, arg_prune_blockchain);
|
|
#endif
|
|
command_line::add_arg(desc, arg_reorg_notify);
|
|
command_line::add_arg(desc, arg_keep_alt_blocks);
|
|
|
|
command_line::add_arg(desc, arg_store_quorum_history);
|
|
command_line::add_arg(desc, arg_omq_quorumnet_public);
|
|
|
|
miner::init_options(desc);
|
|
BlockchainDB::init_options(desc);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::handle_command_line(const boost::program_options::variables_map& vm)
|
|
{
|
|
if (m_nettype != network_type::FAKECHAIN)
|
|
{
|
|
const bool testnet = command_line::get_arg(vm, arg_testnet_on);
|
|
const bool devnet = command_line::get_arg(vm, arg_devnet_on);
|
|
m_nettype = testnet ? network_type::TESTNET : devnet ? network_type::DEVNET : network_type::MAINNET;
|
|
}
|
|
m_check_uptime_proof_interval.interval(get_net_config().UPTIME_PROOF_CHECK_INTERVAL);
|
|
|
|
m_config_folder = fs::u8path(command_line::get_arg(vm, arg_data_dir));
|
|
|
|
test_drop_download_height(command_line::get_arg(vm, arg_test_drop_download_height));
|
|
m_pad_transactions = get_arg(vm, arg_pad_transactions);
|
|
m_offline = get_arg(vm, arg_offline);
|
|
if (command_line::get_arg(vm, arg_test_drop_download) == true)
|
|
test_drop_download();
|
|
|
|
|
|
if (command_line::get_arg(vm, arg_dev_allow_local))
|
|
m_service_node_list.debug_allow_local_ips = true;
|
|
|
|
m_service_node = command_line::get_arg(vm, arg_service_node);
|
|
|
|
if (m_service_node) {
|
|
/// TODO: parse these options early, before we start p2p server etc?
|
|
m_quorumnet_port = command_line::get_arg(vm, arg_quorumnet_port);
|
|
|
|
bool args_okay = true;
|
|
if (m_quorumnet_port == 0) {
|
|
oxen::log::error(logcat, "Quorumnet port cannot be 0; please specify a valid port to listen on with: '--{} <port>'", arg_quorumnet_port.name);
|
|
args_okay = false;
|
|
}
|
|
|
|
const std::string pub_ip = command_line::get_arg(vm, arg_public_ip);
|
|
if (pub_ip.size())
|
|
{
|
|
if (!epee::string_tools::get_ip_int32_from_string(m_sn_public_ip, pub_ip)) {
|
|
oxen::log::error(logcat, "Unable to parse IPv4 public address from: {}", pub_ip);
|
|
args_okay = false;
|
|
}
|
|
|
|
if (!epee::net_utils::is_ip_public(m_sn_public_ip)) {
|
|
if (m_service_node_list.debug_allow_local_ips) {
|
|
oxen::log::warning(logcat, "Address given for public-ip is not public; allowing it because dev-allow-local-ips was specified. This service node WILL NOT WORK ON THE PUBLIC OXEN NETWORK!");
|
|
} else {
|
|
oxen::log::error(logcat, "Address given for public-ip is not public: {}", epee::string_tools::get_ip_string_from_int32(m_sn_public_ip));
|
|
args_okay = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
oxen::log::error(logcat, "Please specify an IPv4 public address which the service node & storage server is accessible from with: '--{} <ip address>'", arg_public_ip.name);
|
|
args_okay = false;
|
|
}
|
|
|
|
if (!args_okay) {
|
|
oxen::log::error(logcat,
|
|
"IMPORTANT: One or more required service node-related configuration settings/options were omitted or invalid please fix them and restart oxend.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
uint64_t core::get_current_blockchain_height() const
|
|
{
|
|
return m_blockchain_storage.get_current_blockchain_height();
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
std::pair<uint64_t, crypto::hash> core::get_blockchain_top() const
|
|
{
|
|
std::pair<uint64_t, crypto::hash> result;
|
|
result.second = m_blockchain_storage.get_tail_id(result.first);
|
|
return result;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::get_blocks(uint64_t start_offset, size_t count, std::vector<std::pair<std::string,block>>& blocks, std::vector<std::string>& txs) const
|
|
{
|
|
return m_blockchain_storage.get_blocks(start_offset, count, blocks, txs);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::get_blocks(uint64_t start_offset, size_t count, std::vector<std::pair<std::string,block>>& blocks) const
|
|
{
|
|
return m_blockchain_storage.get_blocks(start_offset, count, blocks);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::get_blocks(uint64_t start_offset, size_t count, std::vector<block>& blocks) const
|
|
{
|
|
return m_blockchain_storage.get_blocks_only(start_offset, count, blocks);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::get_blocks(const std::vector<crypto::hash>& block_ids, std::vector<std::pair<std::string, block>> blocks, std::unordered_set<crypto::hash>* missed_bs) const
|
|
{
|
|
return m_blockchain_storage.get_blocks(block_ids, blocks, missed_bs);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<std::string>& txs, std::unordered_set<crypto::hash>* missed_txs) const
|
|
{
|
|
return m_blockchain_storage.get_transactions_blobs(txs_ids, txs, missed_txs);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::get_split_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<std::tuple<crypto::hash, std::string, crypto::hash, std::string>>& txs, std::unordered_set<crypto::hash>* missed_txs) const
|
|
{
|
|
return m_blockchain_storage.get_split_transactions_blobs(txs_ids, txs, missed_txs);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<transaction>& txs, std::unordered_set<crypto::hash>* missed_txs) const
|
|
{
|
|
return m_blockchain_storage.get_transactions(txs_ids, txs, missed_txs);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::get_alternative_blocks(std::vector<block>& blocks) const
|
|
{
|
|
return m_blockchain_storage.get_alternative_blocks(blocks);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
size_t core::get_alternative_blocks_count() const
|
|
{
|
|
return m_blockchain_storage.get_alternative_blocks_count();
|
|
}
|
|
|
|
static std::string time_ago_str(time_t now, time_t then) {
|
|
if (then >= now)
|
|
return "now"s;
|
|
if (then == 0)
|
|
return "never"s;
|
|
int seconds = now - then;
|
|
if (seconds >= 60)
|
|
return std::to_string(seconds / 60) + "m" + std::to_string(seconds % 60) + "s";
|
|
return std::to_string(seconds % 60) + "s";
|
|
}
|
|
|
|
// Returns a bool on whether the service node is currently active
|
|
bool core::is_active_sn() const
|
|
{
|
|
auto info = get_my_sn_info();
|
|
return (info && info->is_active());
|
|
}
|
|
|
|
// Returns the service nodes info
|
|
std::shared_ptr<const service_nodes::service_node_info> core::get_my_sn_info() const
|
|
{
|
|
auto& snl = get_service_node_list();
|
|
const auto& pubkey = get_service_keys().pub;
|
|
auto states = snl.get_service_node_list_state({ pubkey });
|
|
if (states.empty())
|
|
return nullptr;
|
|
else
|
|
{
|
|
return states[0].info;
|
|
}
|
|
}
|
|
|
|
// Returns a string for systemd status notifications such as:
|
|
// Height: 1234567, SN: active, proof: 55m12s, storage: 4m48s, lokinet: 47s
|
|
std::string core::get_status_string() const
|
|
{
|
|
std::string s;
|
|
s.reserve(128);
|
|
s += 'v'; s += OXEN_VERSION_STR;
|
|
s += "; Height: ";
|
|
s += std::to_string(get_blockchain_storage().get_current_blockchain_height());
|
|
s += ", SN: ";
|
|
if (!service_node())
|
|
s += "no";
|
|
else
|
|
{
|
|
auto& snl = get_service_node_list();
|
|
const auto& pubkey = get_service_keys().pub;
|
|
auto states = snl.get_service_node_list_state({ pubkey });
|
|
if (states.empty())
|
|
s += "not registered";
|
|
else
|
|
{
|
|
auto &info = *states[0].info;
|
|
if (!info.is_fully_funded())
|
|
s += "awaiting contr.";
|
|
else if (info.is_active())
|
|
s += "active";
|
|
else if (info.is_decommissioned())
|
|
s += "decomm.";
|
|
|
|
uint64_t last_proof = 0;
|
|
snl.access_proof(pubkey, [&](auto& proof) { last_proof = proof.timestamp; });
|
|
s += ", proof: ";
|
|
time_t now = std::time(nullptr);
|
|
s += time_ago_str(now, last_proof);
|
|
s += ", storage: ";
|
|
s += time_ago_str(now, m_last_storage_server_ping);
|
|
s += ", lokinet: ";
|
|
s += time_ago_str(now, m_last_lokinet_ping);
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::init(const boost::program_options::variables_map& vm, const cryptonote::test_options *test_options, const GetCheckpointsCallback& get_checkpoints/* = nullptr */)
|
|
{
|
|
start_time = std::time(nullptr);
|
|
|
|
const bool regtest = command_line::get_arg(vm, arg_regtest_on);
|
|
if (test_options != NULL || regtest)
|
|
{
|
|
m_nettype = network_type::FAKECHAIN;
|
|
}
|
|
|
|
bool r = handle_command_line(vm);
|
|
/// Currently terminating before blockchain is initialized results in a crash
|
|
/// during deinitialization... TODO: fix that
|
|
CHECK_AND_ASSERT_MES(r, false, "Failed to apply command line options.");
|
|
|
|
std::string db_sync_mode = command_line::get_arg(vm, cryptonote::arg_db_sync_mode);
|
|
bool db_salvage = command_line::get_arg(vm, cryptonote::arg_db_salvage) != 0;
|
|
bool fast_sync = command_line::get_arg(vm, arg_fast_block_sync) != 0;
|
|
uint64_t blocks_threads = command_line::get_arg(vm, arg_prep_blocks_threads);
|
|
size_t max_txpool_weight = command_line::get_arg(vm, arg_max_txpool_weight);
|
|
bool const prune_blockchain = false; /* command_line::get_arg(vm, arg_prune_blockchain); */
|
|
bool keep_alt_blocks = command_line::get_arg(vm, arg_keep_alt_blocks);
|
|
bool keep_fakechain = command_line::get_arg(vm, arg_keep_fakechain);
|
|
|
|
r = init_service_keys();
|
|
CHECK_AND_ASSERT_MES(r, false, "Failed to create or load service keys");
|
|
if (m_service_node)
|
|
{
|
|
// Only use our service keys for our service node if we are running in SN mode:
|
|
m_service_node_list.set_my_service_node_keys(&m_service_keys);
|
|
}
|
|
|
|
auto folder = m_config_folder;
|
|
if (m_nettype == network_type::FAKECHAIN)
|
|
folder /= "fake";
|
|
|
|
// make sure the data directory exists, and try to lock it
|
|
if (std::error_code ec; !fs::is_directory(folder, ec) && !fs::create_directories(folder, ec) && ec)
|
|
{
|
|
oxen::log::error(logcat, "Failed to create directory " + folder.u8string() + (ec ? ": " + ec.message() : ""s));
|
|
return false;
|
|
}
|
|
|
|
std::unique_ptr<BlockchainDB> db(new_db());
|
|
if (!db)
|
|
{
|
|
oxen::log::error(logcat, "Failed to initialize a database");
|
|
return false;
|
|
}
|
|
|
|
auto ons_db_file_path = folder / "ons.db";
|
|
if(fs::exists(folder / "lns.db"))
|
|
ons_db_file_path = folder / "lns.db";
|
|
|
|
auto sqlite_db_file_path = folder / "sqlite.db";
|
|
if (m_nettype == network_type::FAKECHAIN)
|
|
{
|
|
sqlite_db_file_path = ":memory:";
|
|
}
|
|
auto sqliteDB = std::make_shared<cryptonote::BlockchainSQLite>(m_nettype, sqlite_db_file_path);
|
|
|
|
folder /= db->get_db_name();
|
|
oxen::log::info(logcat, "Loading blockchain from folder {} ...", folder);
|
|
|
|
// default to fast:async:1 if overridden
|
|
blockchain_db_sync_mode sync_mode = db_defaultsync;
|
|
bool sync_on_blocks = true;
|
|
uint64_t sync_threshold = 1;
|
|
|
|
if (m_nettype == network_type::FAKECHAIN && !keep_fakechain)
|
|
{
|
|
// reset the db by removing the database file before opening it
|
|
if (!db->remove_data_file(folder))
|
|
{
|
|
oxen::log::error(logcat, "Failed to remove data file in {}", folder);
|
|
return false;
|
|
}
|
|
fs::remove(ons_db_file_path);
|
|
}
|
|
|
|
try
|
|
{
|
|
uint64_t db_flags = 0;
|
|
|
|
std::vector<std::string> options;
|
|
boost::trim(db_sync_mode);
|
|
boost::split(options, db_sync_mode, boost::is_any_of(" :"));
|
|
const bool db_sync_mode_is_default = command_line::is_arg_defaulted(vm, cryptonote::arg_db_sync_mode);
|
|
|
|
for(const auto &option : options)
|
|
oxen::log::debug(logcat, "option: {}", option);
|
|
|
|
// default to fast:async:1
|
|
uint64_t DEFAULT_FLAGS = DBF_FAST;
|
|
|
|
if(options.size() == 0)
|
|
{
|
|
// default to fast:async:1
|
|
db_flags = DEFAULT_FLAGS;
|
|
}
|
|
|
|
bool safemode = false;
|
|
if(options.size() >= 1)
|
|
{
|
|
if(options[0] == "safe")
|
|
{
|
|
safemode = true;
|
|
db_flags = DBF_SAFE;
|
|
sync_mode = db_sync_mode_is_default ? db_defaultsync : db_nosync;
|
|
}
|
|
else if(options[0] == "fast")
|
|
{
|
|
db_flags = DBF_FAST;
|
|
sync_mode = db_sync_mode_is_default ? db_defaultsync : db_async;
|
|
}
|
|
else if(options[0] == "fastest")
|
|
{
|
|
db_flags = DBF_FASTEST;
|
|
sync_threshold = 1000; // default to fastest:async:1000
|
|
sync_mode = db_sync_mode_is_default ? db_defaultsync : db_async;
|
|
}
|
|
else
|
|
db_flags = DEFAULT_FLAGS;
|
|
}
|
|
|
|
if(options.size() >= 2 && !safemode)
|
|
{
|
|
if(options[1] == "sync")
|
|
sync_mode = db_sync_mode_is_default ? db_defaultsync : db_sync;
|
|
else if(options[1] == "async")
|
|
sync_mode = db_sync_mode_is_default ? db_defaultsync : db_async;
|
|
}
|
|
|
|
if(options.size() >= 3 && !safemode)
|
|
{
|
|
char *endptr;
|
|
uint64_t threshold = strtoull(options[2].c_str(), &endptr, 0);
|
|
if (*endptr == '\0' || !strcmp(endptr, "blocks"))
|
|
{
|
|
sync_on_blocks = true;
|
|
sync_threshold = threshold;
|
|
}
|
|
else if (!strcmp(endptr, "bytes"))
|
|
{
|
|
sync_on_blocks = false;
|
|
sync_threshold = threshold;
|
|
}
|
|
else
|
|
{
|
|
oxen::log::error(logcat, "Invalid db sync mode: {}", options[2]);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (db_salvage)
|
|
db_flags |= DBF_SALVAGE;
|
|
|
|
db->open(folder, m_nettype, db_flags);
|
|
if(!db->m_open)
|
|
return false;
|
|
}
|
|
catch (const DB_ERROR& e)
|
|
{
|
|
oxen::log::error(logcat, "Error opening database: {}", e.what());
|
|
return false;
|
|
}
|
|
|
|
m_blockchain_storage.set_user_options(blocks_threads,
|
|
sync_on_blocks, sync_threshold, sync_mode, fast_sync);
|
|
|
|
// We need this hook to get added before the block hook below, so that it fires first and
|
|
// catches the start of a reorg before the block hook fires for the block in the reorg.
|
|
try
|
|
{
|
|
if (!command_line::is_arg_defaulted(vm, arg_reorg_notify))
|
|
m_blockchain_storage.hook_block_post_add(
|
|
[this, notify=tools::Notify(command_line::get_arg(vm, arg_reorg_notify))]
|
|
(const auto& info) {
|
|
if (!info.reorg)
|
|
return;
|
|
auto h = get_current_blockchain_height();
|
|
notify.notify(
|
|
"%s", info.split_height,
|
|
"%h", h,
|
|
"%n", h - info.split_height);
|
|
});
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
oxen::log::error(logcat, "Failed to parse reorg notify spec");
|
|
}
|
|
|
|
try
|
|
{
|
|
if (!command_line::is_arg_defaulted(vm, arg_block_notify))
|
|
m_blockchain_storage.hook_block_post_add(
|
|
[notify=tools::Notify(command_line::get_arg(vm, arg_block_notify))]
|
|
(const auto& info) {
|
|
notify.notify("%s", tools::type_to_hex(get_block_hash(info.block)));
|
|
});
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
oxen::log::error(logcat, "Failed to parse block rate notify spec");
|
|
}
|
|
|
|
cryptonote::test_options regtest_test_options{};
|
|
for (auto [it, end] = get_hard_forks(network_type::MAINNET);
|
|
it != end;
|
|
it++) {
|
|
regtest_test_options.hard_forks.push_back(hard_fork{
|
|
it->version, it->snode_revision, regtest_test_options.hard_forks.size(), std::time(nullptr)});
|
|
}
|
|
|
|
// Service Nodes
|
|
m_service_node_list.set_quorum_history_storage(command_line::get_arg(vm, arg_store_quorum_history));
|
|
|
|
// NOTE: Implicit dependency. Service node list needs to be hooked before checkpoints.
|
|
m_blockchain_storage.hook_blockchain_detached([this] (const auto& info) { m_service_node_list.blockchain_detached(info.height); });
|
|
m_blockchain_storage.hook_init([this] { m_service_node_list.init(); });
|
|
m_blockchain_storage.hook_validate_miner_tx([this] (const auto& info) { m_service_node_list.validate_miner_tx(info); });
|
|
m_blockchain_storage.hook_alt_block_add([this] (const auto& info) { m_service_node_list.alt_block_add(info); });
|
|
|
|
// NOTE: There is an implicit dependency on service node lists being hooked first!
|
|
m_blockchain_storage.hook_init([this] { m_quorum_cop.init(); });
|
|
m_blockchain_storage.hook_block_add([this] (const auto& info) { m_quorum_cop.block_add(info.block, info.txs); });
|
|
m_blockchain_storage.hook_blockchain_detached([this] (const auto& info) { m_quorum_cop.blockchain_detached(info.height, info.by_pop_blocks); });
|
|
|
|
m_blockchain_storage.hook_block_post_add([this] (const auto&) { update_omq_sns(); });
|
|
|
|
// Checkpoints
|
|
m_checkpoints_path = m_config_folder / fs::u8path(JSON_HASH_FILE_NAME);
|
|
|
|
sqlite3 *ons_db = ons::init_oxen_name_system(ons_db_file_path, db->is_read_only());
|
|
if (!ons_db) return false;
|
|
|
|
|
|
init_oxenmq(vm);
|
|
|
|
const difficulty_type fixed_difficulty = command_line::get_arg(vm, arg_fixed_difficulty);
|
|
r = m_blockchain_storage.init(db.release(), ons_db, std::move(sqliteDB), m_nettype, m_offline, regtest ? ®test_test_options : test_options, fixed_difficulty, get_checkpoints);
|
|
CHECK_AND_ASSERT_MES(r, false, "Failed to initialize blockchain storage");
|
|
|
|
r = m_mempool.init(max_txpool_weight);
|
|
CHECK_AND_ASSERT_MES(r, false, "Failed to initialize memory pool");
|
|
|
|
// now that we have a valid m_blockchain_storage, we can clean out any
|
|
// transactions in the pool that do not conform to the current fork
|
|
m_mempool.validate(m_blockchain_storage.get_network_version());
|
|
|
|
bool show_time_stats = command_line::get_arg(vm, arg_show_time_stats) != 0;
|
|
m_blockchain_storage.set_show_time_stats(show_time_stats);
|
|
|
|
block_sync_size = command_line::get_arg(vm, arg_block_sync_size);
|
|
if (block_sync_size > BLOCKS_SYNCHRONIZING_MAX_COUNT)
|
|
oxen::log::error(logcat, "Error --block-sync-size cannot be greater than {}", BLOCKS_SYNCHRONIZING_MAX_COUNT);
|
|
|
|
oxen::log::info(logcat, "Loading checkpoints");
|
|
CHECK_AND_ASSERT_MES(update_checkpoints_from_json_file(), false, "One or more checkpoints loaded from json conflicted with existing checkpoints.");
|
|
|
|
r = m_miner.init(vm, m_nettype);
|
|
CHECK_AND_ASSERT_MES(r, false, "Failed to initialize miner instance");
|
|
|
|
if (!keep_alt_blocks && !m_blockchain_storage.get_db().is_read_only())
|
|
m_blockchain_storage.get_db().drop_alt_blocks();
|
|
|
|
if (prune_blockchain)
|
|
{
|
|
// display a message if the blockchain is not pruned yet
|
|
if (!m_blockchain_storage.get_blockchain_pruning_seed())
|
|
{
|
|
oxen::log::info(logcat, "Pruning blockchain...");
|
|
CHECK_AND_ASSERT_MES(m_blockchain_storage.prune_blockchain(), false, "Failed to prune blockchain");
|
|
}
|
|
else
|
|
{
|
|
CHECK_AND_ASSERT_MES(m_blockchain_storage.update_blockchain_pruning(), false, "Failed to update blockchain pruning");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Loads a key pair from disk, if it exists, otherwise generates a new key pair and saves it to
|
|
/// disk.
|
|
///
|
|
/// get_pubkey - a function taking (privkey &, pubkey &) that sets the pubkey from the privkey;
|
|
/// returns true for success/false for failure
|
|
/// generate_pair - a void function taking (privkey &, pubkey &) that sets them to the generated values; can throw on error.
|
|
template <typename Privkey, typename Pubkey, typename GetPubkey, typename GeneratePair>
|
|
bool init_key(const fs::path &keypath, Privkey &privkey, Pubkey &pubkey, GetPubkey get_pubkey, GeneratePair generate_pair) {
|
|
std::error_code ec;
|
|
if (fs::exists(keypath, ec))
|
|
{
|
|
std::string keystr;
|
|
bool r = tools::slurp_file(keypath, keystr);
|
|
memcpy(&unwrap(unwrap(privkey)), keystr.data(), sizeof(privkey));
|
|
memwipe(&keystr[0], keystr.size());
|
|
CHECK_AND_ASSERT_MES(r, false, "failed to load service node key from " + keypath.u8string());
|
|
CHECK_AND_ASSERT_MES(keystr.size() == sizeof(privkey), false,
|
|
"service node key file " + keypath.u8string() + " has an invalid size");
|
|
|
|
r = get_pubkey(privkey, pubkey);
|
|
CHECK_AND_ASSERT_MES(r, false, "failed to generate pubkey from secret key");
|
|
}
|
|
else
|
|
{
|
|
try {
|
|
generate_pair(privkey, pubkey);
|
|
} catch (const std::exception& e) {
|
|
oxen::log::error(logcat, "failed to generate keypair {}", e.what());
|
|
return false;
|
|
}
|
|
|
|
bool r = tools::dump_file(keypath, tools::view_guts(privkey));
|
|
CHECK_AND_ASSERT_MES(r, false, "failed to save service node key to " + keypath.u8string());
|
|
|
|
fs::permissions(keypath, fs::perms::owner_read, ec);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::init_service_keys()
|
|
{
|
|
auto& keys = m_service_keys;
|
|
|
|
static_assert(
|
|
sizeof(crypto::ed25519_public_key) == crypto_sign_ed25519_PUBLICKEYBYTES &&
|
|
sizeof(crypto::ed25519_secret_key) == crypto_sign_ed25519_SECRETKEYBYTES &&
|
|
sizeof(crypto::ed25519_signature) == crypto_sign_BYTES &&
|
|
sizeof(crypto::x25519_public_key) == crypto_scalarmult_curve25519_BYTES &&
|
|
sizeof(crypto::x25519_secret_key) == crypto_scalarmult_curve25519_BYTES,
|
|
"Invalid ed25519/x25519 sizes");
|
|
|
|
// <data>/key_ed25519: Standard ed25519 secret key. We always have this, and generate one if it
|
|
// doesn't exist.
|
|
//
|
|
// As of Loki 8.x, if this exists and `key` doesn't, we use this key for everything. For
|
|
// compatibility with earlier versions we also allow `key` to contain a separate monero privkey
|
|
// for the SN keypair. (The main difference is that the Monero keypair is unclamped and that it
|
|
// only contains the private key value but not the secret key value that we need for full
|
|
// Ed25519 signing).
|
|
//
|
|
if (!init_key(m_config_folder / "key_ed25519", keys.key_ed25519, keys.pub_ed25519,
|
|
[](crypto::ed25519_secret_key &sk, crypto::ed25519_public_key &pk) { crypto_sign_ed25519_sk_to_pk(pk.data, sk.data); return true; },
|
|
[](crypto::ed25519_secret_key &sk, crypto::ed25519_public_key &pk) { crypto_sign_ed25519_keypair(pk.data, sk.data); })
|
|
)
|
|
return false;
|
|
|
|
// Standard x25519 keys generated from the ed25519 keypair, used for encrypted communication between SNs
|
|
int rc = crypto_sign_ed25519_pk_to_curve25519(keys.pub_x25519.data, keys.pub_ed25519.data);
|
|
CHECK_AND_ASSERT_MES(rc == 0, false, "failed to convert ed25519 pubkey to x25519");
|
|
crypto_sign_ed25519_sk_to_curve25519(keys.key_x25519.data, keys.key_ed25519.data);
|
|
|
|
// Legacy primary SN key file; we only load this if it exists, otherwise we use `key_ed25519`
|
|
// for the primary SN keypair. (This key predates the Ed25519 keys and so is needed for
|
|
// backwards compatibility with existing active service nodes.) The legacy key consists of
|
|
// *just* the private point, but not the seed, and so cannot be used for full Ed25519 signatures
|
|
// (which rely on the seed for signing).
|
|
if (m_service_node) {
|
|
if (std::error_code ec; !fs::exists(m_config_folder / "key", ec)) {
|
|
epee::wipeable_string privkey_signhash;
|
|
privkey_signhash.resize(crypto_hash_sha512_BYTES);
|
|
unsigned char* pk_sh_data = reinterpret_cast<unsigned char*>(privkey_signhash.data());
|
|
crypto_hash_sha512(pk_sh_data, keys.key_ed25519.data, 32 /* first 32 bytes are the seed to be SHA512 hashed (the last 32 are just the pubkey) */);
|
|
// Clamp private key (as libsodium does and expects -- see https://www.jcraige.com/an-explainer-on-ed25519-clamping if you want the broader reasons)
|
|
pk_sh_data[0] &= 248;
|
|
pk_sh_data[31] &= 63; // (some implementations put 127 here, but with the |64 in the next line it is the same thing)
|
|
pk_sh_data[31] |= 64;
|
|
// Monero crypto requires a pointless check that the secret key is < basepoint, so calculate
|
|
// it mod basepoint to make it happy:
|
|
sc_reduce32(pk_sh_data);
|
|
std::memcpy(keys.key.data, pk_sh_data, 32);
|
|
if (!crypto::secret_key_to_public_key(keys.key, keys.pub))
|
|
throw std::runtime_error{"Failed to derive primary key from ed25519 key"};
|
|
assert(0 == std::memcmp(keys.pub.data, keys.pub_ed25519.data, 32));
|
|
} else if (!init_key(m_config_folder / "key", keys.key, keys.pub,
|
|
crypto::secret_key_to_public_key,
|
|
[](crypto::secret_key &key, crypto::public_key &pubkey) {
|
|
throw std::runtime_error{"Internal error: old-style public keys are no longer generated"};
|
|
}))
|
|
return false;
|
|
} else {
|
|
keys.key = crypto::null_skey;
|
|
keys.pub = crypto::null_pkey;
|
|
}
|
|
|
|
if (m_service_node) {
|
|
oxen::log::info(logcat, fmt::format(fg(fmt::terminal_color::yellow), "Service node public keys:"));
|
|
oxen::log::info(logcat, fmt::format(fg(fmt::terminal_color::yellow), "- primary: {}", tools::type_to_hex(keys.pub)));
|
|
oxen::log::info(logcat, fmt::format(fg(fmt::terminal_color::yellow), "- ed25519: {}", tools::type_to_hex(keys.pub_ed25519)));
|
|
// .snode address is the ed25519 pubkey, encoded with base32z and with .snode appended:
|
|
oxen::log::info(logcat, fmt::format(fg(fmt::terminal_color::yellow), "- lokinet: {}.snode", oxenc::to_base32z(tools::view_guts(keys.pub_ed25519))));
|
|
oxen::log::info(logcat, fmt::format(fg(fmt::terminal_color::yellow), "- x25519: {}", tools::type_to_hex(keys.pub_x25519)));
|
|
} else {
|
|
// Only print the x25519 version because it's the only thing useful for a non-SN (for
|
|
// encrypted LMQ RPC connections).
|
|
oxen::log::info(logcat, fmt::format(fg(fmt::terminal_color::yellow), "x25519 public key: {}", tools::type_to_hex(keys.pub_x25519)));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
oxenmq::AuthLevel core::omq_check_access(const crypto::x25519_public_key& pubkey) const {
|
|
auto it = m_omq_auth.find(pubkey);
|
|
if (it != m_omq_auth.end())
|
|
return it->second;
|
|
return oxenmq::AuthLevel::denied;
|
|
}
|
|
|
|
// Builds an allow function; takes `*this`, the default auth level, and whether this connection
|
|
// should allow incoming SN connections.
|
|
//
|
|
// default_auth should be AuthLevel::denied if only pre-approved connections may connect,
|
|
// AuthLevel::basic for public RPC, AuthLevel::admin for a (presumably localhost) unrestricted
|
|
// port, and AuthLevel::none for a super restricted mode (generally this is useful when there are
|
|
// also SN-restrictions on commands, i.e. for quorumnet).
|
|
//
|
|
// check_sn is whether we check an incoming key against known service nodes (and thus return
|
|
// "true" for the service node access if it checks out).
|
|
//
|
|
oxenmq::AuthLevel core::omq_allow(std::string_view ip, std::string_view x25519_pubkey_str, oxenmq::AuthLevel default_auth) {
|
|
using namespace oxenmq;
|
|
AuthLevel auth = default_auth;
|
|
if (x25519_pubkey_str.size() == sizeof(crypto::x25519_public_key)) {
|
|
crypto::x25519_public_key x25519_pubkey;
|
|
std::memcpy(x25519_pubkey.data, x25519_pubkey_str.data(), x25519_pubkey_str.size());
|
|
auto user_auth = omq_check_access(x25519_pubkey);
|
|
if (user_auth >= AuthLevel::basic) {
|
|
if (user_auth > auth)
|
|
auth = user_auth;
|
|
oxen::log::info(oxen::log::Cat("omq"), "Incoming {}-authenticated connection", auth);
|
|
}
|
|
|
|
oxen::log::info(oxen::log::Cat("omq"), "Incoming [{}] curve connection from {}/{}", auth, ip, x25519_pubkey);
|
|
}
|
|
else {
|
|
oxen::log::info(oxen::log::Cat("omq"), "Incoming [{}] plain connection from {}", auth, ip);
|
|
}
|
|
return auth;
|
|
}
|
|
|
|
void core::init_oxenmq(const boost::program_options::variables_map& vm) {
|
|
using namespace oxenmq;
|
|
oxen::log::info(omqlogcat, "Starting oxenmq");
|
|
m_omq = std::make_unique<OxenMQ>(
|
|
tools::copy_guts(m_service_keys.pub_x25519),
|
|
tools::copy_guts(m_service_keys.key_x25519),
|
|
m_service_node,
|
|
[this](std::string_view x25519_pk) { return m_service_node_list.remote_lookup(x25519_pk); },
|
|
[](LogLevel omqlevel, const char *file, int line, std::string msg) {
|
|
auto level = *oxen::logging::parse_level(omqlevel);
|
|
if(omqlogcat->should_log(level))
|
|
omqlogcat->log({file, line, "omq"}, level, "{}", msg);
|
|
},
|
|
oxenmq::LogLevel::trace
|
|
);
|
|
|
|
// ping.ping: a simple debugging target for pinging the omq listener
|
|
m_omq->add_category("ping", Access{AuthLevel::none})
|
|
.add_request_command("ping", [](Message& m) {
|
|
oxen::log::info(oxen::log::Cat("omq"), "Received ping from {}", m.conn);
|
|
m.send_reply("pong");
|
|
})
|
|
;
|
|
|
|
if (m_service_node)
|
|
{
|
|
// Service nodes always listen for quorumnet data on the p2p IP, quorumnet port
|
|
std::string listen_ip = vm["p2p-bind-ip"].as<std::string>();
|
|
if (listen_ip.empty())
|
|
listen_ip = "0.0.0.0";
|
|
std::string qnet_listen = "tcp://" + listen_ip + ":" + std::to_string(m_quorumnet_port);
|
|
oxen::log::info(logcat, "- listening on {} (quorumnet)", qnet_listen);
|
|
m_omq->listen_curve(qnet_listen,
|
|
[this, public_=command_line::get_arg(vm, arg_omq_quorumnet_public)](std::string_view ip, std::string_view pk, bool) {
|
|
return omq_allow(ip, pk, public_ ? AuthLevel::basic : AuthLevel::none);
|
|
});
|
|
|
|
m_quorumnet_state = quorumnet_new(*this);
|
|
}
|
|
|
|
quorumnet_init(*this, m_quorumnet_state);
|
|
}
|
|
|
|
void core::start_oxenmq() {
|
|
update_omq_sns(); // Ensure we have SNs set for the current block before starting
|
|
|
|
if (m_service_node)
|
|
{
|
|
m_pulse_thread_id = m_omq->add_tagged_thread("pulse");
|
|
m_omq->add_timer([this]() { pulse::main(m_quorumnet_state, *this); },
|
|
std::chrono::milliseconds(500),
|
|
false,
|
|
m_pulse_thread_id);
|
|
m_omq->add_timer([this]() {this->check_service_node_time();},
|
|
5s,
|
|
false);
|
|
}
|
|
m_omq->start();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::set_genesis_block(const block& b)
|
|
{
|
|
return m_blockchain_storage.reset_and_set_genesis_block(b);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
void core::deinit()
|
|
{
|
|
#ifdef ENABLE_SYSTEMD
|
|
sd_notify(0, "STOPPING=1\nSTATUS=Shutting down");
|
|
#endif
|
|
if (m_quorumnet_state)
|
|
quorumnet_delete(m_quorumnet_state);
|
|
m_omq.reset();
|
|
m_service_node_list.store();
|
|
m_miner.stop();
|
|
m_mempool.deinit();
|
|
m_blockchain_storage.deinit();
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
void core::test_drop_download()
|
|
{
|
|
m_test_drop_download = false;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
void core::test_drop_download_height(uint64_t height)
|
|
{
|
|
m_test_drop_download_height = height;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::get_test_drop_download() const
|
|
{
|
|
return m_test_drop_download;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::get_test_drop_download_height() const
|
|
{
|
|
if (m_test_drop_download_height == 0)
|
|
return true;
|
|
|
|
if (get_blockchain_storage().get_current_blockchain_height() <= m_test_drop_download_height)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
void core::parse_incoming_tx_pre(tx_verification_batch_info &tx_info)
|
|
{
|
|
if(tx_info.blob->size() > MAX_TX_SIZE)
|
|
{
|
|
oxen::log::info(logcat, "WRONG TRANSACTION BLOB, too big size {}, rejected", tx_info.blob->size());
|
|
tx_info.tvc.m_verifivation_failed = true;
|
|
tx_info.tvc.m_too_big = true;
|
|
return;
|
|
}
|
|
else if (tx_info.blob->empty())
|
|
{
|
|
oxen::log::info(logcat, "WRONG TRANSACTION BLOB, blob is empty, rejected");
|
|
tx_info.tvc.m_verifivation_failed = true;
|
|
return;
|
|
}
|
|
|
|
tx_info.parsed = parse_and_validate_tx_from_blob(*tx_info.blob, tx_info.tx, tx_info.tx_hash);
|
|
if(!tx_info.parsed)
|
|
{
|
|
oxen::log::info(logcat, "WRONG TRANSACTION BLOB, Failed to parse, rejected");
|
|
tx_info.tvc.m_verifivation_failed = true;
|
|
return;
|
|
}
|
|
//std::cout << "!"<< tx.vin.size() << std::endl;
|
|
|
|
std::lock_guard lock{bad_semantics_txes_lock};
|
|
for (int idx = 0; idx < 2; ++idx)
|
|
{
|
|
if (bad_semantics_txes[idx].find(tx_info.tx_hash) != bad_semantics_txes[idx].end())
|
|
{
|
|
oxen::log::info(logcat, "Transaction already seen with bad semantics, rejected");
|
|
tx_info.tvc.m_verifivation_failed = true;
|
|
return;
|
|
}
|
|
}
|
|
tx_info.result = true;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
void core::set_semantics_failed(const crypto::hash &tx_hash)
|
|
{
|
|
oxen::log::info(logcat, "WRONG TRANSACTION BLOB, Failed to check tx {} semantic, rejected", tx_hash);
|
|
bad_semantics_txes_lock.lock();
|
|
bad_semantics_txes[0].insert(tx_hash);
|
|
if (bad_semantics_txes[0].size() >= BAD_SEMANTICS_TXES_MAX_SIZE)
|
|
{
|
|
std::swap(bad_semantics_txes[0], bad_semantics_txes[1]);
|
|
bad_semantics_txes[0].clear();
|
|
}
|
|
bad_semantics_txes_lock.unlock();
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
static bool is_canonical_bulletproof_layout(const std::vector<rct::Bulletproof> &proofs)
|
|
{
|
|
if (proofs.size() != 1)
|
|
return false;
|
|
const size_t sz = proofs[0].V.size();
|
|
if (sz == 0 || sz > TX_BULLETPROOF_MAX_OUTPUTS)
|
|
return false;
|
|
return true;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
void core::parse_incoming_tx_accumulated_batch(std::vector<tx_verification_batch_info> &tx_info, bool kept_by_block)
|
|
{
|
|
if (kept_by_block && get_blockchain_storage().is_within_compiled_block_hash_area())
|
|
{
|
|
oxen::log::trace(logcat, "Skipping semantics check for txs kept by block in embedded hash area");
|
|
return;
|
|
}
|
|
|
|
std::vector<const rct::rctSig*> rvv;
|
|
for (size_t n = 0; n < tx_info.size(); ++n)
|
|
{
|
|
if (!tx_info[n].result || tx_info[n].already_have)
|
|
continue;
|
|
|
|
if (!check_tx_semantic(tx_info[n].tx, kept_by_block))
|
|
{
|
|
set_semantics_failed(tx_info[n].tx_hash);
|
|
tx_info[n].tvc.m_verifivation_failed = true;
|
|
tx_info[n].result = false;
|
|
continue;
|
|
}
|
|
|
|
if (!tx_info[n].tx.is_transfer())
|
|
continue;
|
|
const rct::rctSig &rv = tx_info[n].tx.rct_signatures;
|
|
switch (rv.type) {
|
|
case rct::RCTType::Null:
|
|
// coinbase should not come here, so we reject for all other types
|
|
oxen::log::error(oxen::log::Cat("verify"), "Unexpected Null rctSig type");
|
|
set_semantics_failed(tx_info[n].tx_hash);
|
|
tx_info[n].tvc.m_verifivation_failed = true;
|
|
tx_info[n].result = false;
|
|
break;
|
|
case rct::RCTType::Simple:
|
|
if (!rct::verRctSemanticsSimple(rv))
|
|
{
|
|
oxen::log::error(oxen::log::Cat("verify"), "rct signature semantics check failed");
|
|
set_semantics_failed(tx_info[n].tx_hash);
|
|
tx_info[n].tvc.m_verifivation_failed = true;
|
|
tx_info[n].result = false;
|
|
break;
|
|
}
|
|
break;
|
|
case rct::RCTType::Full:
|
|
if (!rct::verRct(rv, true))
|
|
{
|
|
oxen::log::error(oxen::log::Cat("verify"), "rct signature semantics check failed");
|
|
set_semantics_failed(tx_info[n].tx_hash);
|
|
tx_info[n].tvc.m_verifivation_failed = true;
|
|
tx_info[n].result = false;
|
|
break;
|
|
}
|
|
break;
|
|
case rct::RCTType::Bulletproof:
|
|
case rct::RCTType::Bulletproof2:
|
|
case rct::RCTType::CLSAG:
|
|
if (!is_canonical_bulletproof_layout(rv.p.bulletproofs))
|
|
{
|
|
oxen::log::error(oxen::log::Cat("verify"), "Bulletproof does not have canonical form");
|
|
set_semantics_failed(tx_info[n].tx_hash);
|
|
tx_info[n].tvc.m_verifivation_failed = true;
|
|
tx_info[n].result = false;
|
|
break;
|
|
}
|
|
rvv.push_back(&rv); // delayed batch verification
|
|
break;
|
|
default:
|
|
oxen::log::error(oxen::log::Cat("verify"), "Unknown rct type: {}", (int)rv.type);
|
|
set_semantics_failed(tx_info[n].tx_hash);
|
|
tx_info[n].tvc.m_verifivation_failed = true;
|
|
tx_info[n].result = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!rvv.empty() && !rct::verRctSemanticsSimple(rvv))
|
|
{
|
|
oxen::log::info(logcat, "One transaction among this group has bad semantics, verifying one at a time");
|
|
const bool assumed_bad = rvv.size() == 1; // if there's only one tx, it must be the bad one
|
|
for (size_t n = 0; n < tx_info.size(); ++n)
|
|
{
|
|
if (!tx_info[n].result || tx_info[n].already_have)
|
|
continue;
|
|
if (!rct::is_rct_bulletproof(tx_info[n].tx.rct_signatures.type))
|
|
continue;
|
|
if (assumed_bad || !rct::verRctSemanticsSimple(tx_info[n].tx.rct_signatures))
|
|
{
|
|
set_semantics_failed(tx_info[n].tx_hash);
|
|
tx_info[n].tvc.m_verifivation_failed = true;
|
|
tx_info[n].result = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
std::vector<cryptonote::tx_verification_batch_info> core::parse_incoming_txs(const std::vector<std::string>& tx_blobs, const tx_pool_options &opts)
|
|
{
|
|
// Caller needs to do this around both this *and* handle_parsed_txs
|
|
//auto lock = incoming_tx_lock();
|
|
std::vector<cryptonote::tx_verification_batch_info> tx_info(tx_blobs.size());
|
|
|
|
tools::threadpool& tpool = tools::threadpool::getInstance();
|
|
tools::threadpool::waiter waiter;
|
|
for (size_t i = 0; i < tx_blobs.size(); i++) {
|
|
tx_info[i].blob = &tx_blobs[i];
|
|
tpool.submit(&waiter, [this, &info = tx_info[i]] {
|
|
try
|
|
{
|
|
parse_incoming_tx_pre(info);
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
oxen::log::error(oxen::log::Cat("verify"), "Exception in handle_incoming_tx_pre: {}", e.what());
|
|
info.tvc.m_verifivation_failed = true;
|
|
}
|
|
});
|
|
}
|
|
waiter.wait(&tpool);
|
|
|
|
for (auto &info : tx_info) {
|
|
if (!info.result)
|
|
continue;
|
|
|
|
if(m_mempool.have_tx(info.tx_hash))
|
|
{
|
|
oxen::log::debug(logcat, "tx {} already has a transaction in tx_pool", info.tx_hash);
|
|
info.already_have = true;
|
|
}
|
|
else if(m_blockchain_storage.have_tx(info.tx_hash))
|
|
{
|
|
oxen::log::debug(logcat, "tx {} already has a transaction in tx_pool", info.tx_hash);
|
|
info.already_have = true;
|
|
}
|
|
}
|
|
|
|
|
|
parse_incoming_tx_accumulated_batch(tx_info, opts.kept_by_block);
|
|
|
|
return tx_info;
|
|
}
|
|
|
|
bool core::handle_parsed_txs(std::vector<tx_verification_batch_info> &parsed_txs, const tx_pool_options &opts,
|
|
uint64_t *blink_rollback_height)
|
|
{
|
|
// Caller needs to do this around both this *and* parse_incoming_txs
|
|
//auto lock = incoming_tx_lock();
|
|
auto version = m_blockchain_storage.get_network_version();
|
|
bool ok = true;
|
|
if (blink_rollback_height)
|
|
*blink_rollback_height = 0;
|
|
tx_pool_options tx_opts;
|
|
for (size_t i = 0; i < parsed_txs.size(); i++) {
|
|
auto &info = parsed_txs[i];
|
|
if (!info.result)
|
|
{
|
|
ok = false; // Propagate failures (so this can be chained with parse_incoming_txs without an intermediate check)
|
|
continue;
|
|
}
|
|
if (opts.kept_by_block)
|
|
get_blockchain_storage().on_new_tx_from_block(info.tx);
|
|
if (info.already_have)
|
|
continue; // Not a failure
|
|
|
|
const size_t weight = get_transaction_weight(info.tx, info.blob->size());
|
|
const tx_pool_options *local_opts = &opts;
|
|
if (blink_rollback_height && info.approved_blink)
|
|
{
|
|
// If this is an approved blink then pass a copy of the options with the flag added
|
|
tx_opts = opts;
|
|
tx_opts.approved_blink = true;
|
|
local_opts = &tx_opts;
|
|
}
|
|
if (m_mempool.add_tx(info.tx, info.tx_hash, *info.blob, weight, info.tvc, *local_opts, version, blink_rollback_height))
|
|
{
|
|
oxen::log::debug(logcat, "tx added: {}", info.tx_hash);
|
|
}
|
|
else
|
|
{
|
|
ok = false;
|
|
if (info.tvc.m_verifivation_failed)
|
|
oxen::log::error(oxen::log::Cat("verify"), "Transaction verification failed: {}", info.tx_hash);
|
|
else if (info.tvc.m_verifivation_impossible)
|
|
oxen::log::error(oxen::log::Cat("verify"), "Transaction verification impossible: {}", info.tx_hash);
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
std::vector<cryptonote::tx_verification_batch_info> core::handle_incoming_txs(const std::vector<std::string>& tx_blobs, const tx_pool_options &opts)
|
|
{
|
|
auto lock = incoming_tx_lock();
|
|
auto parsed = parse_incoming_txs(tx_blobs, opts);
|
|
handle_parsed_txs(parsed, opts);
|
|
return parsed;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::handle_incoming_tx(const std::string& tx_blob, tx_verification_context& tvc, const tx_pool_options &opts)
|
|
{
|
|
const std::vector<std::string> tx_blobs{{tx_blob}};
|
|
auto parsed = handle_incoming_txs(tx_blobs, opts);
|
|
parsed[0].blob = &tx_blob; // Update pointer to the input rather than the copy in case the caller wants to use it for some reason
|
|
tvc = parsed[0].tvc;
|
|
return parsed[0].result && (parsed[0].already_have || tvc.m_added_to_pool);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
std::pair<std::vector<std::shared_ptr<blink_tx>>, std::unordered_set<crypto::hash>>
|
|
core::parse_incoming_blinks(const std::vector<serializable_blink_metadata> &blinks)
|
|
{
|
|
std::pair<std::vector<std::shared_ptr<blink_tx>>, std::unordered_set<crypto::hash>> results;
|
|
auto &new_blinks = results.first;
|
|
auto &missing_txs = results.second;
|
|
|
|
if (m_blockchain_storage.get_network_version() < feature::BLINK)
|
|
return results;
|
|
|
|
std::vector<uint8_t> want(blinks.size(), false); // Really bools, but std::vector<bool> is broken.
|
|
size_t want_count = 0;
|
|
// Step 1: figure out which referenced transactions we want to keep:
|
|
// - unknown tx (typically an incoming blink)
|
|
// - in mempool without blink sigs (it's possible to get the tx before the blink signatures)
|
|
// - in a recent, still-mutable block with blink sigs (can happen when syncing blocks before
|
|
// retrieving blink signatures)
|
|
{
|
|
std::vector<crypto::hash> hashes;
|
|
hashes.reserve(blinks.size());
|
|
for (auto &bm : blinks)
|
|
hashes.emplace_back(bm.tx_hash);
|
|
|
|
std::unique_lock<Blockchain> lock(m_blockchain_storage);
|
|
|
|
auto tx_block_heights = m_blockchain_storage.get_transactions_heights(hashes);
|
|
auto immutable_height = m_blockchain_storage.get_immutable_height();
|
|
auto &db = m_blockchain_storage.get_db();
|
|
for (size_t i = 0; i < blinks.size(); i++) {
|
|
if (tx_block_heights[i] == 0 /*mempool or unknown*/ || tx_block_heights[i] > immutable_height /*mined but not yet immutable*/)
|
|
{
|
|
want[i] = true;
|
|
want_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
oxen::log::debug(logcat, "Want {} of {} incoming blink signature sets after filtering out immutable txes", want_count, blinks.size());
|
|
if (!want_count) return results;
|
|
|
|
// Step 2: filter out any transactions for which we already have a blink signature
|
|
{
|
|
auto mempool_lock = m_mempool.blink_shared_lock();
|
|
for (size_t i = 0; i < blinks.size(); i++)
|
|
{
|
|
if (want[i] && m_mempool.has_blink(blinks[i].tx_hash))
|
|
{
|
|
oxen::log::debug(logcat, "Ignoring blink data for {}: already have blink signatures", blinks[i].tx_hash);
|
|
want[i] = false; // Already have it, move along
|
|
want_count--;
|
|
}
|
|
}
|
|
}
|
|
|
|
oxen::log::debug(logcat, "Want {} of {} incoming blink signature sets after filtering out existing blink sigs", want_count, blinks.size());
|
|
if (!want_count) return results;
|
|
|
|
// Step 3: create new blink_tx objects for txes and add the blink signatures. We can do all of
|
|
// this without a lock since these are (for now) just local instances.
|
|
new_blinks.reserve(want_count);
|
|
|
|
std::unordered_map<uint64_t, std::shared_ptr<const service_nodes::quorum>> quorum_cache;
|
|
for (size_t i = 0; i < blinks.size(); i++)
|
|
{
|
|
if (!want[i])
|
|
continue;
|
|
auto &bdata = blinks[i];
|
|
new_blinks.push_back(std::make_shared<blink_tx>(bdata.height, bdata.tx_hash));
|
|
auto &blink = *new_blinks.back();
|
|
|
|
// Data structure checks (we have more stringent checks for validity later, but if these fail
|
|
// now then there's no point of even trying to do signature validation.
|
|
if (bdata.signature.size() != bdata.position.size() || // Each signature must have an associated quorum position
|
|
bdata.signature.size() != bdata.quorum.size() || // and quorum index
|
|
bdata.signature.size() < service_nodes::BLINK_MIN_VOTES * tools::enum_count<blink_tx::subquorum> || // too few signatures for possible validity
|
|
bdata.signature.size() > service_nodes::BLINK_SUBQUORUM_SIZE * tools::enum_count<blink_tx::subquorum> || // too many signatures
|
|
blink_tx::quorum_height(bdata.height, blink_tx::subquorum::base) == 0 || // Height is too early (no blink quorum height)
|
|
std::any_of(bdata.position.begin(), bdata.position.end(), [](const auto &p) { return p >= service_nodes::BLINK_SUBQUORUM_SIZE; }) || // invalid position
|
|
std::any_of(bdata.quorum.begin(), bdata.quorum.end(), [](const auto &qi) { return qi >= tools::enum_count<blink_tx::subquorum>; }) // invalid quorum index
|
|
) {
|
|
oxen::log::info(logcat, "Invalid blink tx {}: invalid signature data", bdata.tx_hash);
|
|
continue;
|
|
}
|
|
|
|
bool no_quorum = false;
|
|
std::array<const std::vector<crypto::public_key> *, tools::enum_count<blink_tx::subquorum>> validators;
|
|
for (uint8_t qi = 0; qi < tools::enum_count<blink_tx::subquorum>; qi++)
|
|
{
|
|
auto q_height = blink.quorum_height(static_cast<blink_tx::subquorum>(qi));
|
|
auto &q = quorum_cache[q_height];
|
|
if (!q)
|
|
q = get_quorum(service_nodes::quorum_type::blink, q_height);
|
|
if (!q)
|
|
{
|
|
oxen::log::info(logcat, "Don't have a quorum for height {} (yet?), ignoring this blink", q_height);
|
|
no_quorum = true;
|
|
break;
|
|
}
|
|
validators[qi] = &q->validators;
|
|
}
|
|
if (no_quorum)
|
|
continue;
|
|
|
|
std::vector<std::pair<size_t, std::string>> failures;
|
|
for (size_t s = 0; s < bdata.signature.size(); s++)
|
|
{
|
|
try {
|
|
blink.add_signature(static_cast<blink_tx::subquorum>(bdata.quorum[s]), bdata.position[s], true /*approved*/, bdata.signature[s],
|
|
validators[bdata.quorum[s]]->at(bdata.position[s]));
|
|
} catch (const std::exception &e) {
|
|
failures.emplace_back(s, e.what());
|
|
}
|
|
}
|
|
if (blink.approved())
|
|
{
|
|
oxen::log::info(logcat, "Blink tx {} blink signatures approved with {} signature validation failures", bdata.tx_hash, failures.size());
|
|
for (auto &f : failures)
|
|
oxen::log::debug(logcat, "- failure for quorum {}, position {}: {}", int(bdata.quorum[f.first]), int(bdata.position[f.first]), f.second);
|
|
}
|
|
else
|
|
{
|
|
std::ostringstream os;
|
|
os << "Blink validation failed:";
|
|
for (auto &f : failures)
|
|
os << " [" << int(bdata.quorum[f.first]) << ":" << int(bdata.position[f.first]) << "]: " << f.second;
|
|
oxen::log::info(logcat, "Invalid blink tx {}: {}", bdata.tx_hash, os.str());
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
int core::add_blinks(const std::vector<std::shared_ptr<blink_tx>> &blinks)
|
|
{
|
|
int added = 0;
|
|
if (blinks.empty())
|
|
return added;
|
|
|
|
auto lock = m_mempool.blink_unique_lock();
|
|
|
|
for (auto &b : blinks)
|
|
if (b->approved())
|
|
if (m_mempool.add_existing_blink(b))
|
|
added++;
|
|
|
|
if (added)
|
|
{
|
|
oxen::log::info(logcat, "Added blink signatures for {} blinks", added);
|
|
long_poll_trigger(m_mempool);
|
|
}
|
|
|
|
return added;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------------------
|
|
std::future<std::pair<blink_result, std::string>> core::handle_blink_tx(const std::string &tx_blob)
|
|
{
|
|
return quorumnet_send_blink(*this, tx_blob);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::check_tx_semantic(const transaction& tx, bool keeped_by_block) const
|
|
{
|
|
if (tx.is_transfer())
|
|
{
|
|
if (tx.vin.empty())
|
|
{
|
|
oxen::log::error(oxen::log::Cat("verify"), "tx with empty inputs, rejected for tx id= {}", get_transaction_hash(tx));
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (tx.vin.size() != 0)
|
|
{
|
|
oxen::log::error(oxen::log::Cat("verify"), "tx type: {} must have 0 inputs, received: {}, rejected for tx id = {}", tx.type, tx.vin.size(), get_transaction_hash(tx));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(!check_inputs_types_supported(tx))
|
|
{
|
|
oxen::log::error(oxen::log::Cat("verify"), "unsupported input types for tx id= {}", get_transaction_hash(tx));
|
|
return false;
|
|
}
|
|
|
|
if(!check_outs_valid(tx))
|
|
{
|
|
oxen::log::error(oxen::log::Cat("verify"), "tx with invalid outputs, rejected for tx id= {}", get_transaction_hash(tx));
|
|
return false;
|
|
}
|
|
|
|
if (tx.version >= txversion::v2_ringct)
|
|
{
|
|
if (tx.rct_signatures.outPk.size() != tx.vout.size())
|
|
{
|
|
oxen::log::error(oxen::log::Cat("verify"), "tx with mismatched vout/outPk count, rejected for tx id= {}", get_transaction_hash(tx));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(!check_money_overflow(tx))
|
|
{
|
|
oxen::log::error(oxen::log::Cat("verify"), "tx has money overflow, rejected for tx id= {}", get_transaction_hash(tx));
|
|
return false;
|
|
}
|
|
|
|
if (tx.version == txversion::v1)
|
|
{
|
|
uint64_t amount_in = 0;
|
|
get_inputs_money_amount(tx, amount_in);
|
|
uint64_t amount_out = get_outs_money_amount(tx);
|
|
|
|
if(amount_in <= amount_out)
|
|
{
|
|
oxen::log::error(oxen::log::Cat("verify"), "tx with wrong amounts: ins {}, outs {}, rejected for tx id= {}", amount_in, amount_out, get_transaction_hash(tx));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(!keeped_by_block && get_transaction_weight(tx) >= m_blockchain_storage.get_current_cumulative_block_weight_limit() - COINBASE_BLOB_RESERVED_SIZE)
|
|
{
|
|
oxen::log::error(oxen::log::Cat("verify"), "tx is too large {}, expected not bigger than {}", get_transaction_weight(tx), m_blockchain_storage.get_current_cumulative_block_weight_limit() - COINBASE_BLOB_RESERVED_SIZE);
|
|
return false;
|
|
}
|
|
|
|
if(!check_tx_inputs_keyimages_diff(tx))
|
|
{
|
|
oxen::log::error(oxen::log::Cat("verify"), "tx uses a single key image more than once");
|
|
return false;
|
|
}
|
|
|
|
if (!check_tx_inputs_ring_members_diff(tx))
|
|
{
|
|
oxen::log::error(oxen::log::Cat("verify"), "tx uses duplicate ring members");
|
|
return false;
|
|
}
|
|
|
|
if (!check_tx_inputs_keyimages_domain(tx))
|
|
{
|
|
oxen::log::error(oxen::log::Cat("verify"), "tx uses key image not in the valid domain");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::check_service_node_time()
|
|
{
|
|
|
|
if(!is_active_sn()) {return true;}
|
|
|
|
crypto::public_key pubkey = m_service_node_list.get_random_pubkey();
|
|
crypto::x25519_public_key x_pkey{0};
|
|
constexpr std::array<uint16_t, 3> MIN_TIMESTAMP_VERSION{9,1,0};
|
|
std::array<uint16_t,3> proofversion;
|
|
m_service_node_list.access_proof(pubkey, [&](auto &proof) {
|
|
x_pkey = proof.pubkey_x25519;
|
|
proofversion = proof.proof->version;
|
|
});
|
|
|
|
if (proofversion >= MIN_TIMESTAMP_VERSION && x_pkey) {
|
|
m_omq->request(
|
|
tools::view_guts(x_pkey),
|
|
"quorum.timestamp",
|
|
[this, pubkey](bool success, std::vector<std::string> data) {
|
|
const time_t local_seconds = time(nullptr);
|
|
oxen::log::debug(logcat, "Timestamp message received: {}, local time is: ", data[0], local_seconds);
|
|
if(success){
|
|
int64_t received_seconds;
|
|
if (tools::parse_int(data[0],received_seconds)){
|
|
uint16_t variance;
|
|
if (received_seconds > local_seconds + 65535 || received_seconds < local_seconds - 65535) {
|
|
variance = 65535;
|
|
} else {
|
|
variance = std::abs(local_seconds - received_seconds);
|
|
}
|
|
std::lock_guard<std::mutex> lk(m_sn_timestamp_mutex);
|
|
// Records the variance into the record of our performance (m_sn_times)
|
|
service_nodes::timesync_entry entry{variance <= service_nodes::THRESHOLD_SECONDS_OUT_OF_SYNC};
|
|
m_sn_times.add(entry);
|
|
|
|
// Counts the number of times we have been out of sync
|
|
if (m_sn_times.failures() > (m_sn_times.size() * service_nodes::MAXIMUM_EXTERNAL_OUT_OF_SYNC/100)) {
|
|
oxen::log::warning(logcat, "service node time might be out of sync");
|
|
// If we are out of sync record the other service node as in sync
|
|
m_service_node_list.record_timesync_status(pubkey, true);
|
|
} else {
|
|
m_service_node_list.record_timesync_status(pubkey, variance <= service_nodes::THRESHOLD_SECONDS_OUT_OF_SYNC);
|
|
}
|
|
} else {
|
|
success = false;
|
|
}
|
|
}
|
|
m_service_node_list.record_timestamp_participation(pubkey, success);
|
|
});
|
|
}
|
|
return true;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::is_key_image_spent(const crypto::key_image &key_image) const
|
|
{
|
|
return m_blockchain_storage.have_tx_keyimg_as_spent(key_image);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::are_key_images_spent(const std::vector<crypto::key_image>& key_im, std::vector<bool> &spent) const
|
|
{
|
|
spent.clear();
|
|
for(auto& ki: key_im)
|
|
{
|
|
spent.push_back(m_blockchain_storage.have_tx_keyimg_as_spent(ki));
|
|
}
|
|
return true;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
size_t core::get_block_sync_size(uint64_t height) const
|
|
{
|
|
return block_sync_size > 0 ? block_sync_size : BLOCKS_SYNCHRONIZING_DEFAULT_COUNT;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::are_key_images_spent_in_pool(const std::vector<crypto::key_image>& key_im, std::vector<bool> &spent) const
|
|
{
|
|
spent.clear();
|
|
|
|
return m_mempool.check_for_key_images(key_im, spent);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
std::optional<std::tuple<int64_t, int64_t, int64_t>> core::get_coinbase_tx_sum(uint64_t start_offset, size_t count)
|
|
{
|
|
std::optional<std::tuple<int64_t, int64_t, int64_t>> result{{0, 0, 0}};
|
|
if (count == 0)
|
|
return result;
|
|
|
|
auto& [emission_amount, total_fee_amount, burnt_oxen] = *result;
|
|
|
|
// Caching.
|
|
//
|
|
// Requesting this value from the beginning of the chain is very slow, so we cache it. That
|
|
// still means the first request will be slow, but that's okay. To prevent a bunch of threads
|
|
// getting backed up trying to calculate this, we lock out more than one thread building the
|
|
// cache at a time if we're requesting a large number of block values at once. Any other thread
|
|
// requesting will get a nullopt back.
|
|
|
|
constexpr uint64_t CACHE_LAG = 30; // We cache the values up to this many blocks ago; we lag so that we don't have to worry about small reorgs
|
|
constexpr uint64_t CACHE_EXCLUSIVE = 1000; // If we need to load more than this, we block out other threads
|
|
|
|
// Check if we have a cacheable from-the-beginning result
|
|
uint64_t cache_to = 0;
|
|
std::chrono::steady_clock::time_point cache_build_started;
|
|
if (start_offset == 0) {
|
|
uint64_t height = m_blockchain_storage.get_current_blockchain_height();
|
|
if (count > height) count = height;
|
|
cache_to = height - std::min(CACHE_LAG, height);
|
|
{
|
|
std::shared_lock lock{m_coinbase_cache.mutex};
|
|
if (m_coinbase_cache.height && count >= m_coinbase_cache.height) {
|
|
emission_amount = m_coinbase_cache.emissions;
|
|
total_fee_amount = m_coinbase_cache.fees;
|
|
burnt_oxen = m_coinbase_cache.burnt;
|
|
start_offset = m_coinbase_cache.height + 1;
|
|
count -= m_coinbase_cache.height;
|
|
}
|
|
// else don't change anything; we need a subset of blocks that ends before the cache.
|
|
|
|
if (cache_to <= m_coinbase_cache.height)
|
|
cache_to = 0; // Cache doesn't need updating
|
|
}
|
|
|
|
// If we're loading a lot then acquire an exclusive lock, recheck our variables, and block out
|
|
// other threads until we're done. (We don't do this if we're only loading a few because even
|
|
// if we have some competing cache updates they don't hurt anything).
|
|
if (cache_to > 0 && count > CACHE_EXCLUSIVE) {
|
|
std::unique_lock lock{m_coinbase_cache.mutex};
|
|
if (m_coinbase_cache.building)
|
|
return std::nullopt; // Another thread is already updating the cache
|
|
|
|
if (m_coinbase_cache.height && m_coinbase_cache.height >= start_offset) {
|
|
// Someone else updated the cache while we were acquiring the unique lock, so update our variables
|
|
if (m_coinbase_cache.height >= start_offset + count) {
|
|
// The cache is now *beyond* us, which means we can't use it, so reset start/count back
|
|
// to what they were originally.
|
|
count += start_offset - 1;
|
|
start_offset = 0;
|
|
cache_to = 0;
|
|
} else {
|
|
// The cache is updated and we can still use it, so update our variables.
|
|
emission_amount = m_coinbase_cache.emissions;
|
|
total_fee_amount = m_coinbase_cache.fees;
|
|
burnt_oxen = m_coinbase_cache.burnt;
|
|
count -= m_coinbase_cache.height - start_offset + 1;
|
|
start_offset = m_coinbase_cache.height + 1;
|
|
}
|
|
}
|
|
if (cache_to > 0 && count > CACHE_EXCLUSIVE) {
|
|
cache_build_started = std::chrono::steady_clock::now();
|
|
m_coinbase_cache.building = true; // Block out other threads until we're done
|
|
oxen::log::info(logcat, "Starting slow cache build request for get_coinbase_tx_sum({}, {})", start_offset, count);
|
|
}
|
|
}
|
|
}
|
|
|
|
const uint64_t end = start_offset + count - 1;
|
|
m_blockchain_storage.for_blocks_range(start_offset, end,
|
|
[this, &cache_to, &result, &cache_build_started](uint64_t height, const crypto::hash& hash, const block& b){
|
|
auto& [emission_amount, total_fee_amount, burnt_oxen] = *result;
|
|
std::vector<transaction> txs;
|
|
auto coinbase_amount = static_cast<int64_t>(get_outs_money_amount(b.miner_tx));
|
|
get_transactions(b.tx_hashes, txs);
|
|
int64_t tx_fee_amount = 0;
|
|
for(const auto& tx: txs)
|
|
{
|
|
tx_fee_amount += static_cast<int64_t>(get_tx_miner_fee(tx, b.major_version >= feature::FEE_BURNING));
|
|
if(b.major_version >= feature::FEE_BURNING)
|
|
{
|
|
burnt_oxen += static_cast<int64_t>(get_burned_amount_from_tx_extra(tx.extra));
|
|
}
|
|
}
|
|
|
|
emission_amount += coinbase_amount - tx_fee_amount;
|
|
total_fee_amount += tx_fee_amount;
|
|
if (cache_to && cache_to == height)
|
|
{
|
|
std::unique_lock lock{m_coinbase_cache.mutex};
|
|
if (m_coinbase_cache.height < height)
|
|
{
|
|
m_coinbase_cache.height = height;
|
|
m_coinbase_cache.emissions = emission_amount;
|
|
m_coinbase_cache.fees = total_fee_amount;
|
|
m_coinbase_cache.burnt = burnt_oxen;
|
|
}
|
|
if (m_coinbase_cache.building)
|
|
{
|
|
m_coinbase_cache.building = false;
|
|
oxen::log::info(logcat, "Finishing cache build for get_coinbase_tx_sum in {} s",
|
|
std::chrono::duration<double>{std::chrono::steady_clock::now() - cache_build_started}.count());
|
|
}
|
|
cache_to = 0;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
return result;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::check_tx_inputs_keyimages_diff(const transaction& tx) const
|
|
{
|
|
std::unordered_set<crypto::key_image> ki;
|
|
for(const auto& in: tx.vin)
|
|
{
|
|
CHECKED_GET_SPECIFIC_VARIANT(in, txin_to_key, tokey_in, false);
|
|
if(!ki.insert(tokey_in.k_image).second)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::check_tx_inputs_ring_members_diff(const transaction& tx) const
|
|
{
|
|
const auto version = m_blockchain_storage.get_network_version();
|
|
for(const auto& in: tx.vin)
|
|
{
|
|
CHECKED_GET_SPECIFIC_VARIANT(in, txin_to_key, tokey_in, false);
|
|
for (size_t n = 1; n < tokey_in.key_offsets.size(); ++n)
|
|
if (tokey_in.key_offsets[n] == 0)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::check_tx_inputs_keyimages_domain(const transaction& tx) const
|
|
{
|
|
std::unordered_set<crypto::key_image> ki;
|
|
for(const auto& in: tx.vin)
|
|
{
|
|
CHECKED_GET_SPECIFIC_VARIANT(in, txin_to_key, tokey_in, false);
|
|
if (!(rct::scalarmultKey(rct::ki2rct(tokey_in.k_image), rct::curveOrder()) == rct::identity()))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
size_t core::get_blockchain_total_transactions() const
|
|
{
|
|
return m_blockchain_storage.get_total_transactions();
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::relay_txpool_transactions()
|
|
{
|
|
// we attempt to relay txes that should be relayed, but were not
|
|
std::vector<std::pair<crypto::hash, std::string>> txs;
|
|
if (m_mempool.get_relayable_transactions(txs) && !txs.empty())
|
|
{
|
|
cryptonote_connection_context fake_context{};
|
|
tx_verification_context tvc{};
|
|
NOTIFY_NEW_TRANSACTIONS::request r{};
|
|
for (auto it = txs.begin(); it != txs.end(); ++it)
|
|
{
|
|
r.txs.push_back(it->second);
|
|
}
|
|
get_protocol()->relay_transactions(r, fake_context);
|
|
m_mempool.set_relayed(txs);
|
|
}
|
|
return true;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::submit_uptime_proof()
|
|
{
|
|
if (!m_service_node)
|
|
return true;
|
|
|
|
cryptonote_connection_context fake_context{};
|
|
bool relayed;
|
|
auto height = get_current_blockchain_height();
|
|
|
|
auto proof = m_service_node_list.generate_uptime_proof(m_sn_public_ip, storage_https_port(), storage_omq_port(), ss_version, m_quorumnet_port, lokinet_version);
|
|
NOTIFY_BTENCODED_UPTIME_PROOF::request req = proof.generate_request();
|
|
relayed = get_protocol()->relay_btencoded_uptime_proof(req, fake_context);
|
|
|
|
// TODO: remove after HF19
|
|
if (relayed && tools::view_guts(m_service_keys.pub) != tools::view_guts(m_service_keys.pub_ed25519)) {
|
|
// Temp workaround: nodes with both pub and ed25519 are failing bt-encoded proofs, so send
|
|
// an old-style proof out as well as a workaround.
|
|
NOTIFY_UPTIME_PROOF::request req = m_service_node_list.generate_uptime_proof(m_sn_public_ip, storage_https_port(), storage_omq_port(), m_quorumnet_port);
|
|
get_protocol()->relay_uptime_proof(req, fake_context);
|
|
}
|
|
|
|
if (relayed)
|
|
oxen::log::info(logcat, "Submitted uptime-proof for Service Node (yours): {}", m_service_keys.pub);
|
|
|
|
return true;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::handle_uptime_proof(const NOTIFY_UPTIME_PROOF::request &proof, bool &my_uptime_proof_confirmation)
|
|
{
|
|
crypto::x25519_public_key pkey = {};
|
|
bool result = m_service_node_list.handle_uptime_proof(proof, my_uptime_proof_confirmation, pkey);
|
|
if (result && m_service_node_list.is_service_node(proof.pubkey, true /*require_active*/) && pkey)
|
|
{
|
|
oxenmq::pubkey_set added;
|
|
added.insert(tools::copy_guts(pkey));
|
|
m_omq->update_active_sns(added, {} /*removed*/);
|
|
}
|
|
return result;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::handle_btencoded_uptime_proof(const NOTIFY_BTENCODED_UPTIME_PROOF::request &req, bool &my_uptime_proof_confirmation)
|
|
{
|
|
crypto::x25519_public_key pkey = {};
|
|
auto proof = std::make_unique<uptime_proof::Proof>(req.proof);
|
|
proof->sig = tools::make_from_guts<crypto::signature>(req.sig);
|
|
proof->sig_ed25519 = tools::make_from_guts<crypto::ed25519_signature>(req.ed_sig);
|
|
auto pubkey = proof->pubkey;
|
|
bool result = m_service_node_list.handle_btencoded_uptime_proof(std::move(proof), my_uptime_proof_confirmation, pkey);
|
|
if (result && m_service_node_list.is_service_node(pubkey, true /*require_active*/) && pkey)
|
|
{
|
|
oxenmq::pubkey_set added;
|
|
added.insert(tools::copy_guts(pkey));
|
|
m_omq->update_active_sns(added, {} /*removed*/);
|
|
}
|
|
return result;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
crypto::hash core::on_transaction_relayed(const std::string& tx_blob)
|
|
{
|
|
std::vector<std::pair<crypto::hash, std::string>> txs;
|
|
cryptonote::transaction tx;
|
|
crypto::hash tx_hash;
|
|
if (!parse_and_validate_tx_from_blob(tx_blob, tx, tx_hash))
|
|
{
|
|
oxen::log::error(logcat, "Failed to parse relayed transaction");
|
|
return crypto::null_hash;
|
|
}
|
|
txs.push_back(std::make_pair(tx_hash, std::move(tx_blob)));
|
|
m_mempool.set_relayed(txs);
|
|
return tx_hash;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::relay_service_node_votes()
|
|
{
|
|
auto height = get_current_blockchain_height();
|
|
auto hf_version = get_network_version(m_nettype, height);
|
|
|
|
auto quorum_votes = m_quorum_cop.get_relayable_votes(height, hf_version, true);
|
|
auto p2p_votes = m_quorum_cop.get_relayable_votes(height, hf_version, false);
|
|
if (!quorum_votes.empty() && m_quorumnet_state && m_service_node)
|
|
quorumnet_relay_obligation_votes(m_quorumnet_state, quorum_votes);
|
|
|
|
if (!p2p_votes.empty())
|
|
{
|
|
NOTIFY_NEW_SERVICE_NODE_VOTE::request req{};
|
|
req.votes = std::move(p2p_votes);
|
|
cryptonote_connection_context fake_context{};
|
|
get_protocol()->relay_service_node_votes(req, fake_context);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
void core::set_service_node_votes_relayed(const std::vector<service_nodes::quorum_vote_t> &votes)
|
|
{
|
|
m_quorum_cop.set_votes_relayed(votes);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::create_next_miner_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const std::string& ex_nonce)
|
|
{
|
|
return m_blockchain_storage.create_next_miner_block_template(b, adr, diffic, height, expected_reward, ex_nonce);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::create_miner_block_template(block& b, const crypto::hash *prev_block, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const std::string& ex_nonce)
|
|
{
|
|
return m_blockchain_storage.create_miner_block_template(b, prev_block, adr, diffic, height, expected_reward, ex_nonce);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const
|
|
{
|
|
return m_blockchain_storage.find_blockchain_supplement(qblock_ids, resp);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::vector<std::pair<std::pair<std::string, crypto::hash>, std::vector<std::pair<crypto::hash, std::string> > > >& blocks, uint64_t& total_height, uint64_t& start_height, bool pruned, bool get_miner_tx_hash, size_t max_count) const
|
|
{
|
|
return m_blockchain_storage.find_blockchain_supplement(req_start_block, qblock_ids, blocks, total_height, start_height, pruned, get_miner_tx_hash, max_count);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::get_outs(const rpc::GET_OUTPUTS_BIN::request& req, rpc::GET_OUTPUTS_BIN::response& res) const
|
|
{
|
|
return m_blockchain_storage.get_outs(req, res);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t to_height, uint64_t &start_height, std::vector<uint64_t> &distribution, uint64_t &base) const
|
|
{
|
|
return m_blockchain_storage.get_output_distribution(amount, from_height, to_height, start_height, distribution, base);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
void core::get_output_blacklist(std::vector<uint64_t> &blacklist) const
|
|
{
|
|
m_blockchain_storage.get_output_blacklist(blacklist);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<uint64_t>& indexs) const
|
|
{
|
|
return m_blockchain_storage.get_tx_outputs_gindexs(tx_id, indexs);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::get_tx_outputs_gindexs(const crypto::hash& tx_id, size_t n_txes, std::vector<std::vector<uint64_t>>& indexs) const
|
|
{
|
|
return m_blockchain_storage.get_tx_outputs_gindexs(tx_id, n_txes, indexs);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
void core::pause_mine()
|
|
{
|
|
m_miner.pause();
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
void core::resume_mine()
|
|
{
|
|
m_miner.resume();
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
block_complete_entry get_block_complete_entry(block& b, tx_memory_pool &pool)
|
|
{
|
|
block_complete_entry bce = {};
|
|
bce.block = cryptonote::block_to_blob(b);
|
|
for (const auto &tx_hash: b.tx_hashes)
|
|
{
|
|
std::string txblob;
|
|
CHECK_AND_ASSERT_THROW_MES(pool.get_transaction(tx_hash, txblob), "Transaction not found in pool");
|
|
bce.txs.push_back(txblob);
|
|
}
|
|
return bce;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::handle_block_found(block& b, block_verification_context &bvc)
|
|
{
|
|
bvc = {};
|
|
std::vector<block_complete_entry> blocks;
|
|
m_miner.pause();
|
|
{
|
|
OXEN_DEFER { m_miner.resume(); };
|
|
try
|
|
{
|
|
blocks.push_back(get_block_complete_entry(b, m_mempool));
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
return false;
|
|
}
|
|
std::vector<block> pblocks;
|
|
if (!prepare_handle_incoming_blocks(blocks, pblocks))
|
|
{
|
|
oxen::log::error(logcat, "Block found, but failed to prepare to add");
|
|
return false;
|
|
}
|
|
// add_new_block will verify block and set bvc.m_verification_failed accordingly
|
|
add_new_block(b, bvc, nullptr /*checkpoint*/);
|
|
cleanup_handle_incoming_blocks(true);
|
|
m_miner.on_block_chain_update();
|
|
}
|
|
|
|
if (bvc.m_verifivation_failed)
|
|
{
|
|
bool pulse = cryptonote::block_has_pulse_components(b);
|
|
oxen::log::error(oxen::log::Cat("verify"), "{} block failed verification\n{}", (pulse ? "Pulse" : "Mined"), cryptonote::obj_to_json_str(b));
|
|
return false;
|
|
}
|
|
else if(bvc.m_added_to_main_chain)
|
|
{
|
|
std::unordered_set<crypto::hash> missed_txs;
|
|
std::vector<std::string> txs;
|
|
m_blockchain_storage.get_transactions_blobs(b.tx_hashes, txs, &missed_txs);
|
|
if(missed_txs.size() && m_blockchain_storage.get_block_id_by_height(get_block_height(b)) != get_block_hash(b))
|
|
{
|
|
oxen::log::info(logcat, "Block found but, seems that reorganize just happened after that, do not relay this block");
|
|
return true;
|
|
}
|
|
CHECK_AND_ASSERT_MES(txs.size() == b.tx_hashes.size() && !missed_txs.size(), false, "can't find some transactions in found block:" << get_block_hash(b) << " txs.size()=" << txs.size()
|
|
<< ", b.tx_hashes.size()=" << b.tx_hashes.size() << ", missed_txs.size()" << missed_txs.size());
|
|
|
|
cryptonote_connection_context exclude_context{};
|
|
NOTIFY_NEW_FLUFFY_BLOCK::request arg{};
|
|
arg.current_blockchain_height = m_blockchain_storage.get_current_blockchain_height();
|
|
arg.b = blocks[0];
|
|
|
|
m_pprotocol->relay_block(arg, exclude_context);
|
|
}
|
|
return true;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
void core::on_synchronized()
|
|
{
|
|
m_miner.on_synchronized();
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
void core::safesyncmode(const bool onoff)
|
|
{
|
|
m_blockchain_storage.safesyncmode(onoff);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::add_new_block(const block& b, block_verification_context& bvc, checkpoint_t const *checkpoint)
|
|
{
|
|
bool result = m_blockchain_storage.add_new_block(b, bvc, checkpoint);
|
|
if (result)
|
|
relay_service_node_votes(); // NOTE: nop if synchronising due to not accepting votes whilst syncing
|
|
return result;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::prepare_handle_incoming_blocks(const std::vector<block_complete_entry> &blocks_entry, std::vector<block> &blocks)
|
|
{
|
|
m_incoming_tx_lock.lock();
|
|
if (!m_blockchain_storage.prepare_handle_incoming_blocks(blocks_entry, blocks))
|
|
{
|
|
cleanup_handle_incoming_blocks(false);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::cleanup_handle_incoming_blocks(bool force_sync)
|
|
{
|
|
bool success = false;
|
|
try {
|
|
success = m_blockchain_storage.cleanup_handle_incoming_blocks(force_sync);
|
|
}
|
|
catch (...) {}
|
|
m_incoming_tx_lock.unlock();
|
|
return success;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::handle_incoming_block(const std::string& block_blob, const block *b, block_verification_context& bvc, checkpoint_t *checkpoint, bool update_miner_blocktemplate)
|
|
{
|
|
TRY_ENTRY();
|
|
bvc = {};
|
|
|
|
if (!check_incoming_block_size(block_blob))
|
|
{
|
|
bvc.m_verifivation_failed = true;
|
|
return false;
|
|
}
|
|
|
|
if (((size_t)-1) <= 0xffffffff && block_blob.size() >= 0x3fffffff)
|
|
oxen::log::warning(logcat, "This block's size is {}, closing on the 32 bit limit", block_blob.size());
|
|
|
|
CHECK_AND_ASSERT_MES(update_checkpoints_from_json_file(), false, "One or more checkpoints loaded from json conflicted with existing checkpoints.");
|
|
|
|
block lb;
|
|
if (!b)
|
|
{
|
|
crypto::hash block_hash;
|
|
if(!parse_and_validate_block_from_blob(block_blob, lb, block_hash))
|
|
{
|
|
oxen::log::info(logcat, "Failed to parse and validate new block");
|
|
bvc.m_verifivation_failed = true;
|
|
return false;
|
|
}
|
|
b = &lb;
|
|
}
|
|
|
|
add_new_block(*b, bvc, checkpoint);
|
|
if(update_miner_blocktemplate && bvc.m_added_to_main_chain)
|
|
m_miner.on_block_chain_update();
|
|
return true;
|
|
|
|
CATCH_ENTRY_L0("core::handle_incoming_block()", false);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
// Used by the RPC server to check the size of an incoming
|
|
// block_blob
|
|
bool core::check_incoming_block_size(const std::string& block_blob) const
|
|
{
|
|
// note: we assume block weight is always >= block blob size, so we check incoming
|
|
// blob size against the block weight limit, which acts as a sanity check without
|
|
// having to parse/weigh first; in fact, since the block blob is the block header
|
|
// plus the tx hashes, the weight will typically be much larger than the blob size
|
|
if(block_blob.size() > m_blockchain_storage.get_current_cumulative_block_weight_limit() + BLOCK_SIZE_SANITY_LEEWAY)
|
|
{
|
|
oxen::log::info(logcat, "WRONG BLOCK BLOB, sanity check failed on size {}, rejected", block_blob.size());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void core::update_omq_sns()
|
|
{
|
|
// TODO: let callers (e.g. lokinet, ss) subscribe to callbacks when this fires
|
|
oxenmq::pubkey_set active_sns;
|
|
m_service_node_list.copy_active_x25519_pubkeys(std::inserter(active_sns, active_sns.end()));
|
|
m_omq->set_active_sns(std::move(active_sns));
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
crypto::hash core::get_tail_id() const
|
|
{
|
|
return m_blockchain_storage.get_tail_id();
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
difficulty_type core::get_block_cumulative_difficulty(uint64_t height) const
|
|
{
|
|
return m_blockchain_storage.get_db().get_block_cumulative_difficulty(height);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::have_block(const crypto::hash& id) const
|
|
{
|
|
return m_blockchain_storage.have_block(id);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
crypto::hash core::get_block_id_by_height(uint64_t height) const
|
|
{
|
|
return m_blockchain_storage.get_block_id_by_height(height);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::get_block_by_hash(const crypto::hash &h, block &blk, bool *orphan) const
|
|
{
|
|
return m_blockchain_storage.get_block_by_hash(h, blk, orphan);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::get_block_by_height(uint64_t height, block &blk) const
|
|
{
|
|
return m_blockchain_storage.get_block_by_height(height, blk);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
static bool check_external_ping(time_t last_ping, std::chrono::seconds lifetime, std::string_view what)
|
|
{
|
|
const std::chrono::seconds elapsed{std::time(nullptr) - last_ping};
|
|
if (elapsed > lifetime)
|
|
{
|
|
oxen::log::warning(logcat, "Have not heard from {} {}", what,
|
|
(!last_ping ? "since starting" :
|
|
"since more than " + tools::get_human_readable_timespan(elapsed) + " ago"));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
void core::reset_proof_interval()
|
|
{
|
|
m_check_uptime_proof_interval.reset();
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
void core::do_uptime_proof_call()
|
|
{
|
|
std::vector<service_nodes::service_node_pubkey_info> const states = get_service_node_list_state({ m_service_keys.pub });
|
|
|
|
// wait one block before starting uptime proofs (but not on testnet/devnet, where we sometimes
|
|
// have mass registrations/deregistrations where the waiting causes problems).
|
|
uint64_t delay_blocks = m_nettype == network_type::MAINNET ? 1 : 0;
|
|
if (!states.empty() && (states[0].info->registration_height + delay_blocks) < get_current_blockchain_height())
|
|
{
|
|
m_check_uptime_proof_interval.do_call([this]() {
|
|
// This timer is not perfectly precise and can leak seconds slightly, so send the uptime
|
|
// proof if we are within half a tick of the target time. (Essentially our target proof
|
|
// window becomes the first time this triggers in the 59.75-60.25 minute window).
|
|
uint64_t next_proof_time = 0;
|
|
m_service_node_list.access_proof(m_service_keys.pub, [&](auto &proof) { next_proof_time = proof.timestamp; });
|
|
auto& netconf = get_net_config();
|
|
next_proof_time += std::chrono::seconds{
|
|
netconf.UPTIME_PROOF_FREQUENCY - netconf.UPTIME_PROOF_CHECK_INTERVAL/2}.count();
|
|
|
|
if ((uint64_t) std::time(nullptr) < next_proof_time)
|
|
return;
|
|
|
|
auto pubkey = m_service_node_list.get_pubkey_from_x25519(m_service_keys.pub_x25519);
|
|
if (pubkey != crypto::null_pkey && pubkey != m_service_keys.pub && m_service_node_list.is_service_node(pubkey, false /*don't require active*/))
|
|
{
|
|
oxen::log::info(logcat, fmt::format(fg(fmt::terminal_color::red),
|
|
"Failed to submit uptime proof: another service node on the network is using the same ed/x25519 keys as this service node. This typically means both have the same 'key_ed25519' private key file."));
|
|
return;
|
|
}
|
|
|
|
{
|
|
std::vector<crypto::public_key> sn_pks;
|
|
auto sns = m_service_node_list.get_service_node_list_state();
|
|
sn_pks.reserve(sns.size());
|
|
for (const auto& sni : sns)
|
|
sn_pks.push_back(sni.pubkey);
|
|
|
|
m_service_node_list.for_each_service_node_info_and_proof(sn_pks.begin(), sn_pks.end(), [&](auto& pk, auto& sni, auto& proof) {
|
|
if (pk != m_service_keys.pub && proof.proof->public_ip == m_sn_public_ip &&
|
|
(proof.proof->qnet_port == m_quorumnet_port || (
|
|
m_nettype != network_type::DEVNET && (proof.proof->storage_https_port == storage_https_port() || proof.proof->storage_omq_port == storage_omq_port()))))
|
|
oxen::log::info(logcat, fmt::format(fg(fmt::terminal_color::red), "Another service node ({}) is broadcasting the same public IP and ports as this service node ({}:{}[qnet], :{}[SS-HTTP], :{}[SS-LMQ]). This will lead to deregistration of one or both service nodes if not corrected. (Do both service nodes have the correct IP for the service-node-public-ip setting?)", pk, epee::string_tools::get_ip_string_from_int32(m_sn_public_ip), proof.proof->qnet_port, proof.proof->storage_https_port, proof.proof->storage_omq_port));
|
|
});
|
|
}
|
|
|
|
if (m_nettype != network_type::DEVNET)
|
|
{
|
|
if (!check_external_ping(m_last_storage_server_ping, get_net_config().UPTIME_PROOF_FREQUENCY, "the storage server"))
|
|
{
|
|
oxen::log::info(logcat, fmt::format(fg(fmt::terminal_color::red),
|
|
"Failed to submit uptime proof: have not heard from the storage server recently. Make sure that it is running! It is required to run alongside the Loki daemon"));
|
|
return;
|
|
}
|
|
if (!check_external_ping(m_last_lokinet_ping, get_net_config().UPTIME_PROOF_FREQUENCY, "Lokinet"))
|
|
{
|
|
oxen::log::info(logcat, fmt::format(fg(fmt::terminal_color::red),
|
|
"Failed to submit uptime proof: have not heard from lokinet recently. Make sure that it is running! It is required to run alongside the Loki daemon"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
submit_uptime_proof();
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// reset the interval so that we're ready when we register, OR if we get deregistered this primes us up for re-registration in the same session
|
|
m_check_uptime_proof_interval.reset();
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::on_idle()
|
|
{
|
|
if(!m_starter_message_showed)
|
|
{
|
|
std::string main_message;
|
|
if (m_offline)
|
|
main_message = "The daemon is running offline and will not attempt to sync to the Loki network.";
|
|
else
|
|
main_message = "The daemon will start synchronizing with the network. This may take a long time to complete.";
|
|
oxen::log::info(logcat, fmt::format(fg(fmt::terminal_color::yellow), "\n**********************************************************************\n\
|
|
{}\n\n\
|
|
You can set the level of process detailization through \"set_log <level|categories>\" command,\n\
|
|
where <level> is between 0 (no details) and 4 (very verbose), or custom category based levels (eg, *:WARNING).\n\
|
|
\n\
|
|
Use the \"help\" command to see the list of available commands.\n\
|
|
Use \"help <command>\" to see a command's documentation.\n\
|
|
**********************************************************************\n", main_message));
|
|
m_starter_message_showed = true;
|
|
}
|
|
|
|
m_txpool_auto_relayer.do_call([this] { return relay_txpool_transactions(); });
|
|
m_service_node_vote_relayer.do_call([this] { return relay_service_node_votes(); });
|
|
m_check_disk_space_interval.do_call([this] { return check_disk_space(); });
|
|
m_block_rate_interval.do_call([this] { return check_block_rate(); });
|
|
m_sn_proof_cleanup_interval.do_call([&snl=m_service_node_list] { snl.cleanup_proofs(); return true; });
|
|
|
|
std::chrono::seconds lifetime{time(nullptr) - get_start_time()};
|
|
if (m_service_node && lifetime > get_net_config().UPTIME_PROOF_STARTUP_DELAY) // Give us some time to connect to peers before sending uptimes
|
|
{
|
|
do_uptime_proof_call();
|
|
}
|
|
|
|
m_blockchain_pruning_interval.do_call([this] { return update_blockchain_pruning(); });
|
|
m_miner.on_idle();
|
|
m_mempool.on_idle();
|
|
|
|
#ifdef ENABLE_SYSTEMD
|
|
m_systemd_notify_interval.do_call([this] { sd_notify(0, ("WATCHDOG=1\nSTATUS=" + get_status_string()).c_str()); });
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::check_disk_space()
|
|
{
|
|
uint64_t free_space = get_free_space();
|
|
if (free_space < 1ull * 1024 * 1024 * 1024) // 1 GB
|
|
oxen::log::warning(logcat, fmt::format(fg(fmt::terminal_color::red), "Free space is below 1 GB on {}", m_config_folder));
|
|
return true;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
double factorial(unsigned int n)
|
|
{
|
|
if (n <= 1)
|
|
return 1.0;
|
|
double f = n;
|
|
while (n-- > 1)
|
|
f *= n;
|
|
return f;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
static double probability1(unsigned int blocks, unsigned int expected)
|
|
{
|
|
// https://www.umass.edu/wsp/resources/poisson/#computing
|
|
return pow(expected, blocks) / (factorial(blocks) * exp(expected));
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
static double probability(unsigned int blocks, unsigned int expected)
|
|
{
|
|
double p = 0.0;
|
|
if (blocks <= expected)
|
|
{
|
|
for (unsigned int b = 0; b <= blocks; ++b)
|
|
p += probability1(b, expected);
|
|
}
|
|
else if (blocks > expected)
|
|
{
|
|
for (unsigned int b = blocks; b <= expected * 3 /* close enough */; ++b)
|
|
p += probability1(b, expected);
|
|
}
|
|
return p;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::check_block_rate()
|
|
{
|
|
if (m_offline || m_nettype == network_type::FAKECHAIN || m_target_blockchain_height > get_current_blockchain_height() || m_target_blockchain_height == 0)
|
|
{
|
|
oxen::log::debug(logcat, "Not checking block rate, offline or syncing");
|
|
return true;
|
|
}
|
|
|
|
static constexpr double threshold = 1. / ((24h * 10) / TARGET_BLOCK_TIME); // one false positive every 10 days
|
|
static constexpr unsigned int max_blocks_checked = 150;
|
|
|
|
const time_t now = time(NULL);
|
|
const std::vector<time_t> timestamps = m_blockchain_storage.get_last_block_timestamps(max_blocks_checked);
|
|
|
|
static const unsigned int seconds[] = { 5400, 3600, 1800, 1200, 600 };
|
|
for (size_t n = 0; n < sizeof(seconds)/sizeof(seconds[0]); ++n)
|
|
{
|
|
unsigned int b = 0;
|
|
const time_t time_boundary = now - static_cast<time_t>(seconds[n]);
|
|
for (time_t ts: timestamps) b += ts >= time_boundary;
|
|
const double p = probability(b, seconds[n] / tools::to_seconds(TARGET_BLOCK_TIME));
|
|
oxen::log::debug(logcat, "blocks in the last {} minutes: {} (probability {})", seconds[n] / 60, b, p);
|
|
if (p < threshold)
|
|
{
|
|
oxen::log::warning(logcat, "There were {}{} blocks in the last {} minutes, \
|
|
there might be large hash rate changes, or we might be partitioned, \
|
|
cut off from the Loki network or under attack, or your computer's time is off. \
|
|
Or it could be just sheer bad luck.", b, (b == max_blocks_checked ? " or more" : ""), seconds[n] / 60);
|
|
break; // no need to look further
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
void core::flush_bad_txs_cache()
|
|
{
|
|
bad_semantics_txes_lock.lock();
|
|
for (int idx = 0; idx < 2; ++idx)
|
|
bad_semantics_txes[idx].clear();
|
|
bad_semantics_txes_lock.unlock();
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
void core::flush_invalid_blocks()
|
|
{
|
|
m_blockchain_storage.flush_invalid_blocks();
|
|
}
|
|
bool core::update_blockchain_pruning()
|
|
{
|
|
return m_blockchain_storage.update_blockchain_pruning();
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::check_blockchain_pruning()
|
|
{
|
|
return m_blockchain_storage.check_blockchain_pruning();
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
void core::set_target_blockchain_height(uint64_t target_blockchain_height)
|
|
{
|
|
m_target_blockchain_height = target_blockchain_height;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
uint64_t core::get_target_blockchain_height() const
|
|
{
|
|
return m_target_blockchain_height;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
uint64_t core::prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes)
|
|
{
|
|
return get_blockchain_storage().prevalidate_block_hashes(height, hashes);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
uint64_t core::get_free_space() const
|
|
{
|
|
return fs::space(m_config_folder).available;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
std::shared_ptr<const service_nodes::quorum> core::get_quorum(service_nodes::quorum_type type, uint64_t height, bool include_old, std::vector<std::shared_ptr<const service_nodes::quorum>> *alt_states) const
|
|
{
|
|
return m_service_node_list.get_quorum(type, height, include_old, alt_states);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::is_service_node(const crypto::public_key& pubkey, bool require_active) const
|
|
{
|
|
return m_service_node_list.is_service_node(pubkey, require_active);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
const std::vector<service_nodes::key_image_blacklist_entry> &core::get_service_node_blacklisted_key_images() const
|
|
{
|
|
return m_service_node_list.get_blacklisted_key_images();
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
std::vector<service_nodes::service_node_pubkey_info> core::get_service_node_list_state(const std::vector<crypto::public_key> &service_node_pubkeys) const
|
|
{
|
|
return m_service_node_list.get_service_node_list_state(service_node_pubkeys);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::add_service_node_vote(const service_nodes::quorum_vote_t& vote, vote_verification_context &vvc)
|
|
{
|
|
return m_quorum_cop.handle_vote(vote, vvc);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
uint32_t core::get_blockchain_pruning_seed() const
|
|
{
|
|
return get_blockchain_storage().get_blockchain_pruning_seed();
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
bool core::prune_blockchain(uint32_t pruning_seed)
|
|
{
|
|
return get_blockchain_storage().prune_blockchain(pruning_seed);
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
std::time_t core::get_start_time() const
|
|
{
|
|
return start_time;
|
|
}
|
|
//-----------------------------------------------------------------------------------------------
|
|
void core::graceful_exit()
|
|
{
|
|
raise(SIGTERM);
|
|
}
|
|
}
|