Compare commits

...

8 Commits

Author SHA1 Message Date
Sean cab17962d1
Merge pull request #1636 from darcys22/clang-format
Clang format
2023-04-14 10:38:05 +10:00
Jason Rhinelander 5436df9615
Fix pywallet3 build
- link against logging/fmt/spdlog targets
- make sure we have the proper defines carried over from the main build
2023-04-13 17:15:13 -03:00
Jason Rhinelander 0cef2a9491
Fix horrible tr() in wallet_api; remove boost::format
wallet_api pretend to have `tr()` but really didn't: instead it had it
via a do-nothing macro, but the macro leaked into other places when
headers got sorted that caused all sorts of pain.

This cleans out some of the garbage.
2023-04-13 17:15:13 -03:00
Jason Rhinelander 6f0c74ab9b
Bump oxen-encoding for header include fix 2023-04-13 17:15:13 -03:00
Jason Rhinelander 67818e7cbf
Manual reformatting and post-formatting fixes
clang-format resulted in some screwy reformatting, particularly with
post-commended fields, and also exposed various missing header includes
when re-sorting headers.

This commit contains various small fixes and improvements to deal with
the formatting issues.
2023-04-13 17:15:12 -03:00
Jason Rhinelander 13409ad00e
run clang format 2023-04-13 17:15:12 -03:00
Sean Darcy 45db02de78
clang-format lint check 2023-04-13 17:15:12 -03:00
Sean Darcy 97aa52ed4e
new clang format params 2023-04-13 17:12:39 -03:00
589 changed files with 186046 additions and 158028 deletions

View File

@ -1,56 +1,50 @@
BasedOnStyle: Google
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveAssignments: 'false'
AlignConsecutiveDeclarations: 'false'
AlignEscapedNewlinesLeft: 'true'
AlignOperands: 'false'
AlignTrailingComments: 'true'
AllowShortBlocksOnASingleLine: 'false'
AllowShortCaseLabelsOnASingleLine: 'false'
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: 'false'
AllowShortLoopsOnASingleLine: 'false'
AlwaysBreakAfterDefinitionReturnType: All
AlwaysBreakAfterReturnType: All
AlwaysBreakTemplateDeclarations: 'true'
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: true
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: true
AfterStruct: true
AfterUnion: true
AfterExternBlock: true
BeforeCatch: true
BeforeElse: true
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakBeforeTernaryOperators: 'true'
BreakConstructorInitializersBeforeComma: 'true'
Cpp11BracedListStyle: 'true'
KeepEmptyLinesAtTheStartOfBlocks: 'false'
NamespaceIndentation: All
PenaltyBreakString: '3'
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: AlignAfterOperator
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: false
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
AlwaysBreakBeforeMultilineStrings: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: AfterColon
Cpp11BracedListStyle: true
KeepEmptyLinesAtTheStartOfBlocks: true
NamespaceIndentation: Inner
CompactNamespaces: true
PenaltyBreakString: 3
SpaceBeforeParens: ControlStatements
SpacesInAngles: 'false'
SpacesInContainerLiterals: 'false'
SpacesInParentheses: 'false'
SpacesInSquareBrackets: 'false'
Standard: Cpp11
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: c++17
UseTab: Never
SortIncludes: false
SortIncludes: true
ColumnLimit: 100
IndentWidth: 4
AccessModifierOffset: -2
ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 8
# treat pointers and reference declarations as if part of the type
DerivePointerAlignment: false
PointerAlignment: Left
# when wrapping function calls/declarations, force each parameter to have its own line
BinPackParameters: 'false'
BinPackArguments: 'false'
BinPackParameters: false
BinPackArguments: false

View File

@ -237,6 +237,24 @@ local gui_wallet_step_darwin = {
[
{
name: 'lint check',
kind: 'pipeline',
type: 'docker',
steps: [{
name: 'build',
image: docker_base + 'lint',
pull: 'always',
commands: [
'echo "Building on ${DRONE_STAGE_MACHINE}"',
apt_get_quiet + ' update',
apt_get_quiet + ' install -y eatmydata',
'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y git clang-format-14 jsonnet',
'./contrib/drone-format-verify.sh',
],
}],
},
// Static build to make wallet3:
{
name: 'Static (wallet3)',

7
contrib/drone-format-verify.sh Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
test "x$IGNORE" != "x" && exit 0
set -e
repo=$(readlink -e $(dirname $0)/../)
clang-format-14 -i $(find $repo/{src,pybind} | grep -E '\.[hc](pp)?$')
jsonnetfmt -i $repo/.drone.jsonnet
git --no-pager diff --exit-code --color || (echo -ne '\n\n\e[31;1mLint check failed; please run ./contrib/format.sh\e[0m\n\n' ; exit 1)

View File

@ -1,8 +1,10 @@
#!/usr/bin/env bash
CLANG_FORMAT_DESIRED_VERSION=11
CLANG_FORMAT_DESIRED_VERSION=14
TARGET_DIRS=(src/wallet3 src/sqlitedb src/logging)
TARGET_DIRS=(src pybind)
set -e
binary=$(which clang-format-$CLANG_FORMAT_DESIRED_VERSION 2>/dev/null)
if [ $? -ne 0 ]; then

@ -1 +1 @@
Subproject commit 707a83609fb64d09b61ed1e56c82bf692050d2a1
Subproject commit f6c516dce886b93435e28b4211d4e3a61245a33f

View File

@ -9,7 +9,13 @@ pybind11_add_module(pywallet3 MODULE
wallet/daemon_comms_config.cpp
wallet/rpc_config.cpp
)
target_link_libraries(pywallet3 PUBLIC wallet3)
target_link_libraries(pywallet3
PUBLIC
wallet3
logging
oxen::logging
spdlog::spdlog
fmt::fmt)
target_include_directories(pywallet3 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
file(GENERATE

View File

@ -1,34 +1,25 @@
#pragma once
#include <pybind11/functional.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/functional.h>
namespace py = pybind11;
namespace wallet
{
void
Wallet_Init(py::module& mod);
namespace wallet {
void Wallet_Init(py::module& mod);
void
Keyring_Init(py::module& mod);
void Keyring_Init(py::module& mod);
void
KeyringManager_Init(py::module& mod);
void KeyringManager_Init(py::module& mod);
void
WalletConfig_Init(py::module& mod);
void WalletConfig_Init(py::module& mod);
void
GeneralWalletConfig_Init(py::module& mod);
void GeneralWalletConfig_Init(py::module& mod);
void
DaemonCommsConfig_Init(py::module& mod);
void DaemonCommsConfig_Init(py::module& mod);
void
RPCConfig_Init(py::module& mod);
void RPCConfig_Init(py::module& mod);
void
LoggingConfig_Init(py::module& mod);
void LoggingConfig_Init(py::module& mod);
} // namespace wallet

View File

@ -1,13 +1,12 @@
#include "common.hpp"
PYBIND11_MODULE(pywallet3, m)
{
wallet::Wallet_Init(m);
wallet::Keyring_Init(m);
wallet::KeyringManager_Init(m);
wallet::WalletConfig_Init(m);
wallet::GeneralWalletConfig_Init(m);
wallet::LoggingConfig_Init(m);
wallet::DaemonCommsConfig_Init(m);
wallet::RPCConfig_Init(m);
PYBIND11_MODULE(pywallet3, m) {
wallet::Wallet_Init(m);
wallet::Keyring_Init(m);
wallet::KeyringManager_Init(m);
wallet::WalletConfig_Init(m);
wallet::GeneralWalletConfig_Init(m);
wallet::LoggingConfig_Init(m);
wallet::DaemonCommsConfig_Init(m);
wallet::RPCConfig_Init(m);
}

View File

@ -9,8 +9,15 @@ pywallet3_sources = ['$<TARGET_PROPERTY:pywallet3,SOURCE_DIR>/' + src for src in
'$<JOIN:$<TARGET_PROPERTY:pywallet3,SOURCES>,',
'>')]
pywallet3_include_dirs = [
'$<JOIN:$<TARGET_PROPERTY:wallet3,INCLUDE_DIRECTORIES>,',
'$<JOIN:$<TARGET_PROPERTY:pywallet3,INCLUDE_DIRECTORIES>,',
'>']
pywallet3_defs = []
for d in [
'$<JOIN:$<TARGET_PROPERTY:pywallet3,COMPILE_DEFINITIONS>,',
'>']:
parts = d.split("=", 1)
pywallet3_defs.append((parts[0], parts[1] if len(parts) == 2 else None))
# Note:
# Sort input source files if you glob sources to ensure bit-for-bit
@ -21,6 +28,7 @@ ext_modules = [Pybind11Extension(
pywallet3_sources,
cxx_std=17,
include_dirs=pywallet3_include_dirs,
define_macros=pywallet3_defs,
extra_objects=['$<TARGET_PROPERTY:wallet3,BINARY_DIR>/libwallet3_merged.a'],
),
]

View File

@ -1,14 +1,11 @@
#include "../common.hpp"
#include "wallet3/config/config.hpp"
namespace wallet
{
void
DaemonCommsConfig_Init(py::module& mod)
{
namespace wallet {
void DaemonCommsConfig_Init(py::module& mod) {
py::class_<DaemonCommsConfig, std::shared_ptr<DaemonCommsConfig>>(mod, "DaemonCommsConfig")
.def(py::init<>())
.def_readwrite("address", &DaemonCommsConfig::address);
}
.def(py::init<>())
.def_readwrite("address", &DaemonCommsConfig::address);
}
} // namespace wallet

View File

@ -1,15 +1,15 @@
#include "../common.hpp"
#include "wallet3/config/config.hpp"
namespace wallet
{
void
GeneralWalletConfig_Init(py::module& mod)
{
py::class_<GeneralWalletConfig, std::shared_ptr<GeneralWalletConfig>>(mod, "GeneralWalletConfig")
.def(py::init<>())
.def_readwrite("datadir", &GeneralWalletConfig::datadir)
.def_readwrite("append_network_type_to_datadir", &GeneralWalletConfig::append_network_type_to_datadir);
}
namespace wallet {
void GeneralWalletConfig_Init(py::module& mod) {
py::class_<GeneralWalletConfig, std::shared_ptr<GeneralWalletConfig>>(
mod, "GeneralWalletConfig")
.def(py::init<>())
.def_readwrite("datadir", &GeneralWalletConfig::datadir)
.def_readwrite(
"append_network_type_to_datadir",
&GeneralWalletConfig::append_network_type_to_datadir);
}
} // namespace wallet

View File

@ -1,31 +1,36 @@
#include "../common.hpp"
#include "wallet3/keyring.hpp"
#include <crypto/crypto.h>
#include <common/hex.h>
#include <crypto/crypto.h>
#include <cryptonote_basic/cryptonote_basic.h>
namespace wallet
{
void
Keyring_Init(py::module& mod)
{
py::class_<Keyring, std::shared_ptr<Keyring>>(mod, "Keyring")
.def(py::init([]( std::string ssk, std::string spk, std::string vsk, std::string vpk, std::string nettype) {
auto type = cryptonote::network_type::MAINNET;
if (nettype == "testnet") type = cryptonote::network_type::TESTNET;
else if (nettype == "devnet") type = cryptonote::network_type::DEVNET;
crypto::secret_key spend_priv;
crypto::public_key spend_pub;
crypto::secret_key view_priv;
crypto::public_key view_pub;
tools::hex_to_type<crypto::secret_key>(ssk, spend_priv);
tools::hex_to_type<crypto::public_key>(spk, spend_pub);
tools::hex_to_type<crypto::secret_key>(vsk, view_priv);
tools::hex_to_type<crypto::public_key>(vpk, view_pub);
return Keyring(spend_priv, spend_pub, view_priv, view_pub, std::move(type)); }))
#include "../common.hpp"
.def("get_main_address", &Keyring::get_main_address);
}
namespace wallet {
void Keyring_Init(py::module& mod) {
py::class_<Keyring, std::shared_ptr<Keyring>>(mod, "Keyring")
.def(py::init([](std::string ssk,
std::string spk,
std::string vsk,
std::string vpk,
std::string nettype) {
auto type = cryptonote::network_type::MAINNET;
if (nettype == "testnet")
type = cryptonote::network_type::TESTNET;
else if (nettype == "devnet")
type = cryptonote::network_type::DEVNET;
crypto::secret_key spend_priv;
crypto::public_key spend_pub;
crypto::secret_key view_priv;
crypto::public_key view_pub;
tools::hex_to_type<crypto::secret_key>(ssk, spend_priv);
tools::hex_to_type<crypto::public_key>(spk, spend_pub);
tools::hex_to_type<crypto::secret_key>(vsk, view_priv);
tools::hex_to_type<crypto::public_key>(vpk, view_pub);
return Keyring(spend_priv, spend_pub, view_priv, view_pub, std::move(type));
}))
.def("get_main_address", &Keyring::get_main_address);
}
} // namespace wallet

View File

@ -1,22 +1,24 @@
#include "../common.hpp"
#include "wallet3/keyring_manager.hpp"
#include <crypto/crypto.h>
#include <cryptonote_basic/cryptonote_basic.h>
namespace wallet
{
void
KeyringManager_Init(py::module& mod)
{
py::class_<KeyringManager, std::shared_ptr<KeyringManager>>(mod, "KeyringManager")
.def(py::init([](std::string nettype) {
auto type = cryptonote::network_type::MAINNET;
if (nettype == "testnet") type = cryptonote::network_type::TESTNET;
else if (nettype == "devnet") type = cryptonote::network_type::DEVNET;
return KeyringManager(std::move(type)); }))
#include "../common.hpp"
.def("generate_keyring_from_electrum_seed", &KeyringManager::generate_keyring_from_electrum_seed);
}
namespace wallet {
void KeyringManager_Init(py::module& mod) {
py::class_<KeyringManager, std::shared_ptr<KeyringManager>>(mod, "KeyringManager")
.def(py::init([](std::string nettype) {
auto type = cryptonote::network_type::MAINNET;
if (nettype == "testnet")
type = cryptonote::network_type::TESTNET;
else if (nettype == "devnet")
type = cryptonote::network_type::DEVNET;
return KeyringManager(std::move(type));
}))
.def("generate_keyring_from_electrum_seed",
&KeyringManager::generate_keyring_from_electrum_seed);
}
} // namespace wallet

View File

@ -1,17 +1,14 @@
#include "../common.hpp"
#include "wallet3/config/config.hpp"
namespace wallet
{
void
LoggingConfig_Init(py::module& mod)
{
namespace wallet {
void LoggingConfig_Init(py::module& mod) {
py::class_<LoggingConfig, std::shared_ptr<LoggingConfig>>(mod, "LoggingConfig")
.def(py::init<>())
.def_readwrite("level", &LoggingConfig::level)
.def_readwrite("save_logs_in_subdirectory", &LoggingConfig::save_logs_in_subdirectory)
.def_readwrite("logdir", &LoggingConfig::logdir)
.def_readwrite("log_filename", &LoggingConfig::log_filename);
}
.def(py::init<>())
.def_readwrite("level", &LoggingConfig::level)
.def_readwrite("save_logs_in_subdirectory", &LoggingConfig::save_logs_in_subdirectory)
.def_readwrite("logdir", &LoggingConfig::logdir)
.def_readwrite("log_filename", &LoggingConfig::log_filename);
}
} // namespace wallet

View File

@ -1,14 +1,11 @@
#include "../common.hpp"
#include "wallet3/config/config.hpp"
namespace wallet
{
void
RPCConfig_Init(py::module& mod)
{
namespace wallet {
void RPCConfig_Init(py::module& mod) {
py::class_<rpc::Config, std::shared_ptr<rpc::Config>>(mod, "RPCConfig")
.def(py::init<>())
.def_readwrite("sockname", &rpc::Config::sockname);
}
.def(py::init<>())
.def_readwrite("sockname", &rpc::Config::sockname);
}
} // namespace wallet

View File

@ -1,41 +1,50 @@
#include "../common.hpp"
#include <oxenmq/oxenmq.h>
#include <wallet3/wallet.hpp>
#include <oxen/log.hpp>
#include <wallet3/config/config.hpp>
#include <wallet3/default_daemon_comms.hpp>
#include <wallet3/keyring.hpp>
#include <wallet3/config/config.hpp>
#include <oxenmq/oxenmq.h>
#include <oxen/log.hpp>
#include <wallet3/wallet.hpp>
#include "../common.hpp"
static auto logcat = oxen::log::Cat("omq");
void omq_logger(oxenmq::LogLevel level, const char* file, int line, std::string message) {
constexpr std::string_view format = "[{}:{}]: {}";
switch (level) {
case oxenmq::LogLevel::fatal: oxen::log::critical(logcat, format, file, line, message); break;
case oxenmq::LogLevel::error: oxen::log::error(logcat, format, file, line, message); break;
case oxenmq::LogLevel::warn: oxen::log::warning(logcat, format, file, line, message); break;
case oxenmq::LogLevel::info: oxen::log::info(logcat, format, file, line, message); break;
case oxenmq::LogLevel::debug: oxen::log::debug(logcat, format, file, line, message); break;
case oxenmq::LogLevel::trace: oxen::log::trace(logcat, format, file, line, message); break;
case oxenmq::LogLevel::fatal:
oxen::log::critical(logcat, format, file, line, message);
break;
case oxenmq::LogLevel::error: oxen::log::error(logcat, format, file, line, message); break;
case oxenmq::LogLevel::warn: oxen::log::warning(logcat, format, file, line, message); break;
case oxenmq::LogLevel::info: oxen::log::info(logcat, format, file, line, message); break;
case oxenmq::LogLevel::debug: oxen::log::debug(logcat, format, file, line, message); break;
case oxenmq::LogLevel::trace: oxen::log::trace(logcat, format, file, line, message); break;
}
}
namespace wallet
{
void
Wallet_Init(py::module& mod)
{
namespace wallet {
void Wallet_Init(py::module& mod) {
py::class_<Wallet, std::shared_ptr<Wallet>>(mod, "Wallet")
.def(py::init([](const std::string& wallet_name, std::shared_ptr<Keyring> keyring, Config config) {
auto& comms_config = config.daemon;
auto& omq_rpc_config = config.omq_rpc;
auto oxenmq = std::make_shared<oxenmq::OxenMQ>(omq_logger, oxenmq::LogLevel::info);
auto comms = std::make_shared<DefaultDaemonComms>(oxenmq, comms_config);
return Wallet::create(std::move(oxenmq), std::move(keyring), nullptr, std::move(comms), wallet_name + ".sqlite", "", std::move(config));
}))
.def("get_balance", &Wallet::get_balance)
.def("deregister", &Wallet::deregister);
}
.def(py::init([](const std::string& wallet_name,
std::shared_ptr<Keyring> keyring,
Config config) {
auto& comms_config = config.daemon;
auto& omq_rpc_config = config.omq_rpc;
auto oxenmq = std::make_shared<oxenmq::OxenMQ>(omq_logger, oxenmq::LogLevel::info);
auto comms = std::make_shared<DefaultDaemonComms>(oxenmq, comms_config);
return Wallet::create(
std::move(oxenmq),
std::move(keyring),
nullptr,
std::move(comms),
wallet_name + ".sqlite",
"",
std::move(config));
}))
.def("get_balance", &Wallet::get_balance)
.def("deregister", &Wallet::deregister);
}
} // namespace wallet

View File

@ -1,17 +1,14 @@
#include "../common.hpp"
#include "wallet3/config/config.hpp"
namespace wallet
{
void
WalletConfig_Init(py::module& mod)
{
namespace wallet {
void WalletConfig_Init(py::module& mod) {
py::class_<Config, std::shared_ptr<Config>>(mod, "WalletConfig")
.def(py::init<>())
.def_readwrite("general", &Config::general)
.def_readwrite("logging", &Config::logging)
.def_readwrite("daemon", &Config::daemon)
.def_readwrite("omq_rpc", &Config::omq_rpc);
}
.def(py::init<>())
.def_readwrite("general", &Config::general)
.def_readwrite("logging", &Config::logging)
.def_readwrite("daemon", &Config::daemon)
.def_readwrite("omq_rpc", &Config::omq_rpc);
}
} // namespace wallet

View File

@ -1,22 +1,22 @@
// 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
@ -27,309 +27,288 @@
// 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.
#include "common/string_util.h"
#include "cryptonote_basic/hardfork.h"
#include "cryptonote_core/service_node_rules.h"
#include "checkpoints/checkpoints.h"
#include "epee/string_tools.h"
#include "blockchain_db.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "ringct/rctOps.h"
#include "common/hex.h"
#include "lmdb/db_lmdb.h"
#include <chrono>
namespace cryptonote
{
#include "checkpoints/checkpoints.h"
#include "common/hex.h"
#include "common/string_util.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_basic/hardfork.h"
#include "cryptonote_core/service_node_rules.h"
#include "epee/string_tools.h"
#include "lmdb/db_lmdb.h"
#include "ringct/rctOps.h"
static auto logcat = log::Cat("blockchain.db");
namespace cryptonote {
static auto logcat = log::Cat("blockchain.db");
const command_line::arg_descriptor<std::string> arg_db_sync_mode = {
"db-sync-mode"
, "Specify sync option, using format [safe|fast|fastest]:[sync|async]:[<nblocks_per_sync>[blocks]|<nbytes_per_sync>[bytes]]."
, "fast:async:250000000bytes"
};
const command_line::arg_descriptor<bool> arg_db_salvage = {
"db-salvage"
, "Try to salvage a blockchain database if it seems corrupted"
, false
};
"db-sync-mode",
"Specify sync option, using format "
"[safe|fast|fastest]:[sync|async]:[<nblocks_per_sync>[blocks]|<nbytes_per_sync>[bytes]].",
"fast:async:250000000bytes"};
const command_line::arg_descriptor<bool> arg_db_salvage = {
"db-salvage", "Try to salvage a blockchain database if it seems corrupted", false};
BlockchainDB *new_db()
{
return new BlockchainLMDB();
BlockchainDB* new_db() {
return new BlockchainLMDB();
}
void BlockchainDB::init_options(boost::program_options::options_description& desc)
{
command_line::add_arg(desc, arg_db_sync_mode);
command_line::add_arg(desc, arg_db_salvage);
void BlockchainDB::init_options(boost::program_options::options_description& desc) {
command_line::add_arg(desc, arg_db_sync_mode);
command_line::add_arg(desc, arg_db_salvage);
}
void BlockchainDB::pop_block()
{
block blk;
std::vector<transaction> txs;
pop_block(blk, txs);
void BlockchainDB::pop_block() {
block blk;
std::vector<transaction> txs;
pop_block(blk, txs);
}
void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const std::pair<transaction, std::string>& txp, const crypto::hash* tx_hash_ptr, const crypto::hash* tx_prunable_hash_ptr)
{
const transaction &tx = txp.first;
void BlockchainDB::add_transaction(
const crypto::hash& blk_hash,
const std::pair<transaction, std::string>& txp,
const crypto::hash* tx_hash_ptr,
const crypto::hash* tx_prunable_hash_ptr) {
const transaction& tx = txp.first;
bool miner_tx = false;
crypto::hash tx_hash, tx_prunable_hash;
if (!tx_hash_ptr)
{
// should only need to compute hash for miner transactions
tx_hash = get_transaction_hash(tx);
log::trace(logcat, "null tx_hash_ptr - needed to compute: {}", tx_hash);
}
else
{
tx_hash = *tx_hash_ptr;
}
bool has_blacklisted_outputs = false;
if (tx.version >= cryptonote::txversion::v2_ringct)
{
if (!tx_prunable_hash_ptr)
tx_prunable_hash = get_transaction_prunable_hash(tx, &txp.second);
else
tx_prunable_hash = *tx_prunable_hash_ptr;
crypto::secret_key secret_tx_key;
cryptonote::account_public_address address;
if (get_tx_secret_key_from_tx_extra(tx.extra, secret_tx_key) && get_service_node_contributor_from_tx_extra(tx.extra, address))
has_blacklisted_outputs = true;
}
for (const txin_v& tx_input : tx.vin)
{
if (std::holds_alternative<txin_to_key>(tx_input))
{
add_spent_key(var::get<txin_to_key>(tx_input).k_image);
bool miner_tx = false;
crypto::hash tx_hash, tx_prunable_hash;
if (!tx_hash_ptr) {
// should only need to compute hash for miner transactions
tx_hash = get_transaction_hash(tx);
log::trace(logcat, "null tx_hash_ptr - needed to compute: {}", tx_hash);
} else {
tx_hash = *tx_hash_ptr;
}
else if (std::holds_alternative<txin_gen>(tx_input))
{
/* nothing to do here */
miner_tx = true;
bool has_blacklisted_outputs = false;
if (tx.version >= cryptonote::txversion::v2_ringct) {
if (!tx_prunable_hash_ptr)
tx_prunable_hash = get_transaction_prunable_hash(tx, &txp.second);
else
tx_prunable_hash = *tx_prunable_hash_ptr;
crypto::secret_key secret_tx_key;
cryptonote::account_public_address address;
if (get_tx_secret_key_from_tx_extra(tx.extra, secret_tx_key) &&
get_service_node_contributor_from_tx_extra(tx.extra, address))
has_blacklisted_outputs = true;
}
else
{
log::info(logcat, "Unsupported input type, removing key images and aborting transaction addition");
for (const txin_v& tx_input : tx.vin)
{
if (std::holds_alternative<txin_to_key>(tx_input))
{
remove_spent_key(var::get<txin_to_key>(tx_input).k_image);
for (const txin_v& tx_input : tx.vin) {
if (std::holds_alternative<txin_to_key>(tx_input)) {
add_spent_key(var::get<txin_to_key>(tx_input).k_image);
} else if (std::holds_alternative<txin_gen>(tx_input)) {
/* nothing to do here */
miner_tx = true;
} else {
log::info(
logcat,
"Unsupported input type, removing key images and aborting transaction "
"addition");
for (const txin_v& tx_input : tx.vin) {
if (std::holds_alternative<txin_to_key>(tx_input)) {
remove_spent_key(var::get<txin_to_key>(tx_input).k_image);
}
}
return;
}
}
return;
}
}
uint64_t tx_id = add_transaction_data(blk_hash, txp, tx_hash, tx_prunable_hash);
std::vector<uint64_t> amount_output_indices(tx.vout.size());
// iterate tx.vout using indices instead of C++11 foreach syntax because
// we need the index
for (uint64_t i = 0; i < tx.vout.size(); ++i)
{
uint64_t unlock_time = 0;
if (tx.version >= cryptonote::txversion::v3_per_output_unlock_times)
{
unlock_time = tx.output_unlock_times[i];
}
else
{
unlock_time = tx.unlock_time;
}
// miner v2 txes have their coinbase output in one single out to save space,
// and we store them as rct outputs with an identity mask
if (miner_tx && tx.version >= cryptonote::txversion::v2_ringct)
{
cryptonote::tx_out vout = tx.vout[i];
const rct::key commitment = rct::zeroCommit(vout.amount);
vout.amount = 0;
amount_output_indices[i] = add_output(tx_hash, vout, i, unlock_time,
&commitment);
uint64_t tx_id = add_transaction_data(blk_hash, txp, tx_hash, tx_prunable_hash);
std::vector<uint64_t> amount_output_indices(tx.vout.size());
// iterate tx.vout using indices instead of C++11 foreach syntax because
// we need the index
for (uint64_t i = 0; i < tx.vout.size(); ++i) {
uint64_t unlock_time = 0;
if (tx.version >= cryptonote::txversion::v3_per_output_unlock_times) {
unlock_time = tx.output_unlock_times[i];
} else {
unlock_time = tx.unlock_time;
}
// miner v2 txes have their coinbase output in one single out to save space,
// and we store them as rct outputs with an identity mask
if (miner_tx && tx.version >= cryptonote::txversion::v2_ringct) {
cryptonote::tx_out vout = tx.vout[i];
const rct::key commitment = rct::zeroCommit(vout.amount);
vout.amount = 0;
amount_output_indices[i] = add_output(tx_hash, vout, i, unlock_time, &commitment);
} else {
amount_output_indices[i] = add_output(
tx_hash,
tx.vout[i],
i,
unlock_time,
tx.version >= cryptonote::txversion::v2_ringct
? &tx.rct_signatures.outPk[i].mask
: NULL);
}
}
else
{
amount_output_indices[i] = add_output(tx_hash, tx.vout[i], i, unlock_time,
tx.version >= cryptonote::txversion::v2_ringct ? &tx.rct_signatures.outPk[i].mask : NULL);
if (has_blacklisted_outputs)
add_output_blacklist(amount_output_indices);
add_tx_amount_output_indices(tx_id, amount_output_indices);
}
uint64_t BlockchainDB::add_block(
const std::pair<block, std::string>& blck,
size_t block_weight,
uint64_t long_term_block_weight,
const difficulty_type& cumulative_difficulty,
const uint64_t& coins_generated,
const std::vector<std::pair<transaction, std::string>>& txs) {
const block& blk = blck.first;
// sanity
if (blk.tx_hashes.size() != txs.size())
throw std::runtime_error("Inconsistent tx/hashes sizes");
auto started = std::chrono::steady_clock::now();
crypto::hash blk_hash = get_block_hash(blk);
time_blk_hash += std::chrono::steady_clock::now() - started;
uint64_t prev_height = height();
// call out to add the transactions
started = std::chrono::steady_clock::now();
uint64_t num_rct_outs = 0;
add_transaction(blk_hash, std::make_pair(blk.miner_tx, tx_to_blob(blk.miner_tx)));
if (blk.miner_tx.version >= cryptonote::txversion::v2_ringct)
num_rct_outs += blk.miner_tx.vout.size();
int tx_i = 0;
crypto::hash tx_hash{};
for (const std::pair<transaction, std::string>& tx : txs) {
tx_hash = blk.tx_hashes[tx_i];
add_transaction(blk_hash, tx, &tx_hash);
for (const auto& vout : tx.first.vout) {
if (vout.amount == 0)
++num_rct_outs;
}
++tx_i;
}
}
time_add_transaction += std::chrono::steady_clock::now() - started;
if (has_blacklisted_outputs)
add_output_blacklist(amount_output_indices);
// call out to subclass implementation to add the block & metadata
started = std::chrono::steady_clock::now();
add_block(
blk,
block_weight,
long_term_block_weight,
cumulative_difficulty,
coins_generated,
num_rct_outs,
blk_hash);
time_add_block1 += std::chrono::steady_clock::now() - started;
add_tx_amount_output_indices(tx_id, amount_output_indices);
++num_calls;
return prev_height;
}
uint64_t BlockchainDB::add_block( const std::pair<block, std::string>& blck
, size_t block_weight
, uint64_t long_term_block_weight
, const difficulty_type& cumulative_difficulty
, const uint64_t& coins_generated
, const std::vector<std::pair<transaction, std::string>>& txs
)
{
const block &blk = blck.first;
void BlockchainDB::pop_block(block& blk, std::vector<transaction>& txs) {
blk = get_top_block();
// sanity
if (blk.tx_hashes.size() != txs.size())
throw std::runtime_error("Inconsistent tx/hashes sizes");
remove_block();
auto started = std::chrono::steady_clock::now();
crypto::hash blk_hash = get_block_hash(blk);
time_blk_hash += std::chrono::steady_clock::now() - started;
uint64_t prev_height = height();
// call out to add the transactions
started = std::chrono::steady_clock::now();
uint64_t num_rct_outs = 0;
add_transaction(blk_hash, std::make_pair(blk.miner_tx, tx_to_blob(blk.miner_tx)));
if (blk.miner_tx.version >= cryptonote::txversion::v2_ringct)
num_rct_outs += blk.miner_tx.vout.size();
int tx_i = 0;
crypto::hash tx_hash{};
for (const std::pair<transaction, std::string>& tx : txs)
{
tx_hash = blk.tx_hashes[tx_i];
add_transaction(blk_hash, tx, &tx_hash);
for (const auto &vout: tx.first.vout)
{
if (vout.amount == 0)
++num_rct_outs;
for (auto it = blk.tx_hashes.rbegin(); it != blk.tx_hashes.rend(); ++it) {
auto& h = *it;
cryptonote::transaction tx;
if (!get_tx(h, tx) && !get_pruned_tx(h, tx))
throw DB_ERROR("Failed to get pruned or unpruned transaction from the db");
txs.push_back(std::move(tx));
remove_transaction(h);
}
++tx_i;
}
time_add_transaction += std::chrono::steady_clock::now() - started;
// call out to subclass implementation to add the block & metadata
started = std::chrono::steady_clock::now();
add_block(blk, block_weight, long_term_block_weight, cumulative_difficulty, coins_generated, num_rct_outs, blk_hash);
time_add_block1 += std::chrono::steady_clock::now() - started;
++num_calls;
return prev_height;
remove_transaction(get_transaction_hash(blk.miner_tx));
}
void BlockchainDB::pop_block(block& blk, std::vector<transaction>& txs)
{
blk = get_top_block();
void BlockchainDB::remove_transaction(const crypto::hash& tx_hash) {
transaction tx = get_pruned_tx(tx_hash);
remove_block();
for (auto it = blk.tx_hashes.rbegin(); it != blk.tx_hashes.rend(); ++it)
{
auto& h = *it;
cryptonote::transaction tx;
if (!get_tx(h, tx) && !get_pruned_tx(h, tx))
throw DB_ERROR("Failed to get pruned or unpruned transaction from the db");
txs.push_back(std::move(tx));
remove_transaction(h);
}
remove_transaction(get_transaction_hash(blk.miner_tx));
}
void BlockchainDB::remove_transaction(const crypto::hash& tx_hash)
{
transaction tx = get_pruned_tx(tx_hash);
for (const txin_v& tx_input : tx.vin)
{
if (std::holds_alternative<txin_to_key>(tx_input))
{
remove_spent_key(var::get<txin_to_key>(tx_input).k_image);
for (const txin_v& tx_input : tx.vin) {
if (std::holds_alternative<txin_to_key>(tx_input)) {
remove_spent_key(var::get<txin_to_key>(tx_input).k_image);
}
}
}
// need tx as tx.vout has the tx outputs, and the output amounts are needed
remove_transaction_data(tx_hash, tx);
// need tx as tx.vout has the tx outputs, and the output amounts are needed
remove_transaction_data(tx_hash, tx);
}
block_header BlockchainDB::get_block_header(const crypto::hash& h) const
{
block_header b = get_block_header_from_height(get_block_height(h));
return b;
block_header BlockchainDB::get_block_header(const crypto::hash& h) const {
block_header b = get_block_header_from_height(get_block_height(h));
return b;
}
block BlockchainDB::get_block(const crypto::hash& h) const
{
block b = get_block_from_height(get_block_height(h));
return b;
block BlockchainDB::get_block(const crypto::hash& h) const {
block b = get_block_from_height(get_block_height(h));
return b;
}
bool BlockchainDB::get_tx(const crypto::hash& h, cryptonote::transaction &tx) const
{
std::string bd;
if (!get_tx_blob(h, bd))
return false;
if (!parse_and_validate_tx_from_blob(bd, tx))
throw DB_ERROR("Failed to parse transaction from blob retrieved from the db");
bool BlockchainDB::get_tx(const crypto::hash& h, cryptonote::transaction& tx) const {
std::string bd;
if (!get_tx_blob(h, bd))
return false;
if (!parse_and_validate_tx_from_blob(bd, tx))
throw DB_ERROR("Failed to parse transaction from blob retrieved from the db");
return true;
return true;
}
bool BlockchainDB::get_pruned_tx(const crypto::hash& h, cryptonote::transaction &tx) const
{
std::string bd;
if (!get_pruned_tx_blob(h, bd))
return false;
if (!parse_and_validate_tx_base_from_blob(bd, tx))
{
throw DB_ERROR("Failed to parse transaction base from blob retrieved from the db");
}
bool BlockchainDB::get_pruned_tx(const crypto::hash& h, cryptonote::transaction& tx) const {
std::string bd;
if (!get_pruned_tx_blob(h, bd))
return false;
if (!parse_and_validate_tx_base_from_blob(bd, tx)) {
throw DB_ERROR("Failed to parse transaction base from blob retrieved from the db");
}
return true;
return true;
}
transaction BlockchainDB::get_tx(const crypto::hash& h) const
{
transaction tx;
if (!get_tx(h, tx))
throw TX_DNE("tx with hash " + tools::type_to_hex(h) + " not found in db");
return tx;
transaction BlockchainDB::get_tx(const crypto::hash& h) const {
transaction tx;
if (!get_tx(h, tx))
throw TX_DNE("tx with hash " + tools::type_to_hex(h) + " not found in db");
return tx;
}
uint64_t BlockchainDB::get_output_unlock_time(const uint64_t amount, const uint64_t amount_index) const
{
output_data_t odata = get_output_key(amount, amount_index);
return odata.unlock_time;
uint64_t BlockchainDB::get_output_unlock_time(
const uint64_t amount, const uint64_t amount_index) const {
output_data_t odata = get_output_key(amount, amount_index);
return odata.unlock_time;
}
transaction BlockchainDB::get_pruned_tx(const crypto::hash& h) const
{
transaction tx;
if (!get_pruned_tx(h, tx))
throw TX_DNE("pruned tx with hash " + tools::type_to_hex(h) + " not found in db");
return tx;
transaction BlockchainDB::get_pruned_tx(const crypto::hash& h) const {
transaction tx;
if (!get_pruned_tx(h, tx))
throw TX_DNE("pruned tx with hash " + tools::type_to_hex(h) + " not found in db");
return tx;
}
void BlockchainDB::reset_stats()
{
num_calls = 0;
time_blk_hash = 0ns;
time_tx_exists = 0ns;
time_add_block1 = 0ns;
time_add_transaction = 0ns;
time_commit1 = 0ns;
void BlockchainDB::reset_stats() {
num_calls = 0;
time_blk_hash = 0ns;
time_tx_exists = 0ns;
time_add_block1 = 0ns;
time_add_transaction = 0ns;
time_commit1 = 0ns;
}
void BlockchainDB::show_stats()
{
log::info(logcat, "\n*********************************\n \
void BlockchainDB::show_stats() {
log::info(
logcat,
"\n*********************************\n \
num_calls: {}\n \
time_blk_hash: {}\n \
time_tx_exists: {}\n \
@ -337,134 +316,133 @@ void BlockchainDB::show_stats()
time_add_transaction: {}\n \
time_commit1: {}\n \
*********************************\n",
num_calls, tools::friendly_duration(time_blk_hash), tools::friendly_duration(time_tx_exists), tools::friendly_duration(time_add_block1), tools::friendly_duration(time_add_transaction), tools::friendly_duration(time_commit1));
num_calls,
tools::friendly_duration(time_blk_hash),
tools::friendly_duration(time_tx_exists),
tools::friendly_duration(time_add_block1),
tools::friendly_duration(time_add_transaction),
tools::friendly_duration(time_commit1));
}
void BlockchainDB::fixup(cryptonote::network_type)
{
if (is_read_only()) {
log::info(logcat, "Database is opened read only - skipping fixup check");
return;
}
set_batch_transactions(true);
}
bool BlockchainDB::get_immutable_checkpoint(checkpoint_t *immutable_checkpoint, uint64_t block_height) const
{
size_t constexpr NUM_CHECKPOINTS = service_nodes::CHECKPOINT_NUM_CHECKPOINTS_FOR_CHAIN_FINALITY;
static_assert(NUM_CHECKPOINTS == 2,
"Expect checkpoint finality to be 2, otherwise the immutable logic needs to check for any hardcoded "
"checkpoints inbetween");
std::vector<checkpoint_t> checkpoints = get_checkpoints_range(block_height, 0, NUM_CHECKPOINTS);
if (checkpoints.empty())
return false;
checkpoint_t *checkpoint_ptr = nullptr;
if (checkpoints[0].type != checkpoint_type::service_node) // checkpoint[0] is the first closest checkpoint that is <= my height
{
checkpoint_ptr = &checkpoints[0]; // Must be hard-coded then, always immutable
}
else if (checkpoints.size() == NUM_CHECKPOINTS)
{
// NOTE: The first checkpoint is a service node checkpoint. Go back
// 1 checkpoint, which will either be another service node checkpoint or
// a predefined one.
checkpoint_ptr = &checkpoints[1];
}
else
{
return false; // NOTE: Only one service node checkpoint recorded, we can override this checkpoint.
}
if (immutable_checkpoint)
*immutable_checkpoint = std::move(*checkpoint_ptr);
return true;
}
uint64_t BlockchainDB::get_tx_block_height(const crypto::hash &h) const
{
auto result = get_tx_block_heights({{h}}).front();
if (result == std::numeric_limits<uint64_t>::max())
{
std::string err = "tx_data_t with hash " + tools::type_to_hex(h) + " not found in db";
log::info(logcat, "{}", err);
throw TX_DNE(std::move(err));
}
return result;
}
bool BlockchainDB::get_alt_block_header(const crypto::hash &blkid, alt_block_data_t *data, cryptonote::block_header *header, std::string *checkpoint) const
{
std::string blob;
if (!get_alt_block(blkid, data, &blob, checkpoint))
{
throw BLOCK_DNE("Alt-block with hash " + tools::type_to_hex(blkid) + " not found in db");
return false;
}
try
{
serialization::binary_string_unarchiver ba{blob};
serialization::value(ba, *header);
}
catch(std::exception &e)
{
return false;
}
return true;
}
void BlockchainDB::fill_timestamps_and_difficulties_for_pow(cryptonote::network_type nettype,
std::vector<uint64_t> &timestamps,
std::vector<uint64_t> &difficulties,
uint64_t chain_height,
uint64_t timestamps_difficulty_height) const
{
constexpr uint64_t MIN_CHAIN_HEIGHT = 2;
if (chain_height < MIN_CHAIN_HEIGHT)
return;
uint64_t const top_block_height = chain_height - 1;
bool const before_hf16 = !is_hard_fork_at_least(nettype, hf::hf16_pulse, chain_height);
uint64_t const block_count = old::DIFFICULTY_BLOCKS_COUNT(before_hf16);
timestamps.reserve(block_count);
difficulties.reserve(block_count);
if (timestamps_difficulty_height == 0 ||
(chain_height - timestamps_difficulty_height) != 1 ||
timestamps.size() > block_count ||
difficulties.size() > block_count)
{
// Cache invalidated.
timestamps.clear();
difficulties.clear();
// Fill missing timestamps/difficulties, up to one before the latest (latest is added below).
uint64_t start_height = chain_height - std::min<size_t>(chain_height, block_count);
start_height = std::max<uint64_t>(start_height, 1);
for (uint64_t block_height = start_height; block_height < (chain_height - 1) /*skip latest block*/; block_height++)
{
timestamps.push_back(get_block_timestamp(block_height));
difficulties.push_back(get_block_cumulative_difficulty(block_height));
void BlockchainDB::fixup(cryptonote::network_type) {
if (is_read_only()) {
log::info(logcat, "Database is opened read only - skipping fixup check");
return;
}
}
// Add latest timestamp/difficulty
add_timestamp_and_difficulty(nettype,
chain_height,
timestamps,
difficulties,
get_block_timestamp(top_block_height),
get_block_cumulative_difficulty(top_block_height));
set_batch_transactions(true);
}
bool BlockchainDB::get_immutable_checkpoint(
checkpoint_t* immutable_checkpoint, uint64_t block_height) const {
size_t constexpr NUM_CHECKPOINTS = service_nodes::CHECKPOINT_NUM_CHECKPOINTS_FOR_CHAIN_FINALITY;
static_assert(
NUM_CHECKPOINTS == 2,
"Expect checkpoint finality to be 2, otherwise the immutable logic needs to check for "
"any hardcoded "
"checkpoints inbetween");
std::vector<checkpoint_t> checkpoints = get_checkpoints_range(block_height, 0, NUM_CHECKPOINTS);
if (checkpoints.empty())
return false;
checkpoint_t* checkpoint_ptr = nullptr;
if (checkpoints[0].type != checkpoint_type::service_node) // checkpoint[0] is the first closest
// checkpoint that is <= my height
{
checkpoint_ptr = &checkpoints[0]; // Must be hard-coded then, always immutable
} else if (checkpoints.size() == NUM_CHECKPOINTS) {
// NOTE: The first checkpoint is a service node checkpoint. Go back
// 1 checkpoint, which will either be another service node checkpoint or
// a predefined one.
checkpoint_ptr = &checkpoints[1];
} else {
return false; // NOTE: Only one service node checkpoint recorded, we can override this
// checkpoint.
}
if (immutable_checkpoint)
*immutable_checkpoint = std::move(*checkpoint_ptr);
return true;
}
uint64_t BlockchainDB::get_tx_block_height(const crypto::hash& h) const {
auto result = get_tx_block_heights({{h}}).front();
if (result == std::numeric_limits<uint64_t>::max()) {
std::string err = "tx_data_t with hash " + tools::type_to_hex(h) + " not found in db";
log::info(logcat, "{}", err);
throw TX_DNE(std::move(err));
}
return result;
}
bool BlockchainDB::get_alt_block_header(
const crypto::hash& blkid,
alt_block_data_t* data,
cryptonote::block_header* header,
std::string* checkpoint) const {
std::string blob;
if (!get_alt_block(blkid, data, &blob, checkpoint)) {
throw BLOCK_DNE("Alt-block with hash " + tools::type_to_hex(blkid) + " not found in db");
return false;
}
try {
serialization::binary_string_unarchiver ba{blob};
serialization::value(ba, *header);
} catch (std::exception& e) {
return false;
}
return true;
}
void BlockchainDB::fill_timestamps_and_difficulties_for_pow(
cryptonote::network_type nettype,
std::vector<uint64_t>& timestamps,
std::vector<uint64_t>& difficulties,
uint64_t chain_height,
uint64_t timestamps_difficulty_height) const {
constexpr uint64_t MIN_CHAIN_HEIGHT = 2;
if (chain_height < MIN_CHAIN_HEIGHT)
return;
uint64_t const top_block_height = chain_height - 1;
bool const before_hf16 = !is_hard_fork_at_least(nettype, hf::hf16_pulse, chain_height);
uint64_t const block_count = old::DIFFICULTY_BLOCKS_COUNT(before_hf16);
timestamps.reserve(block_count);
difficulties.reserve(block_count);
if (timestamps_difficulty_height == 0 || (chain_height - timestamps_difficulty_height) != 1 ||
timestamps.size() > block_count || difficulties.size() > block_count) {
// Cache invalidated.
timestamps.clear();
difficulties.clear();
// Fill missing timestamps/difficulties, up to one before the latest (latest is added
// below).
uint64_t start_height = chain_height - std::min<size_t>(chain_height, block_count);
start_height = std::max<uint64_t>(start_height, 1);
for (uint64_t block_height = start_height;
block_height < (chain_height - 1) /*skip latest block*/;
block_height++) {
timestamps.push_back(get_block_timestamp(block_height));
difficulties.push_back(get_block_cumulative_difficulty(block_height));
}
}
// Add latest timestamp/difficulty
add_timestamp_and_difficulty(
nettype,
chain_height,
timestamps,
difficulties,
get_block_timestamp(top_block_height),
get_block_cumulative_difficulty(top_block_height));
}
} // namespace cryptonote

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -26,20 +26,19 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <atomic>
#include <lmdb.h>
#include "blockchain_db/blockchain_db.h"
#include "ringct/rctTypes.h"
#include "common/fs.h"
#include <atomic>
#include <boost/thread/thread.hpp>
#include <boost/thread/tss.hpp>
#include <lmdb.h>
#include "blockchain_db/blockchain_db.h"
#include "common/fs.h"
#include "ringct/rctTypes.h"
#define ENABLE_AUTO_RESIZE
namespace cryptonote
{
namespace cryptonote {
struct mdb_block_info;
@ -48,116 +47,105 @@ struct txindex {
tx_data_t data;
};
struct mdb_txn_cursors
{
MDB_cursor *blocks;
MDB_cursor *block_heights;
MDB_cursor *block_info;
MDB_cursor *block_checkpoints;
struct mdb_txn_cursors {
MDB_cursor* blocks;
MDB_cursor* block_heights;
MDB_cursor* block_info;
MDB_cursor* block_checkpoints;
MDB_cursor *output_txs;
MDB_cursor *output_amounts;
MDB_cursor* output_txs;
MDB_cursor* output_amounts;
MDB_cursor *txs;
MDB_cursor *txs_pruned;
MDB_cursor *txs_prunable;
MDB_cursor *txs_prunable_hash;
MDB_cursor *txs_prunable_tip;
MDB_cursor *tx_indices;
MDB_cursor *tx_outputs;
MDB_cursor* txs;
MDB_cursor* txs_pruned;
MDB_cursor* txs_prunable;
MDB_cursor* txs_prunable_hash;
MDB_cursor* txs_prunable_tip;
MDB_cursor* tx_indices;
MDB_cursor* tx_outputs;
MDB_cursor *spent_keys;
MDB_cursor* spent_keys;
MDB_cursor *txpool_meta;
MDB_cursor *txpool_blob;
MDB_cursor* txpool_meta;
MDB_cursor* txpool_blob;
MDB_cursor *alt_blocks;
MDB_cursor* alt_blocks;
MDB_cursor *hf_versions;
MDB_cursor* hf_versions;
MDB_cursor *service_node_data;
MDB_cursor *service_node_proofs;
MDB_cursor *output_blacklist;
MDB_cursor *properties;
MDB_cursor* service_node_data;
MDB_cursor* service_node_proofs;
MDB_cursor* output_blacklist;
MDB_cursor* properties;
};
struct mdb_rflags
{
bool m_rf_txn;
bool m_rf_blocks;
bool m_rf_block_heights;
bool m_rf_block_info;
bool m_rf_block_checkpoints;
bool m_rf_output_txs;
bool m_rf_output_amounts;
bool m_rf_output_blacklist;
bool m_rf_txs;
bool m_rf_txs_pruned;
bool m_rf_txs_prunable;
bool m_rf_txs_prunable_hash;
bool m_rf_txs_prunable_tip;
bool m_rf_tx_indices;
bool m_rf_tx_outputs;
bool m_rf_spent_keys;
bool m_rf_txpool_meta;
bool m_rf_txpool_blob;
bool m_rf_alt_blocks;
bool m_rf_hf_versions;
bool m_rf_service_node_data;
bool m_rf_service_node_proofs;
bool m_rf_properties;
struct mdb_rflags {
bool m_rf_txn;
bool m_rf_blocks;
bool m_rf_block_heights;
bool m_rf_block_info;
bool m_rf_block_checkpoints;
bool m_rf_output_txs;
bool m_rf_output_amounts;
bool m_rf_output_blacklist;
bool m_rf_txs;
bool m_rf_txs_pruned;
bool m_rf_txs_prunable;
bool m_rf_txs_prunable_hash;
bool m_rf_txs_prunable_tip;
bool m_rf_tx_indices;
bool m_rf_tx_outputs;
bool m_rf_spent_keys;
bool m_rf_txpool_meta;
bool m_rf_txpool_blob;
bool m_rf_alt_blocks;
bool m_rf_hf_versions;
bool m_rf_service_node_data;
bool m_rf_service_node_proofs;
bool m_rf_properties;
};
struct mdb_threadinfo
{
MDB_txn *m_ti_rtxn; // per-thread read txn
mdb_txn_cursors m_ti_rcursors; // per-thread read cursors
mdb_rflags m_ti_rflags; // per-thread read state
struct mdb_threadinfo {
MDB_txn* m_ti_rtxn; // per-thread read txn
mdb_txn_cursors m_ti_rcursors; // per-thread read cursors
mdb_rflags m_ti_rflags; // per-thread read state
~mdb_threadinfo();
~mdb_threadinfo();
};
struct mdb_txn_safe
{
mdb_txn_safe(const bool check=true);
~mdb_txn_safe();
struct mdb_txn_safe {
mdb_txn_safe(const bool check = true);
~mdb_txn_safe();
void commit(std::string message = "");
void commit(std::string message = "");
// This should only be needed for batch transaction which must be ensured to
// be aborted before mdb_env_close, not after. So we can't rely on
// BlockchainLMDB destructor to call mdb_txn_safe destructor, as that's too late
// to properly abort, since mdb_env_close would have been called earlier.
void abort();
void uncheck();
// This should only be needed for batch transaction which must be ensured to
// be aborted before mdb_env_close, not after. So we can't rely on
// BlockchainLMDB destructor to call mdb_txn_safe destructor, as that's too late
// to properly abort, since mdb_env_close would have been called earlier.
void abort();
void uncheck();
operator MDB_txn*()
{
return m_txn;
}
operator MDB_txn*() { return m_txn; }
operator MDB_txn**()
{
return &m_txn;
}
operator MDB_txn**() { return &m_txn; }
uint64_t num_active_tx() const;
uint64_t num_active_tx() const;
static void prevent_new_txns();
static void wait_no_active_txns();
static void allow_new_txns();
static void prevent_new_txns();
static void wait_no_active_txns();
static void allow_new_txns();
mdb_threadinfo* m_tinfo;
MDB_txn* m_txn;
bool m_batch_txn = false;
bool m_check;
static std::atomic<uint64_t> num_active_txns;
mdb_threadinfo* m_tinfo;
MDB_txn* m_txn;
bool m_batch_txn = false;
bool m_check;
static std::atomic<uint64_t> num_active_txns;
// could use a mutex here, but this should be sufficient.
static std::atomic_flag creation_gate;
// could use a mutex here, but this should be sufficient.
static std::atomic_flag creation_gate;
};
// If m_batch_active is set, a batch transaction exists beyond this class, such
// as a batch import with verification enabled, or possibly (later) a batch
// network sync.
@ -171,345 +159,415 @@ struct mdb_txn_safe
// A regular network sync without batch writes is expected to open a new read
// transaction, as those lookups are part of the validation done prior to the
// write for block and tx data, so no write transaction is open at the time.
class BlockchainLMDB : public BlockchainDB
{
public:
BlockchainLMDB(bool batch_transactions=true);
~BlockchainLMDB();
class BlockchainLMDB : public BlockchainDB {
public:
BlockchainLMDB(bool batch_transactions = true);
~BlockchainLMDB();
void open(const fs::path& filename, cryptonote::network_type nettype, const int mdb_flags=0) override;
void open(const fs::path& filename, cryptonote::network_type nettype, const int mdb_flags = 0)
override;
void close() override;
void close() override;
void sync() override;
void sync() override;
void safesyncmode(const bool onoff) override;
void safesyncmode(const bool onoff) override;
void reset() override;
void reset() override;
std::vector<fs::path> get_filenames() const override;
std::vector<fs::path> get_filenames() const override;
bool remove_data_file(const fs::path& folder) const override;
bool remove_data_file(const fs::path& folder) const override;
std::string get_db_name() const override;
std::string get_db_name() const override;
void lock() override;
void lock() override;
bool try_lock() override;
bool try_lock() override;
void unlock() override;
void unlock() override;
bool block_exists(const crypto::hash& h, uint64_t *height = NULL) const override;
bool block_exists(const crypto::hash& h, uint64_t* height = NULL) const override;
uint64_t get_block_height(const crypto::hash& h) const override;
uint64_t get_block_height(const crypto::hash& h) const override;
block get_block_from_height(uint64_t height) const override;
block get_block_from_height(uint64_t height) const override;
block_header get_block_header_from_height(uint64_t height) const override;
block_header get_block_header_from_height(uint64_t height) const override;
std::string get_block_blob(const crypto::hash& h) const override;
std::string get_block_blob(const crypto::hash& h) const override;
std::string get_block_blob_from_height(uint64_t height) const override;
std::string get_block_blob_from_height(uint64_t height) const override;
std::vector<uint64_t> get_block_cumulative_rct_outputs(const std::vector<uint64_t> &heights) const override;
std::vector<uint64_t> get_block_cumulative_rct_outputs(
const std::vector<uint64_t>& heights) const override;
uint64_t get_block_timestamp(const uint64_t& height) const override;
uint64_t get_block_timestamp(const uint64_t& height) const override;
uint64_t get_top_block_timestamp() const override;
uint64_t get_top_block_timestamp() const override;
size_t get_block_weight(const uint64_t& height) const override;
size_t get_block_weight(const uint64_t& height) const override;
std::vector<uint64_t> get_block_weights(uint64_t start_height, size_t count) const override;
std::vector<uint64_t> get_block_weights(uint64_t start_height, size_t count) const override;
difficulty_type get_block_cumulative_difficulty(const uint64_t& height) const override;
difficulty_type get_block_cumulative_difficulty(const uint64_t& height) const override;
difficulty_type get_block_difficulty(const uint64_t& height) const override;
difficulty_type get_block_difficulty(const uint64_t& height) const override;
uint64_t get_block_already_generated_coins(const uint64_t& height) const override;
uint64_t get_block_already_generated_coins(const uint64_t& height) const override;
uint64_t get_block_long_term_weight(const uint64_t& height) const override;
uint64_t get_block_long_term_weight(const uint64_t& height) const override;
std::vector<uint64_t> get_long_term_block_weights(uint64_t start_height, size_t count) const override;
std::vector<uint64_t> get_long_term_block_weights(
uint64_t start_height, size_t count) const override;
crypto::hash get_block_hash_from_height(const uint64_t& height) const override;
crypto::hash get_block_hash_from_height(const uint64_t& height) const override;
std::vector<block> get_blocks_range(const uint64_t& h1, const uint64_t& h2) const override;
std::vector<block> get_blocks_range(const uint64_t& h1, const uint64_t& h2) const override;
std::vector<crypto::hash> get_hashes_range(const uint64_t& h1, const uint64_t& h2) const override;
std::vector<crypto::hash> get_hashes_range(
const uint64_t& h1, const uint64_t& h2) const override;
crypto::hash top_block_hash(uint64_t *block_height = NULL) const override;
crypto::hash top_block_hash(uint64_t* block_height = NULL) const override;
block get_top_block() const override;
block get_top_block() const override;
uint64_t height() const override;
uint64_t height() const override;
bool tx_exists(const crypto::hash& h) const override;
bool tx_exists(const crypto::hash& h, uint64_t& tx_index) const override;
bool tx_exists(const crypto::hash& h) const override;
bool tx_exists(const crypto::hash& h, uint64_t& tx_index) const override;
uint64_t get_tx_unlock_time(const crypto::hash& h) const override;
uint64_t get_tx_unlock_time(const crypto::hash& h) const override;
bool get_tx_blob(const crypto::hash& h, std::string &tx) const override;
bool get_pruned_tx_blob(const crypto::hash& h, std::string &tx) const override;
bool get_pruned_tx_blobs_from(const crypto::hash& h, size_t count, std::vector<std::string> &bd) const override;
bool get_prunable_tx_blob(const crypto::hash& h, std::string &tx) const override;
bool get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const override;
bool get_tx_blob(const crypto::hash& h, std::string& tx) const override;
bool get_pruned_tx_blob(const crypto::hash& h, std::string& tx) const override;
bool get_pruned_tx_blobs_from(
const crypto::hash& h, size_t count, std::vector<std::string>& bd) const override;
bool get_prunable_tx_blob(const crypto::hash& h, std::string& tx) const override;
bool get_prunable_tx_hash(
const crypto::hash& tx_hash, crypto::hash& prunable_hash) const override;
uint64_t get_tx_count() const override;
uint64_t get_tx_count() const override;
std::vector<transaction> get_tx_list(const std::vector<crypto::hash>& hlist) const override;
std::vector<transaction> get_tx_list(const std::vector<crypto::hash>& hlist) const override;
std::vector<uint64_t> get_tx_block_heights(const std::vector<crypto::hash>& hlist) const override;
std::vector<uint64_t> get_tx_block_heights(
const std::vector<crypto::hash>& hlist) const override;
uint64_t get_num_outputs(const uint64_t& amount) const override;
uint64_t get_num_outputs(const uint64_t& amount) const override;
output_data_t get_output_key(const uint64_t& amount, const uint64_t& index, bool include_commitmemt) const override;
void get_output_key(const epee::span<const uint64_t> &amounts, const std::vector<uint64_t> &offsets, std::vector<output_data_t> &outputs, bool allow_partial = false) const override;
output_data_t get_output_key(
const uint64_t& amount, const uint64_t& index, bool include_commitmemt) const override;
void get_output_key(
const epee::span<const uint64_t>& amounts,
const std::vector<uint64_t>& offsets,
std::vector<output_data_t>& outputs,
bool allow_partial = false) const override;
tx_out_index get_output_tx_and_index_from_global(const uint64_t& index) const override;
void get_output_tx_and_index_from_global(const std::vector<uint64_t> &global_indices,
std::vector<tx_out_index> &tx_out_indices) const;
tx_out_index get_output_tx_and_index_from_global(const uint64_t& index) const override;
void get_output_tx_and_index_from_global(
const std::vector<uint64_t>& global_indices,
std::vector<tx_out_index>& tx_out_indices) const;
tx_out_index get_output_tx_and_index(const uint64_t& amount, const uint64_t& index) const override;
void get_output_tx_and_index(const uint64_t& amount, const std::vector<uint64_t> &offsets, std::vector<tx_out_index> &indices) const override;
tx_out_index get_output_tx_and_index(
const uint64_t& amount, const uint64_t& index) const override;
void get_output_tx_and_index(
const uint64_t& amount,
const std::vector<uint64_t>& offsets,
std::vector<tx_out_index>& indices) const override;
std::vector<std::vector<uint64_t>> get_tx_amount_output_indices(const uint64_t tx_id, size_t n_txes) const override;
std::vector<std::vector<uint64_t>> get_tx_amount_output_indices(
const uint64_t tx_id, size_t n_txes) const override;
bool has_key_image(const crypto::key_image& img) const override;
void add_txpool_tx(
const crypto::hash& txid,
const std::string& blob,
const txpool_tx_meta_t& meta) override;
void update_txpool_tx(const crypto::hash& txid, const txpool_tx_meta_t& meta) override;
uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const override;
bool txpool_has_tx(const crypto::hash& txid) const override;
void remove_txpool_tx(const crypto::hash& txid) override;
bool get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t& meta) const override;
bool get_txpool_tx_blob(const crypto::hash& txid, std::string& bd) const override;
std::string get_txpool_tx_blob(const crypto::hash& txid) const override;
uint32_t get_blockchain_pruning_seed() const override;
bool prune_blockchain(uint32_t pruning_seed = 0) override;
bool update_pruning() override;
bool check_pruning() override;
void add_alt_block(
const crypto::hash& blkid,
const cryptonote::alt_block_data_t& data,
const std::string& blob,
const std::string* checkpoint) override;
bool get_alt_block(
const crypto::hash& blkid,
alt_block_data_t* data,
std::string* blob,
std::string* checkpoint) const override;
void remove_alt_block(const crypto::hash& blkid) override;
uint64_t get_alt_block_count() override;
void drop_alt_blocks() override;
bool for_all_txpool_txes(
std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const std::string*)> f,
bool include_blob = false,
bool include_unrelayed_txes = true) const override;
bool for_all_key_images(std::function<bool(const crypto::key_image&)>) const override;
bool for_blocks_range(
const uint64_t& h1,
const uint64_t& h2,
std::function<bool(uint64_t, const crypto::hash&, const cryptonote::block&)>)
const override;
bool for_all_transactions(
std::function<bool(const crypto::hash&, const cryptonote::transaction&)>,
bool pruned) const override;
bool for_all_outputs(
std::function<bool(
uint64_t amount, const crypto::hash& tx_hash, uint64_t height, size_t tx_idx)>
f) const override;
bool for_all_outputs(
uint64_t amount, const std::function<bool(uint64_t height)>& f) const override;
bool for_all_alt_blocks(
std::function<
bool(const crypto::hash& blkid,
const alt_block_data_t& data,
const std::string* block_blob,
const std::string* checkpoint_blob)> f,
bool include_blob = false) const override;
uint64_t add_block(
const std::pair<block, std::string>& blk,
size_t block_weight,
uint64_t long_term_block_weight,
const difficulty_type& cumulative_difficulty,
const uint64_t& coins_generated,
const std::vector<std::pair<transaction, std::string>>& txs) override;
void update_block_checkpoint(checkpoint_t const& checkpoint) override;
void remove_block_checkpoint(uint64_t height) override;
bool get_block_checkpoint(uint64_t height, checkpoint_t& checkpoint) const override;
bool get_top_checkpoint(checkpoint_t& checkpoint) const override;
std::vector<checkpoint_t> get_checkpoints_range(
uint64_t start,
uint64_t end,
size_t num_desired_checkpoints = GET_ALL_CHECKPOINTS) const override;
void set_batch_transactions(bool batch_transactions) override;
bool batch_start(uint64_t batch_num_blocks = 0, uint64_t batch_bytes = 0) override;
void batch_commit();
void batch_stop() override;
void batch_abort() override;
void block_wtxn_start() override;
void block_wtxn_stop() override;
void block_wtxn_abort() override;
bool block_rtxn_start() const override;
void block_rtxn_stop() const override;
void block_rtxn_abort() const override;
bool block_rtxn_start(MDB_txn** mtxn, mdb_txn_cursors** mcur) const;
void pop_block(block& blk, std::vector<transaction>& txs) override;
bool can_thread_bulk_indices() const override { return true; }
/**
* @brief return a histogram of outputs on the blockchain
*
* @param amounts optional set of amounts to lookup
* @param unlocked whether to restrict count to unlocked outputs
* @param recent_cutoff timestamp to determine which outputs are recent
* @param min_count return only amounts with at least that many instances
*
* @return a set of amount/instances
*/
std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> get_output_histogram(
const std::vector<uint64_t>& amounts,
bool unlocked,
uint64_t recent_cutoff,
uint64_t min_count) const override;
bool get_output_distribution(
uint64_t amount,
uint64_t from_height,
uint64_t to_height,
std::vector<uint64_t>& distribution,
uint64_t& base) const override;
void get_output_blacklist(std::vector<uint64_t>& blacklist) const override;
void add_output_blacklist(std::vector<uint64_t> const& blacklist) override;
// helper functions
static int compare_uint64(const MDB_val* a, const MDB_val* b);
static int compare_hash32(const MDB_val* a, const MDB_val* b);
static int compare_string(const MDB_val* a, const MDB_val* b);
private:
void do_resize(uint64_t size_increase = 0);
bool need_resize(uint64_t threshold_size = 0) const;
void check_and_resize_for_batch(uint64_t batch_num_blocks, uint64_t batch_bytes);
uint64_t get_estimated_batch_size(uint64_t batch_num_blocks, uint64_t batch_bytes) const;
void add_block(
const block& blk,
size_t block_weight,
uint64_t long_term_block_weight,
const difficulty_type& cumulative_difficulty,
const uint64_t& coins_generated,
uint64_t num_rct_outs,
const crypto::hash& block_hash) override;
void remove_block() override;
uint64_t add_transaction_data(
const crypto::hash& blk_hash,
const std::pair<transaction, std::string>& tx,
const crypto::hash& tx_hash,
const crypto::hash& tx_prunable_hash) override;
void remove_transaction_data(const crypto::hash& tx_hash, const transaction& tx) override;
uint64_t add_output(
const crypto::hash& tx_hash,
const tx_out& tx_output,
const uint64_t& local_index,
const uint64_t unlock_time,
const rct::key* commitment) override;
void add_tx_amount_output_indices(
const uint64_t tx_id, const std::vector<uint64_t>& amount_output_indices) override;
void remove_tx_outputs(const uint64_t tx_id, const transaction& tx);
void remove_output(const uint64_t amount, const uint64_t& out_index);
void prune_outputs(uint64_t amount) override;
void add_spent_key(const crypto::key_image& k_image) override;
void remove_spent_key(const crypto::key_image& k_image) override;
uint64_t num_outputs() const;
inline void check_open() const;
bool prune_worker(int mode, uint32_t pruning_seed);
bool is_read_only() const override;
uint64_t get_database_size() const override;
std::vector<uint64_t> get_block_info_64bit_fields(
uint64_t start_height, size_t count, uint64_t (*extract)(const mdb_block_info*)) const;
uint64_t get_max_block_size() override;
void add_max_block_size(uint64_t sz) override;
bool has_key_image(const crypto::key_image& img) const override;
// fix up anything that may be wrong due to past bugs
void fixup(cryptonote::network_type nettype) override;
// migrate from older DB version to current
void migrate(const uint32_t oldversion, cryptonote::network_type nettype);
void migrate_0_1();
void migrate_1_2();
void migrate_2_3();
void migrate_3_4();
void migrate_4_5(cryptonote::network_type nettype);
void migrate_5_6();
void migrate_6_7();
void cleanup_batch();
void add_txpool_tx(const crypto::hash &txid, const std::string &blob, const txpool_tx_meta_t& meta) override;
void update_txpool_tx(const crypto::hash &txid, const txpool_tx_meta_t& meta) override;
uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const override;
bool txpool_has_tx(const crypto::hash &txid) const override;
void remove_txpool_tx(const crypto::hash& txid) override;
bool get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t &meta) const override;
bool get_txpool_tx_blob(const crypto::hash& txid, std::string &bd) const override;
std::string get_txpool_tx_blob(const crypto::hash& txid) const override;
uint32_t get_blockchain_pruning_seed() const override;
bool prune_blockchain(uint32_t pruning_seed = 0) override;
bool update_pruning() override;
bool check_pruning() override;
bool get_block_checkpoint_internal(
uint64_t height, checkpoint_t& checkpoint, MDB_cursor_op op) const;
void set_service_node_data(const std::string& data, bool long_term) override;
bool get_service_node_data(std::string& data, bool long_term) const override;
void clear_service_node_data() override;
void add_alt_block(const crypto::hash &blkid, const cryptonote::alt_block_data_t &data, const std::string &blob, const std::string *checkpoint) override;
bool get_alt_block(const crypto::hash &blkid, alt_block_data_t *data, std::string *blob, std::string *checkpoint) const override;
void remove_alt_block(const crypto::hash &blkid) override;
uint64_t get_alt_block_count() override;
void drop_alt_blocks() override;
bool get_service_node_proof(
const crypto::public_key& pubkey, service_nodes::proof_info& proof) const override;
void set_service_node_proof(
const crypto::public_key& pubkey, const service_nodes::proof_info& proof) override;
std::unordered_map<crypto::public_key, service_nodes::proof_info> get_all_service_node_proofs()
const override;
bool remove_service_node_proof(const crypto::public_key& pubkey) override;
bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const std::string*)> f, bool include_blob = false, bool include_unrelayed_txes = true) const override;
private:
template <
typename T,
std::enable_if_t<
std::is_same_v<T, cryptonote::block> ||
std::is_same_v<T, cryptonote::block_header> ||
std::is_same_v<T, std::string>,
int> = 0>
T get_and_convert_block_blob_from_height(uint64_t height) const;
bool for_all_key_images(std::function<bool(const crypto::key_image&)>) const override;
bool for_blocks_range(const uint64_t& h1, const uint64_t& h2, std::function<bool(uint64_t, const crypto::hash&, const cryptonote::block&)>) const override;
bool for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)>, bool pruned) const override;
bool for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, uint64_t height, size_t tx_idx)> f) const override;
bool for_all_outputs(uint64_t amount, const std::function<bool(uint64_t height)> &f) const override;
bool for_all_alt_blocks(std::function<bool(const crypto::hash &blkid, const alt_block_data_t &data, const std::string *block_blob, const std::string *checkpoint_blob)> f, bool include_blob = false) const override;
MDB_env* m_env;
uint64_t add_block( const std::pair<block, std::string>& blk
, size_t block_weight
, uint64_t long_term_block_weight
, const difficulty_type& cumulative_difficulty
, const uint64_t& coins_generated
, const std::vector<std::pair<transaction, std::string>>& txs
) override;
void update_block_checkpoint(checkpoint_t const &checkpoint) override;
void remove_block_checkpoint(uint64_t height) override;
bool get_block_checkpoint (uint64_t height, checkpoint_t &checkpoint) const override;
bool get_top_checkpoint (checkpoint_t &checkpoint) const override;
std::vector<checkpoint_t> get_checkpoints_range(uint64_t start, uint64_t end, size_t num_desired_checkpoints = GET_ALL_CHECKPOINTS) const override;
MDB_dbi m_blocks;
MDB_dbi m_block_heights;
MDB_dbi m_block_info;
MDB_dbi m_block_checkpoints;
void set_batch_transactions(bool batch_transactions) override;
bool batch_start(uint64_t batch_num_blocks=0, uint64_t batch_bytes=0) override;
void batch_commit();
void batch_stop() override;
void batch_abort() override;
MDB_dbi m_txs;
MDB_dbi m_txs_pruned;
MDB_dbi m_txs_prunable;
MDB_dbi m_txs_prunable_hash;
MDB_dbi m_txs_prunable_tip;
MDB_dbi m_tx_indices;
MDB_dbi m_tx_outputs;
void block_wtxn_start() override;
void block_wtxn_stop() override;
void block_wtxn_abort() override;
bool block_rtxn_start() const override;
void block_rtxn_stop() const override;
void block_rtxn_abort() const override;
MDB_dbi m_output_txs;
MDB_dbi m_output_amounts;
MDB_dbi m_output_blacklist;
bool block_rtxn_start(MDB_txn **mtxn, mdb_txn_cursors **mcur) const;
MDB_dbi m_spent_keys;
void pop_block(block& blk, std::vector<transaction>& txs) override;
MDB_dbi m_txpool_meta;
MDB_dbi m_txpool_blob;
bool can_thread_bulk_indices() const override { return true; }
MDB_dbi m_alt_blocks;
/**
* @brief return a histogram of outputs on the blockchain
*
* @param amounts optional set of amounts to lookup
* @param unlocked whether to restrict count to unlocked outputs
* @param recent_cutoff timestamp to determine which outputs are recent
* @param min_count return only amounts with at least that many instances
*
* @return a set of amount/instances
*/
std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff, uint64_t min_count) const override;
MDB_dbi m_hf_starting_heights;
MDB_dbi m_hf_versions;
bool get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t to_height, std::vector<uint64_t> &distribution, uint64_t &base) const override;
void get_output_blacklist(std::vector<uint64_t> &blacklist) const override;
void add_output_blacklist(std::vector<uint64_t> const &blacklist) override;
MDB_dbi m_service_node_data;
MDB_dbi m_service_node_proofs;
// helper functions
static int compare_uint64(const MDB_val *a, const MDB_val *b);
static int compare_hash32(const MDB_val *a, const MDB_val *b);
static int compare_string(const MDB_val *a, const MDB_val *b);
MDB_dbi m_properties;
private:
void do_resize(uint64_t size_increase=0);
bool need_resize(uint64_t threshold_size=0) const;
void check_and_resize_for_batch(uint64_t batch_num_blocks, uint64_t batch_bytes);
uint64_t get_estimated_batch_size(uint64_t batch_num_blocks, uint64_t batch_bytes) const;
void add_block( const block& blk
, size_t block_weight
, uint64_t long_term_block_weight
, const difficulty_type& cumulative_difficulty
, const uint64_t& coins_generated
, uint64_t num_rct_outs
, const crypto::hash& block_hash
) override;
void remove_block() override;
uint64_t add_transaction_data(const crypto::hash& blk_hash, const std::pair<transaction, std::string>& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prunable_hash) override;
void remove_transaction_data(const crypto::hash& tx_hash, const transaction& tx) override;
uint64_t add_output(const crypto::hash& tx_hash,
const tx_out& tx_output,
const uint64_t& local_index,
const uint64_t unlock_time,
const rct::key *commitment
) override;
mutable uint64_t m_cum_size; // used in batch size estimation
mutable unsigned int m_cum_count;
fs::path m_folder;
mdb_txn_safe* m_write_txn; // may point to either a short-lived txn or a batch txn
mdb_txn_safe* m_write_batch_txn; // persist batch txn outside of BlockchainLMDB
boost::thread::id m_writer;
void add_tx_amount_output_indices(const uint64_t tx_id,
const std::vector<uint64_t>& amount_output_indices
) override;
bool m_batch_transactions; // support for batch transactions
bool m_batch_active; // whether batch transaction is in progress
void remove_tx_outputs(const uint64_t tx_id, const transaction& tx);
void remove_output(const uint64_t amount, const uint64_t& out_index);
void prune_outputs(uint64_t amount) override;
void add_spent_key(const crypto::key_image& k_image) override;
void remove_spent_key(const crypto::key_image& k_image) override;
uint64_t num_outputs() const;
inline void check_open() const;
bool prune_worker(int mode, uint32_t pruning_seed);
bool is_read_only() const override;
uint64_t get_database_size() const override;
std::vector<uint64_t> get_block_info_64bit_fields(uint64_t start_height, size_t count, uint64_t (*extract)(const mdb_block_info*)) const;
uint64_t get_max_block_size() override;
void add_max_block_size(uint64_t sz) override;
// fix up anything that may be wrong due to past bugs
void fixup(cryptonote::network_type nettype) override;
// migrate from older DB version to current
void migrate(const uint32_t oldversion, cryptonote::network_type nettype);
void migrate_0_1();
void migrate_1_2();
void migrate_2_3();
void migrate_3_4();
void migrate_4_5(cryptonote::network_type nettype);
void migrate_5_6();
void migrate_6_7();
void cleanup_batch();
bool get_block_checkpoint_internal(uint64_t height, checkpoint_t &checkpoint, MDB_cursor_op op) const;
void set_service_node_data(const std::string& data, bool long_term) override;
bool get_service_node_data(std::string& data, bool long_term) const override;
void clear_service_node_data() override;
bool get_service_node_proof(const crypto::public_key& pubkey, service_nodes::proof_info& proof) const override;
void set_service_node_proof(const crypto::public_key& pubkey, const service_nodes::proof_info& proof) override;
std::unordered_map<crypto::public_key, service_nodes::proof_info> get_all_service_node_proofs() const override;
bool remove_service_node_proof(const crypto::public_key& pubkey) override;
private:
template <typename T,
std::enable_if_t<std::is_same_v<T, cryptonote::block> ||
std::is_same_v<T, cryptonote::block_header> ||
std::is_same_v<T, std::string>, int> = 0>
T get_and_convert_block_blob_from_height(uint64_t height) const;
MDB_env* m_env;
MDB_dbi m_blocks;
MDB_dbi m_block_heights;
MDB_dbi m_block_info;
MDB_dbi m_block_checkpoints;
MDB_dbi m_txs;
MDB_dbi m_txs_pruned;
MDB_dbi m_txs_prunable;
MDB_dbi m_txs_prunable_hash;
MDB_dbi m_txs_prunable_tip;
MDB_dbi m_tx_indices;
MDB_dbi m_tx_outputs;
MDB_dbi m_output_txs;
MDB_dbi m_output_amounts;
MDB_dbi m_output_blacklist;
MDB_dbi m_spent_keys;
MDB_dbi m_txpool_meta;
MDB_dbi m_txpool_blob;
MDB_dbi m_alt_blocks;
MDB_dbi m_hf_starting_heights;
MDB_dbi m_hf_versions;
MDB_dbi m_service_node_data;
MDB_dbi m_service_node_proofs;
MDB_dbi m_properties;
mutable uint64_t m_cum_size; // used in batch size estimation
mutable unsigned int m_cum_count;
fs::path m_folder;
mdb_txn_safe* m_write_txn; // may point to either a short-lived txn or a batch txn
mdb_txn_safe* m_write_batch_txn; // persist batch txn outside of BlockchainLMDB
boost::thread::id m_writer;
bool m_batch_transactions; // support for batch transactions
bool m_batch_active; // whether batch transaction is in progress
mdb_txn_cursors m_wcursors;
mutable boost::thread_specific_ptr<mdb_threadinfo> m_tinfo;
mdb_txn_cursors m_wcursors;
mutable boost::thread_specific_ptr<mdb_threadinfo> m_tinfo;
#if defined(__arm__)
// force a value so it can compile with 32-bit ARM
constexpr static uint64_t DEFAULT_MAPSIZE = 1LL << 31;
// force a value so it can compile with 32-bit ARM
constexpr static uint64_t DEFAULT_MAPSIZE = 1LL << 31;
#else
#if defined(ENABLE_AUTO_RESIZE)
constexpr static uint64_t DEFAULT_MAPSIZE = 1LL << 30;
constexpr static uint64_t DEFAULT_MAPSIZE = 1LL << 30;
#else
constexpr static uint64_t DEFAULT_MAPSIZE = 1LL << 33;
constexpr static uint64_t DEFAULT_MAPSIZE = 1LL << 33;
#endif
#endif
// Guards LMDB resize
std::mutex m_synchronization_lock;
// Guards LMDB resize
std::mutex m_synchronization_lock;
constexpr static float RESIZE_PERCENT = 0.9f;
constexpr static float RESIZE_PERCENT = 0.9f;
};
} // namespace cryptonote

View File

@ -32,27 +32,43 @@
#include "cryptonote_core/blockchain.h"
namespace cryptonote
{
// This class is meant to create a batch when none currently exists.
// If a batch exists, it can't be from another thread, since we can
// only be called with the txpool lock taken, and it is held during
// the whole prepare/handle/cleanup incoming block sequence.
class LockedTXN {
public:
LockedTXN(Blockchain &b): m_db{b.get_db()} {
m_batch = m_db.batch_start();
}
LockedTXN(const LockedTXN &) = delete;
LockedTXN &operator=(const LockedTXN &) = delete;
LockedTXN(LockedTXN &&o) : m_db{o.m_db}, m_batch{o.m_batch} { o.m_batch = false; }
LockedTXN &operator=(LockedTXN &&) = delete;
namespace cryptonote {
// This class is meant to create a batch when none currently exists.
// If a batch exists, it can't be from another thread, since we can
// only be called with the txpool lock taken, and it is held during
// the whole prepare/handle/cleanup incoming block sequence.
class LockedTXN {
public:
LockedTXN(Blockchain& b) : m_db{b.get_db()} { m_batch = m_db.batch_start(); }
LockedTXN(const LockedTXN&) = delete;
LockedTXN& operator=(const LockedTXN&) = delete;
LockedTXN(LockedTXN&& o) : m_db{o.m_db}, m_batch{o.m_batch} { o.m_batch = false; }
LockedTXN& operator=(LockedTXN&&) = delete;
void commit() { try { if (m_batch) { m_db.batch_stop(); m_batch = false; } } catch (const std::exception &e) { log::warning(globallogcat, "LockedTXN::commit filtering exception: {}", e.what()); } }
void abort() { try { if (m_batch) { m_db.batch_abort(); m_batch = false; } } catch (const std::exception &e) { log::warning(globallogcat, "LockedTXN::abort filtering exception: {}", e.what()); } }
~LockedTXN() { this->abort(); }
private:
BlockchainDB &m_db;
bool m_batch;
};
}
void commit() {
try {
if (m_batch) {
m_db.batch_stop();
m_batch = false;
}
} catch (const std::exception& e) {
log::warning(globallogcat, "LockedTXN::commit filtering exception: {}", e.what());
}
}
void abort() {
try {
if (m_batch) {
m_db.batch_abort();
m_batch = false;
}
} catch (const std::exception& e) {
log::warning(globallogcat, "LockedTXN::abort filtering exception: {}", e.what());
}
}
~LockedTXN() { this->abort(); }
private:
BlockchainDB& m_db;
bool m_batch;
};
} // namespace cryptonote

View File

@ -27,41 +27,44 @@
#include "db_sqlite.h"
#include <sqlite3.h>
#include <sodium.h>
#include <fmt/core.h>
#include <sodium.h>
#include <sqlite3.h>
#include <cassert>
#include "common/string_util.h"
#include "cryptonote_basic/hardfork.h"
#include "cryptonote_config.h"
#include "cryptonote_core/blockchain.h"
#include "cryptonote_core/service_node_list.h"
#include "common/string_util.h"
#include "cryptonote_basic/hardfork.h"
namespace cryptonote {
static auto logcat = log::Cat("blockchain.db.sqlite");
BlockchainSQLite::BlockchainSQLite(cryptonote::network_type nettype, fs::path db_path): db::Database(db_path, ""), m_nettype(nettype), filename {db_path.u8string()} {
static auto logcat = log::Cat("blockchain.db.sqlite");
BlockchainSQLite::BlockchainSQLite(cryptonote::network_type nettype, fs::path db_path) :
db::Database(db_path, ""), m_nettype(nettype), filename{db_path.u8string()} {
log::trace(logcat, "BlockchainDB_SQLITE::{}", __func__);
height = 0;
if (!db.tableExists("batched_payments_accrued") || !db.tableExists("batched_payments_raw") || !db.tableExists("batch_db_info")) {
create_schema();
if (!db.tableExists("batched_payments_accrued") || !db.tableExists("batched_payments_raw") ||
!db.tableExists("batch_db_info")) {
create_schema();
}
upgrade_schema();
height = prepared_get<int64_t>("SELECT height FROM batch_db_info");
}
}
void BlockchainSQLite::create_schema() {
void BlockchainSQLite::create_schema() {
log::trace(logcat, "BlockchainDB_SQLITE::{}", __func__);
auto& netconf = cryptonote::get_config(m_nettype);
db.exec(fmt::format(R"(
db.exec(fmt::format(
R"(
CREATE TABLE batched_payments_accrued(
address VARCHAR NOT NULL,
amount BIGINT NOT NULL,
@ -114,29 +117,27 @@ namespace cryptonote {
ON CONFLICT(address) DO UPDATE SET amount = (amount + excluded.amount);
END;
)",
netconf.BATCHING_INTERVAL));
netconf.BATCHING_INTERVAL));
log::debug(logcat, "Database setup complete");
}
}
void BlockchainSQLite::upgrade_schema() {
void BlockchainSQLite::upgrade_schema() {
bool have_offset = false;
SQLite::Statement msg_cols{db, "PRAGMA main.table_info(batched_payments_accrued)"};
while (msg_cols.executeStep()) {
auto [cid, name] = db::get<int64_t, std::string>(msg_cols);
if (name == "payout_offset")
have_offset = true;
auto [cid, name] = db::get<int64_t, std::string>(msg_cols);
if (name == "payout_offset")
have_offset = true;
}
if (!have_offset) {
log::info(logcat, "Adding payout_offset to batching db");
auto& netconf = get_config(m_nettype);
SQLite::Transaction transaction{
db,
SQLite::TransactionBehavior::IMMEDIATE
};
log::info(logcat, "Adding payout_offset to batching db");
auto& netconf = get_config(m_nettype);
SQLite::Transaction transaction{db, SQLite::TransactionBehavior::IMMEDIATE};
db.exec(fmt::format(R"(
db.exec(fmt::format(
R"(
ALTER TABLE batched_payments_accrued ADD COLUMN payout_offset INTEGER NOT NULL DEFAULT -1;
CREATE INDEX batched_payments_accrued_payout_offset_idx ON batched_payments_accrued(payout_offset);
@ -148,39 +149,44 @@ namespace cryptonote {
INSERT INTO batched_payments_accrued(address, payout_offset, amount) VALUES(OLD.address, OLD.height_paid % {}, OLD.amount)
ON CONFLICT(address) DO UPDATE SET amount = (amount + excluded.amount);
END;
)", netconf.BATCHING_INTERVAL));
)",
netconf.BATCHING_INTERVAL));
auto st = prepared_st("UPDATE batched_payments_accrued SET payout_offset = ? WHERE address = ?");
for (const auto& address : prepared_results<std::string>("SELECT address from batched_payments_accrued")) {
cryptonote::address_parse_info addr_info{};
cryptonote::get_account_address_from_str(addr_info, m_nettype, address);
auto offset = static_cast<int>(addr_info.address.modulus(netconf.BATCHING_INTERVAL));
exec_query(st, offset, address);
st->reset();
}
auto st = prepared_st(
"UPDATE batched_payments_accrued SET payout_offset = ? WHERE address = ?");
for (const auto& address : prepared_results<std::string>("SELECT address from "
"batched_payments_accrued")) {
cryptonote::address_parse_info addr_info{};
cryptonote::get_account_address_from_str(addr_info, m_nettype, address);
auto offset = static_cast<int>(addr_info.address.modulus(netconf.BATCHING_INTERVAL));
exec_query(st, offset, address);
st->reset();
}
auto count = prepared_get<int>("SELECT COUNT(*) FROM batched_payments_accrued WHERE payout_offset NOT BETWEEN 0 AND ?",
static_cast<int>(netconf.BATCHING_INTERVAL));
auto count = prepared_get<int>(
"SELECT COUNT(*) FROM batched_payments_accrued WHERE payout_offset NOT BETWEEN 0 "
"AND ?",
static_cast<int>(netconf.BATCHING_INTERVAL));
if (count != 0) {
constexpr auto error = "Batching db update to add offsets failed: not all addresses were converted";
log::error(logcat, error);
throw std::runtime_error{error};
}
if (count != 0) {
constexpr auto error =
"Batching db update to add offsets failed: not all addresses were converted";
log::error(logcat, error);
throw std::runtime_error{error};
}
transaction.commit();
transaction.commit();
}
const auto archive_table_count = prepared_get<int64_t>("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='batched_payments_accrued_archive';");
if(archive_table_count == 0)
{
log::info(logcat, "Adding archiving to batching db");
auto& netconf = get_config(m_nettype);
SQLite::Transaction transaction{
db,
SQLite::TransactionBehavior::IMMEDIATE
};
db.exec(fmt::format(R"(
const auto archive_table_count = prepared_get<int64_t>(
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND "
"name='batched_payments_accrued_archive';");
if (archive_table_count == 0) {
log::info(logcat, "Adding archiving to batching db");
auto& netconf = get_config(m_nettype);
SQLite::Transaction transaction{db, SQLite::TransactionBehavior::IMMEDIATE};
db.exec(fmt::format(
R"(
CREATE TABLE batched_payments_accrued_archive(
address VARCHAR NOT NULL,
amount BIGINT NOT NULL,
@ -204,12 +210,14 @@ namespace cryptonote {
FOR EACH ROW WHEN NEW.height < OLD.height BEGIN
DELETE FROM batched_payments_accrued_archive WHERE archive_height >= NEW.height;
END;
)", netconf.STORE_LONG_TERM_STATE_INTERVAL, 500));
transaction.commit();
)",
netconf.STORE_LONG_TERM_STATE_INTERVAL,
500));
transaction.commit();
}
}
}
void BlockchainSQLite::reset_database() {
void BlockchainSQLite::reset_database() {
log::trace(logcat, "BlockchainDB_SQLITE::{}", __func__);
db.exec(R"(
@ -227,45 +235,43 @@ namespace cryptonote {
create_schema();
upgrade_schema();
log::debug(logcat, "Database reset complete");
}
}
void BlockchainSQLite::update_height(uint64_t new_height) {
void BlockchainSQLite::update_height(uint64_t new_height) {
log::trace(logcat, "BlockchainDB_SQLITE::{} Called with new height: {}", __func__, new_height);
height = new_height;
prepared_exec(
"UPDATE batch_db_info SET height = ?",
static_cast<int64_t>(height));
}
prepared_exec("UPDATE batch_db_info SET height = ?", static_cast<int64_t>(height));
}
void BlockchainSQLite::increment_height() {
void BlockchainSQLite::increment_height() {
log::trace(logcat, "BlockchainDB_SQLITE::{} Called with height: {}", __func__, height + 1);
update_height(height + 1);
}
}
void BlockchainSQLite::decrement_height() {
void BlockchainSQLite::decrement_height() {
log::trace(logcat, "BlockchainDB_SQLITE::{} Called with height: {}", __func__, height - 1);
update_height(height - 1);
}
}
void BlockchainSQLite::blockchain_detached(uint64_t new_height)
{
void BlockchainSQLite::blockchain_detached(uint64_t new_height) {
if (height < new_height)
return;
return;
int64_t revert_to_height = new_height - 1;
auto maybe_prev_interval = prepared_maybe_get<int64_t>(
"SELECT DISTINCT archive_height FROM batched_payments_accrued_archive WHERE archive_height <= ? ORDER BY archive_height DESC LIMIT 1",
revert_to_height);
"SELECT DISTINCT archive_height FROM batched_payments_accrued_archive WHERE "
"archive_height <= ? ORDER BY archive_height DESC LIMIT 1",
revert_to_height);
if (!maybe_prev_interval)
{
auto fork_height = cryptonote::get_hard_fork_heights(m_nettype, hf::hf19_reward_batching);
reset_database();
update_height(fork_height.first.value_or(0));
return;
if (!maybe_prev_interval) {
auto fork_height = cryptonote::get_hard_fork_heights(m_nettype, hf::hf19_reward_batching);
reset_database();
update_height(fork_height.first.value_or(0));
return;
}
const auto prev_interval = *maybe_prev_interval;
db.exec(fmt::format(R"(
db.exec(fmt::format(
R"(
DELETE FROM batched_payments_raw WHERE height_paid > {0};
DELETE FROM batched_payments_accrued;
@ -275,430 +281,494 @@ namespace cryptonote {
FROM batched_payments_accrued_archive WHERE archive_height = {0};
DELETE FROM batched_payments_accrued_archive WHERE archive_height >= {0};
)", prev_interval));
)",
prev_interval));
update_height(prev_interval);
return;
}
}
// Must be called with the address_str_cache_mutex held!
const std::string& BlockchainSQLite::get_address_str(const account_public_address& addr)
{
// Must be called with the address_str_cache_mutex held!
const std::string& BlockchainSQLite::get_address_str(const account_public_address& addr) {
auto& address_str = address_str_cache[addr];
if (address_str.empty())
address_str = cryptonote::get_account_address_as_str(m_nettype, 0, addr);
address_str = cryptonote::get_account_address_as_str(m_nettype, 0, addr);
return address_str;
}
}
bool BlockchainSQLite::add_sn_rewards(const std::vector<cryptonote::batch_sn_payment>& payments) {
bool BlockchainSQLite::add_sn_rewards(const std::vector<cryptonote::batch_sn_payment>& payments) {
log::trace(logcat, "BlockchainDB_SQLITE::{}", __func__);
auto insert_payment = prepared_st(
"INSERT INTO batched_payments_accrued (address, payout_offset, amount) VALUES (?, ?, ?)"
" ON CONFLICT (address) DO UPDATE SET amount = amount + excluded.amount");
"INSERT INTO batched_payments_accrued (address, payout_offset, amount) VALUES (?, ?, ?)"
" ON CONFLICT (address) DO UPDATE SET amount = amount + excluded.amount");
const auto& netconf = get_config(m_nettype);
for (auto& payment: payments) {
auto offset = static_cast<int>(payment.address_info.address.modulus(netconf.BATCHING_INTERVAL));
auto amt = static_cast<int64_t>(payment.amount);
const auto& address_str = get_address_str(payment.address_info.address);
log::trace(logcat, "Adding record for SN reward contributor {} to database with amount {}", address_str, amt);
db::exec_query(insert_payment, address_str, offset, amt);
insert_payment->reset();
for (auto& payment : payments) {
auto offset =
static_cast<int>(payment.address_info.address.modulus(netconf.BATCHING_INTERVAL));
auto amt = static_cast<int64_t>(payment.amount);
const auto& address_str = get_address_str(payment.address_info.address);
log::trace(
logcat,
"Adding record for SN reward contributor {} to database with amount {}",
address_str,
amt);
db::exec_query(insert_payment, address_str, offset, amt);
insert_payment->reset();
}
return true;
}
}
bool BlockchainSQLite::subtract_sn_rewards(const std::vector<cryptonote::batch_sn_payment>& payments) {
bool BlockchainSQLite::subtract_sn_rewards(
const std::vector<cryptonote::batch_sn_payment>& payments) {
log::trace(logcat, "BlockchainDB_SQLITE::{}", __func__);
auto update_payment = prepared_st(
"UPDATE batched_payments_accrued SET amount = (amount - ?) WHERE address = ?");
"UPDATE batched_payments_accrued SET amount = (amount - ?) WHERE address = ?");
for (auto& payment: payments) {
const auto& address_str = get_address_str(payment.address_info.address);
auto result = db::exec_query(update_payment, static_cast<int64_t>(payment.amount), address_str);
if (!result) {
log::error(logcat, "tried to subtract payment from an address that doesn't exist: {}", address_str);
return false;
}
update_payment->reset();
for (auto& payment : payments) {
const auto& address_str = get_address_str(payment.address_info.address);
auto result =
db::exec_query(update_payment, static_cast<int64_t>(payment.amount), address_str);
if (!result) {
log::error(
logcat,
"tried to subtract payment from an address that doesn't exist: {}",
address_str);
return false;
}
update_payment->reset();
}
return true;
}
}
std::vector<cryptonote::batch_sn_payment> BlockchainSQLite::get_sn_payments(uint64_t block_height) {
std::vector<cryptonote::batch_sn_payment> BlockchainSQLite::get_sn_payments(uint64_t block_height) {
log::trace(logcat, "BlockchainDB_SQLITE::{}", __func__);
// <= here because we might have crap in the db that we don't clear until we actually add the HF
// block later on. (This is a pretty slim edge case that happened on devnet and is probably
// virtually impossible on mainnet).
if (m_nettype != cryptonote::network_type::FAKECHAIN && block_height <= cryptonote::get_hard_fork_heights(m_nettype, hf::hf19_reward_batching).first.value_or(0))
return {};
if (m_nettype != cryptonote::network_type::FAKECHAIN &&
block_height <= cryptonote::get_hard_fork_heights(m_nettype, hf::hf19_reward_batching)
.first.value_or(0))
return {};
const auto& conf = get_config(m_nettype);
auto accrued_amounts = prepared_results<std::string_view, int64_t>(
"SELECT address, amount FROM batched_payments_accrued WHERE payout_offset = ? AND amount >= ? ORDER BY address ASC",
static_cast<int>(block_height % conf.BATCHING_INTERVAL),
static_cast<int64_t>(conf.MIN_BATCH_PAYMENT_AMOUNT * BATCH_REWARD_FACTOR));
"SELECT address, amount FROM batched_payments_accrued WHERE payout_offset = ? AND "
"amount >= ? ORDER BY address ASC",
static_cast<int>(block_height % conf.BATCHING_INTERVAL),
static_cast<int64_t>(conf.MIN_BATCH_PAYMENT_AMOUNT * BATCH_REWARD_FACTOR));
std::vector<cryptonote::batch_sn_payment> payments;
for (auto [address, amount] : accrued_amounts) {
auto& p = payments.emplace_back();
p.amount = amount / BATCH_REWARD_FACTOR * BATCH_REWARD_FACTOR; /* truncate to atomic OXEN */
[[maybe_unused]] bool addr_ok = cryptonote::get_account_address_from_str(p.address_info, m_nettype, address);
assert(addr_ok);
auto& p = payments.emplace_back();
p.amount = amount / BATCH_REWARD_FACTOR * BATCH_REWARD_FACTOR; /* truncate to atomic OXEN */
[[maybe_unused]] bool addr_ok =
cryptonote::get_account_address_from_str(p.address_info, m_nettype, address);
assert(addr_ok);
}
return payments;
}
}
uint64_t BlockchainSQLite::get_accrued_earnings(const std::string& address) {
uint64_t BlockchainSQLite::get_accrued_earnings(const std::string& address) {
log::trace(logcat, "BlockchainDB_SQLITE::{}", __func__);
auto earnings = prepared_maybe_get<int64_t>(
"SELECT amount FROM batched_payments_accrued WHERE address = ?",
address);
"SELECT amount FROM batched_payments_accrued WHERE address = ?", address);
return static_cast<uint64_t>(earnings.value_or(0) / 1000);
}
}
std::pair<std::vector<std::string>, std::vector<uint64_t>> BlockchainSQLite::get_all_accrued_earnings() {
std::pair<std::vector<std::string>, std::vector<uint64_t>>
BlockchainSQLite::get_all_accrued_earnings() {
log::trace(logcat, "BlockchainDB_SQLITE::{}", __func__);
std::pair<std::vector<std::string>, std::vector<uint64_t>> result;
auto& [addresses, amounts] = result;
for (auto [addr, amt] : prepared_results<std::string, int64_t>(
"SELECT address, amount FROM batched_payments_accrued")) {
auto amount = static_cast<uint64_t>(amt / 1000);
if (amount > 0) {
addresses.push_back(std::move(addr));
amounts.push_back(amount);
}
for (auto [addr, amt] : prepared_results<std::string, int64_t>("SELECT address, amount FROM "
"batched_payments_accrued")) {
auto amount = static_cast<uint64_t>(amt / 1000);
if (amount > 0) {
addresses.push_back(std::move(addr));
amounts.push_back(amount);
}
}
return result;
}
}
void BlockchainSQLite::calculate_rewards(hf hf_version, uint64_t distribution_amount, const service_nodes::service_node_info& sn_info, std::vector<cryptonote::batch_sn_payment>& payments) {
void BlockchainSQLite::calculate_rewards(
hf hf_version,
uint64_t distribution_amount,
const service_nodes::service_node_info& sn_info,
std::vector<cryptonote::batch_sn_payment>& payments) {
log::trace(logcat, "BlockchainDB_SQLITE::{}", __func__);
// Find out how much is due for the operator: fee_portions/PORTIONS * reward
assert(sn_info.portions_for_operator <= old::STAKING_PORTIONS);
uint64_t operator_fee = mul128_div64(sn_info.portions_for_operator, distribution_amount, old::STAKING_PORTIONS);
uint64_t operator_fee =
mul128_div64(sn_info.portions_for_operator, distribution_amount, old::STAKING_PORTIONS);
assert(operator_fee <= distribution_amount);
payments.clear();
// Pay the operator fee to the operator
if (operator_fee > 0)
payments.emplace_back(sn_info.operator_address, operator_fee);
payments.emplace_back(sn_info.operator_address, operator_fee);
// Pay the balance to all the contributors (including the operator again)
uint64_t total_contributed_to_sn = std::accumulate(
sn_info.contributors.begin(),
sn_info.contributors.end(),
uint64_t(0),
[](auto&& a, auto&& b) { return a + b.amount; });
sn_info.contributors.begin(),
sn_info.contributors.end(),
uint64_t(0),
[](auto&& a, auto&& b) { return a + b.amount; });
for (auto& contributor: sn_info.contributors) {
// This calculates (contributor.amount / total_contributed_to_winner_sn) * (distribution_amount - operator_fee) but using 128 bit integer math
uint64_t c_reward = mul128_div64(contributor.amount, distribution_amount - operator_fee, total_contributed_to_sn);
if (c_reward > 0)
payments.emplace_back(contributor.address, c_reward);
for (auto& contributor : sn_info.contributors) {
// This calculates (contributor.amount / total_contributed_to_winner_sn) *
// (distribution_amount - operator_fee) but using 128 bit integer math
uint64_t c_reward = mul128_div64(
contributor.amount, distribution_amount - operator_fee, total_contributed_to_sn);
if (c_reward > 0)
payments.emplace_back(contributor.address, c_reward);
}
}
}
// Calculates block rewards, then invokes either `add_sn_rewards` (if `add`) or
// `subtract_sn_rewards` (if `!add`) to process them.
bool BlockchainSQLite::reward_handler(
const cryptonote::block& block,
const service_nodes::service_node_list::state_t& service_nodes_state,
bool add)
{
// Calculates block rewards, then invokes either `add_sn_rewards` (if `add`) or
// `subtract_sn_rewards` (if `!add`) to process them.
bool BlockchainSQLite::reward_handler(
const cryptonote::block& block,
const service_nodes::service_node_list::state_t& service_nodes_state,
bool add) {
// The method we call do actually handle the change: either `add_sn_payments` if add is true,
// `subtract_sn_payments` otherwise:
bool (BlockchainSQLite::* add_or_subtract)(const std::vector<cryptonote::batch_sn_payment>&)
= add ? &BlockchainSQLite::add_sn_rewards : &BlockchainSQLite::subtract_sn_rewards;
bool (BlockchainSQLite::*add_or_subtract)(const std::vector<cryptonote::batch_sn_payment>&) =
add ? &BlockchainSQLite::add_sn_rewards : &BlockchainSQLite::subtract_sn_rewards;
// From here on we calculate everything in milli-atomic OXEN (i.e. thousanths of an atomic
// OXEN) so that our integer math has minimal loss from integer division.
if (block.reward > std::numeric_limits<uint64_t>::max() / BATCH_REWARD_FACTOR)
throw std::logic_error{"Reward distribution amount is too large"};
throw std::logic_error{"Reward distribution amount is too large"};
uint64_t block_reward = block.reward * BATCH_REWARD_FACTOR;
uint64_t service_node_reward = cryptonote::service_node_reward_formula(0, block.major_version) * BATCH_REWARD_FACTOR;
uint64_t service_node_reward =
cryptonote::service_node_reward_formula(0, block.major_version) * BATCH_REWARD_FACTOR;
std::vector<cryptonote::batch_sn_payment> payments;
// Step 1: Pay out the block producer their tx fees (note that, unlike the below, this applies
// even if the SN isn't currently payable).
if (block_reward < service_node_reward && m_nettype != cryptonote::network_type::FAKECHAIN)
throw std::logic_error{"Invalid payment: block reward is too small"};
throw std::logic_error{"Invalid payment: block reward is too small"};
std::lock_guard a_s_lock{address_str_cache_mutex};
if (uint64_t tx_fees = block_reward - service_node_reward;
tx_fees > 0
&& block.service_node_winner_key // "service_node_winner_key" tracks the pulse winner; 0 if a mined block
&& crypto_core_ed25519_is_valid_point(block.service_node_winner_key.data())
) {
tx_fees > 0 && block.service_node_winner_key // "service_node_winner_key" tracks the pulse
// winner; 0 if a mined block
&& crypto_core_ed25519_is_valid_point(block.service_node_winner_key.data())) {
if (auto service_node_winner = service_nodes_state.service_nodes_infos.find(block.service_node_winner_key);
service_node_winner != service_nodes_state.service_nodes_infos.end()) {
calculate_rewards(block.major_version, tx_fees, *service_node_winner->second, payments);
// Takes the block producer and adds its contributors to the batching database for the transaction fees
if (!(this->*add_or_subtract)(payments))
return false;
}
if (auto service_node_winner =
service_nodes_state.service_nodes_infos.find(block.service_node_winner_key);
service_node_winner != service_nodes_state.service_nodes_infos.end()) {
calculate_rewards(block.major_version, tx_fees, *service_node_winner->second, payments);
// Takes the block producer and adds its contributors to the batching database for the
// transaction fees
if (!(this->*add_or_subtract)(payments))
return false;
}
}
auto block_height = get_block_height(block);
// Step 2: Iterate over the whole service node list and pay each node 1/service_node_list fraction
const auto payable_service_nodes = service_nodes_state.payable_service_nodes_infos(block_height, m_nettype);
// Step 2: Iterate over the whole service node list and pay each node 1/service_node_list
// fraction
const auto payable_service_nodes =
service_nodes_state.payable_service_nodes_infos(block_height, m_nettype);
size_t total_service_nodes_payable = payable_service_nodes.size();
for (const auto& [node_pubkey, node_info]: payable_service_nodes) {
auto payable_service_node = service_nodes_state.service_nodes_infos.find(node_pubkey);
if (payable_service_node == service_nodes_state.service_nodes_infos.end())
continue;
calculate_rewards(block.major_version, service_node_reward / total_service_nodes_payable, * payable_service_node -> second, payments);
// Takes the node and adds its contributors to the batching database
if (!(this->*add_or_subtract)(payments))
return false;
for (const auto& [node_pubkey, node_info] : payable_service_nodes) {
auto payable_service_node = service_nodes_state.service_nodes_infos.find(node_pubkey);
if (payable_service_node == service_nodes_state.service_nodes_infos.end())
continue;
calculate_rewards(
block.major_version,
service_node_reward / total_service_nodes_payable,
*payable_service_node->second,
payments);
// Takes the node and adds its contributors to the batching database
if (!(this->*add_or_subtract)(payments))
return false;
}
// Step 3: Add Governance reward to the list
if (m_nettype != cryptonote::network_type::FAKECHAIN) {
if (parsed_governance_addr.first != block.major_version) {
cryptonote::get_account_address_from_str(parsed_governance_addr.second, m_nettype,
cryptonote::get_config(m_nettype).governance_wallet_address(block.major_version));
parsed_governance_addr.first = block.major_version;
}
uint64_t foundation_reward = cryptonote::governance_reward_formula(block.major_version) * BATCH_REWARD_FACTOR;
payments.clear();
payments.emplace_back(parsed_governance_addr.second.address, foundation_reward);
if (!(this->*add_or_subtract)(payments))
return false;
if (parsed_governance_addr.first != block.major_version) {
cryptonote::get_account_address_from_str(
parsed_governance_addr.second,
m_nettype,
cryptonote::get_config(m_nettype).governance_wallet_address(
block.major_version));
parsed_governance_addr.first = block.major_version;
}
uint64_t foundation_reward =
cryptonote::governance_reward_formula(block.major_version) * BATCH_REWARD_FACTOR;
payments.clear();
payments.emplace_back(parsed_governance_addr.second.address, foundation_reward);
if (!(this->*add_or_subtract)(payments))
return false;
}
return true;
}
}
bool BlockchainSQLite::add_block(const cryptonote::block& block,
const service_nodes::service_node_list::state_t& service_nodes_state) {
bool BlockchainSQLite::add_block(
const cryptonote::block& block,
const service_nodes::service_node_list::state_t& service_nodes_state) {
auto block_height = get_block_height(block);
log::trace(logcat, "BlockchainDB_SQLITE::{} called on height: {}", __func__, block_height);
auto hf_version = block.major_version;
if (hf_version < hf::hf19_reward_batching) {
update_height(block_height);
return true;
update_height(block_height);
return true;
}
auto fork_height = cryptonote::get_hard_fork_heights(m_nettype, hf::hf19_reward_batching);
if (block_height == fork_height.first.value_or(0)) {
log::debug(logcat, "Batching of Service Node Rewards Begins");
reset_database();
update_height(block_height - 1);
log::debug(logcat, "Batching of Service Node Rewards Begins");
reset_database();
update_height(block_height - 1);
}
if (block_height != height + 1) {
log::error(logcat, "Block height ({}) out of sync with batching database ({})", block_height, height);
return false;
log::error(
logcat,
"Block height ({}) out of sync with batching database ({})",
block_height,
height);
return false;
}
// We query our own database as a source of truth to verify the blocks payments against. The calculated_rewards
// variable contains a known good list of who should have been paid in this block
// We query our own database as a source of truth to verify the blocks payments against. The
// calculated_rewards variable contains a known good list of who should have been paid in this
// block
auto calculated_rewards = get_sn_payments(block_height);
// We iterate through the block's coinbase payments and build a copy of our own list of the payments
// miner_tx_vouts this will be compared against calculated_rewards and if they match we know the block is
// paying the correct people only.
// We iterate through the block's coinbase payments and build a copy of our own list of the
// payments miner_tx_vouts this will be compared against calculated_rewards and if they match we
// know the block is paying the correct people only.
std::vector<std::pair<crypto::public_key, uint64_t>> miner_tx_vouts;
for (auto & vout: block.miner_tx.vout)
miner_tx_vouts.emplace_back(var::get<txout_to_key>(vout.target).key, vout.amount);
for (auto& vout : block.miner_tx.vout)
miner_tx_vouts.emplace_back(var::get<txout_to_key>(vout.target).key, vout.amount);
try {
SQLite::Transaction transaction {
db,
SQLite::TransactionBehavior::IMMEDIATE
};
SQLite::Transaction transaction{db, SQLite::TransactionBehavior::IMMEDIATE};
// Goes through the miner transactions vouts checks they are right and marks them as paid in the database
if (!validate_batch_payment(miner_tx_vouts, calculated_rewards, block_height)) {
return false;
}
// Goes through the miner transactions vouts checks they are right and marks them as paid in
// the database
if (!validate_batch_payment(miner_tx_vouts, calculated_rewards, block_height)) {
return false;
}
if (!reward_handler(block, service_nodes_state, /*add=*/ true))
return false;
if (!reward_handler(block, service_nodes_state, /*add=*/true))
return false;
increment_height();
increment_height();
transaction.commit();
transaction.commit();
} catch (std::exception& e) {
log::error(logcat, "Error adding reward payments: {}", e.what());
return false;
log::error(logcat, "Error adding reward payments: {}", e.what());
return false;
}
return true;
}
}
bool BlockchainSQLite::pop_block(const cryptonote::block& block,
const service_nodes::service_node_list::state_t& service_nodes_state) {
bool BlockchainSQLite::pop_block(
const cryptonote::block& block,
const service_nodes::service_node_list::state_t& service_nodes_state) {
auto block_height = get_block_height(block);
log::trace(logcat, "BlockchainDB_SQLITE::{} called on height: {}", __func__, block_height);
if (height < block_height) {
log::debug(logcat, "Block above batching DB height skipping pop");
return true;
log::debug(logcat, "Block above batching DB height skipping pop");
return true;
}
if (block_height != height) {
log::error(logcat, "Block height out of sync with batching database");
return false;
log::error(logcat, "Block height out of sync with batching database");
return false;
}
const auto& conf = get_config(m_nettype);
auto hf_version = block.major_version;
if (hf_version < hf::hf19_reward_batching) {
decrement_height();
return true;
decrement_height();
return true;
}
try {
SQLite::Transaction transaction {
db,
SQLite::TransactionBehavior::IMMEDIATE
};
SQLite::Transaction transaction{db, SQLite::TransactionBehavior::IMMEDIATE};
if (!reward_handler(block, service_nodes_state, /*add=*/false))
return false;
if (!reward_handler(block, service_nodes_state, /*add=*/ false))
return false;
// Add back to the database payments that had been made in this block
delete_block_payments(block_height);
// Add back to the database payments that had been made in this block
delete_block_payments(block_height);
decrement_height();
transaction.commit();
decrement_height();
transaction.commit();
} catch (std::exception& e) {
log::error(logcat, "Error subtracting reward payments: {}", e.what());
return false;
log::error(logcat, "Error subtracting reward payments: {}", e.what());
return false;
}
return true;
}
}
bool BlockchainSQLite::validate_batch_payment(
const std::vector<std::pair<crypto::public_key, uint64_t>>& miner_tx_vouts,
const std::vector<cryptonote::batch_sn_payment>& calculated_payments_from_batching_db,
uint64_t block_height) {
bool BlockchainSQLite::validate_batch_payment(
const std::vector<std::pair<crypto::public_key, uint64_t>>& miner_tx_vouts,
const std::vector<cryptonote::batch_sn_payment>& calculated_payments_from_batching_db,
uint64_t block_height) {
log::trace(logcat, "BlockchainDB_SQLITE::{}", __func__);
if (miner_tx_vouts.size() != calculated_payments_from_batching_db.size()) {
log::error(logcat, "Length of batch payments ({}) does not match block vouts ({})", calculated_payments_from_batching_db.size(), miner_tx_vouts.size());
return false;
log::error(
logcat,
"Length of batch payments ({}) does not match block vouts ({})",
calculated_payments_from_batching_db.size(),
miner_tx_vouts.size());
return false;
}
int8_t vout_index = 0;
uint64_t total_oxen_payout_in_our_db = std::accumulate(
calculated_payments_from_batching_db.begin(),
calculated_payments_from_batching_db.end(),
uint64_t(0),
[](auto&& a, auto&& b) { return a + b.amount; });
calculated_payments_from_batching_db.begin(),
calculated_payments_from_batching_db.end(),
uint64_t(0),
[](auto&& a, auto&& b) { return a + b.amount; });
uint64_t total_oxen_payout_in_vouts = 0;
std::vector<batch_sn_payment> finalised_payments;
cryptonote::keypair
const deterministic_keypair = cryptonote::get_deterministic_keypair_from_height(block_height);
cryptonote::keypair const deterministic_keypair =
cryptonote::get_deterministic_keypair_from_height(block_height);
for (size_t vout_index = 0; vout_index < miner_tx_vouts.size(); vout_index++) {
const auto& [pubkey, amt] = miner_tx_vouts[vout_index];
uint64_t amount = amt * BATCH_REWARD_FACTOR;
const auto& from_db = calculated_payments_from_batching_db[vout_index];
if (amount != from_db.amount) {
log::error(logcat, "Batched payout amount incorrect. Should be {}, not {}", from_db.amount, amount);
return false;
}
crypto::public_key out_eph_public_key{};
if (!cryptonote::get_deterministic_output_key(from_db.address_info.address, deterministic_keypair, vout_index, out_eph_public_key)) {
log::error(logcat, "Failed to generate output one-time public key");
return false;
}
if (tools::view_guts(pubkey) != tools::view_guts(out_eph_public_key)) {
log::error(logcat, "Output ephemeral public key does not match");
return false;
}
total_oxen_payout_in_vouts += amount;
finalised_payments.emplace_back(from_db.address_info, amount);
const auto& [pubkey, amt] = miner_tx_vouts[vout_index];
uint64_t amount = amt * BATCH_REWARD_FACTOR;
const auto& from_db = calculated_payments_from_batching_db[vout_index];
if (amount != from_db.amount) {
log::error(
logcat,
"Batched payout amount incorrect. Should be {}, not {}",
from_db.amount,
amount);
return false;
}
crypto::public_key out_eph_public_key{};
if (!cryptonote::get_deterministic_output_key(
from_db.address_info.address,
deterministic_keypair,
vout_index,
out_eph_public_key)) {
log::error(logcat, "Failed to generate output one-time public key");
return false;
}
if (tools::view_guts(pubkey) != tools::view_guts(out_eph_public_key)) {
log::error(logcat, "Output ephemeral public key does not match");
return false;
}
total_oxen_payout_in_vouts += amount;
finalised_payments.emplace_back(from_db.address_info, amount);
}
if (total_oxen_payout_in_vouts != total_oxen_payout_in_our_db) {
log::error(logcat, "Total batched payout amount incorrect. Should be {}, not {}", total_oxen_payout_in_our_db, total_oxen_payout_in_vouts);
return false;
log::error(
logcat,
"Total batched payout amount incorrect. Should be {}, not {}",
total_oxen_payout_in_our_db,
total_oxen_payout_in_vouts);
return false;
}
return save_payments(block_height, finalised_payments);
}
}
bool BlockchainSQLite::save_payments(uint64_t block_height, const std::vector<batch_sn_payment>& paid_amounts) {
bool BlockchainSQLite::save_payments(
uint64_t block_height, const std::vector<batch_sn_payment>& paid_amounts) {
log::trace(logcat, "BlockchainDB_SQLITE::{}", __func__);
auto select_sum = prepared_st(
"SELECT amount from batched_payments_accrued WHERE address = ?");
auto select_sum = prepared_st("SELECT amount from batched_payments_accrued WHERE address = ?");
auto update_paid = prepared_st(
"INSERT INTO batched_payments_paid (address, amount, height_paid) VALUES (?,?,?)");
"INSERT INTO batched_payments_paid (address, amount, height_paid) VALUES (?,?,?)");
std::lock_guard a_s_lock{address_str_cache_mutex};
for (const auto& payment: paid_amounts) {
const auto& address_str = get_address_str(payment.address_info.address);
if (auto maybe_amount = db::exec_and_maybe_get<int64_t>(select_sum, address_str))
{
// Truncate the thousanths amount to an atomic OXEN:
auto amount = static_cast<uint64_t>(*maybe_amount) / BATCH_REWARD_FACTOR * BATCH_REWARD_FACTOR;
for (const auto& payment : paid_amounts) {
const auto& address_str = get_address_str(payment.address_info.address);
if (auto maybe_amount = db::exec_and_maybe_get<int64_t>(select_sum, address_str)) {
// Truncate the thousanths amount to an atomic OXEN:
auto amount = static_cast<uint64_t>(*maybe_amount) / BATCH_REWARD_FACTOR *
BATCH_REWARD_FACTOR;
if (amount != payment.amount) {
log::error(logcat, "Invalid amounts passed in to save payments for address {}: received {}, expected {} (truncated from {})", address_str, payment.amount, amount, *maybe_amount);
return false;
if (amount != payment.amount) {
log::error(
logcat,
"Invalid amounts passed in to save payments for address {}: received {}, "
"expected {} (truncated from {})",
address_str,
payment.amount,
amount,
*maybe_amount);
return false;
}
db::exec_query(
update_paid,
address_str,
static_cast<int64_t>(amount),
static_cast<int64_t>(block_height));
update_paid->reset();
} else {
// This shouldn't occur: we validate payout addresses much earlier in the block
// validation.
log::error(
logcat,
"Internal error: Invalid amounts passed in to save payments for address {}: "
"that address has no accrued rewards",
address_str);
return false;
}
db::exec_query(update_paid, address_str, static_cast<int64_t>(amount), static_cast<int64_t>(block_height));
update_paid->reset();
}
else {
// This shouldn't occur: we validate payout addresses much earlier in the block validation.
log::error(logcat, "Internal error: Invalid amounts passed in to save payments for address {}: that address has no accrued rewards", address_str);
return false;
}
select_sum->reset();
select_sum->reset();
}
return true;
}
}
std::vector<cryptonote::batch_sn_payment> BlockchainSQLite::get_block_payments(uint64_t block_height) {
std::vector<cryptonote::batch_sn_payment> BlockchainSQLite::get_block_payments(
uint64_t block_height) {
log::trace(logcat, "BlockchainDB_SQLITE::{} Called with height: {}", __func__, block_height);
std::vector<cryptonote::batch_sn_payment> payments_at_height;
auto paid = prepared_results<std::string_view, int64_t>(
"SELECT address, amount FROM batched_payments_paid WHERE height_paid = ? ORDER BY address",
static_cast<int64_t>(block_height));
"SELECT address, amount FROM batched_payments_paid WHERE height_paid = ? ORDER BY "
"address",
static_cast<int64_t>(block_height));
for (auto [addr, amt] : paid) {
auto& p = payments_at_height.emplace_back();
p.amount = static_cast<uint64_t>(amt);
cryptonote::get_account_address_from_str(p.address_info, m_nettype, addr);
auto& p = payments_at_height.emplace_back();
p.amount = static_cast<uint64_t>(amt);
cryptonote::get_account_address_from_str(p.address_info, m_nettype, addr);
}
return payments_at_height;
}
}
bool BlockchainSQLite::delete_block_payments(uint64_t block_height) {
bool BlockchainSQLite::delete_block_payments(uint64_t block_height) {
log::trace(logcat, "BlockchainDB_SQLITE::{} Called with height: {}", __func__, block_height);
prepared_exec(
"DELETE FROM batched_payments_paid WHERE height_paid >= ?",
static_cast<int64_t>(block_height));
"DELETE FROM batched_payments_paid WHERE height_paid >= ?",
static_cast<int64_t>(block_height));
return true;
}
}
} // namespace cryptonote
} // namespace cryptonote

View File

@ -27,103 +27,116 @@
#pragma once
#include <string>
#include <filesystem>
#include <SQLiteCpp/SQLiteCpp.h>
#include <filesystem>
#include <string>
#include "common/fs.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_core/cryptonote_tx_utils.h"
#include "sqlitedb/database.hpp"
#include "common/fs.h"
#include <SQLiteCpp/SQLiteCpp.h>
namespace cryptonote
{
namespace cryptonote {
fs::path check_if_copy_filename(std::string_view db_path);
class BlockchainSQLite : public db::Database
{
public:
explicit BlockchainSQLite(cryptonote::network_type nettype, fs::path db_path);
BlockchainSQLite(const BlockchainSQLite&) = delete;
class BlockchainSQLite : public db::Database {
public:
explicit BlockchainSQLite(cryptonote::network_type nettype, fs::path db_path);
BlockchainSQLite(const BlockchainSQLite&) = delete;
// Database management functions. Should be called on creation of BlockchainSQLite
void create_schema();
void upgrade_schema();
void reset_database();
// Database management functions. Should be called on creation of BlockchainSQLite
void create_schema();
void upgrade_schema();
void reset_database();
// The batching database maintains a height variable to know if it gets out of sync with the mainchain. Calling increment and decrement is the primary method of interacting with this height variable
void update_height(uint64_t new_height);
void increment_height();
void decrement_height();
// The batching database maintains a height variable to know if it gets out of sync with the
// mainchain. Calling increment and decrement is the primary method of interacting with this
// height variable
void update_height(uint64_t new_height);
void increment_height();
void decrement_height();
void blockchain_detached(uint64_t new_height);
void blockchain_detached(uint64_t new_height);
// add_sn_payments/subtract_sn_payments -> passing an array of addresses and amounts. These will be added or subtracted to the database for each address specified. If the address does not exist it will be created.
bool add_sn_rewards(const std::vector<cryptonote::batch_sn_payment>& payments);
bool subtract_sn_rewards(const std::vector<cryptonote::batch_sn_payment>& payments);
// add_sn_payments/subtract_sn_payments -> passing an array of addresses and amounts. These will
// be added or subtracted to the database for each address specified. If the address does not
// exist it will be created.
bool add_sn_rewards(const std::vector<cryptonote::batch_sn_payment>& payments);
bool subtract_sn_rewards(const std::vector<cryptonote::batch_sn_payment>& payments);
private:
bool reward_handler(
const cryptonote::block& block,
const service_nodes::service_node_list::state_t& service_nodes_state,
bool add);
private:
bool reward_handler(
const cryptonote::block& block,
const service_nodes::service_node_list::state_t& service_nodes_state,
bool add);
std::unordered_map<account_public_address, std::string> address_str_cache;
std::pair<hf, cryptonote::address_parse_info> parsed_governance_addr = {hf::none, {}};
const std::string& get_address_str(const account_public_address& addr);
std::mutex address_str_cache_mutex;
std::unordered_map<account_public_address, std::string> address_str_cache;
std::pair<hf, cryptonote::address_parse_info> parsed_governance_addr = {hf::none, {}};
const std::string& get_address_str(const account_public_address& addr);
std::mutex address_str_cache_mutex;
public:
public:
// get_accrued_earnings -> queries the database for the amount that has been accrued to
// `service_node_address` will return the atomic value in oxen that the service node is owed.
uint64_t get_accrued_earnings(const std::string& address);
// get_all_accrued_earnings -> queries the database for all the amount that has been accrued to
// service nodes will return 2 vectors corresponding to the addresses and the atomic value in
// oxen that the service nodes are owed.
std::pair<std::vector<std::string>, std::vector<uint64_t>> get_all_accrued_earnings();
// get_accrued_earnings -> queries the database for the amount that has been accrued to `service_node_address` will return the atomic value in oxen that
// the service node is owed.
uint64_t get_accrued_earnings(const std::string& address);
// get_all_accrued_earnings -> queries the database for all the amount that has been accrued to service nodes will return
// 2 vectors corresponding to the addresses and the atomic value in oxen that the service nodes are owed.
std::pair<std::vector<std::string>, std::vector<uint64_t>> get_all_accrued_earnings();
// get_payments -> passing a block height will return an array of payments that should be
// created in a coinbase transaction on that block given the current batching DB state.
std::vector<cryptonote::batch_sn_payment> get_sn_payments(uint64_t block_height);
// get_payments -> passing a block height will return an array of payments that should be created in a coinbase transaction on that block given the current batching DB state.
std::vector<cryptonote::batch_sn_payment> get_sn_payments(uint64_t block_height);
// calculate_rewards -> takes the list of contributors from sn_info with their SN contribution
// amounts and will calculate how much of the block rewards should be the allocated to the
// contributors. The function will set a list suitable for passing to add_sn_payments into the
// vector (any existing values will be cleared).
//
// Note that distribution_amount here is typically passed as milli-atomic OXEN for extra
// precision.
void calculate_rewards(
hf hf_version,
uint64_t distribution_amount,
const service_nodes::service_node_info& sn_info,
std::vector<cryptonote::batch_sn_payment>& rewards);
// calculate_rewards -> takes the list of contributors from sn_info with their SN contribution
// amounts and will calculate how much of the block rewards should be the allocated to the
// contributors. The function will set a list suitable for passing to add_sn_payments into the
// vector (any existing values will be cleared).
//
// Note that distribution_amount here is typically passed as milli-atomic OXEN for extra
// precision.
void calculate_rewards(hf hf_version, uint64_t distribution_amount, const service_nodes::service_node_info& sn_info, std::vector<cryptonote::batch_sn_payment>& rewards);
// add/pop_block -> takes a block that contains new block rewards to be batched and added to the
// database and/or batching payments that need to be subtracted from the database, in addition
// it takes a reference to the service node state which it will use to calculate the individual
// payouts. The function will then process this block add and subtracting to the batching DB
// appropriately. This is the primary entry point for the blockchain to add to the batching
// database. Each accepted block should call this passing in the SN list structure.
bool add_block(
const cryptonote::block& block,
const service_nodes::service_node_list::state_t& service_nodes_state);
bool pop_block(
const cryptonote::block& block,
const service_nodes::service_node_list::state_t& service_nodes_state);
// add/pop_block -> takes a block that contains new block rewards to be batched and added to the database
// and/or batching payments that need to be subtracted from the database, in addition it takes a reference to
// the service node state which it will use to calculate the individual payouts.
// The function will then process this block add and subtracting to the batching DB appropriately.
// This is the primary entry point for the blockchain to add to the batching database.
// Each accepted block should call this passing in the SN list structure.
bool add_block(const cryptonote::block& block, const service_nodes::service_node_list::state_t& service_nodes_state);
bool pop_block(const cryptonote::block& block, const service_nodes::service_node_list::state_t& service_nodes_state);
// validate_batch_payment -> used to make sure that list of miner_tx_vouts is correct. Compares
// the miner_tx_vouts with a list previously extracted payments to make sure that the correct
// persons are being paid.
bool validate_batch_payment(
const std::vector<std::pair<crypto::public_key, uint64_t>>& miner_tx_vouts,
const std::vector<cryptonote::batch_sn_payment>& calculated_payments_from_batching_db,
uint64_t block_height);
// validate_batch_payment -> used to make sure that list of miner_tx_vouts is correct. Compares the miner_tx_vouts with a list previously extracted payments to make sure that the correct persons are being paid.
bool validate_batch_payment(
const std::vector<std::pair<crypto::public_key, uint64_t>>& miner_tx_vouts,
const std::vector<cryptonote::batch_sn_payment>& calculated_payments_from_batching_db,
uint64_t block_height);
// these keep track of payments made to SN operators after then payment has been made. Allows for popping blocks back and knowing who got paid in those blocks.
// passing in a list of people to be marked as paid in the paid_amounts vector. Block height will be added to the batched_payments_paid database as height_paid.
bool save_payments(uint64_t block_height, const std::vector<batch_sn_payment>& paid_amounts);
std::vector<cryptonote::batch_sn_payment> get_block_payments(uint64_t block_height);
bool delete_block_payments(uint64_t block_height);
// these keep track of payments made to SN operators after then payment has been made. Allows
// for popping blocks back and knowing who got paid in those blocks. passing in a list of people
// to be marked as paid in the paid_amounts vector. Block height will be added to the
// batched_payments_paid database as height_paid.
bool save_payments(uint64_t block_height, const std::vector<batch_sn_payment>& paid_amounts);
std::vector<cryptonote::batch_sn_payment> get_block_payments(uint64_t block_height);
bool delete_block_payments(uint64_t block_height);
uint64_t height;
protected:
cryptonote::network_type m_nettype;
std::string filename;
uint64_t height;
protected:
cryptonote::network_type m_nettype;
std::string filename;
};
}
} // namespace cryptonote

View File

@ -1,21 +1,21 @@
// Copyright (c) 2014-2019, The Monero 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
@ -25,160 +25,351 @@
// 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
#pragma once
#include <map>
#include <string>
#include <vector>
#include <map>
#include "blockchain_db.h"
#include "cryptonote_core/service_node_list.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_core/service_node_list.h"
namespace cryptonote
{
struct checkpoint_t;
class BaseTestDB: public cryptonote::BlockchainDB {
public:
BaseTestDB() {}
virtual void open(const fs::path& filename, network_type nettype = network_type::FAKECHAIN, const int db_flags = 0) override { }
virtual void close() override {}
virtual void sync() override {}
virtual void safesyncmode(const bool onoff) override {}
virtual void reset() override {}
virtual std::vector<fs::path> get_filenames() const override { return {}; }
virtual bool remove_data_file(const fs::path& folder) const override { return true; }
virtual std::string get_db_name() const override { return std::string(); }
virtual void lock() override { }
virtual bool try_lock() override { return true; }
virtual void unlock() override { }
virtual bool batch_start(uint64_t batch_num_blocks=0, uint64_t batch_bytes=0) override { return true; }
virtual void batch_stop() override {}
virtual void batch_abort() override {}
virtual void set_batch_transactions(bool) override {}
virtual void block_wtxn_start() override {}
virtual void block_wtxn_stop() override {}
virtual void block_wtxn_abort() override {}
virtual bool block_rtxn_start() const override { return true; }
virtual void block_rtxn_stop() const override {}
virtual void block_rtxn_abort() const override {}
namespace cryptonote {
struct checkpoint_t;
class BaseTestDB : public cryptonote::BlockchainDB {
public:
BaseTestDB() {}
virtual void open(
const fs::path& filename,
network_type nettype = network_type::FAKECHAIN,
const int db_flags = 0) override {}
virtual void close() override {}
virtual void sync() override {}
virtual void safesyncmode(const bool onoff) override {}
virtual void reset() override {}
virtual std::vector<fs::path> get_filenames() const override { return {}; }
virtual bool remove_data_file(const fs::path& folder) const override { return true; }
virtual std::string get_db_name() const override { return std::string(); }
virtual void lock() override {}
virtual bool try_lock() override { return true; }
virtual void unlock() override {}
virtual bool batch_start(uint64_t batch_num_blocks = 0, uint64_t batch_bytes = 0) override {
return true;
}
virtual void batch_stop() override {}
virtual void batch_abort() override {}
virtual void set_batch_transactions(bool) override {}
virtual void block_wtxn_start() override {}
virtual void block_wtxn_stop() override {}
virtual void block_wtxn_abort() override {}
virtual bool block_rtxn_start() const override { return true; }
virtual void block_rtxn_stop() const override {}
virtual void block_rtxn_abort() const override {}
virtual bool block_exists(const crypto::hash& h, uint64_t *height) const override { return false; }
virtual std::string get_block_blob_from_height(uint64_t height) const override { return cryptonote::t_serializable_object_to_blob(get_block_from_height(height)); }
virtual std::string get_block_blob(const crypto::hash& h) const override { return std::string(); }
virtual cryptonote::block_header get_block_header_from_height(uint64_t height) const override { return get_block_from_height(height); }
virtual bool get_tx_blob(const crypto::hash& h, std::string &tx) const override { return false; }
virtual bool get_pruned_tx_blob(const crypto::hash& h, std::string &tx) const override { return false; }
virtual bool get_pruned_tx_blobs_from(const crypto::hash& h, size_t count, std::vector<std::string> &bd) const override { return false; }
virtual bool get_prunable_tx_blob(const crypto::hash& h, std::string &tx) const override { return false; }
virtual bool get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const override { return false; }
virtual uint64_t get_block_height(const crypto::hash& h) const override { return 0; }
virtual uint64_t get_block_timestamp(const uint64_t& height) const override { return 0; }
virtual std::vector<uint64_t> get_block_cumulative_rct_outputs(const std::vector<uint64_t> &heights) const override { return {}; }
virtual uint64_t get_top_block_timestamp() const override { return 0; }
virtual size_t get_block_weight(const uint64_t& height) const override { return 128; }
virtual std::vector<uint64_t> get_block_weights(uint64_t start_height, size_t count) const override { return {}; }
virtual cryptonote::difficulty_type get_block_cumulative_difficulty(const uint64_t& height) const override { return 10; }
virtual cryptonote::difficulty_type get_block_difficulty(const uint64_t& height) const override { return 0; }
virtual uint64_t get_block_already_generated_coins(const uint64_t& height) const override { return 10000000000; }
virtual uint64_t get_block_long_term_weight(const uint64_t& height) const override { return 128; }
virtual std::vector<uint64_t> get_long_term_block_weights(uint64_t start_height, size_t count) const override { return {}; }
virtual crypto::hash get_block_hash_from_height(const uint64_t& height) const override { return crypto::hash(); }
virtual std::vector<cryptonote::block> get_blocks_range(const uint64_t& h1, const uint64_t& h2) const override { return std::vector<cryptonote::block>(); }
virtual std::vector<crypto::hash> get_hashes_range(const uint64_t& h1, const uint64_t& h2) const override { return std::vector<crypto::hash>(); }
virtual crypto::hash top_block_hash(uint64_t *block_height = NULL) const override { if (block_height) *block_height = 0; return crypto::hash(); }
virtual cryptonote::block get_top_block() const override { return cryptonote::block(); }
virtual uint64_t height() const override { return 1; }
virtual bool tx_exists(const crypto::hash& h) const override { return false; }
virtual bool tx_exists(const crypto::hash& h, uint64_t& tx_index) const override { return false; }
virtual uint64_t get_tx_unlock_time(const crypto::hash& h) const override { return 0; }
virtual cryptonote::transaction get_tx(const crypto::hash& h) const override { return cryptonote::transaction(); }
virtual bool get_tx(const crypto::hash& h, cryptonote::transaction &tx) const override { return false; }
virtual uint64_t get_tx_count() const override { return 0; }
virtual std::vector<cryptonote::transaction> get_tx_list(const std::vector<crypto::hash>& hlist) const override { return std::vector<cryptonote::transaction>(); }
virtual std::vector<uint64_t> get_tx_block_heights(const std::vector<crypto::hash>& h) const override { return {h.size(), 0}; }
virtual uint64_t get_num_outputs(const uint64_t& amount) const override { return 1; }
virtual uint64_t get_indexing_base() const override { return 0; }
virtual cryptonote::output_data_t get_output_key(const uint64_t& amount, const uint64_t& index, bool include_commitmemt) const override { return cryptonote::output_data_t(); }
virtual cryptonote::tx_out_index get_output_tx_and_index_from_global(const uint64_t& index) const override { return cryptonote::tx_out_index(); }
virtual cryptonote::tx_out_index get_output_tx_and_index(const uint64_t& amount, const uint64_t& index) const override { return cryptonote::tx_out_index(); }
virtual void get_output_tx_and_index(const uint64_t& amount, const std::vector<uint64_t> &offsets, std::vector<cryptonote::tx_out_index> &indices) const override {}
virtual void get_output_key(const epee::span<const uint64_t> &amounts, const std::vector<uint64_t> &offsets, std::vector<cryptonote::output_data_t> &outputs, bool allow_partial = false) const override {}
virtual bool can_thread_bulk_indices() const override { return false; }
virtual std::vector<std::vector<uint64_t>> get_tx_amount_output_indices(const uint64_t tx_index, size_t n_txes) const override { return std::vector<std::vector<uint64_t>>(); }
virtual bool has_key_image(const crypto::key_image& img) const override { return false; }
virtual void remove_block() override { }
virtual uint64_t add_transaction_data(const crypto::hash& blk_hash, const std::pair<cryptonote::transaction, std::string>& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prunable_hash) override {return 0;}
virtual void remove_transaction_data(const crypto::hash& tx_hash, const cryptonote::transaction& tx) override {}
virtual uint64_t add_output(const crypto::hash& tx_hash, const cryptonote::tx_out& tx_output, const uint64_t& local_index, const uint64_t unlock_time, const rct::key *commitment) override {return 0;}
virtual void add_tx_amount_output_indices(const uint64_t tx_index, const std::vector<uint64_t>& amount_output_indices) override {}
virtual void add_spent_key(const crypto::key_image& k_image) override {}
virtual void remove_spent_key(const crypto::key_image& k_image) override {}
virtual bool block_exists(const crypto::hash& h, uint64_t* height) const override {
return false;
}
virtual std::string get_block_blob_from_height(uint64_t height) const override {
return cryptonote::t_serializable_object_to_blob(get_block_from_height(height));
}
virtual std::string get_block_blob(const crypto::hash& h) const override {
return std::string();
}
virtual cryptonote::block_header get_block_header_from_height(uint64_t height) const override {
return get_block_from_height(height);
}
virtual bool get_tx_blob(const crypto::hash& h, std::string& tx) const override {
return false;
}
virtual bool get_pruned_tx_blob(const crypto::hash& h, std::string& tx) const override {
return false;
}
virtual bool get_pruned_tx_blobs_from(
const crypto::hash& h, size_t count, std::vector<std::string>& bd) const override {
return false;
}
virtual bool get_prunable_tx_blob(const crypto::hash& h, std::string& tx) const override {
return false;
}
virtual bool get_prunable_tx_hash(
const crypto::hash& tx_hash, crypto::hash& prunable_hash) const override {
return false;
}
virtual uint64_t get_block_height(const crypto::hash& h) const override { return 0; }
virtual uint64_t get_block_timestamp(const uint64_t& height) const override { return 0; }
virtual std::vector<uint64_t> get_block_cumulative_rct_outputs(
const std::vector<uint64_t>& heights) const override {
return {};
}
virtual uint64_t get_top_block_timestamp() const override { return 0; }
virtual size_t get_block_weight(const uint64_t& height) const override { return 128; }
virtual std::vector<uint64_t> get_block_weights(
uint64_t start_height, size_t count) const override {
return {};
}
virtual cryptonote::difficulty_type get_block_cumulative_difficulty(
const uint64_t& height) const override {
return 10;
}
virtual cryptonote::difficulty_type get_block_difficulty(
const uint64_t& height) const override {
return 0;
}
virtual uint64_t get_block_already_generated_coins(const uint64_t& height) const override {
return 10000000000;
}
virtual uint64_t get_block_long_term_weight(const uint64_t& height) const override {
return 128;
}
virtual std::vector<uint64_t> get_long_term_block_weights(
uint64_t start_height, size_t count) const override {
return {};
}
virtual crypto::hash get_block_hash_from_height(const uint64_t& height) const override {
return crypto::hash();
}
virtual std::vector<cryptonote::block> get_blocks_range(
const uint64_t& h1, const uint64_t& h2) const override {
return std::vector<cryptonote::block>();
}
virtual std::vector<crypto::hash> get_hashes_range(
const uint64_t& h1, const uint64_t& h2) const override {
return std::vector<crypto::hash>();
}
virtual crypto::hash top_block_hash(uint64_t* block_height = NULL) const override {
if (block_height)
*block_height = 0;
return crypto::hash();
}
virtual cryptonote::block get_top_block() const override { return cryptonote::block(); }
virtual uint64_t height() const override { return 1; }
virtual bool tx_exists(const crypto::hash& h) const override { return false; }
virtual bool tx_exists(const crypto::hash& h, uint64_t& tx_index) const override {
return false;
}
virtual uint64_t get_tx_unlock_time(const crypto::hash& h) const override { return 0; }
virtual cryptonote::transaction get_tx(const crypto::hash& h) const override {
return cryptonote::transaction();
}
virtual bool get_tx(const crypto::hash& h, cryptonote::transaction& tx) const override {
return false;
}
virtual uint64_t get_tx_count() const override { return 0; }
virtual std::vector<cryptonote::transaction> get_tx_list(
const std::vector<crypto::hash>& hlist) const override {
return std::vector<cryptonote::transaction>();
}
virtual std::vector<uint64_t> get_tx_block_heights(
const std::vector<crypto::hash>& h) const override {
return {h.size(), 0};
}
virtual uint64_t get_num_outputs(const uint64_t& amount) const override { return 1; }
virtual uint64_t get_indexing_base() const override { return 0; }
virtual cryptonote::output_data_t get_output_key(
const uint64_t& amount, const uint64_t& index, bool include_commitmemt) const override {
return cryptonote::output_data_t();
}
virtual cryptonote::tx_out_index get_output_tx_and_index_from_global(
const uint64_t& index) const override {
return cryptonote::tx_out_index();
}
virtual cryptonote::tx_out_index get_output_tx_and_index(
const uint64_t& amount, const uint64_t& index) const override {
return cryptonote::tx_out_index();
}
virtual void get_output_tx_and_index(
const uint64_t& amount,
const std::vector<uint64_t>& offsets,
std::vector<cryptonote::tx_out_index>& indices) const override {}
virtual void get_output_key(
const epee::span<const uint64_t>& amounts,
const std::vector<uint64_t>& offsets,
std::vector<cryptonote::output_data_t>& outputs,
bool allow_partial = false) const override {}
virtual bool can_thread_bulk_indices() const override { return false; }
virtual std::vector<std::vector<uint64_t>> get_tx_amount_output_indices(
const uint64_t tx_index, size_t n_txes) const override {
return std::vector<std::vector<uint64_t>>();
}
virtual bool has_key_image(const crypto::key_image& img) const override { return false; }
virtual void remove_block() override {}
virtual uint64_t add_transaction_data(
const crypto::hash& blk_hash,
const std::pair<cryptonote::transaction, std::string>& tx,
const crypto::hash& tx_hash,
const crypto::hash& tx_prunable_hash) override {
return 0;
}
virtual void remove_transaction_data(
const crypto::hash& tx_hash, const cryptonote::transaction& tx) override {}
virtual uint64_t add_output(
const crypto::hash& tx_hash,
const cryptonote::tx_out& tx_output,
const uint64_t& local_index,
const uint64_t unlock_time,
const rct::key* commitment) override {
return 0;
}
virtual void add_tx_amount_output_indices(
const uint64_t tx_index, const std::vector<uint64_t>& amount_output_indices) override {}
virtual void add_spent_key(const crypto::key_image& k_image) override {}
virtual void remove_spent_key(const crypto::key_image& k_image) override {}
virtual bool for_all_key_images(std::function<bool(const crypto::key_image&)>) const override { return true; }
virtual bool for_blocks_range(const uint64_t&, const uint64_t&, std::function<bool(uint64_t, const crypto::hash&, const cryptonote::block&)>) const override { return true; }
virtual bool for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)>, bool pruned) const override { return true; }
virtual bool for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, uint64_t height, size_t tx_idx)> f) const override { return true; }
virtual bool for_all_outputs(uint64_t amount, const std::function<bool(uint64_t height)> &f) const override { return true; }
virtual bool is_read_only() const override { return false; }
virtual std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff, uint64_t min_count) const override { return std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>>(); }
virtual bool get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t to_height, std::vector<uint64_t> &distribution, uint64_t &base) const override { return false; }
virtual bool for_all_key_images(std::function<bool(const crypto::key_image&)>) const override {
return true;
}
virtual bool for_blocks_range(
const uint64_t&,
const uint64_t&,
std::function<bool(uint64_t, const crypto::hash&, const cryptonote::block&)>)
const override {
return true;
}
virtual bool for_all_transactions(
std::function<bool(const crypto::hash&, const cryptonote::transaction&)>,
bool pruned) const override {
return true;
}
virtual bool for_all_outputs(
std::function<bool(
uint64_t amount, const crypto::hash& tx_hash, uint64_t height, size_t tx_idx)>
f) const override {
return true;
}
virtual bool for_all_outputs(
uint64_t amount, const std::function<bool(uint64_t height)>& f) const override {
return true;
}
virtual bool is_read_only() const override { return false; }
virtual std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> get_output_histogram(
const std::vector<uint64_t>& amounts,
bool unlocked,
uint64_t recent_cutoff,
uint64_t min_count) const override {
return std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>>();
}
virtual bool get_output_distribution(
uint64_t amount,
uint64_t from_height,
uint64_t to_height,
std::vector<uint64_t>& distribution,
uint64_t& base) const override {
return false;
}
virtual void add_txpool_tx(const crypto::hash &txid, const std::string &blob, const cryptonote::txpool_tx_meta_t& details) override {}
virtual void update_txpool_tx(const crypto::hash &txid, const cryptonote::txpool_tx_meta_t& details) override {}
virtual uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const override { return 0; }
virtual bool txpool_has_tx(const crypto::hash &txid) const override { return false; }
virtual void remove_txpool_tx(const crypto::hash& txid) override {}
virtual bool get_txpool_tx_meta(const crypto::hash& txid, cryptonote::txpool_tx_meta_t &meta) const override { return false; }
virtual bool get_txpool_tx_blob(const crypto::hash& txid, std::string &bd) const override { return false; }
virtual uint64_t get_database_size() const override { return 0; }
virtual std::string get_txpool_tx_blob(const crypto::hash& txid) const override { return ""; }
virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const cryptonote::txpool_tx_meta_t&, const std::string*)>, bool include_blob = false, bool include_unrelayed_txes = false) const override { return false; }
virtual void add_txpool_tx(
const crypto::hash& txid,
const std::string& blob,
const cryptonote::txpool_tx_meta_t& details) override {}
virtual void update_txpool_tx(
const crypto::hash& txid, const cryptonote::txpool_tx_meta_t& details) override {}
virtual uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const override {
return 0;
}
virtual bool txpool_has_tx(const crypto::hash& txid) const override { return false; }
virtual void remove_txpool_tx(const crypto::hash& txid) override {}
virtual bool get_txpool_tx_meta(
const crypto::hash& txid, cryptonote::txpool_tx_meta_t& meta) const override {
return false;
}
virtual bool get_txpool_tx_blob(const crypto::hash& txid, std::string& bd) const override {
return false;
}
virtual uint64_t get_database_size() const override { return 0; }
virtual std::string get_txpool_tx_blob(const crypto::hash& txid) const override { return ""; }
virtual bool for_all_txpool_txes(
std::function<bool(
const crypto::hash&, const cryptonote::txpool_tx_meta_t&, const std::string*)>,
bool include_blob = false,
bool include_unrelayed_txes = false) const override {
return false;
}
virtual void add_block( const cryptonote::block& blk
, size_t block_weight
, uint64_t long_term_block_weight
, const cryptonote::difficulty_type& cumulative_difficulty
, const uint64_t& coins_generated
, uint64_t num_rct_outs
, const crypto::hash& blk_hash
) override { }
virtual cryptonote::block get_block_from_height(uint64_t height) const override { return cryptonote::block(); }
virtual void add_block(
const cryptonote::block& blk,
size_t block_weight,
uint64_t long_term_block_weight,
const cryptonote::difficulty_type& cumulative_difficulty,
const uint64_t& coins_generated,
uint64_t num_rct_outs,
const crypto::hash& blk_hash) override {}
virtual cryptonote::block get_block_from_height(uint64_t height) const override {
return cryptonote::block();
}
virtual uint32_t get_blockchain_pruning_seed() const override { return 0; }
virtual bool prune_blockchain(uint32_t pruning_seed = 0) override { return true; }
virtual bool update_pruning() override { return true; }
virtual bool check_pruning() override { return true; }
virtual void prune_outputs(uint64_t amount) override {}
virtual uint32_t get_blockchain_pruning_seed() const override { return 0; }
virtual bool prune_blockchain(uint32_t pruning_seed = 0) override { return true; }
virtual bool update_pruning() override { return true; }
virtual bool check_pruning() override { return true; }
virtual void prune_outputs(uint64_t amount) override {}
virtual uint64_t get_max_block_size() override { return 100000000; }
virtual void add_max_block_size(uint64_t sz) override { }
virtual uint64_t get_max_block_size() override { return 100000000; }
virtual void add_max_block_size(uint64_t sz) override {}
virtual void update_block_checkpoint(struct checkpoint_t const &checkpoint) override {}
virtual bool get_block_checkpoint (uint64_t height, struct checkpoint_t &checkpoint) const override { return false; }
virtual bool get_top_checkpoint (struct checkpoint_t &checkpoint) const override { return false; }
virtual void remove_block_checkpoint(uint64_t height) override { }
std::vector<cryptonote::checkpoint_t> get_checkpoints_range(uint64_t start, uint64_t end, size_t num_desired_checkpoints = BlockchainDB::GET_ALL_CHECKPOINTS) const override { return {}; }
virtual void update_block_checkpoint(struct checkpoint_t const& checkpoint) override {}
virtual bool get_block_checkpoint(
uint64_t height, struct checkpoint_t& checkpoint) const override {
return false;
}
virtual bool get_top_checkpoint(struct checkpoint_t& checkpoint) const override {
return false;
}
virtual void remove_block_checkpoint(uint64_t height) override {}
std::vector<cryptonote::checkpoint_t> get_checkpoints_range(
uint64_t start,
uint64_t end,
size_t num_desired_checkpoints = BlockchainDB::GET_ALL_CHECKPOINTS) const override {
return {};
}
virtual void get_output_blacklist (std::vector<uint64_t> &blacklist) const override { }
virtual void add_output_blacklist (std::vector<uint64_t> const &blacklist) override { }
virtual void set_service_node_data (const std::string& data, bool long_term) override { }
virtual bool get_service_node_data (std::string& data, bool long_term) const override { return false; }
virtual void clear_service_node_data() override { }
virtual void get_output_blacklist(std::vector<uint64_t>& blacklist) const override {}
virtual void add_output_blacklist(std::vector<uint64_t> const& blacklist) override {}
virtual void set_service_node_data(const std::string& data, bool long_term) override {}
virtual bool get_service_node_data(std::string& data, bool long_term) const override {
return false;
}
virtual void clear_service_node_data() override {}
bool get_service_node_proof(const crypto::public_key &pubkey, service_nodes::proof_info &proof) const override { return false; }
std::unordered_map<crypto::public_key, service_nodes::proof_info> get_all_service_node_proofs() const override { return {}; }
void set_service_node_proof(const crypto::public_key &pubkey, const service_nodes::proof_info &proof) override { }
bool remove_service_node_proof(const crypto::public_key &pubkey) override { return false; }
bool get_service_node_proof(
const crypto::public_key& pubkey, service_nodes::proof_info& proof) const override {
return false;
}
std::unordered_map<crypto::public_key, service_nodes::proof_info> get_all_service_node_proofs()
const override {
return {};
}
void set_service_node_proof(
const crypto::public_key& pubkey, const service_nodes::proof_info& proof) override {}
bool remove_service_node_proof(const crypto::public_key& pubkey) override { return false; }
virtual void add_alt_block(const crypto::hash &blkid, const cryptonote::alt_block_data_t &data, const std::string &blob, const std::string *checkpoint) override {}
virtual bool get_alt_block(const crypto::hash &blkid, alt_block_data_t *data, std::string *blob, std::string *checkpoint) const override { return false; }
virtual void remove_alt_block(const crypto::hash &blkid) override {}
virtual uint64_t get_alt_block_count() override { return 0; }
virtual void drop_alt_blocks() override {}
virtual bool for_all_alt_blocks(std::function<bool(const crypto::hash &blkid, const alt_block_data_t &data, const std::string *block_blob, const std::string *checkpoint_blob)> f, bool include_blob = false) const override { return true; }
virtual void add_alt_block(
const crypto::hash& blkid,
const cryptonote::alt_block_data_t& data,
const std::string& blob,
const std::string* checkpoint) override {}
virtual bool get_alt_block(
const crypto::hash& blkid,
alt_block_data_t* data,
std::string* blob,
std::string* checkpoint) const override {
return false;
}
virtual void remove_alt_block(const crypto::hash& blkid) override {}
virtual uint64_t get_alt_block_count() override { return 0; }
virtual void drop_alt_blocks() override {}
virtual bool for_all_alt_blocks(
std::function<
bool(const crypto::hash& blkid,
const alt_block_data_t& data,
const std::string* block_blob,
const std::string* checkpoint_blob)> f,
bool include_blob = false) const override {
return true;
}
};
}
} // namespace cryptonote

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -26,13 +26,13 @@
// 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.
#include "common/command_line.h"
#include "common/varint.h"
#include "common/median.h"
#include "common/fs-format.h"
#include "cryptonote_core/cryptonote_core.h"
#include "blockchain_objects.h"
#include "blockchain_db/blockchain_db.h"
#include "blockchain_objects.h"
#include "common/command_line.h"
#include "common/fs-format.h"
#include "common/median.h"
#include "common/varint.h"
#include "cryptonote_core/cryptonote_core.h"
#include "cryptonote_core/uptime_proof.h"
#include "version.h"
@ -41,276 +41,264 @@ using namespace cryptonote;
static auto logcat = log::Cat("bcutil");
int main(int argc, char* argv[])
{
TRY_ENTRY();
int main(int argc, char* argv[]) {
TRY_ENTRY();
epee::string_tools::set_module_name_and_folder(argv[0]);
tools::on_startup();
epee::string_tools::set_module_name_and_folder(argv[0]);
tools::on_startup();
auto opt_size = command_line::boost_option_sizes();
auto opt_size = command_line::boost_option_sizes();
po::options_description desc_cmd_only("Command line options", opt_size.first, opt_size.second);
po::options_description desc_cmd_sett("Command line options and settings options", opt_size.first, opt_size.second);
const command_line::arg_descriptor<std::string> arg_log_level = {"log-level", "0-4 or categories", ""};
const command_line::arg_descriptor<std::string> arg_txid = {"txid", "Get min depth for this txid", ""};
const command_line::arg_descriptor<uint64_t> arg_height = {"height", "Get min depth for all txes at this height", 0};
const command_line::arg_descriptor<bool> arg_include_coinbase = {"include-coinbase", "Include coinbase in the average", false};
po::options_description desc_cmd_only("Command line options", opt_size.first, opt_size.second);
po::options_description desc_cmd_sett(
"Command line options and settings options", opt_size.first, opt_size.second);
const command_line::arg_descriptor<std::string> arg_log_level = {
"log-level", "0-4 or categories", ""};
const command_line::arg_descriptor<std::string> arg_txid = {
"txid", "Get min depth for this txid", ""};
const command_line::arg_descriptor<uint64_t> arg_height = {
"height", "Get min depth for all txes at this height", 0};
const command_line::arg_descriptor<bool> arg_include_coinbase = {
"include-coinbase", "Include coinbase in the average", false};
command_line::add_arg(desc_cmd_sett, cryptonote::arg_data_dir);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_testnet_on);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_devnet_on);
command_line::add_arg(desc_cmd_sett, arg_log_level);
command_line::add_arg(desc_cmd_sett, arg_txid);
command_line::add_arg(desc_cmd_sett, arg_height);
command_line::add_arg(desc_cmd_sett, arg_include_coinbase);
command_line::add_arg(desc_cmd_only, command_line::arg_help);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_data_dir);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_testnet_on);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_devnet_on);
command_line::add_arg(desc_cmd_sett, arg_log_level);
command_line::add_arg(desc_cmd_sett, arg_txid);
command_line::add_arg(desc_cmd_sett, arg_height);
command_line::add_arg(desc_cmd_sett, arg_include_coinbase);
command_line::add_arg(desc_cmd_only, command_line::arg_help);
po::options_description desc_options("Allowed options");
desc_options.add(desc_cmd_only).add(desc_cmd_sett);
po::options_description desc_options("Allowed options");
desc_options.add(desc_cmd_only).add(desc_cmd_sett);
po::variables_map vm;
bool r = command_line::handle_error_helper(desc_options, [&]()
{
auto parser = po::command_line_parser(argc, argv).options(desc_options);
po::store(parser.run(), vm);
po::notify(vm);
return true;
});
if (! r)
return 1;
po::variables_map vm;
bool r = command_line::handle_error_helper(desc_options, [&]() {
auto parser = po::command_line_parser(argc, argv).options(desc_options);
po::store(parser.run(), vm);
po::notify(vm);
return true;
});
if (!r)
return 1;
if (command_line::get_arg(vm, command_line::arg_help))
{
std::cout << "Oxen '" << OXEN_RELEASE_NAME << "' (v" << OXEN_VERSION_FULL << ")\n\n";
std::cout << desc_options << std::endl;
return 1;
}
auto m_config_folder = command_line::get_arg(vm, cryptonote::arg_data_dir);
auto log_file_path = m_config_folder + "oxen-blockchain-depth.log";
log::Level log_level;
if(auto level = oxen::logging::parse_level(command_line::get_arg(vm, arg_log_level).c_str())) {
log_level = *level;
} else {
std::cerr << "Incorrect log level: " << command_line::get_arg(vm, arg_log_level).c_str() << std::endl;
throw std::runtime_error{"Incorrect log level"};
}
oxen::logging::init(log_file_path, log_level);
log::warning(logcat, "Starting...");
bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on);
bool opt_devnet = command_line::get_arg(vm, cryptonote::arg_devnet_on);
network_type net_type = opt_testnet ? network_type::TESTNET : opt_devnet ? network_type::DEVNET : network_type::MAINNET;
std::string opt_txid_string = command_line::get_arg(vm, arg_txid);
uint64_t opt_height = command_line::get_arg(vm, arg_height);
bool opt_include_coinbase = command_line::get_arg(vm, arg_include_coinbase);
if (!opt_txid_string.empty() && opt_height)
{
std::cerr << "txid and height cannot be given at the same time" << std::endl;
return 1;
}
crypto::hash opt_txid{};
if (!opt_txid_string.empty())
{
if (!tools::hex_to_type(opt_txid_string, opt_txid))
{
std::cerr << "Invalid txid" << std::endl;
return 1;
if (command_line::get_arg(vm, command_line::arg_help)) {
std::cout << "Oxen '" << OXEN_RELEASE_NAME << "' (v" << OXEN_VERSION_FULL << ")\n\n";
std::cout << desc_options << std::endl;
return 1;
}
}
log::warning(logcat, "Initializing source blockchain (BlockchainDB)");
blockchain_objects_t blockchain_objects = {};
Blockchain *core_storage = &blockchain_objects.m_blockchain;
BlockchainDB *db = new_db();
if (db == NULL)
{
log::error(logcat, "Failed to initialize a database");
throw std::runtime_error("Failed to initialize a database");
}
log::warning(logcat, "database: LMDB");
const fs::path filename = fs::u8path(command_line::get_arg(vm, cryptonote::arg_data_dir)) / db->get_db_name();
log::warning(logcat, "Loading blockchain from folder {} ...", filename);
try
{
db->open(filename, core_storage->nettype(), DBF_RDONLY);
}
catch (const std::exception& e)
{
log::warning(logcat, "Error opening database: {}", e.what());
return 1;
}
r = core_storage->init(db, nullptr /*ons_db*/, nullptr, net_type);
CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage");
log::warning(logcat, "Source blockchain storage initialized OK");
std::vector<crypto::hash> start_txids;
if (!opt_txid_string.empty())
{
start_txids.push_back(opt_txid);
}
else
{
const std::string bd = db->get_block_blob_from_height(opt_height);
cryptonote::block b;
if (!cryptonote::parse_and_validate_block_from_blob(bd, b))
{
log::warning(logcat, "Bad block from db");
return 1;
auto m_config_folder = command_line::get_arg(vm, cryptonote::arg_data_dir);
auto log_file_path = m_config_folder + "oxen-blockchain-depth.log";
log::Level log_level;
if (auto level = oxen::logging::parse_level(command_line::get_arg(vm, arg_log_level).c_str())) {
log_level = *level;
} else {
std::cerr << "Incorrect log level: " << command_line::get_arg(vm, arg_log_level).c_str()
<< std::endl;
throw std::runtime_error{"Incorrect log level"};
}
for (const crypto::hash &txid: b.tx_hashes)
start_txids.push_back(txid);
if (opt_include_coinbase)
start_txids.push_back(cryptonote::get_transaction_hash(b.miner_tx));
}
oxen::logging::init(log_file_path, log_level);
log::warning(logcat, "Starting...");
if (start_txids.empty())
{
log::warning(logcat, "No transaction(s) to check");
return 1;
}
bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on);
bool opt_devnet = command_line::get_arg(vm, cryptonote::arg_devnet_on);
network_type net_type = opt_testnet ? network_type::TESTNET
: opt_devnet ? network_type::DEVNET
: network_type::MAINNET;
std::string opt_txid_string = command_line::get_arg(vm, arg_txid);
uint64_t opt_height = command_line::get_arg(vm, arg_height);
bool opt_include_coinbase = command_line::get_arg(vm, arg_include_coinbase);
std::vector<uint64_t> depths;
for (const crypto::hash &start_txid: start_txids)
{
uint64_t depth = 0;
bool coinbase = false;
log::warning(logcat, "Checking depth for txid {}", start_txid);
std::vector<crypto::hash> txids(1, start_txid);
while (!coinbase)
{
log::warning(logcat, "Considering {} transaction(s) at depth {}", txids.size(), depth);
std::vector<crypto::hash> new_txids;
for (const crypto::hash &txid: txids)
{
std::string bd;
if (!db->get_pruned_tx_blob(txid, bd))
{
log::warning(logcat, "Failed to get txid {} from db", txid);
return 1;
}
cryptonote::transaction tx;
if (!cryptonote::parse_and_validate_tx_base_from_blob(bd, tx))
{
log::warning(logcat, "Bad tx: {}", txid);
return 1;
}
for (size_t ring = 0; ring < tx.vin.size(); ++ring)
{
if (std::holds_alternative<cryptonote::txin_gen>(tx.vin[ring]))
{
log::debug(logcat, "{} is a coinbase transaction", txid);
coinbase = true;
goto done;
}
if (auto* txin = std::get_if<cryptonote::txin_to_key>(&tx.vin[ring]))
{
const uint64_t amount = txin->amount;
auto absolute_offsets = cryptonote::relative_output_offsets_to_absolute(txin->key_offsets);
for (uint64_t offset: absolute_offsets)
{
const output_data_t od = db->get_output_key(amount, offset);
const crypto::hash block_hash = db->get_block_hash_from_height(od.height);
bd = db->get_block_blob(block_hash);
cryptonote::block b;
if (!cryptonote::parse_and_validate_block_from_blob(bd, b))
{
log::warning(logcat, "Bad block from db");
return 1;
}
// find the tx which created this output
bool found = false;
for (size_t out = 0; out < b.miner_tx.vout.size(); ++out)
{
if (auto* txout = std::get_if<cryptonote::txout_to_key>(&b.miner_tx.vout[out].target))
{
if (txout->key == od.pubkey)
{
found = true;
new_txids.push_back(cryptonote::get_transaction_hash(b.miner_tx));
log::debug(logcat, "adding txid: {}", cryptonote::get_transaction_hash(b.miner_tx));
break;
}
}
else
{
log::warning(logcat, "Bad vout type in txid {}", cryptonote::get_transaction_hash(b.miner_tx));
return 1;
}
}
for (const crypto::hash &block_txid: b.tx_hashes)
{
if (found)
break;
if (!db->get_pruned_tx_blob(block_txid, bd))
{
log::warning(logcat, "Failed to get txid {} from db", block_txid);
return 1;
}
cryptonote::transaction tx2;
if (!cryptonote::parse_and_validate_tx_base_from_blob(bd, tx2))
{
log::warning(logcat, "Bad tx: {}", block_txid);
return 1;
}
for (size_t out = 0; out < tx2.vout.size(); ++out)
{
if (auto* txout = std::get_if<cryptonote::txout_to_key>(&tx2.vout[out].target))
{
if (txout->key == od.pubkey)
{
found = true;
new_txids.push_back(block_txid);
log::debug(logcat, "adding txid: {}", block_txid);
break;
}
}
else
{
log::warning(logcat, "Bad vout type in txid {}", block_txid);
return 1;
}
}
}
if (!found)
{
log::warning(logcat, "Output originating transaction not found");
return 1;
}
}
}
else
{
log::warning(logcat, "Bad vin type in txid {}", txid);
if (!opt_txid_string.empty() && opt_height) {
std::cerr << "txid and height cannot be given at the same time" << std::endl;
return 1;
}
crypto::hash opt_txid{};
if (!opt_txid_string.empty()) {
if (!tools::hex_to_type(opt_txid_string, opt_txid)) {
std::cerr << "Invalid txid" << std::endl;
return 1;
}
}
}
if (!coinbase)
{
std::swap(txids, new_txids);
++depth;
}
}
done:
log::warning(logcat, "Min depth for txid {}: {}", start_txid, depth);
depths.push_back(depth);
}
uint64_t cumulative_depth = 0;
for (uint64_t depth: depths)
cumulative_depth += depth;
log::warning(logcat, "Average min depth for {} transaction(s): {}", start_txids.size(), cumulative_depth/(float)depths.size());
log::warning(logcat, "Median min depth for {} transaction(s): {}", start_txids.size(), tools::median(std::move(depths)));
log::warning(logcat, "Initializing source blockchain (BlockchainDB)");
blockchain_objects_t blockchain_objects = {};
Blockchain* core_storage = &blockchain_objects.m_blockchain;
BlockchainDB* db = new_db();
if (db == NULL) {
log::error(logcat, "Failed to initialize a database");
throw std::runtime_error("Failed to initialize a database");
}
log::warning(logcat, "database: LMDB");
core_storage->deinit();
return 0;
const fs::path filename =
fs::u8path(command_line::get_arg(vm, cryptonote::arg_data_dir)) / db->get_db_name();
log::warning(logcat, "Loading blockchain from folder {} ...", filename);
CATCH_ENTRY("Depth query error", 1);
try {
db->open(filename, core_storage->nettype(), DBF_RDONLY);
} catch (const std::exception& e) {
log::warning(logcat, "Error opening database: {}", e.what());
return 1;
}
r = core_storage->init(db, nullptr /*ons_db*/, nullptr, net_type);
CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage");
log::warning(logcat, "Source blockchain storage initialized OK");
std::vector<crypto::hash> start_txids;
if (!opt_txid_string.empty()) {
start_txids.push_back(opt_txid);
} else {
const std::string bd = db->get_block_blob_from_height(opt_height);
cryptonote::block b;
if (!cryptonote::parse_and_validate_block_from_blob(bd, b)) {
log::warning(logcat, "Bad block from db");
return 1;
}
for (const crypto::hash& txid : b.tx_hashes)
start_txids.push_back(txid);
if (opt_include_coinbase)
start_txids.push_back(cryptonote::get_transaction_hash(b.miner_tx));
}
if (start_txids.empty()) {
log::warning(logcat, "No transaction(s) to check");
return 1;
}
std::vector<uint64_t> depths;
for (const crypto::hash& start_txid : start_txids) {
uint64_t depth = 0;
bool coinbase = false;
log::warning(logcat, "Checking depth for txid {}", start_txid);
std::vector<crypto::hash> txids(1, start_txid);
while (!coinbase) {
log::warning(logcat, "Considering {} transaction(s) at depth {}", txids.size(), depth);
std::vector<crypto::hash> new_txids;
for (const crypto::hash& txid : txids) {
std::string bd;
if (!db->get_pruned_tx_blob(txid, bd)) {
log::warning(logcat, "Failed to get txid {} from db", txid);
return 1;
}
cryptonote::transaction tx;
if (!cryptonote::parse_and_validate_tx_base_from_blob(bd, tx)) {
log::warning(logcat, "Bad tx: {}", txid);
return 1;
}
for (size_t ring = 0; ring < tx.vin.size(); ++ring) {
if (std::holds_alternative<cryptonote::txin_gen>(tx.vin[ring])) {
log::debug(logcat, "{} is a coinbase transaction", txid);
coinbase = true;
goto done;
}
if (auto* txin = std::get_if<cryptonote::txin_to_key>(&tx.vin[ring])) {
const uint64_t amount = txin->amount;
auto absolute_offsets =
cryptonote::relative_output_offsets_to_absolute(txin->key_offsets);
for (uint64_t offset : absolute_offsets) {
const output_data_t od = db->get_output_key(amount, offset);
const crypto::hash block_hash =
db->get_block_hash_from_height(od.height);
bd = db->get_block_blob(block_hash);
cryptonote::block b;
if (!cryptonote::parse_and_validate_block_from_blob(bd, b)) {
log::warning(logcat, "Bad block from db");
return 1;
}
// find the tx which created this output
bool found = false;
for (size_t out = 0; out < b.miner_tx.vout.size(); ++out) {
if (auto* txout = std::get_if<cryptonote::txout_to_key>(
&b.miner_tx.vout[out].target)) {
if (txout->key == od.pubkey) {
found = true;
new_txids.push_back(
cryptonote::get_transaction_hash(b.miner_tx));
log::debug(
logcat,
"adding txid: {}",
cryptonote::get_transaction_hash(b.miner_tx));
break;
}
} else {
log::warning(
logcat,
"Bad vout type in txid {}",
cryptonote::get_transaction_hash(b.miner_tx));
return 1;
}
}
for (const crypto::hash& block_txid : b.tx_hashes) {
if (found)
break;
if (!db->get_pruned_tx_blob(block_txid, bd)) {
log::warning(
logcat, "Failed to get txid {} from db", block_txid);
return 1;
}
cryptonote::transaction tx2;
if (!cryptonote::parse_and_validate_tx_base_from_blob(bd, tx2)) {
log::warning(logcat, "Bad tx: {}", block_txid);
return 1;
}
for (size_t out = 0; out < tx2.vout.size(); ++out) {
if (auto* txout = std::get_if<cryptonote::txout_to_key>(
&tx2.vout[out].target)) {
if (txout->key == od.pubkey) {
found = true;
new_txids.push_back(block_txid);
log::debug(logcat, "adding txid: {}", block_txid);
break;
}
} else {
log::warning(
logcat, "Bad vout type in txid {}", block_txid);
return 1;
}
}
}
if (!found) {
log::warning(logcat, "Output originating transaction not found");
return 1;
}
}
} else {
log::warning(logcat, "Bad vin type in txid {}", txid);
return 1;
}
}
}
if (!coinbase) {
std::swap(txids, new_txids);
++depth;
}
}
done:
log::warning(logcat, "Min depth for txid {}: {}", start_txid, depth);
depths.push_back(depth);
}
uint64_t cumulative_depth = 0;
for (uint64_t depth : depths)
cumulative_depth += depth;
log::warning(
logcat,
"Average min depth for {} transaction(s): {}",
start_txids.size(),
cumulative_depth / (float)depths.size());
log::warning(
logcat,
"Median min depth for {} transaction(s): {}",
start_txids.size(),
tools::median(std::move(depths)));
core_storage->deinit();
return 0;
CATCH_ENTRY("Depth query error", 1);
}

View File

@ -27,148 +27,148 @@
// 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.
#include "bootstrap_file.h"
#include "blockchain_objects.h"
#include "blocksdat_file.h"
#include "bootstrap_file.h"
#include "common/command_line.h"
#include "common/fs-format.h"
#include "cryptonote_core/cryptonote_core.h"
#include "blockchain_objects.h"
#include "version.h"
#include "cryptonote_core/uptime_proof.h"
#include "version.h"
namespace po = boost::program_options;
using namespace blockchain_utils;
int main(int argc, char* argv[])
{
using namespace oxen;
auto logcat = log::Cat("bcutil");
int main(int argc, char* argv[]) {
using namespace oxen;
auto logcat = log::Cat("bcutil");
TRY_ENTRY();
TRY_ENTRY();
epee::string_tools::set_module_name_and_folder(argv[0]);
uint64_t block_stop = 0;
bool blocks_dat = false;
tools::on_startup();
auto opt_size = command_line::boost_option_sizes();
epee::string_tools::set_module_name_and_folder(argv[0]);
uint64_t block_stop = 0;
bool blocks_dat = false;
tools::on_startup();
auto opt_size = command_line::boost_option_sizes();
po::options_description desc_cmd_only("Command line options", opt_size.first, opt_size.second);
po::options_description desc_cmd_sett("Command line options and settings options", opt_size.first, opt_size.second);
const command_line::arg_descriptor<std::string> arg_output_file = {"output-file", "Specify output file", "", true};
const command_line::arg_descriptor<std::string> arg_log_level = {"log-level", "0-4 or categories", ""};
const command_line::arg_descriptor<uint64_t> arg_block_stop = {"block-stop", "Stop at block number", block_stop};
const command_line::arg_descriptor<bool> arg_blocks_dat = {"blocksdat", "Output in blocks.dat format", blocks_dat};
po::options_description desc_cmd_only("Command line options", opt_size.first, opt_size.second);
po::options_description desc_cmd_sett(
"Command line options and settings options", opt_size.first, opt_size.second);
const command_line::arg_descriptor<std::string> arg_output_file = {
"output-file", "Specify output file", "", true};
const command_line::arg_descriptor<std::string> arg_log_level = {
"log-level", "0-4 or categories", ""};
const command_line::arg_descriptor<uint64_t> arg_block_stop = {
"block-stop", "Stop at block number", block_stop};
const command_line::arg_descriptor<bool> arg_blocks_dat = {
"blocksdat", "Output in blocks.dat format", blocks_dat};
command_line::add_arg(desc_cmd_sett, cryptonote::arg_data_dir);
command_line::add_arg(desc_cmd_sett, arg_output_file);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_testnet_on);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_devnet_on);
command_line::add_arg(desc_cmd_sett, arg_log_level);
command_line::add_arg(desc_cmd_sett, arg_block_stop);
command_line::add_arg(desc_cmd_sett, arg_blocks_dat);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_data_dir);
command_line::add_arg(desc_cmd_sett, arg_output_file);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_testnet_on);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_devnet_on);
command_line::add_arg(desc_cmd_sett, arg_log_level);
command_line::add_arg(desc_cmd_sett, arg_block_stop);
command_line::add_arg(desc_cmd_sett, arg_blocks_dat);
command_line::add_arg(desc_cmd_only, command_line::arg_help);
command_line::add_arg(desc_cmd_only, command_line::arg_help);
po::options_description desc_options("Allowed options");
desc_options.add(desc_cmd_only).add(desc_cmd_sett);
po::options_description desc_options("Allowed options");
desc_options.add(desc_cmd_only).add(desc_cmd_sett);
po::variables_map vm;
bool r = command_line::handle_error_helper(desc_options, [&]() {
po::store(po::parse_command_line(argc, argv, desc_options), vm);
po::notify(vm);
return true;
});
if (!r)
return 1;
po::variables_map vm;
bool r = command_line::handle_error_helper(desc_options, [&]()
{
po::store(po::parse_command_line(argc, argv, desc_options), vm);
po::notify(vm);
return true;
});
if (! r)
return 1;
if (command_line::get_arg(vm, command_line::arg_help)) {
std::cout << "Oxen '" << OXEN_RELEASE_NAME << "' (v" << OXEN_VERSION_FULL << ")\n\n";
std::cout << desc_options << std::endl;
return 1;
}
if (command_line::get_arg(vm, command_line::arg_help))
{
std::cout << "Oxen '" << OXEN_RELEASE_NAME << "' (v" << OXEN_VERSION_FULL << ")\n\n";
std::cout << desc_options << std::endl;
return 1;
}
block_stop = command_line::get_arg(vm, arg_block_stop);
block_stop = command_line::get_arg(vm, arg_block_stop);
auto m_config_folder = command_line::get_arg(vm, cryptonote::arg_data_dir);
auto log_file_path = m_config_folder + "oxen-blockchain-export.log";
log::Level log_level;
if (auto level = oxen::logging::parse_level(command_line::get_arg(vm, arg_log_level).c_str())) {
log_level = *level;
} else {
std::cerr << "Incorrect log level: " << command_line::get_arg(vm, arg_log_level).c_str()
<< std::endl;
throw std::runtime_error{"Incorrect log level"};
}
oxen::logging::init(log_file_path, log_level);
log::warning(logcat, "Starting...");
auto m_config_folder = command_line::get_arg(vm, cryptonote::arg_data_dir);
auto log_file_path = m_config_folder + "oxen-blockchain-export.log";
log::Level log_level;
if(auto level = oxen::logging::parse_level(command_line::get_arg(vm, arg_log_level).c_str())) {
log_level = *level;
} else {
std::cerr << "Incorrect log level: " << command_line::get_arg(vm, arg_log_level).c_str() << std::endl;
throw std::runtime_error{"Incorrect log level"};
}
oxen::logging::init(log_file_path, log_level);
log::warning(logcat, "Starting...");
bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on);
bool opt_devnet = command_line::get_arg(vm, cryptonote::arg_devnet_on);
if (opt_testnet && opt_devnet) {
std::cerr << "Can't specify more than one of --testnet and --devnet" << std::endl;
return 1;
}
bool opt_blocks_dat = command_line::get_arg(vm, arg_blocks_dat);
bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on);
bool opt_devnet = command_line::get_arg(vm, cryptonote::arg_devnet_on);
if (opt_testnet && opt_devnet)
{
std::cerr << "Can't specify more than one of --testnet and --devnet" << std::endl;
return 1;
}
bool opt_blocks_dat = command_line::get_arg(vm, arg_blocks_dat);
auto config_folder = fs::u8path(command_line::get_arg(vm, cryptonote::arg_data_dir));
auto config_folder = fs::u8path(command_line::get_arg(vm, cryptonote::arg_data_dir));
fs::path output_file_path;
if (command_line::has_arg(vm, arg_output_file))
output_file_path = fs::u8path(command_line::get_arg(vm, arg_output_file));
else
output_file_path = config_folder / "export" / BLOCKCHAIN_RAW;
log::warning(logcat, "Export output file: {}", output_file_path.string());
fs::path output_file_path;
if (command_line::has_arg(vm, arg_output_file))
output_file_path = fs::u8path(command_line::get_arg(vm, arg_output_file));
else
output_file_path = config_folder / "export" / BLOCKCHAIN_RAW;
log::warning(logcat, "Export output file: {}", output_file_path.string());
log::warning(logcat, "Initializing source blockchain (BlockchainDB)");
blockchain_objects_t blockchain_objects = {};
Blockchain* core_storage = &blockchain_objects.m_blockchain;
BlockchainDB* db = new_db();
if (db == NULL) {
log::error(logcat, "Failed to initialize a database");
throw std::runtime_error("Failed to initialize a database");
}
log::warning(logcat, "database: LMDB");
log::warning(logcat, "Initializing source blockchain (BlockchainDB)");
blockchain_objects_t blockchain_objects = {};
Blockchain *core_storage = &blockchain_objects.m_blockchain;
BlockchainDB *db = new_db();
if (db == NULL)
{
log::error(logcat, "Failed to initialize a database");
throw std::runtime_error("Failed to initialize a database");
}
log::warning(logcat, "database: LMDB");
auto filename = config_folder / db->get_db_name();
auto filename = config_folder / db->get_db_name();
log::warning(logcat, "Loading blockchain from folder {} ...", filename);
try {
db->open(filename, core_storage->nettype(), DBF_RDONLY);
} catch (const std::exception& e) {
log::warning(logcat, "Error opening database: {}", e.what());
return 1;
}
r = core_storage->init(
db,
nullptr,
nullptr,
opt_testnet ? cryptonote::network_type::TESTNET
: opt_devnet ? cryptonote::network_type::DEVNET
: cryptonote::network_type::MAINNET);
log::warning(logcat, "Loading blockchain from folder {} ...", filename);
try
{
db->open(filename, core_storage->nettype(), DBF_RDONLY);
}
catch (const std::exception& e)
{
log::warning(logcat, "Error opening database: {}", e.what());
return 1;
}
r = core_storage->init(db, nullptr, nullptr, opt_testnet ? cryptonote::network_type::TESTNET : opt_devnet ? cryptonote::network_type::DEVNET : cryptonote::network_type::MAINNET);
if (core_storage->get_blockchain_pruning_seed() && !opt_blocks_dat) {
log::warning(logcat, "Blockchain is pruned, cannot export");
return 1;
}
if (core_storage->get_blockchain_pruning_seed() && !opt_blocks_dat)
{
log::warning(logcat, "Blockchain is pruned, cannot export");
return 1;
}
CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage");
log::warning(logcat, "Source blockchain storage initialized OK");
log::warning(logcat, "Exporting blockchain raw data...");
CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage");
log::warning(logcat, "Source blockchain storage initialized OK");
log::warning(logcat, "Exporting blockchain raw data...");
if (opt_blocks_dat) {
BlocksdatFile blocksdat;
r = blocksdat.store_blockchain_raw(core_storage, NULL, output_file_path, block_stop);
} else {
BootstrapFile bootstrap;
r = bootstrap.store_blockchain_raw(core_storage, NULL, output_file_path, block_stop);
}
CHECK_AND_ASSERT_MES(r, 1, "Failed to export blockchain raw data");
log::warning(logcat, "Blockchain raw data exported OK");
return 0;
if (opt_blocks_dat)
{
BlocksdatFile blocksdat;
r = blocksdat.store_blockchain_raw(core_storage, NULL, output_file_path, block_stop);
}
else
{
BootstrapFile bootstrap;
r = bootstrap.store_blockchain_raw(core_storage, NULL, output_file_path, block_stop);
}
CHECK_AND_ASSERT_MES(r, 1, "Failed to export blockchain raw data");
log::warning(logcat, "Blockchain raw data exported OK");
return 0;
CATCH_ENTRY("Export error", 1);
CATCH_ENTRY("Export error", 1);
}

File diff suppressed because it is too large Load Diff

View File

@ -2,20 +2,19 @@
#define BLOCKCHAIN_OBJECTS_H
#include "cryptonote_core/blockchain.h"
#include "cryptonote_core/tx_pool.h"
#include "cryptonote_core/service_node_list.h"
#include "cryptonote_core/service_node_voting.h"
#include "cryptonote_core/tx_pool.h"
// NOTE(oxen): This is done this way because of the circular constructors.
struct blockchain_objects_t
{
cryptonote::Blockchain m_blockchain;
cryptonote::tx_memory_pool m_mempool;
service_nodes::service_node_list m_service_node_list;
blockchain_objects_t() :
m_blockchain(m_mempool, m_service_node_list),
m_service_node_list(m_blockchain),
m_mempool(m_blockchain) { }
struct blockchain_objects_t {
cryptonote::Blockchain m_blockchain;
cryptonote::tx_memory_pool m_mempool;
service_nodes::service_node_list m_service_node_list;
blockchain_objects_t() :
m_blockchain(m_mempool, m_service_node_list),
m_service_node_list(m_blockchain),
m_mempool(m_blockchain) {}
};
#endif // BLOCKCHAIN_OBJECTS_H
#endif // BLOCKCHAIN_OBJECTS_H

File diff suppressed because it is too large Load Diff

View File

@ -27,14 +27,14 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifdef _WIN32
#define __STDC_FORMAT_MACROS // NOTE(oxen): Explicitly define the PRIu64 macro on Mingw
#define __STDC_FORMAT_MACROS // NOTE(oxen): Explicitly define the PRIu64 macro on Mingw
#endif
#include "common/command_line.h"
#include "serialization/crypto.h"
#include "cryptonote_core/cryptonote_core.h"
#include "blockchain_objects.h"
#include "blockchain_db/blockchain_db.h"
#include "blockchain_objects.h"
#include "common/command_line.h"
#include "cryptonote_core/cryptonote_core.h"
#include "serialization/crypto.h"
#include "version.h"
namespace po = boost::program_options;
@ -42,244 +42,234 @@ using namespace cryptonote;
static auto logcat = log::Cat("bcutil");
static std::map<uint64_t, uint64_t> load_outputs(const fs::path& filename)
{
std::map<uint64_t, uint64_t> outputs;
uint64_t amount = std::numeric_limits<uint64_t>::max();
static std::map<uint64_t, uint64_t> load_outputs(const fs::path& filename) {
std::map<uint64_t, uint64_t> outputs;
uint64_t amount = std::numeric_limits<uint64_t>::max();
FILE *f =
FILE* f =
#ifdef _WIN32
_wfopen(filename.c_str(), L"r");
_wfopen(filename.c_str(), L"r");
#else
fopen(filename.c_str(), "r");
fopen(filename.c_str(), "r");
#endif
if (!f)
{
log::error(logcat, "Failed to load outputs from {}: {}", filename, strerror(errno));
return {};
}
while (1)
{
char s[256];
if (!fgets(s, sizeof(s), f))
break;
if (feof(f))
break;
const size_t len = strlen(s);
if (len > 0 && s[len - 1] == '\n')
s[len - 1] = 0;
if (!s[0])
continue;
std::pair<uint64_t, uint64_t> output;
uint64_t offset, num_offsets;
if (sscanf(s, "@%" PRIu64, &amount) == 1)
{
continue;
if (!f) {
log::error(logcat, "Failed to load outputs from {}: {}", filename, strerror(errno));
return {};
}
if (amount == std::numeric_limits<uint64_t>::max())
{
log::error(logcat, "Bad format in {}", filename);
continue;
while (1) {
char s[256];
if (!fgets(s, sizeof(s), f))
break;
if (feof(f))
break;
const size_t len = strlen(s);
if (len > 0 && s[len - 1] == '\n')
s[len - 1] = 0;
if (!s[0])
continue;
std::pair<uint64_t, uint64_t> output;
uint64_t offset, num_offsets;
if (sscanf(s, "@%" PRIu64, &amount) == 1) {
continue;
}
if (amount == std::numeric_limits<uint64_t>::max()) {
log::error(logcat, "Bad format in {}", filename);
continue;
}
if (sscanf(s, "%" PRIu64 "*%" PRIu64, &offset, &num_offsets) == 2 &&
num_offsets < std::numeric_limits<uint64_t>::max() - offset) {
outputs[amount] += num_offsets;
} else if (sscanf(s, "%" PRIu64, &offset) == 1) {
outputs[amount] += 1;
} else {
log::error(logcat, "Bad format in {}", filename);
continue;
}
}
if (sscanf(s, "%" PRIu64 "*%" PRIu64, &offset, &num_offsets) == 2 && num_offsets < std::numeric_limits<uint64_t>::max() - offset)
{
outputs[amount] += num_offsets;
}
else if (sscanf(s, "%" PRIu64, &offset) == 1)
{
outputs[amount] += 1;
fclose(f);
return outputs;
}
int main(int argc, char* argv[]) {
TRY_ENTRY();
epee::string_tools::set_module_name_and_folder(argv[0]);
uint32_t log_level = 0;
tools::on_startup();
auto opt_size = command_line::boost_option_sizes();
po::options_description desc_cmd_only("Command line options", opt_size.first, opt_size.second);
po::options_description desc_cmd_sett(
"Command line options and settings options", opt_size.first, opt_size.second);
const command_line::arg_descriptor<std::string> arg_log_level = {
"log-level", "0-4 or categories", ""};
const command_line::arg_descriptor<bool> arg_verbose = {"verbose", "Verbose output", false};
const command_line::arg_descriptor<bool> arg_dry_run = {
"dry-run", "Do not actually prune", false};
const command_line::arg_descriptor<std::string> arg_input = {
"input", "Path to the known spent outputs file"};
command_line::add_arg(desc_cmd_sett, cryptonote::arg_data_dir);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_testnet_on);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_devnet_on);
command_line::add_arg(desc_cmd_sett, arg_log_level);
command_line::add_arg(desc_cmd_sett, arg_verbose);
command_line::add_arg(desc_cmd_sett, arg_dry_run);
command_line::add_arg(desc_cmd_sett, arg_input);
command_line::add_arg(desc_cmd_only, command_line::arg_help);
po::options_description desc_options("Allowed options");
desc_options.add(desc_cmd_only).add(desc_cmd_sett);
po::variables_map vm;
bool r = command_line::handle_error_helper(desc_options, [&]() {
auto parser = po::command_line_parser(argc, argv).options(desc_options);
po::store(parser.run(), vm);
po::notify(vm);
return true;
});
if (!r)
return 1;
if (command_line::get_arg(vm, command_line::arg_help)) {
std::cout << "Oxen '" << OXEN_RELEASE_NAME << "' (v" << OXEN_VERSION_FULL << ")\n\n";
std::cout << desc_options << std::endl;
return 1;
}
mlog_configure(mlog_get_default_log_path("oxen-blockchain-prune-known-spent-data.log"), true);
if (!command_line::is_arg_defaulted(vm, arg_log_level))
mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str());
else
{
log::error(logcat, "Bad format in {}", filename);
continue;
mlog_set_log(std::string(std::to_string(log_level) + ",bcutil:INFO").c_str());
log::warning(logcat, "Starting...");
bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on);
bool opt_devnet = command_line::get_arg(vm, cryptonote::arg_devnet_on);
network_type net_type = opt_testnet ? network_type::TESTNET
: opt_devnet ? network_type::DEVNET
: network_type::MAINNET;
bool opt_verbose = command_line::get_arg(vm, arg_verbose);
bool opt_dry_run = command_line::get_arg(vm, arg_dry_run);
const auto input = fs::u8path(command_line::get_arg(vm, arg_input));
log::warning(logcat, "Initializing source blockchain (BlockchainDB)");
blockchain_objects_t blockchain_objects = {};
Blockchain* core_storage = &blockchain_objects.m_blockchain;
BlockchainDB* db = new_db();
if (db == NULL) {
log::error(logcat, "Failed to initialize a database");
throw std::runtime_error("Failed to initialize a database");
}
}
fclose(f);
return outputs;
}
int main(int argc, char* argv[])
{
TRY_ENTRY();
epee::string_tools::set_module_name_and_folder(argv[0]);
uint32_t log_level = 0;
tools::on_startup();
auto opt_size = command_line::boost_option_sizes();
po::options_description desc_cmd_only("Command line options", opt_size.first, opt_size.second);
po::options_description desc_cmd_sett("Command line options and settings options", opt_size.first, opt_size.second);
const command_line::arg_descriptor<std::string> arg_log_level = {"log-level", "0-4 or categories", ""};
const command_line::arg_descriptor<bool> arg_verbose = {"verbose", "Verbose output", false};
const command_line::arg_descriptor<bool> arg_dry_run = {"dry-run", "Do not actually prune", false};
const command_line::arg_descriptor<std::string> arg_input = {"input", "Path to the known spent outputs file"};
command_line::add_arg(desc_cmd_sett, cryptonote::arg_data_dir);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_testnet_on);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_devnet_on);
command_line::add_arg(desc_cmd_sett, arg_log_level);
command_line::add_arg(desc_cmd_sett, arg_verbose);
command_line::add_arg(desc_cmd_sett, arg_dry_run);
command_line::add_arg(desc_cmd_sett, arg_input);
command_line::add_arg(desc_cmd_only, command_line::arg_help);
po::options_description desc_options("Allowed options");
desc_options.add(desc_cmd_only).add(desc_cmd_sett);
po::variables_map vm;
bool r = command_line::handle_error_helper(desc_options, [&]()
{
auto parser = po::command_line_parser(argc, argv).options(desc_options);
po::store(parser.run(), vm);
po::notify(vm);
return true;
});
if (! r)
return 1;
if (command_line::get_arg(vm, command_line::arg_help))
{
std::cout << "Oxen '" << OXEN_RELEASE_NAME << "' (v" << OXEN_VERSION_FULL << ")\n\n";
std::cout << desc_options << std::endl;
return 1;
}
mlog_configure(mlog_get_default_log_path("oxen-blockchain-prune-known-spent-data.log"), true);
if (!command_line::is_arg_defaulted(vm, arg_log_level))
mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str());
else
mlog_set_log(std::string(std::to_string(log_level) + ",bcutil:INFO").c_str());
log::warning(logcat, "Starting...");
bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on);
bool opt_devnet = command_line::get_arg(vm, cryptonote::arg_devnet_on);
network_type net_type = opt_testnet ? network_type::TESTNET : opt_devnet ? network_type::DEVNET : network_type::MAINNET;
bool opt_verbose = command_line::get_arg(vm, arg_verbose);
bool opt_dry_run = command_line::get_arg(vm, arg_dry_run);
const auto input = fs::u8path(command_line::get_arg(vm, arg_input));
log::warning(logcat, "Initializing source blockchain (BlockchainDB)");
blockchain_objects_t blockchain_objects = {};
Blockchain *core_storage = &blockchain_objects.m_blockchain;
BlockchainDB *db = new_db();
if (db == NULL)
{
log::error(logcat, "Failed to initialize a database");
throw std::runtime_error("Failed to initialize a database");
}
const fs::path filename = fs::u8path(command_line::get_arg(vm, cryptonote::arg_data_dir)) / db->get_db_name();
log::warning(logcat, "Loading blockchain from folder {} ...", filename);
try
{
db->open(filename, core_storage->nettype(), 0);
}
catch (const std::exception& e)
{
log::warning(logcat, "Error opening database: {}", e.what());
return 1;
}
r = core_storage->init(db, nullptr /*ons_db*/, net_type);
CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage");
log::warning(logcat, "Source blockchain storage initialized OK");
std::map<uint64_t, uint64_t> known_spent_outputs;
if (input.empty())
{
std::map<uint64_t, std::pair<uint64_t, uint64_t>> outputs;
log::warning(logcat, "Scanning for known spent data...");
db->for_all_transactions([&](const crypto::hash &txid, const cryptonote::transaction &tx){
const bool miner_tx = tx.vin.size() == 1 && std::holds_alternative<txin_gen>(tx.vin[0]);
for (const auto &in: tx.vin)
if (const auto* txin = std::get_if<txin_to_key>(&in); txin && txin->amount != 0)
outputs[txin->amount].second++;
for (const auto &out: tx.vout)
{
uint64_t amount = out.amount;
if (miner_tx && tx.version >= txversion::v2_ringct)
amount = 0;
if (amount == 0)
continue;
if (!std::holds_alternative<txout_to_key>(out.target))
continue;
outputs[amount].first++;
}
return true;
}, true);
for (const auto &i: outputs)
{
known_spent_outputs[i.first] = i.second.second;
}
}
else
{
log::warning(logcat, "Loading known spent data...");
known_spent_outputs = load_outputs(input);
}
log::warning(logcat, "Pruning known spent data...");
bool stop_requested = false;
tools::signal_handler::install([&stop_requested](int type) {
stop_requested = true;
});
db->batch_start();
size_t num_total_outputs = 0, num_prunable_outputs = 0, num_known_spent_outputs = 0, num_eligible_outputs = 0, num_eligible_known_spent_outputs = 0;
for (auto i = known_spent_outputs.begin(); i != known_spent_outputs.end(); ++i)
{
uint64_t num_outputs = db->get_num_outputs(i->first);
num_total_outputs += num_outputs;
num_known_spent_outputs += i->second;
if (i->first == 0)
{
if (opt_verbose)
log::info(logcat, "Ignoring output value {}, with {} outputs", i->first, num_outputs);
continue;
}
num_eligible_outputs += num_outputs;
num_eligible_known_spent_outputs += i->second;
if (opt_verbose)
log::info(logcat, "{}: {}/{}", i->first, i->second, num_outputs);
if (num_outputs > i->second)
continue;
if (num_outputs && num_outputs < i->second)
{
log::error(logcat, "More outputs are spent than known for amount {}, not touching", i->first);
continue;
}
if (opt_verbose)
log::info(logcat, "Pruning data for {} outputs", num_outputs);
if (!opt_dry_run)
db->prune_outputs(i->first);
num_prunable_outputs += i->second;
}
db->batch_stop();
log::info(logcat, "Total outputs: {}", num_total_outputs);
log::info(logcat, "Known spent outputs: {}", num_known_spent_outputs);
log::info(logcat, "Eligible outputs: {}", num_eligible_outputs);
log::info(logcat, "Eligible known spent outputs: {}", num_eligible_known_spent_outputs);
log::info(logcat, "Prunable outputs: {}", num_prunable_outputs);
log::warning(logcat, "Blockchain known spent data pruned OK");
core_storage->deinit();
return 0;
CATCH_ENTRY("Error", 1);
const fs::path filename =
fs::u8path(command_line::get_arg(vm, cryptonote::arg_data_dir)) / db->get_db_name();
log::warning(logcat, "Loading blockchain from folder {} ...", filename);
try {
db->open(filename, core_storage->nettype(), 0);
} catch (const std::exception& e) {
log::warning(logcat, "Error opening database: {}", e.what());
return 1;
}
r = core_storage->init(db, nullptr /*ons_db*/, net_type);
CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage");
log::warning(logcat, "Source blockchain storage initialized OK");
std::map<uint64_t, uint64_t> known_spent_outputs;
if (input.empty()) {
std::map<uint64_t, std::pair<uint64_t, uint64_t>> outputs;
log::warning(logcat, "Scanning for known spent data...");
db->for_all_transactions(
[&](const crypto::hash& txid, const cryptonote::transaction& tx) {
const bool miner_tx =
tx.vin.size() == 1 && std::holds_alternative<txin_gen>(tx.vin[0]);
for (const auto& in : tx.vin)
if (const auto* txin = std::get_if<txin_to_key>(&in);
txin && txin->amount != 0)
outputs[txin->amount].second++;
for (const auto& out : tx.vout) {
uint64_t amount = out.amount;
if (miner_tx && tx.version >= txversion::v2_ringct)
amount = 0;
if (amount == 0)
continue;
if (!std::holds_alternative<txout_to_key>(out.target))
continue;
outputs[amount].first++;
}
return true;
},
true);
for (const auto& i : outputs) {
known_spent_outputs[i.first] = i.second.second;
}
} else {
log::warning(logcat, "Loading known spent data...");
known_spent_outputs = load_outputs(input);
}
log::warning(logcat, "Pruning known spent data...");
bool stop_requested = false;
tools::signal_handler::install([&stop_requested](int type) { stop_requested = true; });
db->batch_start();
size_t num_total_outputs = 0, num_prunable_outputs = 0, num_known_spent_outputs = 0,
num_eligible_outputs = 0, num_eligible_known_spent_outputs = 0;
for (auto i = known_spent_outputs.begin(); i != known_spent_outputs.end(); ++i) {
uint64_t num_outputs = db->get_num_outputs(i->first);
num_total_outputs += num_outputs;
num_known_spent_outputs += i->second;
if (i->first == 0) {
if (opt_verbose)
log::info(
logcat, "Ignoring output value {}, with {} outputs", i->first, num_outputs);
continue;
}
num_eligible_outputs += num_outputs;
num_eligible_known_spent_outputs += i->second;
if (opt_verbose)
log::info(logcat, "{}: {}/{}", i->first, i->second, num_outputs);
if (num_outputs > i->second)
continue;
if (num_outputs && num_outputs < i->second) {
log::error(
logcat,
"More outputs are spent than known for amount {}, not touching",
i->first);
continue;
}
if (opt_verbose)
log::info(logcat, "Pruning data for {} outputs", num_outputs);
if (!opt_dry_run)
db->prune_outputs(i->first);
num_prunable_outputs += i->second;
}
db->batch_stop();
log::info(logcat, "Total outputs: {}", num_total_outputs);
log::info(logcat, "Known spent outputs: {}", num_known_spent_outputs);
log::info(logcat, "Eligible outputs: {}", num_eligible_outputs);
log::info(logcat, "Eligible known spent outputs: {}", num_eligible_known_spent_outputs);
log::info(logcat, "Prunable outputs: {}", num_prunable_outputs);
log::warning(logcat, "Blockchain known spent data pruned OK");
core_storage->deinit();
return 0;
CATCH_ENTRY("Error", 1);
}

View File

@ -26,295 +26,305 @@
// 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.
#include <date/date.h>
#include <boost/algorithm/string.hpp>
#include <chrono>
#include "blockchain_db/blockchain_db.h"
#include "blockchain_objects.h"
#include "common/command_line.h"
#include "common/varint.h"
#include "common/signal_handler.h"
#include "common/fs-format.h"
#include "common/signal_handler.h"
#include "common/varint.h"
#include "cryptonote_basic/cryptonote_boost_serialization.h"
#include "cryptonote_core/cryptonote_core.h"
#include "blockchain_objects.h"
#include "blockchain_db/blockchain_db.h"
#include "version.h"
#include "cryptonote_core/uptime_proof.h"
#include <date/date.h>
#include "version.h"
namespace po = boost::program_options;
using namespace cryptonote;
static bool stop_requested = false;
int main(int argc, char* argv[])
{
static auto logcat = log::Cat("bcutil");
int main(int argc, char* argv[]) {
static auto logcat = log::Cat("bcutil");
TRY_ENTRY();
TRY_ENTRY();
epee::string_tools::set_module_name_and_folder(argv[0]);
uint64_t block_start = 0;
uint64_t block_stop = 0;
tools::on_startup();
epee::string_tools::set_module_name_and_folder(argv[0]);
uint64_t block_start = 0;
uint64_t block_stop = 0;
tools::on_startup();
auto opt_size = command_line::boost_option_sizes();
auto opt_size = command_line::boost_option_sizes();
po::options_description desc_cmd_only("Command line options", opt_size.first, opt_size.second);
po::options_description desc_cmd_sett("Command line options and settings options", opt_size.first, opt_size.second);
const command_line::arg_descriptor<std::string> arg_log_level = {"log-level", "0-4 or categories", ""};
const command_line::arg_descriptor<uint64_t> arg_block_start = {"block-start", "start at block number", block_start};
const command_line::arg_descriptor<uint64_t> arg_block_stop = {"block-stop", "Stop at block number", block_stop};
const command_line::arg_descriptor<bool> arg_inputs = {"with-inputs", "with input stats", false};
const command_line::arg_descriptor<bool> arg_outputs = {"with-outputs", "with output stats", false};
const command_line::arg_descriptor<bool> arg_ringsize = {"with-ringsize", "with ringsize stats", false};
const command_line::arg_descriptor<bool> arg_hours = {"with-hours", "with txns per hour", false};
po::options_description desc_cmd_only("Command line options", opt_size.first, opt_size.second);
po::options_description desc_cmd_sett(
"Command line options and settings options", opt_size.first, opt_size.second);
const command_line::arg_descriptor<std::string> arg_log_level = {
"log-level", "0-4 or categories", ""};
const command_line::arg_descriptor<uint64_t> arg_block_start = {
"block-start", "start at block number", block_start};
const command_line::arg_descriptor<uint64_t> arg_block_stop = {
"block-stop", "Stop at block number", block_stop};
const command_line::arg_descriptor<bool> arg_inputs = {
"with-inputs", "with input stats", false};
const command_line::arg_descriptor<bool> arg_outputs = {
"with-outputs", "with output stats", false};
const command_line::arg_descriptor<bool> arg_ringsize = {
"with-ringsize", "with ringsize stats", false};
const command_line::arg_descriptor<bool> arg_hours = {
"with-hours", "with txns per hour", false};
command_line::add_arg(desc_cmd_sett, cryptonote::arg_data_dir);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_testnet_on);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_devnet_on);
command_line::add_arg(desc_cmd_sett, arg_log_level);
command_line::add_arg(desc_cmd_sett, arg_block_start);
command_line::add_arg(desc_cmd_sett, arg_block_stop);
command_line::add_arg(desc_cmd_sett, arg_inputs);
command_line::add_arg(desc_cmd_sett, arg_outputs);
command_line::add_arg(desc_cmd_sett, arg_ringsize);
command_line::add_arg(desc_cmd_sett, arg_hours);
command_line::add_arg(desc_cmd_only, command_line::arg_help);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_data_dir);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_testnet_on);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_devnet_on);
command_line::add_arg(desc_cmd_sett, arg_log_level);
command_line::add_arg(desc_cmd_sett, arg_block_start);
command_line::add_arg(desc_cmd_sett, arg_block_stop);
command_line::add_arg(desc_cmd_sett, arg_inputs);
command_line::add_arg(desc_cmd_sett, arg_outputs);
command_line::add_arg(desc_cmd_sett, arg_ringsize);
command_line::add_arg(desc_cmd_sett, arg_hours);
command_line::add_arg(desc_cmd_only, command_line::arg_help);
po::options_description desc_options("Allowed options");
desc_options.add(desc_cmd_only).add(desc_cmd_sett);
po::options_description desc_options("Allowed options");
desc_options.add(desc_cmd_only).add(desc_cmd_sett);
po::variables_map vm;
bool r = command_line::handle_error_helper(desc_options, [&]()
{
auto parser = po::command_line_parser(argc, argv).options(desc_options);
po::store(parser.run(), vm);
po::notify(vm);
return true;
});
if (! r)
return 1;
if (command_line::get_arg(vm, command_line::arg_help))
{
std::cout << "Oxen '" << OXEN_RELEASE_NAME << "' (v" << OXEN_VERSION_FULL << ")\n\n";
std::cout << desc_options << std::endl;
return 1;
}
auto m_config_folder = command_line::get_arg(vm, cryptonote::arg_data_dir);
auto log_file_path = m_config_folder + "oxen-blockchain-stats.log";
log::Level log_level;
if(auto level = oxen::logging::parse_level(command_line::get_arg(vm, arg_log_level).c_str())) {
log_level = *level;
} else {
std::cerr << "Incorrect log level: " << command_line::get_arg(vm, arg_log_level).c_str() << std::endl;
throw std::runtime_error{"Incorrect log level"};
}
oxen::logging::init(log_file_path, log_level);
log::warning(logcat, "Starting...");
std::string opt_data_dir = command_line::get_arg(vm, cryptonote::arg_data_dir);
bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on);
bool opt_devnet = command_line::get_arg(vm, cryptonote::arg_devnet_on);
network_type net_type = opt_testnet ? network_type::TESTNET : opt_devnet ? network_type::DEVNET : network_type::MAINNET;
block_start = command_line::get_arg(vm, arg_block_start);
block_stop = command_line::get_arg(vm, arg_block_stop);
bool do_inputs = command_line::get_arg(vm, arg_inputs);
bool do_outputs = command_line::get_arg(vm, arg_outputs);
bool do_ringsize = command_line::get_arg(vm, arg_ringsize);
bool do_hours = command_line::get_arg(vm, arg_hours);
log::warning(logcat, "Initializing source blockchain (BlockchainDB)");
blockchain_objects_t blockchain_objects = {};
Blockchain *core_storage = &blockchain_objects.m_blockchain;
BlockchainDB *db = new_db();
if (db == NULL)
{
log::error(logcat, "Failed to initialize a database");
throw std::runtime_error("Failed to initialize a database");
}
const fs::path filename = fs::u8path(opt_data_dir) / db->get_db_name();
log::warning(logcat, "Loading blockchain from folder {} ...", filename);
try
{
db->open(filename, core_storage->nettype(), DBF_RDONLY);
}
catch (const std::exception& e)
{
log::warning(logcat, "Error opening database: {}", e.what());
return 1;
}
r = core_storage->init(db, nullptr /*ons_db*/, nullptr, net_type);
CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage");
log::warning(logcat, "Source blockchain storage initialized OK");
tools::signal_handler::install([](int type) {
stop_requested = true;
});
const uint64_t db_height = db->height();
if (!block_stop)
block_stop = db_height;
log::info(logcat, "Starting from height {}, stopping at height {}", block_start, block_stop);
/*
* The default output can be plotted with GnuPlot using these commands:
set key autotitle columnhead
set title "Oxen Blockchain Growth"
set timefmt "%Y-%m-%d"
set xdata time
set xrange ["2014-04-17":*]
set format x "%Y-%m-%d"
set yrange [0:*]
set y2range [0:*]
set ylabel "Txs/Day"
set y2label "Bytes"
set y2tics nomirror
plot 'stats.csv' index "DATA" using (timecolumn(1,"%Y-%m-%d")):4 with lines, '' using (timecolumn(1,"%Y-%m-%d")):7 axes x1y2 with lines
*/
// spit out a comment that GnuPlot can use as an index
std::cout << "\n# DATA\n";
std::cout << "Date\tBlocks/day\tBlocks\tTxs/Day\tTxs\tBytes/Day\tBytes";
if (do_inputs)
std::cout << "\tInMin\tInMax\tInAvg";
if (do_outputs)
std::cout << "\tOutMin\tOutMax\tOutAvg";
if (do_ringsize)
std::cout << "\tRingMin\tRingMax\tRingAvg";
if (do_hours) {
char buf[8];
unsigned int i;
for (i=0; i<24; i++) {
sprintf(buf, "\t%02u:00", i);
std::cout << buf;
}
}
std::cout << "\n";
std::optional<std::chrono::system_clock::time_point> prev_ts;
uint64_t prevsz = 0, currsz = 0;
uint64_t prevtxs = 0, currtxs = 0;
uint64_t currblks = 0;
uint64_t totins = 0, totouts = 0, totrings = 0;
uint32_t minins = 10, maxins = 0;
uint32_t minouts = 10, maxouts = 0;
uint32_t minrings = 50, maxrings = 0;
uint32_t io, tottxs = 0;
uint32_t txhr[24] = {0};
unsigned int i;
for (uint64_t h = block_start; h < block_stop; ++h)
{
std::string bd = db->get_block_blob_from_height(h);
cryptonote::block blk;
if (!cryptonote::parse_and_validate_block_from_blob(bd, blk))
{
log::warning(logcat, "Bad block from db");
return 1;
}
auto ts = std::chrono::system_clock::from_time_t(blk.timestamp);
using namespace date;
year_month_day curr_date{floor<days>(ts)};
if (!prev_ts)
prev_ts = ts;
year_month_day prev_date{floor<days>(*prev_ts)};
// catch change of day
if (curr_date.day() > prev_date.day() || (curr_date.day() == day{1} && prev_date.day() > day{27}))
{
// check for timestamp fudging around month ends
if (curr_date.day() == day{1} && prev_date.day() > day{27})
goto skip;
prev_ts = ts;
std::cout << format("%Y-%m-%d", prev_date) << "\t" << currblks << "\t" << h << "\t" << currtxs << "\t" << prevtxs + currtxs << "\t" << currsz << "\t" << prevsz + currsz;
prevsz += currsz;
currsz = 0;
currblks = 0;
prevtxs += currtxs;
currtxs = 0;
if (!tottxs)
tottxs = 1;
if (do_inputs) {
std::cout << "\t" << (maxins ? minins : 0) << "\t" << maxins << "\t" << totins / tottxs;
minins = 10; maxins = 0; totins = 0;
}
if (do_outputs) {
std::cout << "\t" << (maxouts ? minouts : 0) << "\t" << maxouts << "\t" << totouts / tottxs;
minouts = 10; maxouts = 0; totouts = 0;
}
if (do_ringsize) {
std::cout << "\t" << (maxrings ? minrings : 0) << "\t" << maxrings << "\t" << totrings / tottxs;
minrings = 50; maxrings = 0; totrings = 0;
}
tottxs = 0;
if (do_hours) {
for (i=0; i<24; i++) {
std::cout << "\t" << txhr[i];
txhr[i] = 0;
}
}
std::cout << "\n";
}
skip:
currsz += bd.size();
for (const auto& tx_id : blk.tx_hashes)
{
if (!tx_id)
{
throw std::runtime_error("Aborting: null txid");
}
if (!db->get_pruned_tx_blob(tx_id, bd))
{
throw std::runtime_error("Aborting: tx not found");
}
transaction tx;
if (!parse_and_validate_tx_base_from_blob(bd, tx))
{
log::warning(logcat, "Bad txn from db");
po::variables_map vm;
bool r = command_line::handle_error_helper(desc_options, [&]() {
auto parser = po::command_line_parser(argc, argv).options(desc_options);
po::store(parser.run(), vm);
po::notify(vm);
return true;
});
if (!r)
return 1;
if (command_line::get_arg(vm, command_line::arg_help)) {
std::cout << "Oxen '" << OXEN_RELEASE_NAME << "' (v" << OXEN_VERSION_FULL << ")\n\n";
std::cout << desc_options << std::endl;
return 1;
}
currsz += bd.size();
currtxs++;
if (do_hours)
txhr[hh_mm_ss{ts - floor<days>(ts)}.hours().count()]++;
if (do_inputs) {
io = tx.vin.size();
if (io < minins)
minins = io;
else if (io > maxins)
maxins = io;
totins += io;
}
if (do_ringsize) {
const auto& tx_in_to_key = var::get<cryptonote::txin_to_key>(tx.vin[0]);
io = tx_in_to_key.key_offsets.size();
if (io < minrings)
minrings = io;
else if (io > maxrings)
maxrings = io;
totrings += io;
}
if (do_outputs) {
io = tx.vout.size();
if (io < minouts)
minouts = io;
else if (io > maxouts)
maxouts = io;
totouts += io;
}
tottxs++;
}
currblks++;
if (stop_requested)
break;
}
auto m_config_folder = command_line::get_arg(vm, cryptonote::arg_data_dir);
auto log_file_path = m_config_folder + "oxen-blockchain-stats.log";
log::Level log_level;
if (auto level = oxen::logging::parse_level(command_line::get_arg(vm, arg_log_level).c_str())) {
log_level = *level;
} else {
std::cerr << "Incorrect log level: " << command_line::get_arg(vm, arg_log_level).c_str()
<< std::endl;
throw std::runtime_error{"Incorrect log level"};
}
oxen::logging::init(log_file_path, log_level);
log::warning(logcat, "Starting...");
core_storage->deinit();
return 0;
std::string opt_data_dir = command_line::get_arg(vm, cryptonote::arg_data_dir);
bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on);
bool opt_devnet = command_line::get_arg(vm, cryptonote::arg_devnet_on);
network_type net_type = opt_testnet ? network_type::TESTNET
: opt_devnet ? network_type::DEVNET
: network_type::MAINNET;
block_start = command_line::get_arg(vm, arg_block_start);
block_stop = command_line::get_arg(vm, arg_block_stop);
bool do_inputs = command_line::get_arg(vm, arg_inputs);
bool do_outputs = command_line::get_arg(vm, arg_outputs);
bool do_ringsize = command_line::get_arg(vm, arg_ringsize);
bool do_hours = command_line::get_arg(vm, arg_hours);
CATCH_ENTRY("Stats reporting error", 1);
log::warning(logcat, "Initializing source blockchain (BlockchainDB)");
blockchain_objects_t blockchain_objects = {};
Blockchain* core_storage = &blockchain_objects.m_blockchain;
BlockchainDB* db = new_db();
if (db == NULL) {
log::error(logcat, "Failed to initialize a database");
throw std::runtime_error("Failed to initialize a database");
}
const fs::path filename = fs::u8path(opt_data_dir) / db->get_db_name();
log::warning(logcat, "Loading blockchain from folder {} ...", filename);
try {
db->open(filename, core_storage->nettype(), DBF_RDONLY);
} catch (const std::exception& e) {
log::warning(logcat, "Error opening database: {}", e.what());
return 1;
}
r = core_storage->init(db, nullptr /*ons_db*/, nullptr, net_type);
CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage");
log::warning(logcat, "Source blockchain storage initialized OK");
tools::signal_handler::install([](int type) { stop_requested = true; });
const uint64_t db_height = db->height();
if (!block_stop)
block_stop = db_height;
log::info(logcat, "Starting from height {}, stopping at height {}", block_start, block_stop);
/*
* The default output can be plotted with GnuPlot using these commands:
set key autotitle columnhead
set title "Oxen Blockchain Growth"
set timefmt "%Y-%m-%d"
set xdata time
set xrange ["2014-04-17":*]
set format x "%Y-%m-%d"
set yrange [0:*]
set y2range [0:*]
set ylabel "Txs/Day"
set y2label "Bytes"
set y2tics nomirror
plot 'stats.csv' index "DATA" using (timecolumn(1,"%Y-%m-%d")):4 with lines, '' using
(timecolumn(1,"%Y-%m-%d")):7 axes x1y2 with lines
*/
// spit out a comment that GnuPlot can use as an index
std::cout << "\n# DATA\n";
std::cout << "Date\tBlocks/day\tBlocks\tTxs/Day\tTxs\tBytes/Day\tBytes";
if (do_inputs)
std::cout << "\tInMin\tInMax\tInAvg";
if (do_outputs)
std::cout << "\tOutMin\tOutMax\tOutAvg";
if (do_ringsize)
std::cout << "\tRingMin\tRingMax\tRingAvg";
if (do_hours) {
char buf[8];
unsigned int i;
for (i = 0; i < 24; i++) {
sprintf(buf, "\t%02u:00", i);
std::cout << buf;
}
}
std::cout << "\n";
std::optional<std::chrono::system_clock::time_point> prev_ts;
uint64_t prevsz = 0, currsz = 0;
uint64_t prevtxs = 0, currtxs = 0;
uint64_t currblks = 0;
uint64_t totins = 0, totouts = 0, totrings = 0;
uint32_t minins = 10, maxins = 0;
uint32_t minouts = 10, maxouts = 0;
uint32_t minrings = 50, maxrings = 0;
uint32_t io, tottxs = 0;
uint32_t txhr[24] = {0};
unsigned int i;
for (uint64_t h = block_start; h < block_stop; ++h) {
std::string bd = db->get_block_blob_from_height(h);
cryptonote::block blk;
if (!cryptonote::parse_and_validate_block_from_blob(bd, blk)) {
log::warning(logcat, "Bad block from db");
return 1;
}
auto ts = std::chrono::system_clock::from_time_t(blk.timestamp);
using namespace date;
year_month_day curr_date{floor<days>(ts)};
if (!prev_ts)
prev_ts = ts;
year_month_day prev_date{floor<days>(*prev_ts)};
// catch change of day
if (curr_date.day() > prev_date.day() ||
(curr_date.day() == day{1} && prev_date.day() > day{27})) {
// check for timestamp fudging around month ends
if (curr_date.day() == day{1} && prev_date.day() > day{27})
goto skip;
prev_ts = ts;
std::cout << format("%Y-%m-%d", prev_date) << "\t" << currblks << "\t" << h << "\t"
<< currtxs << "\t" << prevtxs + currtxs << "\t" << currsz << "\t"
<< prevsz + currsz;
prevsz += currsz;
currsz = 0;
currblks = 0;
prevtxs += currtxs;
currtxs = 0;
if (!tottxs)
tottxs = 1;
if (do_inputs) {
std::cout << "\t" << (maxins ? minins : 0) << "\t" << maxins << "\t"
<< totins / tottxs;
minins = 10;
maxins = 0;
totins = 0;
}
if (do_outputs) {
std::cout << "\t" << (maxouts ? minouts : 0) << "\t" << maxouts << "\t"
<< totouts / tottxs;
minouts = 10;
maxouts = 0;
totouts = 0;
}
if (do_ringsize) {
std::cout << "\t" << (maxrings ? minrings : 0) << "\t" << maxrings << "\t"
<< totrings / tottxs;
minrings = 50;
maxrings = 0;
totrings = 0;
}
tottxs = 0;
if (do_hours) {
for (i = 0; i < 24; i++) {
std::cout << "\t" << txhr[i];
txhr[i] = 0;
}
}
std::cout << "\n";
}
skip:
currsz += bd.size();
for (const auto& tx_id : blk.tx_hashes) {
if (!tx_id) {
throw std::runtime_error("Aborting: null txid");
}
if (!db->get_pruned_tx_blob(tx_id, bd)) {
throw std::runtime_error("Aborting: tx not found");
}
transaction tx;
if (!parse_and_validate_tx_base_from_blob(bd, tx)) {
log::warning(logcat, "Bad txn from db");
return 1;
}
currsz += bd.size();
currtxs++;
if (do_hours)
txhr[hh_mm_ss{ts - floor<days>(ts)}.hours().count()]++;
if (do_inputs) {
io = tx.vin.size();
if (io < minins)
minins = io;
else if (io > maxins)
maxins = io;
totins += io;
}
if (do_ringsize) {
const auto& tx_in_to_key = var::get<cryptonote::txin_to_key>(tx.vin[0]);
io = tx_in_to_key.key_offsets.size();
if (io < minrings)
minrings = io;
else if (io > maxrings)
maxrings = io;
totrings += io;
}
if (do_outputs) {
io = tx.vout.size();
if (io < minouts)
minouts = io;
else if (io > maxouts)
maxouts = io;
totouts += io;
}
tottxs++;
}
currblks++;
if (stop_requested)
break;
}
core_storage->deinit();
return 0;
CATCH_ENTRY("Stats reporting error", 1);
}

View File

@ -27,12 +27,12 @@
// 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.
#include "common/command_line.h"
#include "common/varint.h"
#include "common/fs-format.h"
#include "cryptonote_core/cryptonote_core.h"
#include "blockchain_objects.h"
#include "blockchain_db/blockchain_db.h"
#include "blockchain_objects.h"
#include "common/command_line.h"
#include "common/fs-format.h"
#include "common/varint.h"
#include "cryptonote_core/cryptonote_core.h"
#include "cryptonote_core/uptime_proof.h"
#include "version.h"
@ -41,203 +41,207 @@ using namespace cryptonote;
static auto logcat = log::Cat("quorum_cop");
struct output_data
{
uint64_t amount;
uint64_t index;
mutable bool coinbase;
mutable uint64_t height;
output_data(uint64_t a, uint64_t i, bool cb, uint64_t h): amount(a), index(i), coinbase(cb), height(h) {}
bool operator==(const output_data &other) const { return other.amount == amount && other.index == index; }
void info(bool c, uint64_t h) const { coinbase = c; height =h; }
};
namespace std
{
template<> struct hash<output_data>
{
size_t operator()(const output_data &od) const
{
const uint64_t data[2] = {od.amount, od.index};
crypto::hash h;
crypto::cn_fast_hash(data, 2 * sizeof(uint64_t), h);
return reinterpret_cast<const std::size_t &>(h);
struct output_data {
uint64_t amount;
uint64_t index;
mutable bool coinbase;
mutable uint64_t height;
output_data(uint64_t a, uint64_t i, bool cb, uint64_t h) :
amount(a), index(i), coinbase(cb), height(h) {}
bool operator==(const output_data& other) const {
return other.amount == amount && other.index == index;
}
};
}
void info(bool c, uint64_t h) const {
coinbase = c;
height = h;
}
};
namespace std {
template <>
struct hash<output_data> {
size_t operator()(const output_data& od) const {
const uint64_t data[2] = {od.amount, od.index};
crypto::hash h;
crypto::cn_fast_hash(data, 2 * sizeof(uint64_t), h);
return reinterpret_cast<const std::size_t&>(h);
}
};
} // namespace std
struct reference
{
uint64_t height;
uint64_t ring_size;
uint64_t position;
reference(uint64_t h, uint64_t rs, uint64_t p): height(h), ring_size(rs), position(p) {}
struct reference {
uint64_t height;
uint64_t ring_size;
uint64_t position;
reference(uint64_t h, uint64_t rs, uint64_t p) : height(h), ring_size(rs), position(p) {}
};
int main(int argc, char* argv[])
{
TRY_ENTRY();
int main(int argc, char* argv[]) {
TRY_ENTRY();
epee::string_tools::set_module_name_and_folder(argv[0]);
tools::on_startup();
auto opt_size = command_line::boost_option_sizes();
epee::string_tools::set_module_name_and_folder(argv[0]);
tools::on_startup();
auto opt_size = command_line::boost_option_sizes();
po::options_description desc_cmd_only("Command line options", opt_size.first, opt_size.second);
po::options_description desc_cmd_sett("Command line options and settings options", opt_size.first, opt_size.second);
const command_line::arg_descriptor<std::string> arg_log_level = {"log-level", "0-4 or categories", ""};
const command_line::arg_descriptor<bool> arg_rct_only = {"rct-only", "Only work on ringCT outputs", false};
const command_line::arg_descriptor<std::string> arg_input = {"input", ""};
po::options_description desc_cmd_only("Command line options", opt_size.first, opt_size.second);
po::options_description desc_cmd_sett(
"Command line options and settings options", opt_size.first, opt_size.second);
const command_line::arg_descriptor<std::string> arg_log_level = {
"log-level", "0-4 or categories", ""};
const command_line::arg_descriptor<bool> arg_rct_only = {
"rct-only", "Only work on ringCT outputs", false};
const command_line::arg_descriptor<std::string> arg_input = {"input", ""};
command_line::add_arg(desc_cmd_sett, cryptonote::arg_testnet_on);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_devnet_on);
command_line::add_arg(desc_cmd_sett, arg_log_level);
command_line::add_arg(desc_cmd_sett, arg_rct_only);
command_line::add_arg(desc_cmd_sett, arg_input);
command_line::add_arg(desc_cmd_only, command_line::arg_help);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_testnet_on);
command_line::add_arg(desc_cmd_sett, cryptonote::arg_devnet_on);
command_line::add_arg(desc_cmd_sett, arg_log_level);
command_line::add_arg(desc_cmd_sett, arg_rct_only);
command_line::add_arg(desc_cmd_sett, arg_input);
command_line::add_arg(desc_cmd_only, command_line::arg_help);
po::options_description desc_options("Allowed options");
desc_options.add(desc_cmd_only).add(desc_cmd_sett);
po::options_description desc_options("Allowed options");
desc_options.add(desc_cmd_only).add(desc_cmd_sett);
po::positional_options_description positional_options;
positional_options.add(arg_input.name, -1);
po::positional_options_description positional_options;
positional_options.add(arg_input.name, -1);
po::variables_map vm;
bool r = command_line::handle_error_helper(desc_options, [&]()
{
auto parser = po::command_line_parser(argc, argv).options(desc_options).positional(positional_options);
po::store(parser.run(), vm);
po::notify(vm);
return true;
});
if (! r)
return 1;
po::variables_map vm;
bool r = command_line::handle_error_helper(desc_options, [&]() {
auto parser = po::command_line_parser(argc, argv)
.options(desc_options)
.positional(positional_options);
po::store(parser.run(), vm);
po::notify(vm);
return true;
});
if (!r)
return 1;
if (command_line::get_arg(vm, command_line::arg_help))
{
std::cout << "Oxen '" << OXEN_RELEASE_NAME << "' (v" << OXEN_VERSION_FULL << ")\n\n";
std::cout << desc_options << std::endl;
return 1;
}
auto m_config_folder = command_line::get_arg(vm, cryptonote::arg_data_dir);
auto log_file_path = m_config_folder + "oxen-blockchain-usage.log";
log::Level log_level;
if(auto level = oxen::logging::parse_level(command_line::get_arg(vm, arg_log_level).c_str())) {
log_level = *level;
} else {
std::cerr << "Incorrect log level: " << command_line::get_arg(vm, arg_log_level).c_str() << std::endl;
throw std::runtime_error{"Incorrect log level"};
}
oxen::logging::init(log_file_path, log_level);
log::warning(logcat, "Starting...");
bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on);
bool opt_devnet = command_line::get_arg(vm, cryptonote::arg_devnet_on);
network_type net_type = opt_testnet ? network_type::TESTNET : opt_devnet ? network_type::DEVNET : network_type::MAINNET;
bool opt_rct_only = command_line::get_arg(vm, arg_rct_only);
// If we wanted to use the memory pool, we would set up a fake_core.
// Use Blockchain instead of lower-level BlockchainDB for two reasons:
// 1. Blockchain has the init() method for easy setup
// 2. exporter needs to use get_current_blockchain_height(), get_block_id_by_height(), get_block_by_hash()
//
// cannot match blockchain_storage setup above with just one line,
// e.g.
// Blockchain* core_storage = new Blockchain(NULL);
// because unlike blockchain_storage constructor, which takes a pointer to
// tx_memory_pool, Blockchain's constructor takes tx_memory_pool object.
log::warning(logcat, "Initializing source blockchain (BlockchainDB)");
const std::string input = command_line::get_arg(vm, arg_input);
blockchain_objects_t blockchain_objects = {};
Blockchain *core_storage = &blockchain_objects.m_blockchain;
tx_memory_pool& m_mempool = blockchain_objects.m_mempool;
BlockchainDB *db = new_db();
if (db == NULL)
{
log::error(logcat, "Failed to initialize a database");
throw std::runtime_error("Failed to initialize a database");
}
log::warning(logcat, "database: LMDB");
const fs::path filename = fs::u8path(input);
log::warning(logcat, "Loading blockchain from folder {} ...", filename);
try
{
db->open(filename, core_storage->nettype(), DBF_RDONLY);
}
catch (const std::exception& e)
{
log::warning(logcat, "Error opening database: {}", e.what());
return 1;
}
r = core_storage->init(db, nullptr /*ons_db*/, nullptr, net_type);
CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage");
log::warning(logcat, "Source blockchain storage initialized OK");
log::warning(logcat, "Building usage patterns...");
size_t done = 0;
std::unordered_map<output_data, std::list<reference>> outputs;
std::unordered_map<uint64_t,uint64_t> indices;
log::warning(logcat, "Reading blockchain from {}", input);
core_storage->for_all_transactions([&](const crypto::hash &hash, const cryptonote::transaction &tx)->bool
{
const bool coinbase = tx.vin.size() == 1 && std::holds_alternative<txin_gen>(tx.vin[0]);
const uint64_t height = core_storage->get_db().get_tx_block_height(hash);
// create new outputs
for (const auto &out: tx.vout)
{
if (opt_rct_only && out.amount)
continue;
uint64_t index = indices[out.amount]++;
output_data od(out.amount, indices[out.amount], coinbase, height);
auto itb = outputs.emplace(od, std::list<reference>());
itb.first->first.info(coinbase, height);
if (command_line::get_arg(vm, command_line::arg_help)) {
std::cout << "Oxen '" << OXEN_RELEASE_NAME << "' (v" << OXEN_VERSION_FULL << ")\n\n";
std::cout << desc_options << std::endl;
return 1;
}
for (const auto &in: tx.vin)
{
const auto* txin = std::get_if<txin_to_key>(&in);
if (!txin || (opt_rct_only && txin->amount != 0))
continue;
const std::vector<uint64_t> absolute = cryptonote::relative_output_offsets_to_absolute(txin->key_offsets);
for (size_t n = 0; n < txin->key_offsets.size(); ++n)
{
output_data od(txin->amount, absolute[n], coinbase, height);
outputs[od].push_back(reference(height, txin->key_offsets.size(), n));
}
auto m_config_folder = command_line::get_arg(vm, cryptonote::arg_data_dir);
auto log_file_path = m_config_folder + "oxen-blockchain-usage.log";
log::Level log_level;
if (auto level = oxen::logging::parse_level(command_line::get_arg(vm, arg_log_level).c_str())) {
log_level = *level;
} else {
std::cerr << "Incorrect log level: " << command_line::get_arg(vm, arg_log_level).c_str()
<< std::endl;
throw std::runtime_error{"Incorrect log level"};
}
return true;
}, true);
oxen::logging::init(log_file_path, log_level);
log::warning(logcat, "Starting...");
std::unordered_map<uint64_t, uint64_t> counts;
size_t total = 0;
for (const auto &out: outputs)
{
counts[out.second.size()]++;
total++;
}
if (total > 0)
{
for (const auto &c: counts)
{
float percent = 100.f * c.second / total;
log::info(logcat, "{} outputs used {} times ({}%)", std::to_string(c.second), c.first, percent);
bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on);
bool opt_devnet = command_line::get_arg(vm, cryptonote::arg_devnet_on);
network_type net_type = opt_testnet ? network_type::TESTNET
: opt_devnet ? network_type::DEVNET
: network_type::MAINNET;
bool opt_rct_only = command_line::get_arg(vm, arg_rct_only);
// If we wanted to use the memory pool, we would set up a fake_core.
// Use Blockchain instead of lower-level BlockchainDB for two reasons:
// 1. Blockchain has the init() method for easy setup
// 2. exporter needs to use get_current_blockchain_height(), get_block_id_by_height(),
// get_block_by_hash()
//
// cannot match blockchain_storage setup above with just one line,
// e.g.
// Blockchain* core_storage = new Blockchain(NULL);
// because unlike blockchain_storage constructor, which takes a pointer to
// tx_memory_pool, Blockchain's constructor takes tx_memory_pool object.
log::warning(logcat, "Initializing source blockchain (BlockchainDB)");
const std::string input = command_line::get_arg(vm, arg_input);
blockchain_objects_t blockchain_objects = {};
Blockchain* core_storage = &blockchain_objects.m_blockchain;
tx_memory_pool& m_mempool = blockchain_objects.m_mempool;
BlockchainDB* db = new_db();
if (db == NULL) {
log::error(logcat, "Failed to initialize a database");
throw std::runtime_error("Failed to initialize a database");
}
}
else
{
log::info(logcat, "No outputs to process");
}
log::warning(logcat, "database: LMDB");
log::warning(logcat, "Blockchain usage exported OK");
return 0;
const fs::path filename = fs::u8path(input);
log::warning(logcat, "Loading blockchain from folder {} ...", filename);
CATCH_ENTRY("Export error", 1);
try {
db->open(filename, core_storage->nettype(), DBF_RDONLY);
} catch (const std::exception& e) {
log::warning(logcat, "Error opening database: {}", e.what());
return 1;
}
r = core_storage->init(db, nullptr /*ons_db*/, nullptr, net_type);
CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage");
log::warning(logcat, "Source blockchain storage initialized OK");
log::warning(logcat, "Building usage patterns...");
size_t done = 0;
std::unordered_map<output_data, std::list<reference>> outputs;
std::unordered_map<uint64_t, uint64_t> indices;
log::warning(logcat, "Reading blockchain from {}", input);
core_storage->for_all_transactions(
[&](const crypto::hash& hash, const cryptonote::transaction& tx) -> bool {
const bool coinbase =
tx.vin.size() == 1 && std::holds_alternative<txin_gen>(tx.vin[0]);
const uint64_t height = core_storage->get_db().get_tx_block_height(hash);
// create new outputs
for (const auto& out : tx.vout) {
if (opt_rct_only && out.amount)
continue;
uint64_t index = indices[out.amount]++;
output_data od(out.amount, indices[out.amount], coinbase, height);
auto itb = outputs.emplace(od, std::list<reference>());
itb.first->first.info(coinbase, height);
}
for (const auto& in : tx.vin) {
const auto* txin = std::get_if<txin_to_key>(&in);
if (!txin || (opt_rct_only && txin->amount != 0))
continue;
const std::vector<uint64_t> absolute =
cryptonote::relative_output_offsets_to_absolute(txin->key_offsets);
for (size_t n = 0; n < txin->key_offsets.size(); ++n) {
output_data od(txin->amount, absolute[n], coinbase, height);
outputs[od].push_back(reference(height, txin->key_offsets.size(), n));
}
}
return true;
},
true);
std::unordered_map<uint64_t, uint64_t> counts;
size_t total = 0;
for (const auto& out : outputs) {
counts[out.second.size()]++;
total++;
}
if (total > 0) {
for (const auto& c : counts) {
float percent = 100.f * c.second / total;
log::info(
logcat,
"{} outputs used {} times ({}%)",
std::to_string(c.second),
c.first,
percent);
}
} else {
log::info(logcat, "No outputs to process");
}
log::warning(logcat, "Blockchain usage exported OK");
return 0;
CATCH_ENTRY("Export error", 1);
}

View File

@ -30,11 +30,12 @@
#include "version.h"
namespace blockchain_utils {
// bounds checking is done before writing to buffer, but buffer size
// should be a sensible maximum
#define BUFFER_SIZE (2 * 1024 * 1024)
#define CHUNK_SIZE_WARNING_THRESHOLD 500000
#define NUM_BLOCKS_PER_CHUNK 1
#define BLOCKCHAIN_RAW "blockchain.raw"
// bounds checking is done before writing to buffer, but buffer size should be a sensible maximum
inline constexpr size_t BUFFER_SIZE = 2 * 1024 * 1024;
inline constexpr size_t CHUNK_SIZE_WARNING_THRESHOLD = 500000;
inline constexpr size_t NUM_BLOCKS_PER_CHUNK = 1;
inline constexpr const char* BLOCKCHAIN_RAW = "blockchain.raw";
} // namespace blockchain_utils

View File

@ -28,144 +28,134 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "blocksdat_file.h"
#include "common/fs-format.h"
using namespace cryptonote;
using namespace blockchain_utils;
namespace
{
static auto logcat = log::Cat("bcutil");
namespace {
static auto logcat = log::Cat("bcutil");
std::string refresh_string = "\r \r";
}
std::string refresh_string = "\r \r";
} // namespace
bool BlocksdatFile::open_writer(const fs::path& file_path, uint64_t block_stop) {
const fs::path dir_path = file_path.parent_path();
if (!dir_path.empty()) {
if (fs::exists(dir_path)) {
if (!fs::is_directory(dir_path)) {
log::error(logcat, "export directory path is a file: {}", dir_path);
return false;
}
} else {
if (!fs::create_directory(dir_path)) {
log::error(logcat, "Failed to create directory {}", dir_path);
return false;
}
}
}
m_raw_data_file = new std::ofstream();
bool BlocksdatFile::open_writer(const fs::path& file_path, uint64_t block_stop)
{
const fs::path dir_path = file_path.parent_path();
if (!dir_path.empty())
{
if (fs::exists(dir_path))
{
if (!fs::is_directory(dir_path))
{
log::error(logcat, "export directory path is a file: {}", dir_path);
log::info(logcat, "creating file");
m_raw_data_file->open(
file_path.string(), std::ios_base::binary | std::ios_base::out | std::ios::trunc);
if (m_raw_data_file->fail())
return false;
}
initialize_file(block_stop);
return true;
}
bool BlocksdatFile::initialize_file(uint64_t block_stop) {
const uint32_t nblocks = (block_stop + 1) / HASH_OF_HASHES_STEP;
unsigned char nblocksc[4];
nblocksc[0] = nblocks & 0xff;
nblocksc[1] = (nblocks >> 8) & 0xff;
nblocksc[2] = (nblocks >> 16) & 0xff;
nblocksc[3] = (nblocks >> 24) & 0xff;
// 4 bytes little endian
*m_raw_data_file << nblocksc[0];
*m_raw_data_file << nblocksc[1];
*m_raw_data_file << nblocksc[2];
*m_raw_data_file << nblocksc[3];
return true;
}
void BlocksdatFile::write_block(const crypto::hash& block_hash) {
m_hashes.push_back(block_hash);
while (m_hashes.size() >= HASH_OF_HASHES_STEP) {
crypto::hash hash;
crypto::cn_fast_hash(m_hashes.data(), HASH_OF_HASHES_STEP * sizeof(crypto::hash), hash);
memmove(m_hashes.data(),
m_hashes.data() + HASH_OF_HASHES_STEP,
(m_hashes.size() - HASH_OF_HASHES_STEP) * sizeof(crypto::hash));
m_hashes.resize(m_hashes.size() - HASH_OF_HASHES_STEP);
m_raw_data_file->write(reinterpret_cast<const char*>(hash.data()), hash.size());
}
else
{
if (!fs::create_directory(dir_path))
{
log::error(logcat, "Failed to create directory {}", dir_path);
}
bool BlocksdatFile::close() {
if (m_raw_data_file->fail())
return false;
}
m_raw_data_file->flush();
delete m_raw_data_file;
return true;
}
bool BlocksdatFile::store_blockchain_raw(
Blockchain* _blockchain_storage,
tx_memory_pool* _tx_pool,
fs::path& output_file,
uint64_t requested_block_stop) {
uint64_t num_blocks_written = 0;
m_blockchain_storage = _blockchain_storage;
uint64_t progress_interval = 100;
block b;
uint64_t block_start = 0;
uint64_t block_stop = 0;
log::info(
logcat,
"source blockchain height: {}",
m_blockchain_storage->get_current_blockchain_height() - 1);
if ((requested_block_stop > 0) &&
(requested_block_stop < m_blockchain_storage->get_current_blockchain_height())) {
log::info(logcat, "Using requested block height: {}", requested_block_stop);
block_stop = requested_block_stop;
} else {
block_stop = m_blockchain_storage->get_current_blockchain_height() - 1;
log::info(logcat, "Using block height of source blockchain: {}", block_stop);
}
}
m_raw_data_file = new std::ofstream();
log::info(logcat, "creating file");
m_raw_data_file->open(file_path.string(), std::ios_base::binary | std::ios_base::out | std::ios::trunc);
if (m_raw_data_file->fail())
return false;
initialize_file(block_stop);
return true;
}
bool BlocksdatFile::initialize_file(uint64_t block_stop)
{
const uint32_t nblocks = (block_stop + 1) / HASH_OF_HASHES_STEP;
unsigned char nblocksc[4];
nblocksc[0] = nblocks & 0xff;
nblocksc[1] = (nblocks >> 8) & 0xff;
nblocksc[2] = (nblocks >> 16) & 0xff;
nblocksc[3] = (nblocks >> 24) & 0xff;
// 4 bytes little endian
*m_raw_data_file << nblocksc[0];
*m_raw_data_file << nblocksc[1];
*m_raw_data_file << nblocksc[2];
*m_raw_data_file << nblocksc[3];
return true;
}
void BlocksdatFile::write_block(const crypto::hash& block_hash)
{
m_hashes.push_back(block_hash);
while (m_hashes.size() >= HASH_OF_HASHES_STEP)
{
crypto::hash hash;
crypto::cn_fast_hash(m_hashes.data(), HASH_OF_HASHES_STEP * sizeof(crypto::hash), hash);
memmove(m_hashes.data(), m_hashes.data() + HASH_OF_HASHES_STEP, (m_hashes.size() - HASH_OF_HASHES_STEP) * sizeof(crypto::hash));
m_hashes.resize(m_hashes.size() - HASH_OF_HASHES_STEP);
m_raw_data_file->write(reinterpret_cast<const char*>(hash.data()), hash.size());
}
}
bool BlocksdatFile::close()
{
if (m_raw_data_file->fail())
return false;
m_raw_data_file->flush();
delete m_raw_data_file;
return true;
}
bool BlocksdatFile::store_blockchain_raw(Blockchain* _blockchain_storage, tx_memory_pool* _tx_pool, fs::path& output_file, uint64_t requested_block_stop)
{
uint64_t num_blocks_written = 0;
m_blockchain_storage = _blockchain_storage;
uint64_t progress_interval = 100;
block b;
uint64_t block_start = 0;
uint64_t block_stop = 0;
log::info(logcat, "source blockchain height: {}", m_blockchain_storage->get_current_blockchain_height()-1);
if ((requested_block_stop > 0) && (requested_block_stop < m_blockchain_storage->get_current_blockchain_height()))
{
log::info(logcat, "Using requested block height: {}", requested_block_stop);
block_stop = requested_block_stop;
}
else
{
block_stop = m_blockchain_storage->get_current_blockchain_height() - 1;
log::info(logcat, "Using block height of source blockchain: {}", block_stop);
}
log::info(logcat, "Storing blocks raw data...");
if (!BlocksdatFile::open_writer(output_file, block_stop))
{
log::error(logcat, "failed to open raw file for write");
return false;
}
for (m_cur_height = block_start; m_cur_height <= block_stop; ++m_cur_height)
{
// this method's height refers to 0-based height (genesis block = height 0)
crypto::hash hash = m_blockchain_storage->get_block_id_by_height(m_cur_height);
write_block(hash);
if (m_cur_height % NUM_BLOCKS_PER_CHUNK == 0) {
num_blocks_written += NUM_BLOCKS_PER_CHUNK;
log::info(logcat, "Storing blocks raw data...");
if (!BlocksdatFile::open_writer(output_file, block_stop)) {
log::error(logcat, "failed to open raw file for write");
return false;
}
if (m_cur_height % progress_interval == 0) {
std::cout << refresh_string;
std::cout << "block " << m_cur_height << "/" << block_stop << std::flush;
for (m_cur_height = block_start; m_cur_height <= block_stop; ++m_cur_height) {
// this method's height refers to 0-based height (genesis block = height 0)
crypto::hash hash = m_blockchain_storage->get_block_id_by_height(m_cur_height);
write_block(hash);
if (m_cur_height % NUM_BLOCKS_PER_CHUNK == 0) {
num_blocks_written += NUM_BLOCKS_PER_CHUNK;
}
if (m_cur_height % progress_interval == 0) {
std::cout << refresh_string;
std::cout << "block " << m_cur_height << "/" << block_stop << std::flush;
}
}
}
// print message for last block, which may not have been printed yet due to progress_interval
std::cout << refresh_string;
std::cout << "block " << m_cur_height-1 << "/" << block_stop << "\n";
// print message for last block, which may not have been printed yet due to progress_interval
std::cout << refresh_string;
std::cout << "block " << m_cur_height - 1 << "/" << block_stop << "\n";
log::info(logcat, "Number of blocks exported: {}", num_blocks_written);
log::info(logcat, "Number of blocks exported: {}", num_blocks_written);
return BlocksdatFile::close();
return BlocksdatFile::close();
}

View File

@ -28,53 +28,47 @@
#pragma once
#include <boost/iostreams/stream_buffer.hpp>
#include <boost/iostreams/stream.hpp>
#include <algorithm>
#include <atomic>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/filtering_streambuf.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/stream_buffer.hpp>
#include <cstdio>
#include <fstream>
#include "blockchain_db/blockchain_db.h"
#include "blockchain_utilities.h"
#include "common/command_line.h"
#include "common/fs.h"
#include "cryptonote_basic/cryptonote_basic.h"
#include "cryptonote_basic/cryptonote_boost_serialization.h"
#include "cryptonote_core/blockchain.h"
#include "blockchain_db/blockchain_db.h"
#include <algorithm>
#include <cstdio>
#include <fstream>
#include <boost/iostreams/copy.hpp>
#include <atomic>
#include "common/command_line.h"
#include "common/fs.h"
#include "version.h"
#include "blockchain_utilities.h"
using namespace cryptonote;
class BlocksdatFile {
public:
bool store_blockchain_raw(
cryptonote::Blockchain* cs,
cryptonote::tx_memory_pool* txp,
fs::path& output_file,
uint64_t use_block_height = 0);
class BlocksdatFile
{
public:
protected:
Blockchain* m_blockchain_storage;
bool store_blockchain_raw(cryptonote::Blockchain* cs, cryptonote::tx_memory_pool* txp,
fs::path& output_file, uint64_t use_block_height=0);
std::ofstream* m_raw_data_file;
protected:
// open export file for write
bool open_writer(const fs::path& file_path, uint64_t block_stop);
bool initialize_file(uint64_t block_stop);
bool close();
void write_block(const crypto::hash& block_hash);
Blockchain* m_blockchain_storage;
std::ofstream * m_raw_data_file;
// open export file for write
bool open_writer(const fs::path& file_path, uint64_t block_stop);
bool initialize_file(uint64_t block_stop);
bool close();
void write_block(const crypto::hash &block_hash);
private:
uint64_t m_cur_height; // tracks current height during export
std::vector<crypto::hash> m_hashes;
private:
uint64_t m_cur_height; // tracks current height during export
std::vector<crypto::hash> m_hashes;
};

View File

@ -27,483 +27,494 @@
// 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.
#include "bootstrap_serialization.h"
#include "serialization/binary_utils.h" // dump_binary(), parse_binary()
#include "bootstrap_file.h"
#include "bootstrap_serialization.h"
#include "common/fs-format.h"
#include "serialization/binary_utils.h" // dump_binary(), parse_binary()
using namespace cryptonote;
using namespace blockchain_utils;
namespace
{
// This number was picked by taking the leading 4 bytes from this output:
// echo Oxen bootstrap file | sha1sum
const uint32_t blockchain_raw_magic = 0x28721586;
const uint32_t header_size = 1024;
namespace {
// This number was picked by taking the leading 4 bytes from this output:
// echo Oxen bootstrap file | sha1sum
const uint32_t blockchain_raw_magic = 0x28721586;
const uint32_t header_size = 1024;
std::string refresh_string = "\r \r";
auto logcat = log::Cat("bcutil");
}
std::string refresh_string = "\r \r";
auto logcat = log::Cat("bcutil");
} // namespace
bool BootstrapFile::open_writer(const fs::path& file_path)
{
const auto dir_path = file_path.parent_path();
if (!dir_path.empty())
{
if (fs::exists(dir_path))
{
if (!fs::is_directory(dir_path))
{
log::error(logcat, "export directory path is a file: {}", dir_path);
return false;
}
bool BootstrapFile::open_writer(const fs::path& file_path) {
const auto dir_path = file_path.parent_path();
if (!dir_path.empty()) {
if (fs::exists(dir_path)) {
if (!fs::is_directory(dir_path)) {
log::error(logcat, "export directory path is a file: {}", dir_path);
return false;
}
} else {
if (!fs::create_directory(dir_path)) {
log::error(logcat, "Failed to create directory {}", dir_path);
return false;
}
}
}
m_raw_data_file = new std::ofstream();
bool do_initialize_file = false;
uint64_t num_blocks = 0;
if (!fs::exists(file_path)) {
log::debug(logcat, "creating file");
do_initialize_file = true;
num_blocks = 0;
} else {
num_blocks = count_blocks(file_path.string());
log::debug(
logcat,
"appending to existing file with height: {} total blocks: {}",
num_blocks - 1,
num_blocks);
}
m_height = num_blocks;
if (do_initialize_file)
m_raw_data_file->open(
file_path.string(), std::ios_base::binary | std::ios_base::out | std::ios::trunc);
else
{
if (!fs::create_directory(dir_path))
{
log::error(logcat, "Failed to create directory {}", dir_path);
m_raw_data_file->open(
file_path.string(),
std::ios_base::binary | std::ios_base::out | std::ios::app | std::ios::ate);
if (m_raw_data_file->fail())
return false;
}
}
}
m_raw_data_file = new std::ofstream();
m_output_stream =
new boost::iostreams::stream<boost::iostreams::back_insert_device<buffer_type>>(
m_buffer);
if (m_output_stream == nullptr)
return false;
bool do_initialize_file = false;
uint64_t num_blocks = 0;
if (do_initialize_file)
initialize_file();
if (! fs::exists(file_path))
{
log::debug(logcat, "creating file");
do_initialize_file = true;
num_blocks = 0;
}
else
{
num_blocks = count_blocks(file_path.string());
log::debug(logcat, "appending to existing file with height: {} total blocks: {}", num_blocks-1, num_blocks);
}
m_height = num_blocks;
if (do_initialize_file)
m_raw_data_file->open(file_path.string(), std::ios_base::binary | std::ios_base::out | std::ios::trunc);
else
m_raw_data_file->open(file_path.string(), std::ios_base::binary | std::ios_base::out | std::ios::app | std::ios::ate);
if (m_raw_data_file->fail())
return false;
m_output_stream = new boost::iostreams::stream<boost::iostreams::back_insert_device<buffer_type>>(m_buffer);
if (m_output_stream == nullptr)
return false;
if (do_initialize_file)
initialize_file();
return true;
return true;
}
bool BootstrapFile::initialize_file() {
const uint32_t file_magic = blockchain_raw_magic;
bool BootstrapFile::initialize_file()
{
const uint32_t file_magic = blockchain_raw_magic;
std::string blob;
try {
blob = serialization::dump_binary(file_magic);
} catch (const std::exception& e) {
throw std::runtime_error("Error in serialization of file magic: "s + e.what());
}
*m_raw_data_file << blob;
bootstrap::file_info bfi;
bfi.major_version = 0;
bfi.minor_version = 1;
bfi.header_size = header_size;
bootstrap::blocks_info bbi;
bbi.block_first = 0;
bbi.block_last = 0;
bbi.block_last_pos = 0;
buffer_type buffer2;
boost::iostreams::stream<boost::iostreams::back_insert_device<buffer_type>> output_stream_header(buffer2);
uint32_t bd_size = 0;
std::string bd = t_serializable_object_to_blob(bfi);
log::debug(logcat, "bootstrap::file_info size: {}", bd.size());
bd_size = bd.size();
try {
blob = serialization::dump_binary(bd_size);
} catch (const std::exception& e) {
throw std::runtime_error("Error in serialization of bootstrap::file_info size: "s + e.what());
}
output_stream_header << blob;
output_stream_header << bd;
bd = t_serializable_object_to_blob(bbi);
log::debug(logcat, "bootstrap::blocks_info size: {}", bd.size());
bd_size = bd.size();
try {
blob = serialization::dump_binary(bd_size);
} catch (const std::exception& e) {
throw std::runtime_error("Error in serialization of bootstrap::blocks_info size: "s + e.what());
}
output_stream_header << blob;
output_stream_header << bd;
output_stream_header.flush();
output_stream_header << std::string(header_size-buffer2.size(), 0); // fill in rest with null bytes
output_stream_header.flush();
std::copy(buffer2.begin(), buffer2.end(), std::ostreambuf_iterator<char>(*m_raw_data_file));
return true;
}
void BootstrapFile::flush_chunk()
{
m_output_stream->flush();
uint32_t chunk_size = m_buffer.size();
// log::trace(logcat, "chunk_size {}", chunk_size);
if (chunk_size > BUFFER_SIZE)
{
log::warning(logcat, "WARNING: chunk_size {} > BUFFER_SIZE {}", chunk_size, BUFFER_SIZE);
}
std::string blob;
try {
blob = serialization::dump_binary(chunk_size);
} catch (const std::exception& e) {
throw std::runtime_error("Error in serialization of chunk size: "s + e.what());
}
*m_raw_data_file << blob;
if (m_max_chunk < chunk_size)
{
m_max_chunk = chunk_size;
}
long pos_before = m_raw_data_file->tellp();
std::copy(m_buffer.begin(), m_buffer.end(), std::ostreambuf_iterator<char>(*m_raw_data_file));
m_raw_data_file->flush();
long pos_after = m_raw_data_file->tellp();
long num_chars_written = pos_after - pos_before;
if (static_cast<unsigned long>(num_chars_written) != chunk_size)
{
log::error(logcat, "Error writing chunk: height: {} chunk_size: {} num chars written: {}", m_cur_height, chunk_size, num_chars_written);
throw std::runtime_error("Error writing chunk");
}
m_buffer.clear();
delete m_output_stream;
m_output_stream = new boost::iostreams::stream<boost::iostreams::back_insert_device<buffer_type>>(m_buffer);
log::debug(logcat, "flushed chunk: chunk_size: {}", chunk_size);
}
void BootstrapFile::write_block(block& block)
{
bootstrap::block_package bp;
bp.block = block;
std::vector<transaction> txs;
uint64_t block_height = var::get<txin_gen>(block.miner_tx.vin.front()).height;
// now add all regular transactions
for (const auto& tx_id : block.tx_hashes)
{
if (!tx_id)
{
throw std::runtime_error("Aborting: null txid");
}
transaction tx = m_blockchain_storage->get_db().get_tx(tx_id);
txs.push_back(tx);
}
// these non-coinbase txs will be serialized using this structure
bp.txs = txs;
// These three attributes are currently necessary for a fast import that adds blocks without verification.
bool include_extra_block_data = true;
if (include_extra_block_data)
{
size_t block_weight = m_blockchain_storage->get_db().get_block_weight(block_height);
difficulty_type cumulative_difficulty = m_blockchain_storage->get_db().get_block_cumulative_difficulty(block_height);
uint64_t coins_generated = m_blockchain_storage->get_db().get_block_already_generated_coins(block_height);
bp.block_weight = block_weight;
bp.cumulative_difficulty = cumulative_difficulty;
bp.coins_generated = coins_generated;
}
std::string bd = t_serializable_object_to_blob(bp);
m_output_stream->write((const char*)bd.data(), bd.size());
}
bool BootstrapFile::close()
{
if (m_raw_data_file->fail())
return false;
m_raw_data_file->flush();
delete m_output_stream;
delete m_raw_data_file;
return true;
}
bool BootstrapFile::store_blockchain_raw(Blockchain* _blockchain_storage, tx_memory_pool* _tx_pool, fs::path& output_file, uint64_t requested_block_stop)
{
uint64_t num_blocks_written = 0;
m_max_chunk = 0;
m_blockchain_storage = _blockchain_storage;
m_tx_pool = _tx_pool;
uint64_t progress_interval = 100;
log::info(logcat, "Storing blocks raw data...");
if (!BootstrapFile::open_writer(output_file))
{
log::error(logcat, "failed to open raw file for write");
return false;
}
block b;
// block_start, block_stop use 0-based height. m_height uses 1-based height. So to resume export
// from last exported block, block_start doesn't need to add 1 here, as it's already at the next
// height.
uint64_t block_start = m_height;
uint64_t block_stop = 0;
log::info(logcat, "source blockchain height: {}", m_blockchain_storage->get_current_blockchain_height()-1);
if ((requested_block_stop > 0) && (requested_block_stop < m_blockchain_storage->get_current_blockchain_height()))
{
log::info(logcat, "Using requested block height: {}", requested_block_stop);
block_stop = requested_block_stop;
}
else
{
block_stop = m_blockchain_storage->get_current_blockchain_height() - 1;
log::info(logcat, "Using block height of source blockchain: {}", block_stop);
}
for (m_cur_height = block_start; m_cur_height <= block_stop; ++m_cur_height)
{
// this method's height refers to 0-based height (genesis block = height 0)
crypto::hash hash = m_blockchain_storage->get_block_id_by_height(m_cur_height);
m_blockchain_storage->get_block_by_hash(hash, b);
write_block(b);
if (m_cur_height % NUM_BLOCKS_PER_CHUNK == 0) {
flush_chunk();
num_blocks_written += NUM_BLOCKS_PER_CHUNK;
}
if (m_cur_height % progress_interval == 0) {
std::cout << refresh_string;
std::cout << "block " << m_cur_height << "/" << block_stop << "\r" << std::flush;
}
}
// NOTE: use of NUM_BLOCKS_PER_CHUNK is a placeholder in case multi-block chunks are later supported.
if (m_cur_height % NUM_BLOCKS_PER_CHUNK != 0)
{
flush_chunk();
}
// print message for last block, which may not have been printed yet due to progress_interval
std::cout << refresh_string;
std::cout << "block " << m_cur_height-1 << "/" << block_stop << "\n";
log::info(logcat, "Number of blocks exported: {}", num_blocks_written);
if (num_blocks_written > 0)
log::info(logcat, "Largest chunk: {} bytes", m_max_chunk);
return BootstrapFile::close();
}
uint64_t BootstrapFile::seek_to_first_chunk(fs::ifstream& import_file)
{
uint32_t file_magic;
std::string str1;
char buf1[2048];
import_file.read(buf1, sizeof(file_magic));
if (! import_file)
throw std::runtime_error("Error reading expected number of bytes");
str1.assign(buf1, sizeof(file_magic));
try {
serialization::parse_binary(str1, file_magic);
} catch (const std::exception& e) {
throw std::runtime_error("Error in deserialization of file_magic: "s + e.what());
}
if (file_magic != blockchain_raw_magic)
{
log::error(logcat, "bootstrap file not recognized");
throw std::runtime_error("Aborting");
}
else
log::info(logcat, "bootstrap file recognized");
uint32_t buflen_file_info;
import_file.read(buf1, sizeof(buflen_file_info));
str1.assign(buf1, sizeof(buflen_file_info));
if (! import_file)
throw std::runtime_error("Error reading expected number of bytes");
try {
serialization::parse_binary(str1, buflen_file_info);
} catch (const std::exception& e) {
throw std::runtime_error("Error in deserialization of buflen_file_info: "s + e.what());
}
log::info(logcat, "bootstrap::file_info size: {}", buflen_file_info);
if (buflen_file_info > sizeof(buf1))
throw std::runtime_error("Error: bootstrap::file_info size exceeds buffer size");
import_file.read(buf1, buflen_file_info);
if (! import_file)
throw std::runtime_error("Error reading expected number of bytes");
str1.assign(buf1, buflen_file_info);
bootstrap::file_info bfi;
try {
serialization::parse_binary(str1, bfi);
} catch (const std::exception& e) {
throw std::runtime_error("Error in deserialization of bootstrap::file_info: "s + e.what());
}
log::info(logcat, "bootstrap file v{}.{}", unsigned(bfi.major_version), unsigned(bfi.minor_version));
log::info(logcat, "bootstrap magic size: {}", sizeof(file_magic));
log::info(logcat, "bootstrap header size: {}", bfi.header_size);
uint64_t full_header_size = sizeof(file_magic) + bfi.header_size;
import_file.seekg(full_header_size);
return full_header_size;
}
uint64_t BootstrapFile::count_bytes(fs::ifstream& import_file, uint64_t blocks, uint64_t& h, bool& quit)
{
uint64_t bytes_read = 0;
uint32_t chunk_size;
char buf1[sizeof(chunk_size)];
std::string str1;
h = 0;
while (1)
{
import_file.read(buf1, sizeof(chunk_size));
if (!import_file) {
std::cout << refresh_string;
log::debug(logcat, "End of file reached");
quit = true;
break;
}
bytes_read += sizeof(chunk_size);
str1.assign(buf1, sizeof(chunk_size));
std::string blob;
try {
serialization::parse_binary(str1, chunk_size);
blob = serialization::dump_binary(file_magic);
} catch (const std::exception& e) {
throw std::runtime_error("Error in deserialization of chunk_size: "s + e.what());
throw std::runtime_error("Error in serialization of file magic: "s + e.what());
}
log::debug(logcat, "chunk_size: {}", chunk_size);
*m_raw_data_file << blob;
if (chunk_size > BUFFER_SIZE)
{
std::cout << refresh_string;
log::warning(logcat, "WARNING: chunk_size {} > BUFFER_SIZE {} height: {}, offset {}", chunk_size, BUFFER_SIZE, h-1, bytes_read);
throw std::runtime_error("Aborting: chunk size exceeds buffer size");
bootstrap::file_info bfi;
bfi.major_version = 0;
bfi.minor_version = 1;
bfi.header_size = header_size;
bootstrap::blocks_info bbi;
bbi.block_first = 0;
bbi.block_last = 0;
bbi.block_last_pos = 0;
buffer_type buffer2;
boost::iostreams::stream<boost::iostreams::back_insert_device<buffer_type>>
output_stream_header(buffer2);
uint32_t bd_size = 0;
std::string bd = t_serializable_object_to_blob(bfi);
log::debug(logcat, "bootstrap::file_info size: {}", bd.size());
bd_size = bd.size();
try {
blob = serialization::dump_binary(bd_size);
} catch (const std::exception& e) {
throw std::runtime_error(
"Error in serialization of bootstrap::file_info size: "s + e.what());
}
if (chunk_size > CHUNK_SIZE_WARNING_THRESHOLD)
{
std::cout << refresh_string;
log::debug(logcat, "NOTE: chunk_size {} > {} height: {}, offset {}", chunk_size, CHUNK_SIZE_WARNING_THRESHOLD, h-1, bytes_read);
output_stream_header << blob;
output_stream_header << bd;
bd = t_serializable_object_to_blob(bbi);
log::debug(logcat, "bootstrap::blocks_info size: {}", bd.size());
bd_size = bd.size();
try {
blob = serialization::dump_binary(bd_size);
} catch (const std::exception& e) {
throw std::runtime_error(
"Error in serialization of bootstrap::blocks_info size: "s + e.what());
}
else if (chunk_size <= 0) {
std::cout << refresh_string;
log::debug(logcat, "ERROR: chunk_size {} <= 0 height: {}, offset {}", chunk_size, h-1, bytes_read);
throw std::runtime_error("Aborting");
}
// skip to next expected block size value
import_file.seekg(chunk_size, std::ios_base::cur);
if (! import_file) {
std::cout << refresh_string;
log::error(logcat, "ERROR: unexpected end of file: bytes read before error: {} of chunk_size {}", import_file.gcount(), chunk_size);
throw std::runtime_error("Aborting");
}
bytes_read += chunk_size;
h += NUM_BLOCKS_PER_CHUNK;
if (h >= blocks)
break;
}
return bytes_read;
output_stream_header << blob;
output_stream_header << bd;
output_stream_header.flush();
output_stream_header << std::string(
header_size - buffer2.size(), 0); // fill in rest with null bytes
output_stream_header.flush();
std::copy(buffer2.begin(), buffer2.end(), std::ostreambuf_iterator<char>(*m_raw_data_file));
return true;
}
uint64_t BootstrapFile::count_blocks(const fs::path& import_file_path)
{
std::streampos dummy_pos;
uint64_t dummy_height = 0;
return count_blocks(import_file_path, dummy_pos, dummy_height);
void BootstrapFile::flush_chunk() {
m_output_stream->flush();
uint32_t chunk_size = m_buffer.size();
// log::trace(logcat, "chunk_size {}", chunk_size);
if (chunk_size > BUFFER_SIZE) {
log::warning(logcat, "WARNING: chunk_size {} > BUFFER_SIZE {}", chunk_size, BUFFER_SIZE);
}
std::string blob;
try {
blob = serialization::dump_binary(chunk_size);
} catch (const std::exception& e) {
throw std::runtime_error("Error in serialization of chunk size: "s + e.what());
}
*m_raw_data_file << blob;
if (m_max_chunk < chunk_size) {
m_max_chunk = chunk_size;
}
long pos_before = m_raw_data_file->tellp();
std::copy(m_buffer.begin(), m_buffer.end(), std::ostreambuf_iterator<char>(*m_raw_data_file));
m_raw_data_file->flush();
long pos_after = m_raw_data_file->tellp();
long num_chars_written = pos_after - pos_before;
if (static_cast<unsigned long>(num_chars_written) != chunk_size) {
log::error(
logcat,
"Error writing chunk: height: {} chunk_size: {} num chars written: {}",
m_cur_height,
chunk_size,
num_chars_written);
throw std::runtime_error("Error writing chunk");
}
m_buffer.clear();
delete m_output_stream;
m_output_stream =
new boost::iostreams::stream<boost::iostreams::back_insert_device<buffer_type>>(
m_buffer);
log::debug(logcat, "flushed chunk: chunk_size: {}", chunk_size);
}
void BootstrapFile::write_block(block& block) {
bootstrap::block_package bp;
bp.block = block;
std::vector<transaction> txs;
uint64_t block_height = var::get<txin_gen>(block.miner_tx.vin.front()).height;
// now add all regular transactions
for (const auto& tx_id : block.tx_hashes) {
if (!tx_id) {
throw std::runtime_error("Aborting: null txid");
}
transaction tx = m_blockchain_storage->get_db().get_tx(tx_id);
txs.push_back(tx);
}
// these non-coinbase txs will be serialized using this structure
bp.txs = txs;
// These three attributes are currently necessary for a fast import that adds blocks without
// verification.
bool include_extra_block_data = true;
if (include_extra_block_data) {
size_t block_weight = m_blockchain_storage->get_db().get_block_weight(block_height);
difficulty_type cumulative_difficulty =
m_blockchain_storage->get_db().get_block_cumulative_difficulty(block_height);
uint64_t coins_generated =
m_blockchain_storage->get_db().get_block_already_generated_coins(block_height);
bp.block_weight = block_weight;
bp.cumulative_difficulty = cumulative_difficulty;
bp.coins_generated = coins_generated;
}
std::string bd = t_serializable_object_to_blob(bp);
m_output_stream->write((const char*)bd.data(), bd.size());
}
bool BootstrapFile::close() {
if (m_raw_data_file->fail())
return false;
m_raw_data_file->flush();
delete m_output_stream;
delete m_raw_data_file;
return true;
}
bool BootstrapFile::store_blockchain_raw(
Blockchain* _blockchain_storage,
tx_memory_pool* _tx_pool,
fs::path& output_file,
uint64_t requested_block_stop) {
uint64_t num_blocks_written = 0;
m_max_chunk = 0;
m_blockchain_storage = _blockchain_storage;
m_tx_pool = _tx_pool;
uint64_t progress_interval = 100;
log::info(logcat, "Storing blocks raw data...");
if (!BootstrapFile::open_writer(output_file)) {
log::error(logcat, "failed to open raw file for write");
return false;
}
block b;
// block_start, block_stop use 0-based height. m_height uses 1-based height. So to resume export
// from last exported block, block_start doesn't need to add 1 here, as it's already at the next
// height.
uint64_t block_start = m_height;
uint64_t block_stop = 0;
log::info(
logcat,
"source blockchain height: {}",
m_blockchain_storage->get_current_blockchain_height() - 1);
if ((requested_block_stop > 0) &&
(requested_block_stop < m_blockchain_storage->get_current_blockchain_height())) {
log::info(logcat, "Using requested block height: {}", requested_block_stop);
block_stop = requested_block_stop;
} else {
block_stop = m_blockchain_storage->get_current_blockchain_height() - 1;
log::info(logcat, "Using block height of source blockchain: {}", block_stop);
}
for (m_cur_height = block_start; m_cur_height <= block_stop; ++m_cur_height) {
// this method's height refers to 0-based height (genesis block = height 0)
crypto::hash hash = m_blockchain_storage->get_block_id_by_height(m_cur_height);
m_blockchain_storage->get_block_by_hash(hash, b);
write_block(b);
if (m_cur_height % NUM_BLOCKS_PER_CHUNK == 0) {
flush_chunk();
num_blocks_written += NUM_BLOCKS_PER_CHUNK;
}
if (m_cur_height % progress_interval == 0) {
std::cout << refresh_string;
std::cout << "block " << m_cur_height << "/" << block_stop << "\r" << std::flush;
}
}
// NOTE: use of NUM_BLOCKS_PER_CHUNK is a placeholder in case multi-block chunks are later
// supported.
if (m_cur_height % NUM_BLOCKS_PER_CHUNK != 0) {
flush_chunk();
}
// print message for last block, which may not have been printed yet due to progress_interval
std::cout << refresh_string;
std::cout << "block " << m_cur_height - 1 << "/" << block_stop << "\n";
log::info(logcat, "Number of blocks exported: {}", num_blocks_written);
if (num_blocks_written > 0)
log::info(logcat, "Largest chunk: {} bytes", m_max_chunk);
return BootstrapFile::close();
}
uint64_t BootstrapFile::seek_to_first_chunk(fs::ifstream& import_file) {
uint32_t file_magic;
std::string str1;
char buf1[2048];
import_file.read(buf1, sizeof(file_magic));
if (!import_file)
throw std::runtime_error("Error reading expected number of bytes");
str1.assign(buf1, sizeof(file_magic));
try {
serialization::parse_binary(str1, file_magic);
} catch (const std::exception& e) {
throw std::runtime_error("Error in deserialization of file_magic: "s + e.what());
}
if (file_magic != blockchain_raw_magic) {
log::error(logcat, "bootstrap file not recognized");
throw std::runtime_error("Aborting");
} else
log::info(logcat, "bootstrap file recognized");
uint32_t buflen_file_info;
import_file.read(buf1, sizeof(buflen_file_info));
str1.assign(buf1, sizeof(buflen_file_info));
if (!import_file)
throw std::runtime_error("Error reading expected number of bytes");
try {
serialization::parse_binary(str1, buflen_file_info);
} catch (const std::exception& e) {
throw std::runtime_error("Error in deserialization of buflen_file_info: "s + e.what());
}
log::info(logcat, "bootstrap::file_info size: {}", buflen_file_info);
if (buflen_file_info > sizeof(buf1))
throw std::runtime_error("Error: bootstrap::file_info size exceeds buffer size");
import_file.read(buf1, buflen_file_info);
if (!import_file)
throw std::runtime_error("Error reading expected number of bytes");
str1.assign(buf1, buflen_file_info);
bootstrap::file_info bfi;
try {
serialization::parse_binary(str1, bfi);
} catch (const std::exception& e) {
throw std::runtime_error("Error in deserialization of bootstrap::file_info: "s + e.what());
}
log::info(
logcat,
"bootstrap file v{}.{}",
unsigned(bfi.major_version),
unsigned(bfi.minor_version));
log::info(logcat, "bootstrap magic size: {}", sizeof(file_magic));
log::info(logcat, "bootstrap header size: {}", bfi.header_size);
uint64_t full_header_size = sizeof(file_magic) + bfi.header_size;
import_file.seekg(full_header_size);
return full_header_size;
}
uint64_t BootstrapFile::count_bytes(
fs::ifstream& import_file, uint64_t blocks, uint64_t& h, bool& quit) {
uint64_t bytes_read = 0;
uint32_t chunk_size;
char buf1[sizeof(chunk_size)];
std::string str1;
h = 0;
while (1) {
import_file.read(buf1, sizeof(chunk_size));
if (!import_file) {
std::cout << refresh_string;
log::debug(logcat, "End of file reached");
quit = true;
break;
}
bytes_read += sizeof(chunk_size);
str1.assign(buf1, sizeof(chunk_size));
try {
serialization::parse_binary(str1, chunk_size);
} catch (const std::exception& e) {
throw std::runtime_error("Error in deserialization of chunk_size: "s + e.what());
}
log::debug(logcat, "chunk_size: {}", chunk_size);
if (chunk_size > BUFFER_SIZE) {
std::cout << refresh_string;
log::warning(
logcat,
"WARNING: chunk_size {} > BUFFER_SIZE {} height: {}, offset {}",
chunk_size,
BUFFER_SIZE,
h - 1,
bytes_read);
throw std::runtime_error("Aborting: chunk size exceeds buffer size");
}
if (chunk_size > CHUNK_SIZE_WARNING_THRESHOLD) {
std::cout << refresh_string;
log::debug(
logcat,
"NOTE: chunk_size {} > {} height: {}, offset {}",
chunk_size,
CHUNK_SIZE_WARNING_THRESHOLD,
h - 1,
bytes_read);
} else if (chunk_size <= 0) {
std::cout << refresh_string;
log::debug(
logcat,
"ERROR: chunk_size {} <= 0 height: {}, offset {}",
chunk_size,
h - 1,
bytes_read);
throw std::runtime_error("Aborting");
}
// skip to next expected block size value
import_file.seekg(chunk_size, std::ios_base::cur);
if (!import_file) {
std::cout << refresh_string;
log::error(
logcat,
"ERROR: unexpected end of file: bytes read before error: {} of chunk_size {}",
import_file.gcount(),
chunk_size);
throw std::runtime_error("Aborting");
}
bytes_read += chunk_size;
h += NUM_BLOCKS_PER_CHUNK;
if (h >= blocks)
break;
}
return bytes_read;
}
uint64_t BootstrapFile::count_blocks(const fs::path& import_file_path) {
std::streampos dummy_pos;
uint64_t dummy_height = 0;
return count_blocks(import_file_path, dummy_pos, dummy_height);
}
// If seek_height is non-zero on entry, return a stream position <= this height when finished.
// And return the actual height corresponding to this position. Allows the caller to locate its
// starting position without having to reread the entire file again.
uint64_t BootstrapFile::count_blocks(const fs::path& import_file_path, std::streampos &start_pos, uint64_t& seek_height)
{
if (std::error_code ec; !fs::exists(import_file_path, ec))
{
log::error(logcat, "bootstrap file not found: {}", import_file_path);
throw std::runtime_error("Aborting");
}
fs::ifstream import_file{import_file_path, std::ios::binary};
uint64_t start_height = seek_height;
uint64_t h = 0;
if (import_file.fail())
{
log::error(logcat, "import_file.open() fail");
throw std::runtime_error("Aborting");
}
uint64_t full_header_size; // 4 byte magic + length of header structures
full_header_size = seek_to_first_chunk(import_file);
log::info(logcat, "Scanning blockchain from bootstrap file...");
bool quit = false;
uint64_t bytes_read = 0, blocks;
int progress_interval = 10;
while (! quit)
{
if (start_height && h + progress_interval >= start_height - 1)
{
start_height = 0;
start_pos = import_file.tellg();
seek_height = h;
uint64_t BootstrapFile::count_blocks(
const fs::path& import_file_path, std::streampos& start_pos, uint64_t& seek_height) {
if (std::error_code ec; !fs::exists(import_file_path, ec)) {
log::error(logcat, "bootstrap file not found: {}", import_file_path);
throw std::runtime_error("Aborting");
}
bytes_read += count_bytes(import_file, progress_interval, blocks, quit);
h += blocks;
std::cout << "\r" << "block height: " << h-1 <<
" \r" <<
std::flush;
fs::ifstream import_file{import_file_path, std::ios::binary};
// std::cout << refresh_string;
log::debug(logcat, "Number bytes scanned: {}", bytes_read);
}
uint64_t start_height = seek_height;
uint64_t h = 0;
if (import_file.fail()) {
log::error(logcat, "import_file.open() fail");
throw std::runtime_error("Aborting");
}
import_file.close();
uint64_t full_header_size; // 4 byte magic + length of header structures
full_header_size = seek_to_first_chunk(import_file);
std::cout << "\nDone scanning bootstrap file";
std::cout << "\nFull header length: " << full_header_size << " bytes";
std::cout << "\nScanned for blocks: " << bytes_read << " bytes";
std::cout << "\nTotal: " << full_header_size + bytes_read << " bytes";
std::cout << "\nNumber of blocks: " << h;
std::cout << std::endl;
log::info(logcat, "Scanning blockchain from bootstrap file...");
bool quit = false;
uint64_t bytes_read = 0, blocks;
int progress_interval = 10;
// NOTE: h is the number of blocks.
// Note that a block's stored height is zero-based, but parts of the code use
// one-based height.
return h;
while (!quit) {
if (start_height && h + progress_interval >= start_height - 1) {
start_height = 0;
start_pos = import_file.tellg();
seek_height = h;
}
bytes_read += count_bytes(import_file, progress_interval, blocks, quit);
h += blocks;
std::cout << "\r"
<< "block height: " << h - 1 << " \r" << std::flush;
// std::cout << refresh_string;
log::debug(logcat, "Number bytes scanned: {}", bytes_read);
}
import_file.close();
std::cout << "\nDone scanning bootstrap file";
std::cout << "\nFull header length: " << full_header_size << " bytes";
std::cout << "\nScanned for blocks: " << bytes_read << " bytes";
std::cout << "\nTotal: " << full_header_size + bytes_read << " bytes";
std::cout << "\nNumber of blocks: " << h;
std::cout << std::endl;
// NOTE: h is the number of blocks.
// Note that a block's stored height is zero-based, but parts of the code use
// one-based height.
return h;
}

View File

@ -28,62 +28,57 @@
#pragma once
#include <boost/iostreams/stream_buffer.hpp>
#include <boost/iostreams/stream.hpp>
#include <algorithm>
#include <atomic>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/filtering_streambuf.hpp>
#include "cryptonote_basic/cryptonote_basic.h"
#include "cryptonote_core/blockchain.h"
#include <algorithm>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/stream_buffer.hpp>
#include <cstdio>
#include <fstream>
#include <boost/iostreams/copy.hpp>
#include <atomic>
#include "common/command_line.h"
#include "common/fs.h"
#include "version.h"
#include "blockchain_utilities.h"
#include "common/command_line.h"
#include "common/fs.h"
#include "cryptonote_basic/cryptonote_basic.h"
#include "cryptonote_core/blockchain.h"
#include "version.h"
using namespace cryptonote;
class BootstrapFile {
public:
uint64_t count_bytes(fs::ifstream& import_file, uint64_t blocks, uint64_t& h, bool& quit);
uint64_t count_blocks(
const fs::path& dir_path, std::streampos& start_pos, uint64_t& seek_height);
uint64_t count_blocks(const fs::path& dir_path);
uint64_t seek_to_first_chunk(fs::ifstream& import_file);
class BootstrapFile
{
public:
bool store_blockchain_raw(
cryptonote::Blockchain* cs,
cryptonote::tx_memory_pool* txp,
fs::path& output_file,
uint64_t use_block_height = 0);
uint64_t count_bytes(fs::ifstream& import_file, uint64_t blocks, uint64_t& h, bool& quit);
uint64_t count_blocks(const fs::path& dir_path, std::streampos& start_pos, uint64_t& seek_height);
uint64_t count_blocks(const fs::path& dir_path);
uint64_t seek_to_first_chunk(fs::ifstream& import_file);
protected:
Blockchain* m_blockchain_storage;
bool store_blockchain_raw(cryptonote::Blockchain* cs, cryptonote::tx_memory_pool* txp,
fs::path& output_file, uint64_t use_block_height=0);
tx_memory_pool* m_tx_pool;
typedef std::vector<char> buffer_type;
std::ofstream* m_raw_data_file;
buffer_type m_buffer;
boost::iostreams::stream<boost::iostreams::back_insert_device<buffer_type>>* m_output_stream;
protected:
// open export file for write
bool open_writer(const fs::path& file_path);
bool initialize_file();
bool close();
void write_block(block& block);
void flush_chunk();
Blockchain* m_blockchain_storage;
tx_memory_pool* m_tx_pool;
typedef std::vector<char> buffer_type;
std::ofstream * m_raw_data_file;
buffer_type m_buffer;
boost::iostreams::stream<boost::iostreams::back_insert_device<buffer_type>>* m_output_stream;
// open export file for write
bool open_writer(const fs::path& file_path);
bool initialize_file();
bool close();
void write_block(block& block);
void flush_chunk();
private:
uint64_t m_height;
uint64_t m_cur_height; // tracks current height during export
uint32_t m_max_chunk;
private:
uint64_t m_height;
uint64_t m_cur_height; // tracks current height during export
uint32_t m_max_chunk;
};

View File

@ -31,58 +31,49 @@
#include "cryptonote_basic/cryptonote_boost_serialization.h"
#include "cryptonote_basic/difficulty.h"
namespace cryptonote { namespace bootstrap {
namespace cryptonote
{
namespace bootstrap
{
struct file_info {
uint8_t major_version;
uint8_t minor_version;
uint32_t header_size;
struct file_info
{
uint8_t major_version;
uint8_t minor_version;
uint32_t header_size;
BEGIN_SERIALIZE_OBJECT()
BEGIN_SERIALIZE_OBJECT()
FIELD(major_version);
FIELD(minor_version);
VARINT_FIELD(header_size);
END_SERIALIZE()
END_SERIALIZE()
};
struct blocks_info
{
// block heights of file's first and last blocks, zero-based indexes
uint64_t block_first;
uint64_t block_last;
struct blocks_info {
// block heights of file's first and last blocks, zero-based indexes
uint64_t block_first;
uint64_t block_last;
// file position, for directly reading last block
uint64_t block_last_pos;
// file position, for directly reading last block
uint64_t block_last_pos;
BEGIN_SERIALIZE_OBJECT()
BEGIN_SERIALIZE_OBJECT()
VARINT_FIELD(block_first);
VARINT_FIELD(block_last);
VARINT_FIELD(block_last_pos);
END_SERIALIZE()
END_SERIALIZE()
};
struct block_package
{
cryptonote::block block;
std::vector<transaction> txs;
size_t block_weight;
difficulty_type cumulative_difficulty;
uint64_t coins_generated;
struct block_package {
cryptonote::block block;
std::vector<transaction> txs;
size_t block_weight;
difficulty_type cumulative_difficulty;
uint64_t coins_generated;
BEGIN_SERIALIZE()
BEGIN_SERIALIZE()
FIELD(block)
FIELD(txs)
VARINT_FIELD(block_weight)
VARINT_FIELD(cumulative_difficulty)
VARINT_FIELD(coins_generated)
END_SERIALIZE()
END_SERIALIZE()
};
}
}
}} // namespace cryptonote::bootstrap

View File

@ -2,16 +2,18 @@
extern "C" {
#include <sodium.h>
}
#include <iostream>
#include <fstream>
#include <oxenc/hex.h>
#include <oxenc/base32z.h>
#include <string_view>
#include <string>
#include <list>
#include <oxenc/hex.h>
#include <array>
#include <cstring>
#include <fstream>
#include <iostream>
#include <list>
#include <optional>
#include <string>
#include <string_view>
#include "common/fs.h"
std::string_view arg0;
@ -91,7 +93,8 @@ std::array<unsigned char, crypto_core_ed25519_BYTES> pubkey_from_privkey(ustring
return pubkey;
}
template <size_t N, std::enable_if_t<(N >= 32), int> = 0>
std::array<unsigned char, crypto_core_ed25519_BYTES> pubkey_from_privkey(const std::array<unsigned char, N>& privkey) {
std::array<unsigned char, crypto_core_ed25519_BYTES> pubkey_from_privkey(
const std::array<unsigned char, N>& privkey) {
return pubkey_from_privkey(ustring_view{privkey.data(), 32});
}
@ -117,7 +120,11 @@ int generate(bool ed25519, std::list<std::string_view> args) {
overwrite = true;
if (!overwrite && fs::exists(fs::u8path(filename)))
return error(2, filename + " to generate already exists, pass `--overwrite' if you want to overwrite it");
return error(
2,
filename +
" to generate already exists, pass `--overwrite' if you want to overwrite "
"it");
std::array<unsigned char, crypto_sign_PUBLICKEYBYTES> pubkey;
std::array<unsigned char, crypto_sign_SECRETKEYBYTES> seckey;
@ -147,18 +154,20 @@ int generate(bool ed25519, std::list<std::string_view> args) {
out.write(reinterpret_cast<const char*>(privkey.data()), privkey.size());
if (!out.good())
return error(2, "Failed to write to output file '" + filename + "': " + std::strerror(errno));
return error(
2, "Failed to write to output file '" + filename + "': " + std::strerror(errno));
std::cout << "Generated SN " << (ed25519 ? "Ed25519 secret key" : "legacy private key") << " in " << filename << "\n";
std::cout << "Generated SN " << (ed25519 ? "Ed25519 secret key" : "legacy private key")
<< " in " << filename << "\n";
if (ed25519) {
std::array<unsigned char, crypto_scalarmult_curve25519_BYTES> x_pubkey;
if (0 != crypto_sign_ed25519_pk_to_curve25519(x_pubkey.data(), pubkey.data()))
return error(14, "Internal error: unable to convert Ed25519 pubkey to X25519 pubkey");
std::cout <<
"Public key: " << oxenc::to_hex(pubkey.begin(), pubkey.end()) <<
"\nX25519 pubkey: " << oxenc::to_hex(x_pubkey.begin(), x_pubkey.end()) <<
"\nLokinet address: " << oxenc::to_base32z(pubkey.begin(), pubkey.end()) << ".snode\n";
std::cout << "Public key: " << oxenc::to_hex(pubkey.begin(), pubkey.end())
<< "\nX25519 pubkey: " << oxenc::to_hex(x_pubkey.begin(), x_pubkey.end())
<< "\nLokinet address: " << oxenc::to_base32z(pubkey.begin(), pubkey.end())
<< ".snode\n";
} else {
std::cout << "Public key: " << oxenc::to_hex(pubkey.begin(), pubkey.end()) << "\n";
}
@ -204,10 +213,15 @@ int show(std::list<std::string_view> args) {
ed25519 = true;
}
if (!legacy && !ed25519)
return error(2, "Could not autodetect key type from " + std::to_string(size) + "-byte file; check the file or pass the --ed25519 or --legacy argument");
return error(
2,
"Could not autodetect key type from " + std::to_string(size) +
"-byte file; check the file or pass the --ed25519 or --legacy argument");
if (size < 32)
return error(2, "File size (" + std::to_string(size) + " bytes) is too small to be a secret key");
return error(
2,
"File size (" + std::to_string(size) + " bytes) is too small to be a secret key");
std::array<unsigned char, crypto_core_ed25519_BYTES> pubkey;
std::array<unsigned char, crypto_scalarmult_curve25519_BYTES> x_pubkey;
@ -219,9 +233,10 @@ int show(std::list<std::string_view> args) {
if (legacy) {
pubkey = pubkey_from_privkey(seckey);
std::cout << filename.u8string() << " (legacy SN keypair)" << "\n==========" <<
"\nPrivate key: " << oxenc::to_hex(seckey.begin(), seckey.begin() + 32) <<
"\nPublic key: " << oxenc::to_hex(pubkey.begin(), pubkey.end()) << "\n\n";
std::cout << filename.u8string() << " (legacy SN keypair)"
<< "\n=========="
<< "\nPrivate key: " << oxenc::to_hex(seckey.begin(), seckey.begin() + 32)
<< "\nPublic key: " << oxenc::to_hex(pubkey.begin(), pubkey.end()) << "\n\n";
return 0;
}
@ -233,17 +248,27 @@ int show(std::list<std::string_view> args) {
ustring_view privkey{privkey_signhash.data(), 32};
pubkey = pubkey_from_privkey(privkey);
if (size >= 64 && ustring_view{pubkey.data(), pubkey.size()} != ustring_view{seckey.data() + 32, 32})
return error(13, "Error: derived pubkey (" + oxenc::to_hex(pubkey.begin(), pubkey.end()) + ")"
" != embedded pubkey (" + oxenc::to_hex(seckey.begin() + 32, seckey.end()) + ")");
if (size >= 64 &&
ustring_view{pubkey.data(), pubkey.size()} != ustring_view{seckey.data() + 32, 32})
return error(
13,
"Error: derived pubkey (" + oxenc::to_hex(pubkey.begin(), pubkey.end()) +
")"
" != embedded pubkey (" +
oxenc::to_hex(seckey.begin() + 32, seckey.end()) + ")");
if (0 != crypto_sign_ed25519_pk_to_curve25519(x_pubkey.data(), pubkey.data()))
return error(14, "Unable to convert Ed25519 pubkey to X25519 pubkey; is this a really valid secret key?");
return error(
14,
"Unable to convert Ed25519 pubkey to X25519 pubkey; is this a really valid secret "
"key?");
std::cout << filename << " (Ed25519 SN keypair)" << "\n==========" <<
"\nSecret key: " << oxenc::to_hex(seckey.begin(), seckey.begin() + 32) <<
"\nPublic key: " << oxenc::to_hex(pubkey.begin(), pubkey.end()) <<
"\nX25519 pubkey: " << oxenc::to_hex(x_pubkey.begin(), x_pubkey.end()) <<
"\nLokinet address: " << oxenc::to_base32z(pubkey.begin(), pubkey.end()) << ".snode\n\n";
std::cout << filename << " (Ed25519 SN keypair)"
<< "\n=========="
<< "\nSecret key: " << oxenc::to_hex(seckey.begin(), seckey.begin() + 32)
<< "\nPublic key: " << oxenc::to_hex(pubkey.begin(), pubkey.end())
<< "\nX25519 pubkey: " << oxenc::to_hex(x_pubkey.begin(), x_pubkey.end())
<< "\nLokinet address: " << oxenc::to_base32z(pubkey.begin(), pubkey.end())
<< ".snode\n\n";
return 0;
}
@ -298,23 +323,33 @@ int restore(bool ed25519, std::list<std::string_view> args) {
if (ed25519) {
std::array<unsigned char, crypto_scalarmult_curve25519_BYTES> x_pubkey;
if (0 != crypto_sign_ed25519_pk_to_curve25519(x_pubkey.data(), pubkey.data()))
return error(14, "Unable to convert Ed25519 pubkey to X25519 pubkey; is this a really valid secret key?");
std::cout << "X25519 pubkey: " << oxenc::to_hex(x_pubkey.begin(), x_pubkey.end()) <<
"\nLokinet address: " << oxenc::to_base32z(pubkey.begin(), pubkey.end()) << ".snode";
return error(
14,
"Unable to convert Ed25519 pubkey to X25519 pubkey; is this a really valid "
"secret key?");
std::cout << "X25519 pubkey: " << oxenc::to_hex(x_pubkey.begin(), x_pubkey.end())
<< "\nLokinet address: " << oxenc::to_base32z(pubkey.begin(), pubkey.end())
<< ".snode";
}
if (pubkey_expected) {
if (*pubkey_expected != pubkey)
return error(2, "Derived pubkey (" + oxenc::to_hex(pubkey.begin(), pubkey.end()) + ") doesn't match "
"provided pubkey (" + oxenc::to_hex(pubkey_expected->begin(), pubkey_expected->end()) + ")");
return error(
2,
"Derived pubkey (" + oxenc::to_hex(pubkey.begin(), pubkey.end()) +
") doesn't match "
"provided pubkey (" +
oxenc::to_hex(pubkey_expected->begin(), pubkey_expected->end()) + ")");
} else {
if (ed25519 && filename.size() >= 4 && filename.substr(filename.size() - 4) == "/key") {
std::cout << "\n\n\x1b[31;1m"
"Warning: You are trying to restore a file named 'key' using the 'restore'\n"
"command, which is intended for the key_ed25519 key file; for old service nodes\n"
"with both key files you want to use 'restore-legacy' to restore the old\n"
"(pre-Loki 8.x) pubkey.\x1b[0m\n";
"Warning: You are trying to restore a file named 'key' using the "
"'restore'\n"
"command, which is intended for the key_ed25519 key file; for old service "
"nodes\n"
"with both key files you want to use 'restore-legacy' to restore the old\n"
"(pre-Loki 8.x) pubkey.\x1b[0m\n";
}
std::cout << "\nIs this correct? Press Enter to continue, Ctrl-C to cancel.\n";
@ -328,7 +363,11 @@ int restore(bool ed25519, std::list<std::string_view> args) {
auto filepath = fs::u8path(filename);
if (!overwrite && fs::exists(filepath))
return error(2, filename + " to generate already exists, pass `--overwrite' if you want to overwrite it");
return error(
2,
filename +
" to generate already exists, pass `--overwrite' if you want to overwrite "
"it");
fs::ofstream out{filepath, std::ios::trunc | std::ios::binary};
if (!out.good())
@ -339,13 +378,13 @@ int restore(bool ed25519, std::list<std::string_view> args) {
out.write(reinterpret_cast<const char*>(seed.data()), seed.size());
if (!out.good())
return error(2, "Failed to write to output file '" + filename + "': " + std::strerror(errno));
return error(
2, "Failed to write to output file '" + filename + "': " + std::strerror(errno));
std::cout << "Saved secret key to " << filename << "\n";
return 0;
}
int main(int argc, char* argv[]) {
arg0 = argv[0];
if (argc < 2)

View File

@ -1,22 +1,30 @@
#pragma once
#include "cryptonote_config.h"
#include <string_view>
namespace blocks
{
#include "cryptonote_config.h"
template <cryptonote::network_type Network> std::string_view checkpoint_data() { return ""sv; }
template<> std::string_view checkpoint_data<cryptonote::network_type::MAINNET>();
template<> std::string_view checkpoint_data<cryptonote::network_type::DEVNET>();
template<> std::string_view checkpoint_data<cryptonote::network_type::TESTNET>();
namespace blocks {
inline std::string_view GetCheckpointsData(cryptonote::network_type network)
{
if (network == cryptonote::network_type::MAINNET) return checkpoint_data<cryptonote::network_type::MAINNET>();
if (network == cryptonote::network_type::TESTNET) return checkpoint_data<cryptonote::network_type::TESTNET>();
if (network == cryptonote::network_type::DEVNET) return checkpoint_data<cryptonote::network_type::DEVNET>();
template <cryptonote::network_type Network>
std::string_view checkpoint_data() {
return ""sv;
}
template <>
std::string_view checkpoint_data<cryptonote::network_type::MAINNET>();
template <>
std::string_view checkpoint_data<cryptonote::network_type::DEVNET>();
template <>
std::string_view checkpoint_data<cryptonote::network_type::TESTNET>();
inline std::string_view GetCheckpointsData(cryptonote::network_type network) {
if (network == cryptonote::network_type::MAINNET)
return checkpoint_data<cryptonote::network_type::MAINNET>();
if (network == cryptonote::network_type::TESTNET)
return checkpoint_data<cryptonote::network_type::TESTNET>();
if (network == cryptonote::network_type::DEVNET)
return checkpoint_data<cryptonote::network_type::DEVNET>();
return ""sv;
}
}
} // namespace blocks

View File

@ -31,296 +31,298 @@
#include "checkpoints.h"
#include "epee/string_tools.h"
#include "epee/storages/portable_storage_template_helper.h" // epee json include
#include "epee/serialization/keyvalue_serialization.h"
#include "cryptonote_core/service_node_rules.h"
#include <vector>
#include "blockchain_db/blockchain_db.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "common/oxen.h"
#include "blockchain_db/blockchain_db.h"
#include "common/file.h"
#include "common/fs-format.h"
#include "common/hex.h"
#include "common/oxen.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_core/service_node_rules.h"
#include "epee/serialization/keyvalue_serialization.h"
#include "epee/storages/portable_storage_template_helper.h" // epee json include
#include "epee/string_tools.h"
namespace cryptonote
{
namespace cryptonote {
static auto logcat = log::Cat("checkpoints");
static auto logcat = log::Cat("checkpoints");
bool checkpoint_t::check(crypto::hash const &hash) const
{
bool checkpoint_t::check(crypto::hash const& hash) const {
bool result = block_hash == hash;
if (result)
log::info(logcat, "CHECKPOINT PASSED FOR HEIGHT {} {}", height, block_hash);
log::info(logcat, "CHECKPOINT PASSED FOR HEIGHT {} {}", height, block_hash);
else
log::warning(logcat, "CHECKPOINT FAILED FOR HEIGHT {}. EXPECTED HASH {}GIVEN HASH: {}", height, block_hash, hash);
log::warning(
logcat,
"CHECKPOINT FAILED FOR HEIGHT {}. EXPECTED HASH {}GIVEN HASH: {}",
height,
block_hash,
hash);
return result;
};
};
height_to_hash const HARDCODED_MAINNET_CHECKPOINTS[] =
{
{0, "08ff156d993012b0bdf2816c4bee47c9bbc7930593b70ee02574edddf15ee933"},
{1, "647997953a5ea9b5ab329c2291d4cbb08eed587c287e451eeeb2c79bab9b940f"},
{10, "4a7cd8b9bff380d48d6f3533a5e0509f8589cc77d18218b3f7218846e77738fc"},
{100, "01b8d33a50713ff837f8ad7146021b8e3060e0316b5e4afc407e46cdb50b6760"},
{1000, "5e3b0a1f931885bc0ab1d6ecdc625816576feae29e2f9ac94c5ccdbedb1465ac"},
{86535, "52b7c5a60b97bf1efbf0d63a0aa1a313e8f0abe4627eb354b0c5a73cb1f4391e"},
{97407, "504af73abbaba85a14ddc16634658bf4dcc241dc288b1eaad09e216836b71023"},
{98552, "2058d5c675bd91284f4996435593499c9ab84a5a0f569f57a86cde2e815e57da"},
{144650, "a1ab207afc790675070ecd7aac874eb0691eb6349ea37c44f8f58697a5d6cbc4"},
{266284, "c42801a37a41e3e9f934a266063483646072a94bfc7269ace178e93c91414b1f"},
{301187, "e23e4cf3a2fe3e9f0ffced5cc76426e5bdffd3aad822268f4ad63d82cb958559"},
};
height_to_hash const HARDCODED_MAINNET_CHECKPOINTS[] = {
{0, "08ff156d993012b0bdf2816c4bee47c9bbc7930593b70ee02574edddf15ee933"},
{1, "647997953a5ea9b5ab329c2291d4cbb08eed587c287e451eeeb2c79bab9b940f"},
{10, "4a7cd8b9bff380d48d6f3533a5e0509f8589cc77d18218b3f7218846e77738fc"},
{100, "01b8d33a50713ff837f8ad7146021b8e3060e0316b5e4afc407e46cdb50b6760"},
{1000, "5e3b0a1f931885bc0ab1d6ecdc625816576feae29e2f9ac94c5ccdbedb1465ac"},
{86535, "52b7c5a60b97bf1efbf0d63a0aa1a313e8f0abe4627eb354b0c5a73cb1f4391e"},
{97407, "504af73abbaba85a14ddc16634658bf4dcc241dc288b1eaad09e216836b71023"},
{98552, "2058d5c675bd91284f4996435593499c9ab84a5a0f569f57a86cde2e815e57da"},
{144650, "a1ab207afc790675070ecd7aac874eb0691eb6349ea37c44f8f58697a5d6cbc4"},
{266284, "c42801a37a41e3e9f934a266063483646072a94bfc7269ace178e93c91414b1f"},
{301187, "e23e4cf3a2fe3e9f0ffced5cc76426e5bdffd3aad822268f4ad63d82cb958559"},
};
crypto::hash get_newest_hardcoded_checkpoint(cryptonote::network_type nettype, uint64_t *height)
{
crypto::hash get_newest_hardcoded_checkpoint(cryptonote::network_type nettype, uint64_t* height) {
crypto::hash result{};
*height = 0;
if (nettype != network_type::MAINNET && nettype != network_type::TESTNET)
return result;
return result;
if (nettype == network_type::MAINNET)
{
uint64_t last_index = oxen::array_count(HARDCODED_MAINNET_CHECKPOINTS) - 1;
height_to_hash const &entry = HARDCODED_MAINNET_CHECKPOINTS[last_index];
if (nettype == network_type::MAINNET) {
uint64_t last_index = oxen::array_count(HARDCODED_MAINNET_CHECKPOINTS) - 1;
height_to_hash const& entry = HARDCODED_MAINNET_CHECKPOINTS[last_index];
if (tools::hex_to_type(entry.hash, result))
*height = entry.height;
if (tools::hex_to_type(entry.hash, result))
*height = entry.height;
}
return result;
}
}
bool load_checkpoints_from_json(const fs::path& json_hashfile_fullpath, std::vector<height_to_hash>& checkpoint_hashes)
{
if (std::error_code ec; !fs::exists(json_hashfile_fullpath, ec))
{
log::debug(logcat, "Blockchain checkpoints file not found");
return true;
bool load_checkpoints_from_json(
const fs::path& json_hashfile_fullpath, std::vector<height_to_hash>& checkpoint_hashes) {
if (std::error_code ec; !fs::exists(json_hashfile_fullpath, ec)) {
log::debug(logcat, "Blockchain checkpoints file not found");
return true;
}
height_to_hash_json hashes;
if (std::string contents;
!tools::slurp_file(json_hashfile_fullpath, contents) ||
!epee::serialization::load_t_from_json(hashes, contents))
{
log::error(logcat, "Error loading checkpoints from {}", json_hashfile_fullpath);
return false;
if (std::string contents; !tools::slurp_file(json_hashfile_fullpath, contents) ||
!epee::serialization::load_t_from_json(hashes, contents)) {
log::error(logcat, "Error loading checkpoints from {}", json_hashfile_fullpath);
return false;
}
checkpoint_hashes = std::move(hashes.hashlines);
return true;
}
}
bool checkpoints::get_checkpoint(uint64_t height, checkpoint_t &checkpoint) const
{
try
{
auto guard = db_rtxn_guard(m_db);
return m_db->get_block_checkpoint(height, checkpoint);
bool checkpoints::get_checkpoint(uint64_t height, checkpoint_t& checkpoint) const {
try {
auto guard = db_rtxn_guard(m_db);
return m_db->get_block_checkpoint(height, checkpoint);
} catch (const std::exception& e) {
log::error(
logcat,
"Get block checkpoint from DB failed at height: {}, what = {}",
height,
e.what());
return false;
}
catch (const std::exception &e)
{
log::error(logcat, "Get block checkpoint from DB failed at height: {}, what = {}", height, e.what());
return false;
}
}
//---------------------------------------------------------------------------
bool checkpoints::add_checkpoint(uint64_t height, const std::string& hash_str)
{
}
//---------------------------------------------------------------------------
bool checkpoints::add_checkpoint(uint64_t height, const std::string& hash_str) {
crypto::hash h{};
bool r = tools::hex_to_type(hash_str, h);
CHECK_AND_ASSERT_MES(r, false, "Failed to parse checkpoint hash string into binary representation!");
bool r = tools::hex_to_type(hash_str, h);
CHECK_AND_ASSERT_MES(
r, false, "Failed to parse checkpoint hash string into binary representation!");
checkpoint_t checkpoint = {};
if (get_checkpoint(height, checkpoint))
{
crypto::hash const &curr_hash = checkpoint.block_hash;
CHECK_AND_ASSERT_MES(h == curr_hash, false, "Checkpoint at given height already exists, and hash for new checkpoint was different!");
}
else
{
checkpoint.type = checkpoint_type::hardcoded;
checkpoint.height = height;
checkpoint.block_hash = h;
r = update_checkpoint(checkpoint);
if (get_checkpoint(height, checkpoint)) {
crypto::hash const& curr_hash = checkpoint.block_hash;
CHECK_AND_ASSERT_MES(
h == curr_hash,
false,
"Checkpoint at given height already exists, and hash for new checkpoint was "
"different!");
} else {
checkpoint.type = checkpoint_type::hardcoded;
checkpoint.height = height;
checkpoint.block_hash = h;
r = update_checkpoint(checkpoint);
}
return r;
}
bool checkpoints::update_checkpoint(checkpoint_t const &checkpoint)
{
}
bool checkpoints::update_checkpoint(checkpoint_t const& checkpoint) {
// NOTE(oxen): Assumes checkpoint is valid
bool result = true;
bool result = true;
bool batch_started = false;
try
{
batch_started = m_db->batch_start();
m_db->update_block_checkpoint(checkpoint);
}
catch (const std::exception& e)
{
log::error(logcat, "Failed to add checkpoint with hash: {} at height: {}, what = {}", checkpoint.block_hash, checkpoint.height, e.what());
result = false;
try {
batch_started = m_db->batch_start();
m_db->update_block_checkpoint(checkpoint);
} catch (const std::exception& e) {
log::error(
logcat,
"Failed to add checkpoint with hash: {} at height: {}, what = {}",
checkpoint.block_hash,
checkpoint.height,
e.what());
result = false;
}
if (batch_started)
m_db->batch_stop();
m_db->batch_stop();
return result;
}
//---------------------------------------------------------------------------
void checkpoints::block_add(const block_add_info& info)
{
}
//---------------------------------------------------------------------------
void checkpoints::block_add(const block_add_info& info) {
uint64_t const height = get_block_height(info.block);
if (height < service_nodes::CHECKPOINT_STORE_PERSISTENTLY_INTERVAL || info.block.major_version < hf::hf12_checkpointing)
return;
if (height < service_nodes::CHECKPOINT_STORE_PERSISTENTLY_INTERVAL ||
info.block.major_version < hf::hf12_checkpointing)
return;
uint64_t end_cull_height = 0;
{
checkpoint_t immutable_checkpoint;
if (m_db->get_immutable_checkpoint(&immutable_checkpoint, height + 1))
end_cull_height = immutable_checkpoint.height;
checkpoint_t immutable_checkpoint;
if (m_db->get_immutable_checkpoint(&immutable_checkpoint, height + 1))
end_cull_height = immutable_checkpoint.height;
}
uint64_t start_cull_height = (end_cull_height < service_nodes::CHECKPOINT_STORE_PERSISTENTLY_INTERVAL)
? 0
: end_cull_height - service_nodes::CHECKPOINT_STORE_PERSISTENTLY_INTERVAL;
uint64_t start_cull_height =
(end_cull_height < service_nodes::CHECKPOINT_STORE_PERSISTENTLY_INTERVAL)
? 0
: end_cull_height - service_nodes::CHECKPOINT_STORE_PERSISTENTLY_INTERVAL;
if ((start_cull_height % service_nodes::CHECKPOINT_INTERVAL) > 0)
start_cull_height += (service_nodes::CHECKPOINT_INTERVAL - (start_cull_height % service_nodes::CHECKPOINT_INTERVAL));
start_cull_height +=
(service_nodes::CHECKPOINT_INTERVAL -
(start_cull_height % service_nodes::CHECKPOINT_INTERVAL));
m_last_cull_height = std::max(m_last_cull_height, start_cull_height);
auto guard = db_wtxn_guard(m_db);
for (; m_last_cull_height < end_cull_height; m_last_cull_height += service_nodes::CHECKPOINT_INTERVAL)
{
if (m_last_cull_height % service_nodes::CHECKPOINT_STORE_PERSISTENTLY_INTERVAL == 0)
continue;
auto guard = db_wtxn_guard(m_db);
for (; m_last_cull_height < end_cull_height;
m_last_cull_height += service_nodes::CHECKPOINT_INTERVAL) {
if (m_last_cull_height % service_nodes::CHECKPOINT_STORE_PERSISTENTLY_INTERVAL == 0)
continue;
try
{
m_db->remove_block_checkpoint(m_last_cull_height);
}
catch (const std::exception &e)
{
log::error(logcat, "Pruning block checkpoint on block added failed non-trivially at height: {}, what = {}", m_last_cull_height, e.what());
}
try {
m_db->remove_block_checkpoint(m_last_cull_height);
} catch (const std::exception& e) {
log::error(
logcat,
"Pruning block checkpoint on block added failed non-trivially at height: {}, "
"what = {}",
m_last_cull_height,
e.what());
}
}
if (info.checkpoint)
update_checkpoint(*info.checkpoint);
}
//---------------------------------------------------------------------------
void checkpoints::blockchain_detached(uint64_t height)
{
}
//---------------------------------------------------------------------------
void checkpoints::blockchain_detached(uint64_t height) {
m_last_cull_height = std::min(m_last_cull_height, height);
checkpoint_t top_checkpoint;
auto guard = db_wtxn_guard(m_db);
if (m_db->get_top_checkpoint(top_checkpoint))
{
uint64_t start_height = top_checkpoint.height;
for (size_t delete_height = start_height;
delete_height >= height && delete_height >= service_nodes::CHECKPOINT_INTERVAL;
delete_height -= service_nodes::CHECKPOINT_INTERVAL)
{
try
{
m_db->remove_block_checkpoint(delete_height);
if (m_db->get_top_checkpoint(top_checkpoint)) {
uint64_t start_height = top_checkpoint.height;
for (size_t delete_height = start_height;
delete_height >= height && delete_height >= service_nodes::CHECKPOINT_INTERVAL;
delete_height -= service_nodes::CHECKPOINT_INTERVAL) {
try {
m_db->remove_block_checkpoint(delete_height);
} catch (const std::exception& e) {
log::error(
logcat,
"Remove block checkpoint on detach failed non-trivially at height: {}, "
"what = {}",
delete_height,
e.what());
}
}
catch (const std::exception &e)
{
log::error(logcat, "Remove block checkpoint on detach failed non-trivially at height: {}, what = {}", delete_height, e.what());
}
}
}
}
//---------------------------------------------------------------------------
bool checkpoints::is_in_checkpoint_zone(uint64_t height) const
{
}
//---------------------------------------------------------------------------
bool checkpoints::is_in_checkpoint_zone(uint64_t height) const {
uint64_t top_checkpoint_height = 0;
checkpoint_t top_checkpoint;
if (m_db->get_top_checkpoint(top_checkpoint))
top_checkpoint_height = top_checkpoint.height;
top_checkpoint_height = top_checkpoint.height;
return height <= top_checkpoint_height;
}
//---------------------------------------------------------------------------
bool checkpoints::check_block(uint64_t height, const crypto::hash& h, bool* is_a_checkpoint, bool *service_node_checkpoint) const
{
}
//---------------------------------------------------------------------------
bool checkpoints::check_block(
uint64_t height,
const crypto::hash& h,
bool* is_a_checkpoint,
bool* service_node_checkpoint) const {
checkpoint_t checkpoint;
bool found = get_checkpoint(height, checkpoint);
if (is_a_checkpoint) *is_a_checkpoint = found;
if (service_node_checkpoint) *service_node_checkpoint = false;
if (is_a_checkpoint)
*is_a_checkpoint = found;
if (service_node_checkpoint)
*service_node_checkpoint = false;
if(!found)
return true;
if (!found)
return true;
bool result = checkpoint.check(h);
if (service_node_checkpoint)
*service_node_checkpoint = (checkpoint.type == checkpoint_type::service_node);
*service_node_checkpoint = (checkpoint.type == checkpoint_type::service_node);
return result;
}
//---------------------------------------------------------------------------
bool checkpoints::is_alternative_block_allowed(uint64_t blockchain_height, uint64_t block_height, bool *service_node_checkpoint)
{
}
//---------------------------------------------------------------------------
bool checkpoints::is_alternative_block_allowed(
uint64_t blockchain_height, uint64_t block_height, bool* service_node_checkpoint) {
if (service_node_checkpoint)
*service_node_checkpoint = false;
*service_node_checkpoint = false;
if (0 == block_height)
return false;
return false;
{
std::vector<checkpoint_t> const first_checkpoint = m_db->get_checkpoints_range(0, blockchain_height, 1);
if (first_checkpoint.empty() || blockchain_height < first_checkpoint[0].height)
return true;
std::vector<checkpoint_t> const first_checkpoint =
m_db->get_checkpoints_range(0, blockchain_height, 1);
if (first_checkpoint.empty() || blockchain_height < first_checkpoint[0].height)
return true;
}
checkpoint_t immutable_checkpoint;
uint64_t immutable_height = 0;
if (m_db->get_immutable_checkpoint(&immutable_checkpoint, blockchain_height))
{
immutable_height = immutable_checkpoint.height;
if (service_node_checkpoint)
*service_node_checkpoint = (immutable_checkpoint.type == checkpoint_type::service_node);
if (m_db->get_immutable_checkpoint(&immutable_checkpoint, blockchain_height)) {
immutable_height = immutable_checkpoint.height;
if (service_node_checkpoint)
*service_node_checkpoint = (immutable_checkpoint.type == checkpoint_type::service_node);
}
m_immutable_height = std::max(immutable_height, m_immutable_height);
bool result = block_height > m_immutable_height;
bool result = block_height > m_immutable_height;
return result;
}
//---------------------------------------------------------------------------
uint64_t checkpoints::get_max_height() const
{
}
//---------------------------------------------------------------------------
uint64_t checkpoints::get_max_height() const {
uint64_t result = 0;
checkpoint_t top_checkpoint;
if (m_db->get_top_checkpoint(top_checkpoint))
result = top_checkpoint.height;
result = top_checkpoint.height;
return result;
}
//---------------------------------------------------------------------------
bool checkpoints::init(network_type nettype, BlockchainDB *db)
{
*this = {};
m_db = db;
}
//---------------------------------------------------------------------------
bool checkpoints::init(network_type nettype, BlockchainDB* db) {
*this = {};
m_db = db;
m_nettype = nettype;
if (db->is_read_only())
return true;
return true;
if (nettype == network_type::MAINNET)
{
for (size_t i = 0; i < oxen::array_count(HARDCODED_MAINNET_CHECKPOINTS); ++i)
{
height_to_hash const &checkpoint = HARDCODED_MAINNET_CHECKPOINTS[i];
bool added = add_checkpoint(checkpoint.height, checkpoint.hash);
CHECK_AND_ASSERT(added, false);
}
if (nettype == network_type::MAINNET) {
for (size_t i = 0; i < oxen::array_count(HARDCODED_MAINNET_CHECKPOINTS); ++i) {
height_to_hash const& checkpoint = HARDCODED_MAINNET_CHECKPOINTS[i];
bool added = add_checkpoint(checkpoint.height, checkpoint.hash);
CHECK_AND_ASSERT(added, false);
}
}
return true;
}
}
} // namespace cryptonote

View File

@ -31,91 +31,85 @@
#pragma once
#include <vector>
#include "common/fs.h"
#include "crypto/hash.h"
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "cryptonote_config.h"
#include "cryptonote_core/service_node_voting.h"
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "common/fs.h"
namespace cryptonote {
constexpr std::string_view JSON_HASH_FILE_NAME = "checkpoints.json"sv;
namespace cryptonote
{
constexpr std::string_view JSON_HASH_FILE_NAME = "checkpoints.json"sv;
enum struct checkpoint_type
{
enum struct checkpoint_type {
hardcoded,
service_node,
count,
};
};
struct checkpoint_t
{
uint8_t version = 0;
checkpoint_type type;
uint64_t height;
crypto::hash block_hash;
std::vector<service_nodes::quorum_signature> signatures; // Only service node checkpoints use signatures
uint64_t prev_height; // TODO(doyle): Unused
struct checkpoint_t {
uint8_t version = 0;
checkpoint_type type;
uint64_t height;
crypto::hash block_hash;
std::vector<service_nodes::quorum_signature>
signatures; // Only service node checkpoints use signatures
uint64_t prev_height; // TODO(doyle): Unused
bool check (crypto::hash const &block_hash) const;
static char const *type_to_string(checkpoint_type type)
{
switch(type)
{
case checkpoint_type::hardcoded: return "Hardcoded";
case checkpoint_type::service_node: return "ServiceNode";
default: assert(false); return "XXUnhandledVersion";
}
bool check(crypto::hash const& block_hash) const;
static char const* type_to_string(checkpoint_type type) {
switch (type) {
case checkpoint_type::hardcoded: return "Hardcoded";
case checkpoint_type::service_node: return "ServiceNode";
default: assert(false); return "XXUnhandledVersion";
}
}
BEGIN_SERIALIZE()
FIELD(version)
ENUM_FIELD(type, type < checkpoint_type::count);
FIELD(height)
FIELD(block_hash)
FIELD(signatures)
FIELD(prev_height)
FIELD(version)
ENUM_FIELD(type, type < checkpoint_type::count);
FIELD(height)
FIELD(block_hash)
FIELD(signatures)
FIELD(prev_height)
END_SERIALIZE()
};
};
struct height_to_hash
{
uint64_t height; //!< the height of the checkpoint
std::string hash; //!< the hash for the checkpoint
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(height)
KV_SERIALIZE(hash)
END_KV_SERIALIZE_MAP()
};
struct height_to_hash {
uint64_t height; //!< the height of the checkpoint
std::string hash; //!< the hash for the checkpoint
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(height)
KV_SERIALIZE(hash)
END_KV_SERIALIZE_MAP()
};
/**
* @brief struct for loading many checkpoints from json
*/
struct height_to_hash_json {
std::vector<height_to_hash> hashlines; //!< the checkpoint lines from the file
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(hashlines)
END_KV_SERIALIZE_MAP()
};
/**
* @brief struct for loading many checkpoints from json
*/
struct height_to_hash_json {
std::vector<height_to_hash> hashlines; //!< the checkpoint lines from the file
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(hashlines)
END_KV_SERIALIZE_MAP()
};
crypto::hash get_newest_hardcoded_checkpoint(cryptonote::network_type nettype, uint64_t *height);
bool load_checkpoints_from_json (const fs::path& json_hashfile_fullpath, std::vector<height_to_hash>& checkpoint_hashes);
crypto::hash get_newest_hardcoded_checkpoint(cryptonote::network_type nettype, uint64_t* height);
bool load_checkpoints_from_json(
const fs::path& json_hashfile_fullpath, std::vector<height_to_hash>& checkpoint_hashes);
/**
* @brief A container for blockchain checkpoints
*
* A checkpoint is a pre-defined hash for the block at a given height.
* Some of these are compiled-in, while others can be loaded at runtime
* either from a json file or via DNS from a checkpoint-hosting server.
*/
class checkpoints
{
/**
* @brief A container for blockchain checkpoints
*
* A checkpoint is a pre-defined hash for the block at a given height.
* Some of these are compiled-in, while others can be loaded at runtime
* either from a json file or via DNS from a checkpoint-hosting server.
*/
class checkpoints {
public:
void block_add(const block_add_info& info);
void blockchain_detached(uint64_t height);
bool get_checkpoint(uint64_t height, checkpoint_t &checkpoint) const;
bool get_checkpoint(uint64_t height, checkpoint_t& checkpoint) const;
/**
* @brief adds a checkpoint to the container
*
@ -128,7 +122,7 @@ namespace cryptonote
*/
bool add_checkpoint(uint64_t height, const std::string& hash_str);
bool update_checkpoint(checkpoint_t const &checkpoint);
bool update_checkpoint(checkpoint_t const& checkpoint);
/**
* @brief checks if there is a checkpoint in the future
@ -153,13 +147,18 @@ namespace cryptonote
* @param height the height to be checked
* @param h the hash to be checked
* @param blockchain the blockchain to query ancestor blocks from the current height
* @param is_a_checkpoint optional return-by-pointer if there is a checkpoint at the given height
* @param is_a_checkpoint optional return-by-pointer if there is a checkpoint at the given
* height
*
* @return true if there is no checkpoint at the given height,
* true if the passed parameters match the stored checkpoint,
* false otherwise
*/
bool check_block(uint64_t height, const crypto::hash& h, bool *is_a_checkpoint = nullptr, bool *service_node_checkpoint = nullptr) const;
bool check_block(
uint64_t height,
const crypto::hash& h,
bool* is_a_checkpoint = nullptr,
bool* service_node_checkpoint = nullptr) const;
/**
* @brief checks if alternate chain blocks should be kept for a given height and updates
@ -176,7 +175,10 @@ namespace cryptonote
* @return true if alternate blocks are allowed given the parameters,
* otherwise false
*/
bool is_alternative_block_allowed(uint64_t blockchain_height, uint64_t block_height, bool *service_node_checkpoint = nullptr);
bool is_alternative_block_allowed(
uint64_t blockchain_height,
uint64_t block_height,
bool* service_node_checkpoint = nullptr);
/**
* @brief gets the highest checkpoint height
@ -191,13 +193,13 @@ namespace cryptonote
*
* @return true unless adding a checkpoint fails
*/
bool init(network_type nettype, class BlockchainDB *db);
bool init(network_type nettype, class BlockchainDB* db);
private:
network_type m_nettype = network_type::UNDEFINED;
uint64_t m_last_cull_height = 0;
uint64_t m_immutable_height = 0;
BlockchainDB *m_db;
};
BlockchainDB* m_db;
};
}
} // namespace cryptonote

View File

@ -1,21 +1,21 @@
// Copyright (c) 2017-2019, The Monero 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
@ -26,114 +26,111 @@
// 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.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include "aligned.h"
static inline int is_power_of_2(size_t n) { return n && (n & (n-1)) == 0; }
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static inline int is_power_of_2(size_t n) {
return n && (n & (n - 1)) == 0;
}
#define MAGIC 0xaa0817161500ff81
#define MAGIC_FREED 0xaa0817161500ff82
static void local_abort(const char *msg)
{
fprintf(stderr, "%s\n", msg);
static void local_abort(const char* msg) {
fprintf(stderr, "%s\n", msg);
#ifdef NDEBUG
_exit(1);
_exit(1);
#else
abort();
abort();
#endif
}
typedef struct
{
uint64_t magic;
void *raw;
size_t bytes;
size_t align;
typedef struct {
uint64_t magic;
void* raw;
size_t bytes;
size_t align;
} control;
void *aligned_malloc(size_t bytes, size_t align)
{
void *raw, *ptr;
control *ctrl;
void* aligned_malloc(size_t bytes, size_t align) {
void *raw, *ptr;
control* ctrl;
if (!is_power_of_2(align))
return NULL;
if (bytes > (size_t)-1 - align)
return NULL;
if (bytes + align > (size_t)-1 - sizeof(control))
return NULL;
if (!is_power_of_2(align))
return NULL;
if (bytes > (size_t)-1 - align)
return NULL;
if (bytes + align > (size_t)-1 - sizeof(control))
return NULL;
raw = malloc(bytes + sizeof(control) + align);
if (!raw)
return NULL;
ptr = (void*)(((uintptr_t)raw + align + sizeof(control) - 1) & ~(align-1));
ctrl = ((control*)ptr) - 1;
ctrl->magic = MAGIC;
ctrl->raw = raw;
ctrl->bytes = bytes;
ctrl->align = align;
return ptr;
}
void *aligned_realloc(void *ptr, size_t bytes, size_t align)
{
void *raw, *ptr2;
control *ctrl, *ctrl2;
if (!ptr)
return aligned_malloc(bytes, align);
if (!bytes)
{
aligned_free(ptr);
return NULL;
}
if (!is_power_of_2(align))
return NULL;
ctrl = ((control*)ptr) - 1;
if (ctrl->magic == MAGIC_FREED)
local_abort("Double free detected");
if (ctrl->magic != MAGIC)
local_abort("Freeing unallocated memory");
if (ctrl->align != align)
return NULL;
if (ctrl->bytes >= bytes)
raw = malloc(bytes + sizeof(control) + align);
if (!raw)
return NULL;
ptr = (void*)(((uintptr_t)raw + align + sizeof(control) - 1) & ~(align - 1));
ctrl = ((control*)ptr) - 1;
ctrl->magic = MAGIC;
ctrl->raw = raw;
ctrl->bytes = bytes;
ctrl->align = align;
return ptr;
if (ctrl->bytes > (size_t)-1 - ctrl->align)
return NULL;
if (ctrl->bytes + ctrl->align > (size_t)-1 - sizeof(control))
return NULL;
raw = malloc(bytes + sizeof(control) + ctrl->align);
if (!raw)
return NULL;
ptr2 = (void*)(((uintptr_t)raw + ctrl->align + sizeof(control) - 1) & ~(ctrl->align-1));
memcpy(ptr2, ptr, ctrl->bytes);
ctrl2 = ((control*)ptr2) - 1;
ctrl2->magic = MAGIC;
ctrl2->raw = raw;
ctrl2->bytes = bytes;
ctrl2->align = ctrl->align;
ctrl->magic = MAGIC_FREED;
free(ctrl->raw);
return ptr2;
}
void aligned_free(void *ptr)
{
if (!ptr)
return;
control *ctrl = ((control*)ptr) - 1;
if (ctrl->magic == MAGIC_FREED)
local_abort("Double free detected");
if (ctrl->magic != MAGIC)
local_abort("Freeing unallocated memory");
ctrl->magic = MAGIC_FREED;
free(ctrl->raw);
void* aligned_realloc(void* ptr, size_t bytes, size_t align) {
void *raw, *ptr2;
control *ctrl, *ctrl2;
if (!ptr)
return aligned_malloc(bytes, align);
if (!bytes) {
aligned_free(ptr);
return NULL;
}
if (!is_power_of_2(align))
return NULL;
ctrl = ((control*)ptr) - 1;
if (ctrl->magic == MAGIC_FREED)
local_abort("Double free detected");
if (ctrl->magic != MAGIC)
local_abort("Freeing unallocated memory");
if (ctrl->align != align)
return NULL;
if (ctrl->bytes >= bytes)
return ptr;
if (ctrl->bytes > (size_t)-1 - ctrl->align)
return NULL;
if (ctrl->bytes + ctrl->align > (size_t)-1 - sizeof(control))
return NULL;
raw = malloc(bytes + sizeof(control) + ctrl->align);
if (!raw)
return NULL;
ptr2 = (void*)(((uintptr_t)raw + ctrl->align + sizeof(control) - 1) & ~(ctrl->align - 1));
memcpy(ptr2, ptr, ctrl->bytes);
ctrl2 = ((control*)ptr2) - 1;
ctrl2->magic = MAGIC;
ctrl2->raw = raw;
ctrl2->bytes = bytes;
ctrl2->align = ctrl->align;
ctrl->magic = MAGIC_FREED;
free(ctrl->raw);
return ptr2;
}
void aligned_free(void* ptr) {
if (!ptr)
return;
control* ctrl = ((control*)ptr) - 1;
if (ctrl->magic == MAGIC_FREED)
local_abort("Double free detected");
if (ctrl->magic != MAGIC)
local_abort("Freeing unallocated memory");
ctrl->magic = MAGIC_FREED;
free(ctrl->raw);
}

View File

@ -1,21 +1,21 @@
// Copyright (c) 2019, The Monero 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
@ -26,15 +26,17 @@
// 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.
#pragma once
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void *aligned_malloc(size_t bytes, size_t align);
void *aligned_realloc(void *ptr, size_t bytes, size_t align);
void aligned_free(void *ptr);
#include <stddef.h>
void* aligned_malloc(size_t bytes, size_t align);
void* aligned_realloc(void* ptr, size_t bytes, size_t align);
void aligned_free(void* ptr);
#ifdef __cplusplus
}

View File

@ -1,21 +1,21 @@
// Copyright (c) 2017-2019, The Monero 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
@ -25,56 +25,50 @@
// 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.
//
//
// Most of this file is originally copyright (c) 2017 Raymond Chen, Microsoft
// This algorithm is adapted from Raymond Chen's code:
// https://blogs.msdn.microsoft.com/oldnewthing/20170109-00/?p=95145
#pragma once
#include <vector>
#include <functional>
#include <vector>
#include "logging/oxen_logger.h"
namespace tools
{
namespace tools {
template<typename F>
void apply_permutation(std::vector<size_t> permutation, const F &swap)
{
//sanity check
for (size_t n = 0; n < permutation.size(); ++n)
if (std::find(permutation.begin(), permutation.end(), n) == permutation.end())
{
log::error(globallogcat, "Bad permutation");
throw std::runtime_error("Bad permutation");
return;
template <typename F>
void apply_permutation(std::vector<size_t> permutation, const F& swap) {
// sanity check
for (size_t n = 0; n < permutation.size(); ++n)
if (std::find(permutation.begin(), permutation.end(), n) == permutation.end()) {
log::error(globallogcat, "Bad permutation");
throw std::runtime_error("Bad permutation");
return;
}
for (size_t i = 0; i < permutation.size(); ++i) {
size_t current = i;
while (i != permutation[current]) {
size_t next = permutation[current];
swap(current, next);
permutation[current] = current;
current = next;
}
permutation[current] = current;
}
}
for (size_t i = 0; i < permutation.size(); ++i)
{
size_t current = i;
while (i != permutation[current])
{
size_t next = permutation[current];
swap(current, next);
permutation[current] = current;
current = next;
template <typename T>
void apply_permutation(const std::vector<size_t>& permutation, std::vector<T>& v) {
if (permutation.size() != v.size()) {
log::error(globallogcat, "Mismatched vector sizes");
throw std::runtime_error("Mismatched vector sizes");
return;
}
permutation[current] = current;
}
apply_permutation(permutation, [&v](size_t i0, size_t i1) { std::swap(v[i0], v[i1]); });
}
template<typename T>
void apply_permutation(const std::vector<size_t> &permutation, std::vector<T> &v)
{
if (permutation.size() != v.size())
{
log::error(globallogcat, "Mismatched vector sizes");
throw std::runtime_error("Mismatched vector sizes");
return;
}
apply_permutation(permutation, [&v](size_t i0, size_t i1){ std::swap(v[i0], v[i1]); });
}
}
} // namespace tools

View File

@ -1,21 +1,21 @@
// Copyright (c) 2014-2019, The Monero 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
@ -25,202 +25,201 @@
// 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 "base58.h"
#include <cassert>
#include <cstring>
#include <vector>
#include <string_view>
#include <vector>
#include "crypto/hash.h"
#include "epee/int-util.h"
#include "varint.h"
namespace tools
{
using namespace std::literals;
namespace base58
{
namespace
{
constexpr std::string_view alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"sv;
constexpr size_t full_block_size = 8;
constexpr std::array<uint8_t, full_block_size + 1> encoded_block_sizes = {0, 2, 3, 5, 6, 7, 9, 10, 11};
constexpr size_t full_encoded_block_size = encoded_block_sizes.back();
constexpr std::array<int8_t, full_encoded_block_size + 1> decoded_block_sizes = {0, -1, 1, 2, -1, 3, 4, 5, -1, 6, 7, 8};
constexpr size_t addr_checksum_size = 4;
namespace tools {
using namespace std::literals;
namespace base58 {
namespace {
constexpr std::string_view alphabet =
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"sv;
constexpr size_t full_block_size = 8;
constexpr std::array<uint8_t, full_block_size + 1> encoded_block_sizes = {
0, 2, 3, 5, 6, 7, 9, 10, 11};
constexpr size_t full_encoded_block_size = encoded_block_sizes.back();
constexpr std::array<int8_t, full_encoded_block_size + 1> decoded_block_sizes = {
0, -1, 1, 2, -1, 3, 4, 5, -1, 6, 7, 8};
constexpr size_t addr_checksum_size = 4;
struct reverse_alphabet_table
{
std::array<int8_t, 256> from_b58_lut;
constexpr reverse_alphabet_table() noexcept : from_b58_lut{}
{
for (size_t i = 0; i < from_b58_lut.size(); ++i)
from_b58_lut[i] = -1;
for (size_t i = 0; i < alphabet.size(); i++)
from_b58_lut[alphabet[i]] = i;
struct reverse_alphabet_table {
std::array<int8_t, 256> from_b58_lut;
constexpr reverse_alphabet_table() noexcept : from_b58_lut{} {
for (size_t i = 0; i < from_b58_lut.size(); ++i)
from_b58_lut[i] = -1;
for (size_t i = 0; i < alphabet.size(); i++)
from_b58_lut[alphabet[i]] = i;
}
constexpr int8_t operator[](char letter) const {
return from_b58_lut[static_cast<unsigned char>(letter)];
}
} constexpr reverse_alphabet;
uint64_t uint_8be_to_64(const uint8_t* data, size_t size) {
assert(1 <= size && size <= sizeof(uint64_t));
uint64_t res = 0;
memcpy(reinterpret_cast<uint8_t*>(&res) + sizeof(uint64_t) - size, data, size);
return SWAP64BE(res);
}
constexpr int8_t operator[](char letter) const
{
return from_b58_lut[static_cast<unsigned char>(letter)];
}
} constexpr reverse_alphabet;
void uint_64_to_8be(uint64_t num, size_t size, uint8_t* data) {
assert(1 <= size && size <= sizeof(uint64_t));
uint64_t uint_8be_to_64(const uint8_t* data, size_t size)
{
assert(1 <= size && size <= sizeof(uint64_t));
uint64_t res = 0;
memcpy(reinterpret_cast<uint8_t*>(&res) + sizeof(uint64_t) - size, data, size);
return SWAP64BE(res);
}
void uint_64_to_8be(uint64_t num, size_t size, uint8_t* data)
{
assert(1 <= size && size <= sizeof(uint64_t));
uint64_t num_be = SWAP64BE(num);
memcpy(data, reinterpret_cast<uint8_t*>(&num_be) + sizeof(uint64_t) - size, size);
}
void encode_block(const char* block, size_t size, char* res)
{
assert(1 <= size && size <= full_block_size);
uint64_t num = uint_8be_to_64(reinterpret_cast<const uint8_t*>(block), size);
int i = static_cast<int>(encoded_block_sizes[size]) - 1;
while (0 < num)
{
uint64_t remainder = num % alphabet.size();
num /= alphabet.size();
res[i] = alphabet[remainder];
--i;
}
}
bool decode_block(const char* block, size_t size, char* res)
{
assert(1 <= size && size <= full_encoded_block_size);
int res_size = decoded_block_sizes[size];
if (res_size <= 0)
return false; // Invalid block size
uint64_t res_num = 0;
uint64_t order = 1;
for (size_t i = size - 1; i < size; --i)
{
auto digit = reverse_alphabet[block[i]];
if (digit < 0)
return false; // Invalid symbol
uint64_t product_hi;
uint64_t tmp = res_num + mul128(order, digit, &product_hi);
if (tmp < res_num || 0 != product_hi)
return false; // Overflow
res_num = tmp;
order *= alphabet.size(); // Never overflows, 58^10 < 2^64
uint64_t num_be = SWAP64BE(num);
memcpy(data, reinterpret_cast<uint8_t*>(&num_be) + sizeof(uint64_t) - size, size);
}
if (static_cast<size_t>(res_size) < full_block_size && (UINT64_C(1) << (8 * res_size)) <= res_num)
return false; // Overflow
void encode_block(const char* block, size_t size, char* res) {
assert(1 <= size && size <= full_block_size);
uint_64_to_8be(res_num, res_size, reinterpret_cast<uint8_t*>(res));
uint64_t num = uint_8be_to_64(reinterpret_cast<const uint8_t*>(block), size);
int i = static_cast<int>(encoded_block_sizes[size]) - 1;
while (0 < num) {
uint64_t remainder = num % alphabet.size();
num /= alphabet.size();
res[i] = alphabet[remainder];
--i;
}
}
bool decode_block(const char* block, size_t size, char* res) {
assert(1 <= size && size <= full_encoded_block_size);
int res_size = decoded_block_sizes[size];
if (res_size <= 0)
return false; // Invalid block size
uint64_t res_num = 0;
uint64_t order = 1;
for (size_t i = size - 1; i < size; --i) {
auto digit = reverse_alphabet[block[i]];
if (digit < 0)
return false; // Invalid symbol
uint64_t product_hi;
uint64_t tmp = res_num + mul128(order, digit, &product_hi);
if (tmp < res_num || 0 != product_hi)
return false; // Overflow
res_num = tmp;
order *= alphabet.size(); // Never overflows, 58^10 < 2^64
}
if (static_cast<size_t>(res_size) < full_block_size &&
(UINT64_C(1) << (8 * res_size)) <= res_num)
return false; // Overflow
uint_64_to_8be(res_num, res_size, reinterpret_cast<uint8_t*>(res));
return true;
}
} // namespace
std::string encode(std::string_view data) {
if (data.empty())
return std::string();
size_t full_block_count = data.size() / full_block_size;
size_t last_block_size = data.size() % full_block_size;
size_t res_size =
full_block_count * full_encoded_block_size + encoded_block_sizes[last_block_size];
std::string res(res_size, alphabet[0]);
for (size_t i = 0; i < full_block_count; ++i) {
encode_block(
data.data() + i * full_block_size,
full_block_size,
&res[i * full_encoded_block_size]);
}
if (0 < last_block_size) {
encode_block(
data.data() + full_block_count * full_block_size,
last_block_size,
&res[full_block_count * full_encoded_block_size]);
}
return res;
}
bool decode(std::string_view enc, std::string& data) {
if (enc.empty()) {
data.clear();
return true;
}
size_t full_block_count = enc.size() / full_encoded_block_size;
size_t last_block_size = enc.size() % full_encoded_block_size;
int8_t last_block_decoded_size = decoded_block_sizes[last_block_size];
if (last_block_decoded_size < 0)
return false; // Invalid enc length
size_t data_size = full_block_count * full_block_size + last_block_decoded_size;
data.resize(data_size, 0);
for (size_t i = 0; i < full_block_count; ++i) {
if (!decode_block(
enc.data() + i * full_encoded_block_size,
full_encoded_block_size,
&data[i * full_block_size]))
return false;
}
if (0 < last_block_size) {
if (!decode_block(
enc.data() + full_block_count * full_encoded_block_size,
last_block_size,
&data[full_block_count * full_block_size]))
return false;
}
return true;
}
}
std::string encode(std::string_view data)
{
if (data.empty())
return std::string();
size_t full_block_count = data.size() / full_block_size;
size_t last_block_size = data.size() % full_block_size;
size_t res_size = full_block_count * full_encoded_block_size + encoded_block_sizes[last_block_size];
std::string res(res_size, alphabet[0]);
for (size_t i = 0; i < full_block_count; ++i)
{
encode_block(data.data() + i * full_block_size, full_block_size, &res[i * full_encoded_block_size]);
}
if (0 < last_block_size)
{
encode_block(data.data() + full_block_count * full_block_size, last_block_size, &res[full_block_count * full_encoded_block_size]);
}
return res;
std::string encode_addr(uint64_t tag, std::string_view data) {
std::string buf = get_varint_data(tag);
buf += data;
crypto::hash hash = crypto::cn_fast_hash(buf.data(), buf.size());
const char* hash_data = reinterpret_cast<const char*>(&hash);
buf.append(hash_data, addr_checksum_size);
return encode(buf);
}
bool decode(std::string_view enc, std::string& data)
{
if (enc.empty())
{
data.clear();
bool decode_addr(std::string_view addr, uint64_t& tag, std::string& data) {
std::string addr_data;
bool r = decode(addr, addr_data);
if (!r)
return false;
if (addr_data.size() <= addr_checksum_size)
return false;
std::string checksum(addr_checksum_size, '\0');
checksum = addr_data.substr(addr_data.size() - addr_checksum_size);
addr_data.resize(addr_data.size() - addr_checksum_size);
crypto::hash hash = crypto::cn_fast_hash(addr_data.data(), addr_data.size());
std::string expected_checksum(reinterpret_cast<const char*>(&hash), addr_checksum_size);
if (expected_checksum != checksum)
return false;
int read = tools::read_varint(addr_data.begin(), addr_data.end(), tag);
if (read <= 0)
return false;
data = addr_data.substr(read);
return true;
}
size_t full_block_count = enc.size() / full_encoded_block_size;
size_t last_block_size = enc.size() % full_encoded_block_size;
int8_t last_block_decoded_size = decoded_block_sizes[last_block_size];
if (last_block_decoded_size < 0)
return false; // Invalid enc length
size_t data_size = full_block_count * full_block_size + last_block_decoded_size;
data.resize(data_size, 0);
for (size_t i = 0; i < full_block_count; ++i)
{
if (!decode_block(enc.data() + i * full_encoded_block_size, full_encoded_block_size, &data[i * full_block_size]))
return false;
}
if (0 < last_block_size)
{
if (!decode_block(enc.data() + full_block_count * full_encoded_block_size, last_block_size,
&data[full_block_count * full_block_size]))
return false;
}
return true;
}
std::string encode_addr(uint64_t tag, std::string_view data)
{
std::string buf = get_varint_data(tag);
buf += data;
crypto::hash hash = crypto::cn_fast_hash(buf.data(), buf.size());
const char* hash_data = reinterpret_cast<const char*>(&hash);
buf.append(hash_data, addr_checksum_size);
return encode(buf);
}
bool decode_addr(std::string_view addr, uint64_t& tag, std::string& data)
{
std::string addr_data;
bool r = decode(addr, addr_data);
if (!r) return false;
if (addr_data.size() <= addr_checksum_size) return false;
std::string checksum(addr_checksum_size, '\0');
checksum = addr_data.substr(addr_data.size() - addr_checksum_size);
addr_data.resize(addr_data.size() - addr_checksum_size);
crypto::hash hash = crypto::cn_fast_hash(addr_data.data(), addr_data.size());
std::string expected_checksum(reinterpret_cast<const char*>(&hash), addr_checksum_size);
if (expected_checksum != checksum) return false;
int read = tools::read_varint(addr_data.begin(), addr_data.end(), tag);
if (read <= 0) return false;
data = addr_data.substr(read);
return true;
}
}
}
} // namespace base58
} // namespace tools

View File

@ -1,21 +1,21 @@
// Copyright (c) 2014-2019, The Monero 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
@ -25,7 +25,7 @@
// 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
#pragma once
@ -34,14 +34,10 @@
#include <string>
#include <string_view>
namespace tools
{
namespace base58
{
namespace tools { namespace base58 {
std::string encode(std::string_view data);
bool decode(std::string_view enc, std::string& data);
std::string encode_addr(uint64_t tag, std::string_view data);
bool decode_addr(std::string_view addr, uint64_t& tag, std::string& data);
}
}
}} // namespace tools::base58

View File

@ -1,21 +1,21 @@
// Copyright (c) 2014-2019, The Monero 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
@ -25,66 +25,60 @@
// 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
#pragma once
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/portable_binary_oarchive.hpp>
#include <boost/archive/portable_binary_iarchive.hpp>
#include "epee/misc_log_ex.h"
#include "fs.h"
#include <boost/archive/portable_binary_oarchive.hpp>
#include <fstream>
#include "epee/misc_log_ex.h"
#include "fs.h"
namespace tools
{
template <class T>
bool serialize_obj_to_file(T& obj, const fs::path& file_path)
{
namespace tools {
template <class T>
bool serialize_obj_to_file(T& obj, const fs::path& file_path) {
TRY_ENTRY();
fs::ofstream data_file{file_path, std::ios::binary | std::ios::trunc};
if (data_file.fail())
return false;
return false;
boost::archive::portable_binary_oarchive{data_file} << obj;
if (data_file.fail())
return false;
return false;
data_file.flush();
return true;
CATCH_ENTRY_L0("serialize_obj_to_file", false);
}
}
template <class T>
bool unserialize_obj_from_file(T& obj, const fs::path& file_path)
{
template <class T>
bool unserialize_obj_from_file(T& obj, const fs::path& file_path) {
TRY_ENTRY();
fs::ifstream data_file{file_path, std::ios_base::binary};
if (data_file.fail())
return false;
try
{
// first try reading in portable mode
boost::archive::portable_binary_iarchive{data_file} >> obj;
}
catch(...)
{
// if failed, try reading in unportable mode
auto unportable = file_path;
unportable += ".unportable";
fs::copy_file(file_path, unportable, fs::copy_options::overwrite_existing);
data_file.close();
data_file.open(file_path, std::ios_base::binary);
if (data_file.fail())
return false;
boost::archive::binary_iarchive{data_file} >> obj;
try {
// first try reading in portable mode
boost::archive::portable_binary_iarchive{data_file} >> obj;
} catch (...) {
// if failed, try reading in unportable mode
auto unportable = file_path;
unportable += ".unportable";
fs::copy_file(file_path, unportable, fs::copy_options::overwrite_existing);
data_file.close();
data_file.open(file_path, std::ios_base::binary);
if (data_file.fail())
return false;
boost::archive::binary_iarchive{data_file} >> obj;
}
return !data_file.fail();
CATCH_ENTRY_L0("unserialize_obj_from_file", false);
}
}
} // namespace tools

View File

@ -32,19 +32,18 @@
namespace tools {
uint64_t combinations_count(uint32_t k, uint32_t n)
{
if (k > n) {
throw std::runtime_error("k must not be greater than n");
}
uint64_t combinations_count(uint32_t k, uint32_t n) {
if (k > n) {
throw std::runtime_error("k must not be greater than n");
}
uint64_t c = 1;
for (uint64_t i = 1; i <= k; ++i) {
c *= n--;
c /= i;
}
uint64_t c = 1;
for (uint64_t i = 1; i <= k; ++i) {
c *= n--;
c /= i;
}
return c;
return c;
}
}
} // namespace tools

View File

@ -30,69 +30,64 @@
#pragma once
#include <vector>
#include <stdexcept>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <vector>
namespace tools {
uint64_t combinations_count(uint32_t k, uint32_t n);
template<typename T>
template <typename T>
class Combinator {
public:
Combinator(const std::vector<T>& v) : origin(v) { }
public:
Combinator(const std::vector<T>& v) : origin(v) {}
std::vector<std::vector<T>> combine(size_t k);
std::vector<std::vector<T>> combine(size_t k);
private:
void doCombine(size_t from, size_t k);
private:
void doCombine(size_t from, size_t k);
std::vector<T> origin;
std::vector<std::vector<T>> combinations;
std::vector<size_t> current;
std::vector<T> origin;
std::vector<std::vector<T>> combinations;
std::vector<size_t> current;
};
template<typename T>
std::vector<std::vector<T>> Combinator<T>::combine(size_t k)
{
if (k > origin.size())
{
throw std::runtime_error("k must be smaller than elements number");
}
if (k == 0)
{
throw std::runtime_error("k must be greater than zero");
}
combinations.clear();
doCombine(0, k);
return combinations;
}
template<typename T>
void Combinator<T>::doCombine(size_t from, size_t k)
{
current.push_back(0);
for (size_t i = from; i <= origin.size() - k; ++i)
{
current.back() = i;
if (k > 1) {
doCombine(i + 1, k - 1);
} else {
std::vector<T> comb;
for (auto ind: current) {
comb.push_back(origin[ind]);
}
combinations.push_back(comb);
template <typename T>
std::vector<std::vector<T>> Combinator<T>::combine(size_t k) {
if (k > origin.size()) {
throw std::runtime_error("k must be smaller than elements number");
}
}
current.pop_back();
if (k == 0) {
throw std::runtime_error("k must be greater than zero");
}
combinations.clear();
doCombine(0, k);
return combinations;
}
} //namespace tools
template <typename T>
void Combinator<T>::doCombine(size_t from, size_t k) {
current.push_back(0);
for (size_t i = from; i <= origin.size() - k; ++i) {
current.back() = i;
if (k > 1) {
doCombine(i + 1, k - 1);
} else {
std::vector<T> comb;
for (auto ind : current) {
comb.push_back(origin[ind]);
}
combinations.push_back(comb);
}
}
current.pop_back();
}
} // namespace tools

View File

@ -1,21 +1,21 @@
// Copyright (c) 2014-2019, The Monero 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
@ -25,22 +25,22 @@
// 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 "command_line.h"
#include "common/i18n.h"
#include "common/string_util.h"
#ifdef HAVE_READLINE
# include "epee/readline_buffer.h"
#include "epee/readline_buffer.h"
#endif
#include <iostream>
#ifdef _WIN32
#include "windows.h"
#endif
namespace command_line
{
namespace command_line {
const arg_descriptor<bool> arg_help = {"help", "Produce help message"};
const arg_descriptor<bool> arg_version = {"version", "Output version information"};
@ -51,80 +51,77 @@ const arg_descriptor<bool> arg_version = {"version", "Output version information
#ifdef __linux__
extern "C" {
#include <sys/ioctl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
}
std::pair<unsigned, unsigned> terminal_size() {
struct winsize w;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1)
return {w.ws_col, w.ws_row};
return {w.ws_col, w.ws_row};
return {0, 0};
}
#else
std::pair<unsigned, unsigned> terminal_size() { return {0, 0}; }
std::pair<unsigned, unsigned> terminal_size() {
return {0, 0};
}
#endif
std::pair<unsigned, unsigned> boost_option_sizes() {
std::pair<unsigned, unsigned> result;
std::pair<unsigned, unsigned> result;
result.first = std::max(
terminal_size().first,
boost::program_options::options_description::m_default_line_length);
result.first = std::max(
terminal_size().first,
boost::program_options::options_description::m_default_line_length);
result.second = result.first - boost::program_options::options_description::m_default_line_length / 2;
result.second =
result.first - boost::program_options::options_description::m_default_line_length / 2;
return result;
return result;
}
void clear_screen()
{
void clear_screen() {
#ifdef HAVE_READLINE
rdln::clear_screen();
rdln::clear_screen();
#else
std::cout << "\033[2K"; // clear whole line
std::cout << "\033c"; // clear current screen and scrollback
std::cout << "\033[2J"; // clear current screen only, scrollback is still around
std::cout << "\033[3J"; // does nothing, should clear current screen and scrollback
std::cout << "\033[1;1H"; // move cursor top/left
std::cout << "\r \r" << std::flush; // erase odd chars if the ANSI codes were printed raw
#ifdef _WIN32
COORD coord{0, 0};
CONSOLE_SCREEN_BUFFER_INFO csbi;
HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
if (GetConsoleScreenBufferInfo(h, &csbi))
{
DWORD cbConSize = csbi.dwSize.X * csbi.dwSize.Y, w;
FillConsoleOutputCharacter(h, (TCHAR)' ', cbConSize, coord, &w);
if (GetConsoleScreenBufferInfo(h, &csbi))
FillConsoleOutputAttribute(h, csbi.wAttributes, cbConSize, coord, &w);
SetConsoleCursorPosition(h, coord);
}
#endif
std::cout << "\033[2K"; // clear whole line
std::cout << "\033c"; // clear current screen and scrollback
std::cout << "\033[2J"; // clear current screen only, scrollback is still around
std::cout << "\033[3J"; // does nothing, should clear current screen and scrollback
std::cout << "\033[1;1H"; // move cursor top/left
std::cout << "\r \r"
<< std::flush; // erase odd chars if the ANSI codes were printed raw
#ifdef _WIN32
COORD coord{0, 0};
CONSOLE_SCREEN_BUFFER_INFO csbi;
HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
if (GetConsoleScreenBufferInfo(h, &csbi)) {
DWORD cbConSize = csbi.dwSize.X * csbi.dwSize.Y, w;
FillConsoleOutputCharacter(h, (TCHAR)' ', cbConSize, coord, &w);
if (GetConsoleScreenBufferInfo(h, &csbi))
FillConsoleOutputAttribute(h, csbi.wAttributes, cbConSize, coord, &w);
SetConsoleCursorPosition(h, coord);
}
#endif
#endif
}
bool handle_error_helper(const boost::program_options::options_description& desc, std::function<bool()> parser) {
try
{
return parser();
}
catch (const std::exception& e)
{
std::cerr << "Failed to parse arguments: " << e.what() << std::endl;
std::cerr << desc << std::endl;
return false;
}
catch (...)
{
std::cerr << "Failed to parse arguments: unknown exception" << std::endl;
std::cerr << desc << std::endl;
return false;
}
bool handle_error_helper(
const boost::program_options::options_description& desc, std::function<bool()> parser) {
try {
return parser();
} catch (const std::exception& e) {
std::cerr << "Failed to parse arguments: " << e.what() << std::endl;
std::cerr << desc << std::endl;
return false;
} catch (...) {
std::cerr << "Failed to parse arguments: unknown exception" << std::endl;
std::cerr << desc << std::endl;
return false;
}
}
}
} // namespace command_line

View File

@ -1,21 +1,21 @@
// Copyright (c) 2014-2019, The Monero 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
@ -25,74 +25,80 @@
// 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
#pragma once
#include <functional>
#include <array>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/variables_map.hpp>
#include <functional>
#include <type_traits>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include "common/string_util.h"
#include "common/i18n.h"
#include "common/format.h"
#include "common/i18n.h"
#include "common/string_util.h"
#include "logging/oxen_logger.h"
namespace command_line
{
namespace log = oxen::log;
namespace command_line {
namespace log = oxen::log;
inline const char* tr(const char* str) { return i18n_translate(str, "command_line"); }
inline const char* tr(const char* str) {
return i18n_translate(str, "command_line");
}
/// @return True if `str` is (case-insensitively) y, yes, a potentially translated yes, or any of
/// the optional extra arguments passed in.
template <typename S, typename... More>
bool is_yes(const S& str, const More&... more) { return tools::string_iequal_any(str, "y", "yes", tr("yes"), more...); }
/// @return True if `str` is (case-insensitively) n, no, or a potentially translated no, or any of
/// the optional extra arguments passed in.
template <typename S, typename... More>
bool is_no(const S& str, const More&... more) { return tools::string_iequal_any(str, "n", "no", tr("no"), more...); }
/// @return True if `str` is (case-insensitively) c, cancel, or a potentially translated cancel,
/// or any of the optional extra arguments passed in.
template <typename S, typename... More>
bool is_cancel(const S& str, const More&... more) { return tools::string_iequal_any(str, "c", "cancel", tr("cancel"), more...); }
/// @return True if `str` is (case-insensitively) b, back, or a potentially translated back, or
/// any of the optional extra arguments passed in.
template <typename S, typename... More>
bool is_back(const S& str, const More&... more) { return tools::string_iequal_any(str, "b", "back", tr("back"), more...); }
/// @return True if `str` is (case-insensitively) y, yes, a potentially translated yes, or any of
/// the optional extra arguments passed in.
template <typename S, typename... More>
bool is_yes(const S& str, const More&... more) {
return tools::string_iequal_any(str, "y", "yes", tr("yes"), more...);
}
/// @return True if `str` is (case-insensitively) n, no, or a potentially translated no, or any of
/// the optional extra arguments passed in.
template <typename S, typename... More>
bool is_no(const S& str, const More&... more) {
return tools::string_iequal_any(str, "n", "no", tr("no"), more...);
}
/// @return True if `str` is (case-insensitively) c, cancel, or a potentially translated cancel,
/// or any of the optional extra arguments passed in.
template <typename S, typename... More>
bool is_cancel(const S& str, const More&... more) {
return tools::string_iequal_any(str, "c", "cancel", tr("cancel"), more...);
}
/// @return True if `str` is (case-insensitively) b, back, or a potentially translated back, or
/// any of the optional extra arguments passed in.
template <typename S, typename... More>
bool is_back(const S& str, const More&... more) {
return tools::string_iequal_any(str, "b", "back", tr("back"), more...);
}
template<typename T, bool required = false, bool dependent = false, int NUM_DEPS = 1>
struct arg_descriptor;
template <typename T, bool required = false, bool dependent = false, int NUM_DEPS = 1>
struct arg_descriptor;
template<typename T>
struct arg_descriptor<T, false>
{
template <typename T>
struct arg_descriptor<T, false> {
using value_type = T;
const char* name;
const char* description;
T default_value;
bool not_use_default;
};
};
template<typename T>
struct arg_descriptor<T, true>
{
template <typename T>
struct arg_descriptor<T, true> {
static_assert(!std::is_same_v<T, bool>, "Boolean switch can't be required");
using value_type = T;
const char* name;
const char* description;
};
};
template<typename T>
struct arg_descriptor<T, false, true>
{
template <typename T>
struct arg_descriptor<T, false, true> {
using value_type = T;
const char* name;
@ -104,11 +110,10 @@ namespace command_line
std::function<T(bool, bool, T)> depf;
bool not_use_default;
};
};
template<typename T, int NUM_DEPS>
struct arg_descriptor<T, false, true, NUM_DEPS>
{
template <typename T, int NUM_DEPS>
struct arg_descriptor<T, false, true, NUM_DEPS> {
using value_type = T;
const char* name;
@ -116,200 +121,209 @@ namespace command_line
T default_value;
std::array<const arg_descriptor<bool, false> *, NUM_DEPS> ref;
std::array<const arg_descriptor<bool, false>*, NUM_DEPS> ref;
std::function<T(std::array<bool, NUM_DEPS>, bool, T)> depf;
bool not_use_default;
};
};
template<typename T>
boost::program_options::typed_value<T, char>* make_semantic(const arg_descriptor<T, true>& /*arg*/)
{
template <typename T>
boost::program_options::typed_value<T, char>* make_semantic(
const arg_descriptor<T, true>& /*arg*/) {
return boost::program_options::value<T>()->required();
}
}
template<typename T>
boost::program_options::typed_value<T, char>* make_semantic(const arg_descriptor<T, false>& arg)
{
template <typename T>
boost::program_options::typed_value<T, char>* make_semantic(const arg_descriptor<T, false>& arg) {
auto semantic = boost::program_options::value<T>();
if (!arg.not_use_default)
semantic->default_value(arg.default_value);
semantic->default_value(arg.default_value);
return semantic;
}
}
namespace {
namespace {
template <typename T>
std::string arg_stringify(const T& a) {
return "{}"_format(a);
return "{}"_format(a);
}
template <typename T>
std::string arg_stringify(const std::vector<T>& v) {
return "{{{}}}"_format(fmt::join(v, ","));
return "{{{}}}"_format(fmt::join(v, ","));
}
}
} // namespace
template<typename T>
boost::program_options::typed_value<T, char>* make_semantic(const arg_descriptor<T, false, true>& arg)
{
template <typename T>
boost::program_options::typed_value<T, char>* make_semantic(
const arg_descriptor<T, false, true>& arg) {
auto semantic = boost::program_options::value<T>();
if (!arg.not_use_default) {
auto default_display = "{}, {} if '{}'"_format(
arg_stringify(arg.depf(false, true, arg.default_value)),
arg_stringify(arg.depf(true, true, arg.default_value)),
arg.ref.name);
semantic->default_value(arg.depf(arg.ref.default_value, true, arg.default_value), default_display);
auto default_display = "{}, {} if '{}'"_format(
arg_stringify(arg.depf(false, true, arg.default_value)),
arg_stringify(arg.depf(true, true, arg.default_value)),
arg.ref.name);
semantic->default_value(
arg.depf(arg.ref.default_value, true, arg.default_value), default_display);
}
return semantic;
}
}
template<typename T, int NUM_DEPS>
boost::program_options::typed_value<T, char>* make_semantic(const arg_descriptor<T, false, true, NUM_DEPS>& arg)
{
template <typename T, int NUM_DEPS>
boost::program_options::typed_value<T, char>* make_semantic(
const arg_descriptor<T, false, true, NUM_DEPS>& arg) {
auto semantic = boost::program_options::value<T>();
if (!arg.not_use_default) {
std::array<bool, NUM_DEPS> depval;
depval.fill(false);
auto default_display = arg_stringify(arg.depf(depval, true, arg.default_value));
for (size_t i = 0; i < depval.size(); ++i)
{
std::array<bool, NUM_DEPS> depval;
depval.fill(false);
depval[i] = true;
fmt::format_to(std::back_inserter(default_display),
", {} if '{}'",
arg_stringify(arg.depf(depval, true, arg.default_value)),
arg.ref[i]->name);
}
for (size_t i = 0; i < depval.size(); ++i)
depval[i] = arg.ref[i]->default_value;
semantic->default_value(arg.depf(depval, true, arg.default_value), default_display);
auto default_display = arg_stringify(arg.depf(depval, true, arg.default_value));
for (size_t i = 0; i < depval.size(); ++i) {
depval.fill(false);
depval[i] = true;
fmt::format_to(
std::back_inserter(default_display),
", {} if '{}'",
arg_stringify(arg.depf(depval, true, arg.default_value)),
arg.ref[i]->name);
}
for (size_t i = 0; i < depval.size(); ++i)
depval[i] = arg.ref[i]->default_value;
semantic->default_value(arg.depf(depval, true, arg.default_value), default_display);
}
return semantic;
}
}
template<typename T>
boost::program_options::typed_value<T, char>* make_semantic(const arg_descriptor<T, false>& arg, const T& def)
{
template <typename T>
boost::program_options::typed_value<T, char>* make_semantic(
const arg_descriptor<T, false>& arg, const T& def) {
auto semantic = boost::program_options::value<T>();
if (!arg.not_use_default)
semantic->default_value(def);
semantic->default_value(def);
return semantic;
}
}
template<typename T>
boost::program_options::typed_value<std::vector<T>, char>* make_semantic(const arg_descriptor<std::vector<T>, false>& /*arg*/)
{
auto semantic = boost::program_options::value< std::vector<T> >();
template <typename T>
boost::program_options::typed_value<std::vector<T>, char>* make_semantic(
const arg_descriptor<std::vector<T>, false>& /*arg*/) {
auto semantic = boost::program_options::value<std::vector<T>>();
semantic->default_value(std::vector<T>(), "");
return semantic;
}
}
template<typename T, bool required, bool dependent, int NUM_DEPS>
void add_arg(boost::program_options::options_description& description, const arg_descriptor<T, required, dependent, NUM_DEPS>& arg, bool unique = true)
{
if (0 != description.find_nothrow(arg.name, false))
{
if (!unique)
log::error(globallogcat, "Argument already exists: {}", arg.name);
return;
template <typename T, bool required, bool dependent, int NUM_DEPS>
void add_arg(
boost::program_options::options_description& description,
const arg_descriptor<T, required, dependent, NUM_DEPS>& arg,
bool unique = true) {
if (0 != description.find_nothrow(arg.name, false)) {
if (!unique)
log::error(globallogcat, "Argument already exists: {}", arg.name);
return;
}
description.add_options()(arg.name, make_semantic(arg), arg.description);
}
}
template<typename T>
void add_arg(boost::program_options::options_description& description, const arg_descriptor<T, false>& arg, const T& def, bool unique = true)
{
if (0 != description.find_nothrow(arg.name, false))
{
if (!unique)
log::error(globallogcat, "Argument already exists: {}", arg.name);
return;
template <typename T>
void add_arg(
boost::program_options::options_description& description,
const arg_descriptor<T, false>& arg,
const T& def,
bool unique = true) {
if (0 != description.find_nothrow(arg.name, false)) {
if (!unique)
log::error(globallogcat, "Argument already exists: {}", arg.name);
return;
}
description.add_options()(arg.name, make_semantic(arg, def), arg.description);
}
}
template<>
inline void add_arg(boost::program_options::options_description& description, const arg_descriptor<bool, false>& arg, bool unique)
{
if (0 != description.find_nothrow(arg.name, false))
{
if (!unique)
log::error(globallogcat, "Argument already exists: {}", arg.name);
return;
template <>
inline void add_arg(
boost::program_options::options_description& description,
const arg_descriptor<bool, false>& arg,
bool unique) {
if (0 != description.find_nothrow(arg.name, false)) {
if (!unique)
log::error(globallogcat, "Argument already exists: {}", arg.name);
return;
}
description.add_options()(arg.name, boost::program_options::bool_switch(), arg.description);
}
}
template<typename charT>
boost::program_options::basic_parsed_options<charT> parse_command_line(int argc, const charT* const argv[],
const boost::program_options::options_description& desc, bool allow_unregistered = false)
{
template <typename charT>
boost::program_options::basic_parsed_options<charT> parse_command_line(
int argc,
const charT* const argv[],
const boost::program_options::options_description& desc,
bool allow_unregistered = false) {
auto parser = boost::program_options::command_line_parser(argc, argv);
parser.options(desc);
if (allow_unregistered)
{
parser.allow_unregistered();
if (allow_unregistered) {
parser.allow_unregistered();
}
return parser.run();
}
}
bool handle_error_helper(const boost::program_options::options_description& desc, std::function<bool()> parser);
bool handle_error_helper(
const boost::program_options::options_description& desc, std::function<bool()> parser);
template<typename T, bool required, bool dependent, int NUM_DEPS>
std::enable_if_t<!std::is_same_v<T, bool>, bool> has_arg(const boost::program_options::variables_map& vm, const arg_descriptor<T, required, dependent, NUM_DEPS>& arg)
{
template <typename T, bool required, bool dependent, int NUM_DEPS>
std::enable_if_t<!std::is_same_v<T, bool>, bool> has_arg(
const boost::program_options::variables_map& vm,
const arg_descriptor<T, required, dependent, NUM_DEPS>& arg) {
auto value = vm[arg.name];
return !value.empty();
}
}
template<typename T, bool required, bool dependent, int NUM_DEPS>
bool is_arg_defaulted(const boost::program_options::variables_map& vm, const arg_descriptor<T, required, dependent, NUM_DEPS>& arg)
{
template <typename T, bool required, bool dependent, int NUM_DEPS>
bool is_arg_defaulted(
const boost::program_options::variables_map& vm,
const arg_descriptor<T, required, dependent, NUM_DEPS>& arg) {
return vm[arg.name].defaulted();
}
}
template<typename T>
T get_arg(const boost::program_options::variables_map& vm, const arg_descriptor<T, false, true>& arg)
{
template <typename T>
T get_arg(
const boost::program_options::variables_map& vm,
const arg_descriptor<T, false, true>& arg) {
return arg.depf(get_arg(vm, arg.ref), is_arg_defaulted(vm, arg), vm[arg.name].template as<T>());
}
}
template<typename T, int NUM_DEPS>
T get_arg(const boost::program_options::variables_map& vm, const arg_descriptor<T, false, true, NUM_DEPS>& arg)
{
template <typename T, int NUM_DEPS>
T get_arg(
const boost::program_options::variables_map& vm,
const arg_descriptor<T, false, true, NUM_DEPS>& arg) {
std::array<bool, NUM_DEPS> depval;
for (size_t i = 0; i < depval.size(); ++i)
depval[i] = get_arg(vm, *arg.ref[i]);
depval[i] = get_arg(vm, *arg.ref[i]);
return arg.depf(depval, is_arg_defaulted(vm, arg), vm[arg.name].template as<T>());
}
template<typename T, bool required>
T get_arg(const boost::program_options::variables_map& vm, const arg_descriptor<T, required>& arg)
{
return vm[arg.name].template as<T>();
}
template<bool dependent, int NUM_DEPS>
inline bool has_arg(const boost::program_options::variables_map& vm, const arg_descriptor<bool, false, dependent, NUM_DEPS>& arg)
{
return get_arg(vm, arg);
}
extern const arg_descriptor<bool> arg_help;
extern const arg_descriptor<bool> arg_version;
/// Returns the terminal width and height (in characters), if supported on this system and
/// available. Returns {0,0} if not available or could not be determined.
std::pair<unsigned, unsigned> terminal_size();
/// Returns the ideal line width and description width values for
/// boost::program_options::options_description, using the terminal width (if available). Returns
/// the boost defaults if terminal width isn't available.
std::pair<unsigned, unsigned> boost_option_sizes();
// Clears the screen using readline, if available, otherwise trying some terminal escape hacks.
void clear_screen();
}
template <typename T, bool required>
T get_arg(const boost::program_options::variables_map& vm, const arg_descriptor<T, required>& arg) {
return vm[arg.name].template as<T>();
}
template <bool dependent, int NUM_DEPS>
inline bool has_arg(
const boost::program_options::variables_map& vm,
const arg_descriptor<bool, false, dependent, NUM_DEPS>& arg) {
return get_arg(vm, arg);
}
extern const arg_descriptor<bool> arg_help;
extern const arg_descriptor<bool> arg_version;
/// Returns the terminal width and height (in characters), if supported on this system and
/// available. Returns {0,0} if not available or could not be determined.
std::pair<unsigned, unsigned> terminal_size();
/// Returns the ideal line width and description width values for
/// boost::program_options::options_description, using the terminal width (if available). Returns
/// the boost defaults if terminal width isn't available.
std::pair<unsigned, unsigned> boost_option_sizes();
// Clears the screen using readline, if available, otherwise trying some terminal escape hacks.
void clear_screen();
} // namespace command_line

View File

@ -25,15 +25,14 @@
// 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
#pragma once
namespace tools
{
struct login;
class password_container;
class t_http_connection;
class threadpool;
}
namespace tools {
struct login;
class password_container;
class t_http_connection;
class threadpool;
} // namespace tools

View File

@ -1,10 +1,11 @@
#include <fnmatch.h>
#include <glob.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <cstddef>
#include <cstdint>
#include <strings.h>
#include <string.h>
#include <glob.h>
#include <unistd.h>
#include <fnmatch.h>
#if defined(HAVE_SYS_SELECT_H)
#include <sys/select.h>
@ -12,9 +13,8 @@
// Prior to GLIBC_2.14, memcpy was aliased to memmove.
extern "C" void* memmove(void* a, const void* b, size_t c);
//extern "C" void* memset(void* a, int b, long unsigned int c);
extern "C" void* memcpy(void* a, const void* b, size_t c)
{
// extern "C" void* memset(void* a, int b, long unsigned int c);
extern "C" void* memcpy(void* a, const void* b, size_t c) {
return memmove(a, b, c);
}
@ -24,8 +24,7 @@ extern "C" void __chk_fail(void) __attribute__((__noreturn__));
extern "C" int64_t __udivmoddi4(uint64_t u, uint64_t v, uint64_t* rp);
extern "C" int64_t __wrap___divmoddi4(int64_t u, int64_t v, int64_t* rp)
{
extern "C" int64_t __wrap___divmoddi4(int64_t u, int64_t v, int64_t* rp) {
int32_t c1 = 0, c2 = 0;
int64_t uu = u, vv = v;
int64_t w;
@ -54,24 +53,20 @@ extern "C" int64_t __wrap___divmoddi4(int64_t u, int64_t v, int64_t* rp)
#undef explicit_bzero
/* Set LEN bytes of S to 0. The compiler will not delete a call to
this function, even if S is dead after the call. */
void
explicit_bzero (void *s, size_t len)
{
memset (s, '\0', len);
/* Compiler barrier. */
asm volatile ("" ::: "memory");
void explicit_bzero(void* s, size_t len) {
memset(s, '\0', len);
/* Compiler barrier. */
asm volatile("" ::: "memory");
}
// Redefine explicit_bzero_chk
void
__explicit_bzero_chk (void *dst, size_t len, size_t dstlen)
{
/* Inline __memset_chk to avoid a PLT reference to __memset_chk. */
if (__glibc_unlikely (dstlen < len))
__chk_fail ();
memset (dst, '\0', len);
/* Compiler barrier. */
asm volatile ("" ::: "memory");
void __explicit_bzero_chk(void* dst, size_t len, size_t dstlen) {
/* Inline __memset_chk to avoid a PLT reference to __memset_chk. */
if (__glibc_unlikely(dstlen < len))
__chk_fail();
memset(dst, '\0', len);
/* Compiler barrier. */
asm volatile("" ::: "memory");
}
/* libc-internal references use the hidden
__explicit_bzero_chk_internal symbol. This is necessary if
@ -80,7 +75,11 @@ __explicit_bzero_chk (void *dst, size_t len, size_t dstlen)
#define strong_alias (__explicit_bzero_chk, __explicit_bzero_chk_internal)
#undef glob
extern "C" int glob_old(const char * pattern, int flags, int (*errfunc) (const char *epath, int eerrno), glob_t *pglob);
extern "C" int glob_old(
const char* pattern,
int flags,
int (*errfunc)(const char* epath, int eerrno),
glob_t* pglob);
#ifdef __i386__
__asm__(".symver glob_old,glob@GLIBC_2.0");
#elif defined(__amd64__)
@ -91,8 +90,10 @@ __asm(".symver glob_old,glob@GLIBC_2.4");
__asm__(".symver glob_old,glob@GLIBC_2.17");
#endif
extern "C" int __wrap_glob(const char * pattern, int flags, int (*errfunc) (const char *epath, int eerrno), glob_t *pglob)
{
extern "C" int __wrap_glob(
const char* pattern,
int flags,
int (*errfunc)(const char* epath, int eerrno),
glob_t* pglob) {
return glob_old(pattern, flags, errfunc, pglob);
}

View File

@ -28,49 +28,36 @@
#include <string>
namespace
{
struct category final : std::error_category
{
virtual const char* name() const noexcept override final
{
return "common_category()";
namespace {
struct category final : std::error_category {
virtual const char* name() const noexcept override final { return "common_category()"; }
virtual std::string message(int value) const override final {
switch (common_error(value)) {
case common_error::kInvalidArgument:
return std::error_code{static_cast<int>(std::errc::invalid_argument), *this}
.message();
case common_error::kInvalidErrorCode:
return "expect<T> was given an error value of zero";
default: break;
}
return "Unknown basic_category() value";
}
virtual std::string message(int value) const override final
{
switch (common_error(value))
{
case common_error::kInvalidArgument:
return std::error_code{static_cast<int>(std::errc::invalid_argument), *this}.message();
case common_error::kInvalidErrorCode:
return "expect<T> was given an error value of zero";
default:
break;
}
return "Unknown basic_category() value";
virtual std::error_condition default_error_condition(int value) const noexcept override final {
// maps specific errors to generic `std::errc` cases.
switch (common_error(value)) {
case common_error::kInvalidArgument:
case common_error::kInvalidErrorCode: return std::errc::invalid_argument;
default: break;
}
return std::error_condition{value, *this};
}
};
virtual std::error_condition default_error_condition(int value) const noexcept override final
{
// maps specific errors to generic `std::errc` cases.
switch (common_error(value))
{
case common_error::kInvalidArgument:
case common_error::kInvalidErrorCode:
return std::errc::invalid_argument;
default:
break;
}
return std::error_condition{value, *this};
}
};
const category instance{};
} // namespace
const category instance{};
}
std::error_category const& common_category() noexcept
{
std::error_category const& common_category() noexcept {
return instance;
}

View File

@ -28,24 +28,19 @@
#include <system_error>
enum class common_error : int
{
enum class common_error : int {
// 0 is reserved for no error, as per expect<T>
kInvalidArgument = 1, //!< A function argument is invalid
kInvalidErrorCode //!< Default `std::error_code` given to `expect<T>`
kInvalidArgument = 1, //!< A function argument is invalid
kInvalidErrorCode //!< Default `std::error_code` given to `expect<T>`
};
std::error_category const& common_category() noexcept;
inline std::error_code make_error_code(::common_error value) noexcept
{
inline std::error_code make_error_code(::common_error value) noexcept {
return std::error_code{static_cast<int>(value), common_category()};
}
namespace std
{
template<>
struct is_error_code_enum<::common_error>
: true_type
{};
}
namespace std {
template <>
struct is_error_code_enum<::common_error> : true_type {};
} // namespace std

View File

@ -26,43 +26,38 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "expect.h"
#include "common/fs.h"
#include <string>
namespace detail
{
namespace
{
std::string generate_error(const char* msg, const char* file, unsigned line)
{
std::string error_msg{};
if (msg)
{
error_msg.append(msg);
if (file)
error_msg.append(" (");
}
#include "common/fs.h"
namespace detail {
namespace {
std::string generate_error(const char* msg, const char* file, unsigned line) {
std::string error_msg{};
if (msg) {
error_msg.append(msg);
if (file)
{
error_msg.append("thrown at ");
// remove path, get just filename + extension
error_msg.append(fs::path(file).filename().string());
error_msg.push_back(':');
error_msg.append(std::to_string(line));
}
if (msg && file)
error_msg.push_back(')');
return error_msg;
error_msg.append(" (");
}
}
if (file) {
error_msg.append("thrown at ");
void expect::throw_(std::error_code ec, const char* msg, const char* file, unsigned line)
{
if (msg || file)
throw std::system_error{ec, generate_error(msg, file, line)};
throw std::system_error{ec};
// remove path, get just filename + extension
error_msg.append(fs::path(file).filename().string());
error_msg.push_back(':');
error_msg.append(std::to_string(line));
}
if (msg && file)
error_msg.push_back(')');
return error_msg;
}
} // detail
} // namespace
void expect::throw_(std::error_code ec, const char* msg, const char* file, unsigned line) {
if (msg || file)
throw std::system_error{ec, generate_error(msg, file, line)};
throw std::system_error{ec};
}
} // namespace detail

View File

@ -37,19 +37,17 @@
//! If precondition fails, return `::error::kInvalidArgument` in current scope.
#define MONERO_PRECOND(...) \
do \
{ \
if (!( __VA_ARGS__ )) \
do { \
if (!(__VA_ARGS__)) \
return {::common_error::kInvalidArgument}; \
} while (0)
//! Check `expect<void>` and return errors in current scope.
#define MONERO_CHECK(...) \
do \
{ \
const ::expect<void> result = __VA_ARGS__ ; \
if (!result) \
return result.error(); \
#define MONERO_CHECK(...) \
do { \
const ::expect<void> result = __VA_ARGS__; \
if (!result) \
return result.error(); \
} while (0)
/*! Get `T` from `expect<T>` by `std::move` as-if by function call.
@ -57,38 +55,34 @@
\throw std::system_error with `expect<T>::error()`, filename and line
number when `expect<T>::has_error() == true`.*/
#define MONERO_UNWRAP(...) \
::detail::expect::unwrap( __VA_ARGS__ , nullptr, __FILE__ , __LINE__ )
#define MONERO_UNWRAP(...) ::detail::expect::unwrap(__VA_ARGS__, nullptr, __FILE__, __LINE__)
/* \throw std::system_error with `code` and `msg` as part of the details. The
filename and line number will automatically be injected into the explanation
string. `code` can be any enum convertible to `std::error_code`. */
#define MONERO_THROW(code, msg) \
::detail::expect::throw_( code , msg , __FILE__ , __LINE__ )
#define MONERO_THROW(code, msg) ::detail::expect::throw_(code, msg, __FILE__, __LINE__)
template <typename>
class expect;
template<typename> class expect;
namespace detail {
struct expect {
//! \throw std::system_error with `ec`, optional `msg` and/or optional `file` + `line`.
static void throw_(std::error_code ec, const char* msg, const char* file, unsigned line);
namespace detail
{
struct expect
{
//! \throw std::system_error with `ec`, optional `msg` and/or optional `file` + `line`.
static void throw_(std::error_code ec, const char* msg, const char* file, unsigned line);
//! If `result.has_error()` call `throw_`. Otherwise, \return `*result` by move.
template <typename T>
static T unwrap(::expect<T>&& result, const char* error_msg, const char* file, unsigned line) {
if (!result)
throw_(result.error(), error_msg, file, line);
return std::move(*result);
}
//! If `result.has_error()` call `throw_`. Otherwise, \return `*result` by move.
template<typename T>
static T unwrap(::expect<T>&& result, const char* error_msg, const char* file, unsigned line)
{
if (!result)
throw_(result.error(), error_msg, file, line);
return std::move(*result);
}
//! If `result.has_error()` call `throw_`.
static void unwrap(::expect<void>&& result, const char* error_msg, const char* file, unsigned line);
};
}
//! If `result.has_error()` call `throw_`.
static void unwrap(
::expect<void>&& result, const char* error_msg, const char* file, unsigned line);
};
} // namespace detail
/*!
`expect<T>` is a value or error implementation, similar to Rust std::result
@ -105,8 +99,8 @@ namespace detail
types have a `operator==` defined between them (i.e.
`assert(expect<int>{100} == expect<short>{100});`). Comparisons can also be
done against `std::error_code` objects or error code enums directly (i.e.
`assert(expect<int>{make_error_code(common_error::kInvalidArgument)} == error::kInvalidArgument)`).
Comparison of default constructed `std::error_code` will always fail.
`assert(expect<int>{make_error_code(common_error::kInvalidArgument)} ==
error::kInvalidArgument)`). Comparison of default constructed `std::error_code` will always fail.
"Generic" comparisons can be done with `std::error_condition` via the `matches`
method only (i.e.
`assert(expect<int>{make_error_code{common_error::kInvalidErrorCode}.matches(std::errc::invalid_argument))`),
@ -124,16 +118,13 @@ namespace detail
\note See `src/common/error.h` for creating a custom error enum.
*/
template<typename T>
class expect
{
template <typename T>
class expect {
static_assert(std::is_nothrow_destructible<T>(), "T must have a nothrow destructor");
template<typename U>
static constexpr bool is_convertible() noexcept
{
return std::is_constructible<T, U>() &&
std::is_convertible<U, T>();
template <typename U>
static constexpr bool is_convertible() noexcept {
return std::is_constructible<T, U>() && std::is_convertible<U, T>();
}
// MEMBERS
@ -141,32 +132,28 @@ class expect
std::aligned_storage_t<sizeof(T), alignof(T)> storage_;
// MEMBERS
T& get() noexcept
{
T& get() noexcept {
assert(has_value());
return *reinterpret_cast<T*>(std::addressof(storage_));
}
T const& get() const noexcept
{
T const& get() const noexcept {
assert(has_value());
return *reinterpret_cast<T const*>(std::addressof(storage_));
}
template<typename U>
void store(U&& value) noexcept(std::is_nothrow_constructible<T, U>())
{
template <typename U>
void store(U&& value) noexcept(std::is_nothrow_constructible<T, U>()) {
new (std::addressof(storage_)) T{std::forward<U>(value)};
code_ = std::error_code{};
}
void maybe_throw() const
{
void maybe_throw() const {
if (has_error())
::detail::expect::throw_(error(), nullptr, nullptr, 0);
}
public:
public:
using value_type = T;
using error_type = std::error_code;
@ -175,62 +162,52 @@ public:
/*! Store an error, `code`, in the `expect` object. If `code` creates a
`std::error_code` object whose `.value() == 0`, then `error()` will be set
to `::common_error::kInvalidErrorCode`. */
expect(std::error_code const& code) noexcept
: code_(code), storage_()
{
expect(std::error_code const& code) noexcept : code_(code), storage_() {
if (!has_error())
code_ = ::common_error::kInvalidErrorCode;
}
//! Store a value, `val`, in the `expect` object.
expect(T val) noexcept(std::is_nothrow_move_constructible<T>())
: code_(), storage_()
{
expect(T val) noexcept(std::is_nothrow_move_constructible<T>()) : code_(), storage_() {
store(std::move(val));
}
expect(expect const& src) noexcept(std::is_nothrow_copy_constructible<T>())
: code_(src.error()), storage_()
{
expect(expect const& src) noexcept(std::is_nothrow_copy_constructible<T>()) :
code_(src.error()), storage_() {
if (src.has_value())
store(src.get());
}
//! Copy conversion from `U` to `T`.
template<typename U, typename = std::enable_if_t<is_convertible<U const&>()>>
expect(expect<U> const& src) noexcept(std::is_nothrow_constructible<T, U const&>())
: code_(src.error()), storage_()
{
template <typename U, typename = std::enable_if_t<is_convertible<U const&>()>>
expect(expect<U> const& src) noexcept(std::is_nothrow_constructible<T, U const&>()) :
code_(src.error()), storage_() {
if (src.has_value())
store(*src);
}
expect(expect&& src) noexcept(std::is_nothrow_move_constructible<T>())
: code_(src.error()), storage_()
{
expect(expect&& src) noexcept(std::is_nothrow_move_constructible<T>()) :
code_(src.error()), storage_() {
if (src.has_value())
store(std::move(src.get()));
}
//! Move conversion from `U` to `T`.
template<typename U, typename = std::enable_if_t<is_convertible<U>()>>
expect(expect<U>&& src) noexcept(std::is_nothrow_constructible<T, U>())
: code_(src.error()), storage_()
{
template <typename U, typename = std::enable_if_t<is_convertible<U>()>>
expect(expect<U>&& src) noexcept(std::is_nothrow_constructible<T, U>()) :
code_(src.error()), storage_() {
if (src.has_value())
store(std::move(*src));
}
~expect() noexcept
{
~expect() noexcept {
if (has_value())
get().~T();
}
expect& operator=(expect const& src) noexcept(std::is_nothrow_copy_constructible<T>() && std::is_nothrow_copy_assignable<T>())
{
if (this != std::addressof(src))
{
expect& operator=(expect const& src) noexcept(
std::is_nothrow_copy_constructible<T>() && std::is_nothrow_copy_assignable<T>()) {
if (this != std::addressof(src)) {
if (has_value() && src.has_value())
get() = src.get();
else if (has_value())
@ -244,10 +221,9 @@ public:
/*! Move `src` into `this`. If `src.has_value() && addressof(src) != this`
then `src.value() will be in a "moved from state". */
expect& operator=(expect&& src) noexcept(std::is_nothrow_move_constructible<T>() && std::is_nothrow_move_assignable<T>())
{
if (this != std::addressof(src))
{
expect& operator=(expect&& src) noexcept(
std::is_nothrow_move_constructible<T>() && std::is_nothrow_move_assignable<T>()) {
if (this != std::addressof(src)) {
if (has_value() && src.has_value())
get() = std::move(src.get());
else if (has_value())
@ -272,23 +248,20 @@ public:
std::error_code error() const noexcept { return code_; }
//! \return Value if `has_value()` otherwise \throw `std::system_error{error()}`.
T& value() &
{
T& value() & {
maybe_throw();
return get();
}
//! \return Value if `has_value()` otherwise \throw `std::system_error{error()}`.
T const& value() const &
{
T const& value() const& {
maybe_throw();
return get();
}
/*! Same as other overloads, but expressions such as `foo(bar().value())`
will automatically perform moves with no copies. */
T&& value() &&
{
T&& value() && {
maybe_throw();
return std::move(get());
}
@ -302,57 +275,45 @@ public:
//! \return Value, \pre `has_value()`.
T const& operator*() const noexcept { return get(); }
/*!
/*!
\note This function is `noexcept` when `U == T` is `noexcept`.
\return True if `has_value() == rhs.has_value()` and if values or errors are equal.
*/
template<typename U>
bool equal(expect<U> const& rhs) const noexcept(noexcept(*std::declval<expect<T>>() == *rhs))
{
return has_value() && rhs.has_value() ?
get() == *rhs : error() == rhs.error();
template <typename U>
bool equal(expect<U> const& rhs) const noexcept(noexcept(*std::declval<expect<T>>() == *rhs)) {
return has_value() && rhs.has_value() ? get() == *rhs : error() == rhs.error();
}
//! \return False if `has_value()`, otherwise `error() == rhs`.
bool equal(std::error_code const& rhs) const noexcept
{
return has_error() && error() == rhs;
}
bool equal(std::error_code const& rhs) const noexcept { return has_error() && error() == rhs; }
/*!
\note This function is `noexcept` when `U == T` is `noexcept`.
\return False if `has_error()`, otherwise `value() == rhs`.
*/
template<typename U, typename = std::enable_if_t<!std::is_constructible_v<std::error_code, U>>>
bool equal(U const& rhs) const noexcept(noexcept(*std::declval<expect<T>>() == rhs))
{
template <typename U, typename = std::enable_if_t<!std::is_constructible_v<std::error_code, U>>>
bool equal(U const& rhs) const noexcept(noexcept(*std::declval<expect<T>>() == rhs)) {
return has_value() && get() == rhs;
}
//! \return False if `has_value()`, otherwise `error() == rhs`.
bool matches(std::error_condition const& rhs) const noexcept
{
bool matches(std::error_condition const& rhs) const noexcept {
return has_error() && error() == rhs;
}
};
template<>
class expect<void>
{
template <>
class expect<void> {
std::error_code code_;
public:
public:
using value_type = void;
using error_type = std::error_code;
//! Create a successful object.
expect() noexcept
: code_()
{}
expect() noexcept : code_() {}
expect(std::error_code const& code) noexcept
: code_(code)
{
expect(std::error_code const& code) noexcept : code_(code) {
if (!has_error())
code_ = ::common_error::kInvalidErrorCode;
}
@ -371,75 +332,58 @@ public:
std::error_code error() const noexcept { return code_; }
//! \return `error() == rhs.error()`.
bool equal(expect const& rhs) const noexcept
{
return error() == rhs.error();
}
bool equal(expect const& rhs) const noexcept { return error() == rhs.error(); }
//! \return `has_error() && error() == rhs`.
bool equal(std::error_code const& rhs) const noexcept
{
return has_error() && error() == rhs;
}
bool equal(std::error_code const& rhs) const noexcept { return has_error() && error() == rhs; }
//! \return False if `has_value()`, otherwise `error() == rhs`.
bool matches(std::error_condition const& rhs) const noexcept
{
bool matches(std::error_condition const& rhs) const noexcept {
return has_error() && error() == rhs;
}
};
//! \return An `expect<void>` object with `!has_error()`.
inline expect<void> success() noexcept { return expect<void>{}; }
inline expect<void> success() noexcept {
return expect<void>{};
}
template<typename T, typename U>
inline
bool operator==(expect<T> const& lhs, expect<U> const& rhs) noexcept(noexcept(lhs.equal(rhs)))
{
template <typename T, typename U>
inline bool operator==(expect<T> const& lhs, expect<U> const& rhs) noexcept(
noexcept(lhs.equal(rhs))) {
return lhs.equal(rhs);
}
template<typename T, typename U>
inline
bool operator==(expect<T> const& lhs, U const& rhs) noexcept(noexcept(lhs.equal(rhs)))
{
template <typename T, typename U>
inline bool operator==(expect<T> const& lhs, U const& rhs) noexcept(noexcept(lhs.equal(rhs))) {
return lhs.equal(rhs);
}
template<typename T, typename U>
inline
bool operator==(T const& lhs, expect<U> const& rhs) noexcept(noexcept(rhs.equal(lhs)))
{
template <typename T, typename U>
inline bool operator==(T const& lhs, expect<U> const& rhs) noexcept(noexcept(rhs.equal(lhs))) {
return rhs.equal(lhs);
}
template<typename T, typename U>
inline
bool operator!=(expect<T> const& lhs, expect<U> const& rhs) noexcept(noexcept(lhs.equal(rhs)))
{
template <typename T, typename U>
inline bool operator!=(expect<T> const& lhs, expect<U> const& rhs) noexcept(
noexcept(lhs.equal(rhs))) {
return !lhs.equal(rhs);
}
template<typename T, typename U>
inline
bool operator!=(expect<T> const& lhs, U const& rhs) noexcept(noexcept(lhs.equal(rhs)))
{
template <typename T, typename U>
inline bool operator!=(expect<T> const& lhs, U const& rhs) noexcept(noexcept(lhs.equal(rhs))) {
return !lhs.equal(rhs);
}
template<typename T, typename U>
inline
bool operator!=(T const& lhs, expect<U> const& rhs) noexcept(noexcept(rhs.equal(lhs)))
{
template <typename T, typename U>
inline bool operator!=(T const& lhs, expect<U> const& rhs) noexcept(noexcept(rhs.equal(lhs))) {
return !rhs.equal(lhs);
}
namespace detail
{
inline void expect::unwrap(::expect<void>&& result, const char* error_msg, const char* file, unsigned line)
{
if (!result)
throw_(result.error(), error_msg, file, line);
}
namespace detail {
inline void expect::unwrap(
::expect<void>&& result, const char* error_msg, const char* file, unsigned line) {
if (!result)
throw_(result.error(), error_msg, file, line);
}
} // namespace detail

View File

@ -1,22 +1,22 @@
// Copyright (c) 2018-2020, The Loki Project
// Copyright (c) 2014-2019, The Monero 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
@ -26,48 +26,51 @@
// 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 "file.h"
#include "fs-format.h"
#include "logging/oxen_logger.h"
#include <unistd.h>
#include <cstdio>
#include <fstream>
#include "fs-format.h"
#include "logging/oxen_logger.h"
#ifdef WIN32
#include "epee/string_tools.h"
#ifndef STRSAFE_NO_DEPRECATE
#define STRSAFE_NO_DEPRECATE
#endif
#include <windows.h>
#include <shlobj.h>
#include <strsafe.h>
#else
#include <sys/file.h>
#include <sys/utsname.h>
#include <sys/stat.h>
#include <shlobj.h>
#include <strsafe.h>
#include <windows.h>
#else
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#endif
#ifdef __GLIBC__
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <cstring>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cctype>
#include <cstring>
#endif
#include "cryptonote_config.h"
namespace tools {
static auto logcat = log::Cat("util");
static auto logcat = log::Cat("util");
#ifndef _WIN32
static int flock_exnb(int fd)
{
static int flock_exnb(int fd) {
struct flock fl;
int ret;
@ -78,253 +81,236 @@ namespace tools {
fl.l_len = 0;
ret = fcntl(fd, F_SETLK, &fl);
if (ret < 0)
log::error(logcat, "Error locking fd {}: {} ({})", fd, errno, strerror(errno));
log::error(logcat, "Error locking fd {}: {} ({})", fd, errno, strerror(errno));
return ret;
}
}
#endif
private_file::private_file() noexcept : m_handle(), m_filename() {}
private_file::private_file() noexcept : m_handle(), m_filename() {}
private_file::private_file(std::FILE* handle, fs::path filename) noexcept
: m_handle(handle), m_filename(std::move(filename)) {}
private_file::private_file(std::FILE* handle, fs::path filename) noexcept :
m_handle(handle), m_filename(std::move(filename)) {}
private_file private_file::create(fs::path name)
{
private_file private_file::create(fs::path name) {
#ifdef WIN32
struct close_handle
{
void operator()(HANDLE handle) const noexcept
{
CloseHandle(handle);
}
struct close_handle {
void operator()(HANDLE handle) const noexcept { CloseHandle(handle); }
};
std::unique_ptr<void, close_handle> process = nullptr;
{
HANDLE temp{};
const bool fail = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, std::addressof(temp)) == 0;
process.reset(temp);
if (fail)
return {};
HANDLE temp{};
const bool fail =
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, std::addressof(temp)) == 0;
process.reset(temp);
if (fail)
return {};
}
DWORD sid_size = 0;
GetTokenInformation(process.get(), TokenOwner, nullptr, 0, std::addressof(sid_size));
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
return {};
return {};
std::unique_ptr<char[]> sid{new char[sid_size]};
if (!GetTokenInformation(process.get(), TokenOwner, sid.get(), sid_size, std::addressof(sid_size)))
return {};
if (!GetTokenInformation(
process.get(), TokenOwner, sid.get(), sid_size, std::addressof(sid_size)))
return {};
const PSID psid = reinterpret_cast<const PTOKEN_OWNER>(sid.get())->Owner;
const DWORD daclSize =
sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(psid) - sizeof(DWORD);
sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(psid) - sizeof(DWORD);
const std::unique_ptr<char[]> dacl{new char[daclSize]};
if (!InitializeAcl(reinterpret_cast<PACL>(dacl.get()), daclSize, ACL_REVISION))
return {};
return {};
if (!AddAccessAllowedAce(reinterpret_cast<PACL>(dacl.get()), ACL_REVISION, (READ_CONTROL | FILE_GENERIC_READ | DELETE), psid))
return {};
if (!AddAccessAllowedAce(
reinterpret_cast<PACL>(dacl.get()),
ACL_REVISION,
(READ_CONTROL | FILE_GENERIC_READ | DELETE),
psid))
return {};
SECURITY_DESCRIPTOR descriptor{};
if (!InitializeSecurityDescriptor(std::addressof(descriptor), SECURITY_DESCRIPTOR_REVISION))
return {};
return {};
if (!SetSecurityDescriptorDacl(std::addressof(descriptor), true, reinterpret_cast<PACL>(dacl.get()), false))
return {};
if (!SetSecurityDescriptorDacl(
std::addressof(descriptor), true, reinterpret_cast<PACL>(dacl.get()), false))
return {};
SECURITY_ATTRIBUTES attributes{sizeof(SECURITY_ATTRIBUTES), std::addressof(descriptor), false};
std::unique_ptr<void, close_handle> file{
CreateFileW(
name.c_str(),
GENERIC_WRITE, FILE_SHARE_READ,
std::addressof(attributes),
CREATE_NEW, (FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE),
nullptr
)
};
if (file)
{
const int fd = _open_osfhandle(reinterpret_cast<intptr_t>(file.get()), 0);
if (0 <= fd)
{
file.release();
std::FILE* real_file = _fdopen(fd, "w");
if (!real_file)
{
_close(fd);
std::unique_ptr<void, close_handle> file{CreateFileW(
name.c_str(),
GENERIC_WRITE,
FILE_SHARE_READ,
std::addressof(attributes),
CREATE_NEW,
(FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE),
nullptr)};
if (file) {
const int fd = _open_osfhandle(reinterpret_cast<intptr_t>(file.get()), 0);
if (0 <= fd) {
file.release();
std::FILE* real_file = _fdopen(fd, "w");
if (!real_file) {
_close(fd);
}
return {real_file, std::move(name)};
}
return {real_file, std::move(name)};
}
}
#else
const int fdr = open(name.c_str(), (O_RDONLY | O_CREAT), S_IRUSR);
if (0 <= fdr)
{
struct stat rstats = {};
if (fstat(fdr, std::addressof(rstats)) != 0)
{
close(fdr);
return {};
}
fchmod(fdr, (S_IRUSR | S_IWUSR));
const int fdw = open(name.c_str(), O_RDWR);
fchmod(fdr, rstats.st_mode);
close(fdr);
if (0 <= fdw)
{
struct stat wstats = {};
if (fstat(fdw, std::addressof(wstats)) == 0 &&
rstats.st_dev == wstats.st_dev && rstats.st_ino == wstats.st_ino &&
flock_exnb(fdw) == 0 && ftruncate(fdw, 0) == 0)
{
std::FILE* file = fdopen(fdw, "w");
if (file) return {file, std::move(name)};
if (0 <= fdr) {
struct stat rstats = {};
if (fstat(fdr, std::addressof(rstats)) != 0) {
close(fdr);
return {};
}
fchmod(fdr, (S_IRUSR | S_IWUSR));
const int fdw = open(name.c_str(), O_RDWR);
fchmod(fdr, rstats.st_mode);
close(fdr);
if (0 <= fdw) {
struct stat wstats = {};
if (fstat(fdw, std::addressof(wstats)) == 0 && rstats.st_dev == wstats.st_dev &&
rstats.st_ino == wstats.st_ino && flock_exnb(fdw) == 0 && ftruncate(fdw, 0) == 0) {
std::FILE* file = fdopen(fdw, "w");
if (file)
return {file, std::move(name)};
}
close(fdw);
}
close(fdw);
}
}
#endif
return {};
}
}
private_file::~private_file() noexcept
{
private_file::~private_file() noexcept {
std::error_code ignored;
fs::remove(filename(), ignored);
}
}
file_locker::file_locker(const fs::path& filename)
{
file_locker::file_locker(const fs::path& filename) {
#ifdef WIN32
m_fd = INVALID_HANDLE_VALUE;
m_fd = CreateFileW(filename.c_str(), GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (m_fd != INVALID_HANDLE_VALUE)
{
OVERLAPPED ov;
memset(&ov, 0, sizeof(ov));
if (!LockFileEx(m_fd, LOCKFILE_FAIL_IMMEDIATELY | LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &ov))
{
log::error(logcat, "Failed to lock {}: {}", filename, std::error_code(GetLastError(), std::system_category()).message());
CloseHandle(m_fd);
m_fd = INVALID_HANDLE_VALUE;
}
}
else
{
log::error(logcat, "Failed to open {}: {}", filename, std::error_code(GetLastError(), std::system_category()).message());
m_fd = CreateFileW(
filename.c_str(), GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (m_fd != INVALID_HANDLE_VALUE) {
OVERLAPPED ov;
memset(&ov, 0, sizeof(ov));
if (!LockFileEx(m_fd, LOCKFILE_FAIL_IMMEDIATELY | LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &ov)) {
log::error(
logcat,
"Failed to lock {}: {}",
filename,
std::error_code(GetLastError(), std::system_category()).message());
CloseHandle(m_fd);
m_fd = INVALID_HANDLE_VALUE;
}
} else {
log::error(
logcat,
"Failed to open {}: {}",
filename,
std::error_code(GetLastError(), std::system_category()).message());
}
#else
m_fd = open(filename.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0666);
if (m_fd != -1)
{
if (flock_exnb(m_fd) == -1)
{
log::error(logcat, "Failed to lock {}: {}", filename, std::strerror(errno));
close(m_fd);
m_fd = -1;
}
}
else
{
log::error(logcat, "Failed to open {}: {}", filename, std::strerror(errno));
if (m_fd != -1) {
if (flock_exnb(m_fd) == -1) {
log::error(logcat, "Failed to lock {}: {}", filename, std::strerror(errno));
close(m_fd);
m_fd = -1;
}
} else {
log::error(logcat, "Failed to open {}: {}", filename, std::strerror(errno));
}
#endif
}
file_locker::~file_locker()
{
if (locked())
{
}
file_locker::~file_locker() {
if (locked()) {
#ifdef WIN32
CloseHandle(m_fd);
CloseHandle(m_fd);
#else
close(m_fd);
close(m_fd);
#endif
}
}
bool file_locker::locked() const
{
}
bool file_locker::locked() const {
#ifdef WIN32
return m_fd != INVALID_HANDLE_VALUE;
#else
return m_fd != -1;
#endif
}
}
#ifdef _WIN32
fs::path get_special_folder_path(int nfolder, bool iscreate)
{
fs::path get_special_folder_path(int nfolder, bool iscreate) {
WCHAR psz_path[MAX_PATH] = L"";
if (SHGetSpecialFolderPathW(NULL, psz_path, nfolder, iscreate))
{
return fs::path{psz_path};
if (SHGetSpecialFolderPathW(NULL, psz_path, nfolder, iscreate)) {
return fs::path{psz_path};
}
log::error(logcat, "SHGetSpecialFolderPathW() failed, could not obtain requested path.");
return "";
}
}
#endif
// Windows < Vista: C:\Documents and Settings\Username\Application Data\...
// Windows >= Vista: C:\Users\Username\AppData\Roaming\...
// Sane OSes: ~/
static fs::path get_default_parent_dir() {
// Windows < Vista: C:\Documents and Settings\Username\Application Data\...
// Windows >= Vista: C:\Users\Username\AppData\Roaming\...
// Sane OSes: ~/
static fs::path get_default_parent_dir() {
#ifdef _WIN32
return get_special_folder_path(CSIDL_COMMON_APPDATA, true);
#else
char* home = std::getenv("HOME");
return home && std::strlen(home) ? fs::u8path(home) : fs::current_path();
#endif
}
}
fs::path get_default_data_dir()
{
fs::path get_default_data_dir() {
return get_default_parent_dir() / fs::u8path(cryptonote::DATA_DIRNAME);
}
fs::path get_depreciated_default_data_dir()
{
}
fs::path get_depreciated_default_data_dir() {
return get_default_parent_dir() / fs::u8path(cryptonote::old::DATA_DIRNAME);
}
}
void set_strict_default_file_permissions(bool strict)
{
void set_strict_default_file_permissions(bool strict) {
#if defined(__MINGW32__) || defined(__MINGW__)
// no clue about the odd one out
#else
mode_t mode = strict ? 077 : 0;
umask(mode);
#endif
}
}
bool slurp_file(const fs::path& filename, std::string& contents)
{
bool slurp_file(const fs::path& filename, std::string& contents) {
try {
fs::ifstream in(filename, std::ios::binary);
std::string content((std::istreambuf_iterator<char>(in)), (std::istreambuf_iterator<char>()));
contents = std::move(content);
return true;
fs::ifstream in(filename, std::ios::binary);
std::string content(
(std::istreambuf_iterator<char>(in)), (std::istreambuf_iterator<char>()));
contents = std::move(content);
return true;
} catch (...) {
return false;
return false;
}
}
}
bool dump_file(const fs::path& filename, std::string_view contents)
{
bool dump_file(const fs::path& filename, std::string_view contents) {
fs::ofstream out;
out.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try {
out.open(filename, std::ios::binary | std::ios::out | std::ios::trunc);
out.write(contents.data(), contents.size());
return true;
out.open(filename, std::ios::binary | std::ios::out | std::ios::trunc);
out.write(contents.data(), contents.size());
return true;
} catch (...) {
return false;
return false;
}
}
}
} // namespace tools

View File

@ -1,22 +1,22 @@
// Copyright (c) 2018-2020, The Loki Project
// Copyright (c) 2014-2019, The Monero 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
@ -26,49 +26,46 @@
// 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
//
#pragma once
#include <string>
#include <cstdio>
#include <memory>
#include <optional>
#include <string>
#include <system_error>
#include "fs.h"
#ifdef _WIN32
#include "windows.h"
#endif
/**
* Various utilities for dealing with files/directories.
*/
namespace tools {
//! Functional class for closing C file handles.
struct close_file
{
void operator()(std::FILE* handle) const noexcept
{
if (handle)
{
std::fclose(handle);
}
//! Functional class for closing C file handles.
struct close_file {
void operator()(std::FILE* handle) const noexcept {
if (handle) {
std::fclose(handle);
}
}
};
};
//! A file restricted to process owner AND process. Deletes file on destruction.
class private_file {
//! A file restricted to process owner AND process. Deletes file on destruction.
class private_file {
std::unique_ptr<std::FILE, close_file> m_handle;
fs::path m_filename;
private_file(std::FILE* handle, fs::path filename) noexcept;
public:
public:
//! `handle() == nullptr && filename.empty()`.
private_file() noexcept;
@ -84,55 +81,55 @@ namespace tools {
std::FILE* handle() const noexcept { return m_handle.get(); }
const fs::path& filename() const noexcept { return m_filename; }
};
};
class file_locker
{
class file_locker {
public:
file_locker(const fs::path& filename);
~file_locker();
bool locked() const;
private:
#ifdef WIN32
HANDLE m_fd;
#else
int m_fd;
#endif
};
};
/*! \brief Returns the default data directory.
*
* \details Windows < Vista: C:\\Documents and Settings\\Username\\Application Data\\CRYPTONOTE_NAME
*
* Windows >= Vista: C:\\Users\\Username\\AppData\\Roaming\\CRYPTONOTE_NAME
*
* Mac: ~/Library/Application Support/CRYPTONOTE_NAME
*
* Unix: ~/.CRYPTONOTE_NAME
*/
fs::path get_default_data_dir();
fs::path get_depreciated_default_data_dir();
/*! \brief Returns the default data directory.
*
* \details Windows < Vista: C:\\Documents and Settings\\Username\\Application Data\\CRYPTONOTE_NAME
*
* Windows >= Vista: C:\\Users\\Username\\AppData\\Roaming\\CRYPTONOTE_NAME
*
* Mac: ~/Library/Application Support/CRYPTONOTE_NAME
*
* Unix: ~/.CRYPTONOTE_NAME
*/
fs::path get_default_data_dir();
fs::path get_depreciated_default_data_dir();
#ifdef WIN32
/**
* @brief
*
* @param nfolder
* @param iscreate
*
* @return
*/
fs::path get_special_folder_path(int nfolder, bool iscreate);
/**
* @brief
*
* @param nfolder
* @param iscreate
*
* @return
*/
fs::path get_special_folder_path(int nfolder, bool iscreate);
#endif
void set_strict_default_file_permissions(bool strict);
void set_strict_default_file_permissions(bool strict);
void closefrom(int fd);
void closefrom(int fd);
/// Reads a (binary) file from disk into the string `contents`.
bool slurp_file(const fs::path& filename, std::string& contents);
/// Reads a (binary) file from disk into the string `contents`.
bool slurp_file(const fs::path& filename, std::string& contents);
/// Dumps (binary) string contents to disk. The file is overwritten if it already exists.
bool dump_file(const fs::path& filename, std::string_view contents);
/// Dumps (binary) string contents to disk. The file is overwritten if it already exists.
bool dump_file(const fs::path& filename, std::string_view contents);
}
} // namespace tools

View File

@ -1,117 +1,117 @@
#pragma once
#include <fmt/format.h>
#include <string_view>
#include <type_traits>
#include <utility>
namespace formattable {
// Types can opt-in to being formattable as a string by specializing this to true. Such a type
// must have one of:
//
// - a `to_string()` method on the type; when formatted we will call `val.to_string()` to format
// it as a string.
// - a `to_string(val)` function in the same namespace as the type; we will call it to format it
// as a string.
//
// The function should return something string-like (string, string_view, const char*).
//
// For instance to opt-in MyType for such string formatting, use:
//
// template <> inline constexpr bool formattable::via_to_string<MyType> = true;
//
// You can also partially specialize; for instance to make all derived classes of a common base
// type formattable via to_string you could do:
//
// template <T> inline constexpr bool formattable::via_to_string<T,
// std::enable_if_t<std::is_base_of_v<MyBaseType, T>>> = true;
//
// Types can opt-in to being formattable as a string by specializing this to true. Such a type
// must have one of:
//
// - a `to_string()` method on the type; when formatted we will call `val.to_string()` to format
// it as a string.
// - a `to_string(val)` function in the same namespace as the type; we will call it to format it
// as a string.
//
// The function should return something string-like (string, string_view, const char*).
//
// For instance to opt-in MyType for such string formatting, use:
//
// template <> inline constexpr bool formattable::via_to_string<MyType> = true;
//
// You can also partially specialize; for instance to make all derived classes of a common base
// type formattable via to_string you could do:
//
// template <T> inline constexpr bool formattable::via_to_string<T,
// std::enable_if_t<std::is_base_of_v<MyBaseType, T>>> = true;
//
template <typename T, typename SFINAE = void>
constexpr bool via_to_string = false;
// Same as above, but looks for a to_hex_string() instead of to_string(), for types that get
// dumped as hex.
template <typename T, typename SFINAE = void>
constexpr bool via_to_hex_string = false;
// Scoped enums can alternatively be formatted as their underlying integer value by specializing
// this function to true:
template <typename T, typename SFINAE = void>
constexpr bool via_underlying = false;
namespace detail {
template <typename T, typename SFINAE = void>
constexpr bool via_to_string = false;
// Same as above, but looks for a to_hex_string() instead of to_string(), for types that get
// dumped as hex.
constexpr bool has_to_string_method = false;
template <typename T, typename SFINAE = void>
constexpr bool via_to_hex_string = false;
// Scoped enums can alternatively be formatted as their underlying integer value by specializing
// this function to true:
template <typename T, typename SFINAE = void>
constexpr bool via_underlying = false;
namespace detail {
template <typename T, typename SFINAE = void>
constexpr bool has_to_string_method = false;
template <typename T, typename SFINAE = void>
constexpr bool has_to_hex_string_method = false;
template <typename T>
inline constexpr bool has_to_string_method<T,
std::void_t<decltype(std::declval<const T&>().to_string())>
> = true;
template <typename T>
inline constexpr bool has_to_hex_string_method<T,
std::void_t<decltype(std::declval<const T&>().to_hex_string())>
> = true;
} // namespace detail
constexpr bool has_to_hex_string_method = false;
template <typename T>
struct to_string_formatter : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(const T& val, FormatContext& ctx) const {
if constexpr (::formattable::detail::has_to_string_method<T>)
return formatter<std::string_view>::format(val.to_string(), ctx);
else
return formatter<std::string_view>::format(to_string(val), ctx);
}
};
inline constexpr bool
has_to_string_method<T, std::void_t<decltype(std::declval<const T&>().to_string())>> =
true;
template <typename T>
struct to_hex_string_formatter : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(const T& val, FormatContext& ctx) const {
if constexpr (::formattable::detail::has_to_hex_string_method<T>)
return formatter<std::string_view>::format(val.to_hex_string(), ctx);
else
return formatter<std::string_view>::format(to_hex_string(val), ctx);
}
};
inline constexpr bool has_to_hex_string_method<
T,
std::void_t<decltype(std::declval<const T&>().to_hex_string())>> = true;
template <typename T>
struct underlying_t_formatter : fmt::formatter<std::underlying_type_t<T>> {
#ifdef __cpp_lib_is_scoped_enum // C++23
static_assert(std::is_scoped_enum_v<T>);
} // namespace detail
template <typename T>
struct to_string_formatter : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(const T& val, FormatContext& ctx) const {
if constexpr (::formattable::detail::has_to_string_method<T>)
return formatter<std::string_view>::format(val.to_string(), ctx);
else
return formatter<std::string_view>::format(to_string(val), ctx);
}
};
template <typename T>
struct to_hex_string_formatter : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(const T& val, FormatContext& ctx) const {
if constexpr (::formattable::detail::has_to_hex_string_method<T>)
return formatter<std::string_view>::format(val.to_hex_string(), ctx);
else
return formatter<std::string_view>::format(to_hex_string(val), ctx);
}
};
template <typename T>
struct underlying_t_formatter : fmt::formatter<std::underlying_type_t<T>> {
#ifdef __cpp_lib_is_scoped_enum // C++23
static_assert(std::is_scoped_enum_v<T>);
#else
static_assert(std::is_enum_v<T> && !std::is_convertible_v<T, std::underlying_type_t<T>>,
"formattable::via_underlying<T> type is not a scoped enum");
static_assert(
std::is_enum_v<T> && !std::is_convertible_v<T, std::underlying_type_t<T>>,
"formattable::via_underlying<T> type is not a scoped enum");
#endif
template <typename FormatContext>
auto format(const T& val, FormatContext& ctx) const {
using Underlying = std::underlying_type_t<T>;
return fmt::formatter<Underlying>::format(static_cast<Underlying>(val), ctx);
}
};
template <typename FormatContext>
auto format(const T& val, FormatContext& ctx) const {
using Underlying = std::underlying_type_t<T>;
return fmt::formatter<Underlying>::format(static_cast<Underlying>(val), ctx);
}
};
} // namespace formattable
namespace fmt {
template <typename T, typename Char>
struct formatter<T, Char, std::enable_if_t<::formattable::via_to_string<T>>>
template <typename T, typename Char>
struct formatter<T, Char, std::enable_if_t<::formattable::via_to_string<T>>>
: ::formattable::to_string_formatter<T> {};
template <typename T, typename Char>
struct formatter<T, Char, std::enable_if_t<::formattable::via_to_hex_string<T>>>
template <typename T, typename Char>
struct formatter<T, Char, std::enable_if_t<::formattable::via_to_hex_string<T>>>
: ::formattable::to_hex_string_formatter<T> {};
template <typename T, typename Char>
struct formatter<T, Char, std::enable_if_t<::formattable::via_underlying<T>>>
template <typename T, typename Char>
struct formatter<T, Char, std::enable_if_t<::formattable::via_underlying<T>>>
: ::formattable::underlying_t_formatter<T> {};
} // namespace fmt

View File

@ -8,13 +8,12 @@
#include <fmt/core.h>
namespace fmt {
template <>
struct formatter<ghc::filesystem::path> : formatter<std::string>
{
template <typename FormatContext>
auto format(const ghc::filesystem::path& val, FormatContext& ctx) const {
return formatter<std::string>::format(val.u8string(), ctx);
}
};
}
template <>
struct formatter<ghc::filesystem::path> : formatter<std::string> {
template <typename FormatContext>
auto format(const ghc::filesystem::path& val, FormatContext& ctx) const {
return formatter<std::string>::format(val.u8string(), ctx);
}
};
} // namespace fmt
#endif

View File

@ -12,11 +12,11 @@
#include <filesystem>
namespace fs {
using namespace std::filesystem;
using ifstream = std::ifstream;
using ofstream = std::ofstream;
using fstream = std::fstream;
}
using namespace std::filesystem;
using ifstream = std::ifstream;
using ofstream = std::ofstream;
using fstream = std::fstream;
} // namespace fs
#else
#include <ghc/filesystem.hpp>

View File

@ -1,28 +1,33 @@
#pragma once
#include <oxenc/hex.h>
#include <type_traits>
#include "epee/span.h" // epee
#include "epee/span.h" // epee
namespace tools {
// Reads a hex string directly into a trivially copyable type T without performing any temporary
// allocation. Returns false if the given string is not hex or does not match T in length,
// otherwise copies directly into `x` and returns true.
template <typename T, typename = std::enable_if_t<
!std::is_const_v<T> && (std::is_trivially_copyable_v<T> || epee::is_byte_spannable<T>)
>>
bool hex_to_type(std::string_view hex, T& x) {
if (!oxenc::is_hex(hex) || hex.size() != 2*sizeof(T))
return false;
// Reads a hex string directly into a trivially copyable type T without performing any temporary
// allocation. Returns false if the given string is not hex or does not match T in length,
// otherwise copies directly into `x` and returns true.
template <
typename T,
typename = std::enable_if_t<
!std::is_const_v<T> &&
(std::is_trivially_copyable_v<T> || epee::is_byte_spannable<T>)>>
bool hex_to_type(std::string_view hex, T& x) {
if (!oxenc::is_hex(hex) || hex.size() != 2 * sizeof(T))
return false;
oxenc::from_hex(hex.begin(), hex.end(), reinterpret_cast<char*>(&x));
return true;
}
/// Converts a standard layout, padding-free type into a hex string of its contents.
template <typename T, typename = std::enable_if_t<
(std::is_standard_layout_v<T> && std::has_unique_object_representations_v<T>)
|| epee::is_byte_spannable<T>
>>
std::string type_to_hex(const T& val) {
return oxenc::to_hex(std::string_view{reinterpret_cast<const char*>(&val), sizeof(val)});
}
}
/// Converts a standard layout, padding-free type into a hex string of its contents.
template <
typename T,
typename = std::enable_if_t<
(std::is_standard_layout_v<T> && std::has_unique_object_representations_v<T>) ||
epee::is_byte_spannable<T>>>
std::string type_to_hex(const T& val) {
return oxenc::to_hex(std::string_view{reinterpret_cast<const char*>(&val), sizeof(val)});
}
} // namespace tools

View File

@ -1,22 +1,22 @@
// 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
@ -28,303 +28,310 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "i18n.h"
#include <cstdlib>
#include <cstring>
#include <logging/oxen_logger.h>
#include <algorithm>
#include <cctype>
#include <cstdint>
#include <string>
#include <cstdlib>
#include <cstring>
#include <map>
#include <string>
#include <utility>
#include <algorithm>
#include "file.h"
#include <logging/oxen_logger.h>
static auto logcat = oxen::log::Cat("i18n");
#define MAX_LANGUAGE_SIZE 16
static const unsigned char qm_magic[16] = {0x3c, 0xb8, 0x64, 0x18, 0xca, 0xef, 0x9c, 0x95, 0xcd, 0x21, 0x1c, 0xbf, 0x60, 0xa1, 0xbd, 0xdd};
static const unsigned char qm_magic[16] = {
0x3c,
0xb8,
0x64,
0x18,
0xca,
0xef,
0x9c,
0x95,
0xcd,
0x21,
0x1c,
0xbf,
0x60,
0xa1,
0xbd,
0xdd};
static std::map<std::string,std::string> i18n_entries;
static std::map<std::string, std::string> i18n_entries;
using namespace std::literals;
/* Logging isn't initialized yet when this is run */
/* add std::flush, because std::endl doesn't seem to flush, contrary to expected */
// #define i18n_log(x) do { std::cout << __FILE__ << ":" << __LINE__ << ": " << x << std::endl; std::cout << std::flush; } while(0)
// #define i18n_log(x) do { std::cout << __FILE__ << ":" << __LINE__ << ": " << x << std::endl;
// std::cout << std::flush; } while(0)
#define i18n_log(x) ((void)0)
std::string i18n_get_language()
{
const char *e;
std::string i18n_get_language() {
const char* e;
e = getenv("LANG");
i18n_log("LANG=" << e);
if (!e || !*e) {
e = getenv("LC_ALL");
i18n_log("LC_ALL=" << e);
}
if (!e || !*e)
e = "en";
e = getenv("LANG");
i18n_log("LANG=" << e);
if (!e || !*e) {
e = getenv("LC_ALL");
i18n_log("LC_ALL=" << e);
}
if (!e || !*e)
e = "en";
std::string language = e;
language = language.substr(0, language.find("."));
language = language.substr(0, language.find("@"));
std::string language = e;
language = language.substr(0, language.find("."));
language = language.substr(0, language.find("@"));
// check valid values
for (char c: language)
if (!strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-.@", c))
return "en";
// check valid values
for (char c : language)
if (!strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-.@", c))
return "en";
std::transform(language.begin(), language.end(), language.begin(), tolower);
if (language.size() > MAX_LANGUAGE_SIZE)
{
i18n_log("Language from LANG/LC_ALL suspiciously long, defaulting to en");
return "en";
}
return language;
std::transform(language.begin(), language.end(), language.begin(), tolower);
if (language.size() > MAX_LANGUAGE_SIZE) {
i18n_log("Language from LANG/LC_ALL suspiciously long, defaulting to en");
return "en";
}
return language;
}
static uint32_t be32(const unsigned char *data)
{
return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
static uint32_t be32(const unsigned char* data) {
return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
}
static std::string utf16(const unsigned char *data, uint32_t len)
{
std::string s;
while (len >= 2) {
uint32_t code = (data[0] << 8) | data[1];
data += 2;
len -= 2;
if (code >= 0xd800 && code <= 0xdbfff && len >= 2) {
uint32_t next = (data[0] << 8) | data[1];
if (next >= 0xdc00 && next <= 0xdfff) {
code = (code << 10) + next - 0x35dfc00;
static std::string utf16(const unsigned char* data, uint32_t len) {
std::string s;
while (len >= 2) {
uint32_t code = (data[0] << 8) | data[1];
data += 2;
len -= 2;
}
if (code >= 0xd800 && code <= 0xdbfff && len >= 2) {
uint32_t next = (data[0] << 8) | data[1];
if (next >= 0xdc00 && next <= 0xdfff) {
code = (code << 10) + next - 0x35dfc00;
data += 2;
len -= 2;
}
}
if (code <= 0x7f) {
s += (char)code;
} else if (code <= 0x7ff) {
s += 0xc0 | (code >> 6);
s += 0x80 | (code & 0x3f);
} else if (code <= 0xffff) {
s += 0xe0 | (code >> 12);
s += 0x80 | ((code >> 6) & 0x3f);
s += 0x80 | (code & 0x3f);
} else {
s += 0xf0 | (code >> 18);
s += 0x80 | ((code >> 12) & 0x3f);
s += 0x80 | ((code >> 6) & 0x3f);
s += 0x80 | (code & 0x3f);
}
}
if (code <= 0x7f) {
s += (char)code;
}
else if (code <= 0x7ff) {
s += 0xc0 | (code >> 6);
s += 0x80 | (code & 0x3f);
}
else if (code <= 0xffff) {
s += 0xe0 | (code >> 12);
s += 0x80 | ((code >> 6) & 0x3f);
s += 0x80 | (code & 0x3f);
}
else {
s += 0xf0 | (code >> 18);
s += 0x80 | ((code >> 12) & 0x3f);
s += 0x80 | ((code >> 6) & 0x3f);
s += 0x80 | (code & 0x3f);
}
}
return s;
return s;
}
static std::string utf8(const unsigned char *data, uint32_t len)
{
/* assume well formedness */
return std::string((const char *)data,len);
static std::string utf8(const unsigned char* data, uint32_t len) {
/* assume well formedness */
return std::string((const char*)data, len);
}
int i18n_set_language(const char *directory, const char *base, std::string language)
{
i18n_log("i18n_set_language(" << directory << "," << base << ")");
if (!directory || !base)
return -1;
int i18n_set_language(const char* directory, const char* base, std::string language) {
i18n_log("i18n_set_language(" << directory << "," << base << ")");
if (!directory || !base)
return -1;
if (language.empty())
language = i18n_get_language();
std::string basename = base + "_"s + language + ".qm";
auto filename = fs::u8path(directory) / fs::u8path(basename);
i18n_log("Loading translations for language " << language);
if (language.empty())
language = i18n_get_language();
std::string basename = base + "_"s + language + ".qm";
auto filename = fs::u8path(directory) / fs::u8path(basename);
i18n_log("Loading translations for language " << language);
std::string contents;
if (std::error_code ec; fs::exists(filename, ec)) {
if (!tools::slurp_file(filename, contents)) {
i18n_log("Failed to load translations file: " << filename);
return -1;
}
} else {
i18n_log("Translations file not found: " << filename);
if (!find_embedded_file(basename, contents)) {
i18n_log("Embedded translations file not found: " << basename);
const char *underscore = strchr(language.c_str(), '_');
if (underscore) {
std::string fallback_language = std::string(language, 0, underscore - language.c_str());
basename = base + "_"s + fallback_language + ".qm";
filename.replace_filename(fs::u8path(basename));
i18n_log("Loading translations for language " << fallback_language);
if (std::error_code ec; fs::exists(filename, ec)) {
if (!tools::slurp_file(filename, contents)) {
std::string contents;
if (std::error_code ec; fs::exists(filename, ec)) {
if (!tools::slurp_file(filename, contents)) {
i18n_log("Failed to load translations file: " << filename);
return -1;
}
} else {
i18n_log("Translations file not found: " << filename);
if (!find_embedded_file(basename, contents)) {
i18n_log("Embedded translations file not found: " << filename);
return -1;
}
}
} else {
return -1;
}
}
}
const unsigned char *data = reinterpret_cast<const unsigned char*>(contents.c_str());
size_t datalen = contents.size();
size_t idx = 0;
i18n_log("Translations file size: " << datalen);
/* Format of the QM file (AFAICT):
* 16 bytes magic
* chunk list: N instances of chunks:
* 1 byte: chunk type (0x42: offsets, 0x69: messages)
* 4 bytes: chunk length, big endian
* D bytes: "chunk length" bytes of data
*
* 0x42 chunk: N instances of subchunks:
* 1 byte: subchunk type
* 0x01: end, no data
* 0x02: unsupported
* 0x03: translation
* 4 bytes: string length, big endian
* N bytes: string data, UTF-16 (or UCS2-BE ?)
* 0x04: unsupported
* 0x05: obsolete, unsupported
* 0x06: source text
* 0x07: context
* 0x08: obsolete, unsupported
* other: unsupported
* 4 bytes: subchunk length, big endian - except for 0x01, which has none
* S bytes: "chunk length" bytes of data
* 0x69 chunk:
* string data indexed by the 0x42 chunk data
*/
if (datalen < sizeof(qm_magic) || memcmp(data, qm_magic, sizeof(qm_magic))) {
i18n_log("Bad translations file format: " << filename);
return -1;
}
idx += sizeof(qm_magic);
unsigned char chunk_type;
uint32_t chunk_size;
uint32_t num_messages = (uint32_t)-1;
uint32_t messages_idx = (uint32_t)-1;
uint32_t offsets_idx = (uint32_t)-1;
while (idx < datalen) {
if (idx + 5 > datalen) {
i18n_log("Bad translations file format: " << filename);
return -1;
}
chunk_type = data[idx++];
chunk_size = be32(data+idx);
idx += 4;
i18n_log("Found " << chunk_type << " of " << chunk_size << " bytes");
if (chunk_size >= datalen || idx > datalen - chunk_size) {
i18n_log("Bad translations file format: " << filename);
return -1;
} else {
i18n_log("Translations file not found: " << filename);
if (!find_embedded_file(basename, contents)) {
i18n_log("Embedded translations file not found: " << basename);
const char* underscore = strchr(language.c_str(), '_');
if (underscore) {
std::string fallback_language =
std::string(language, 0, underscore - language.c_str());
basename = base + "_"s + fallback_language + ".qm";
filename.replace_filename(fs::u8path(basename));
i18n_log("Loading translations for language " << fallback_language);
if (std::error_code ec; fs::exists(filename, ec)) {
if (!tools::slurp_file(filename, contents)) {
i18n_log("Failed to load translations file: " << filename);
return -1;
}
} else {
i18n_log("Translations file not found: " << filename);
if (!find_embedded_file(basename, contents)) {
i18n_log("Embedded translations file not found: " << filename);
return -1;
}
}
} else {
return -1;
}
}
}
switch (chunk_type) {
case 0x42:
i18n_log("Found offsets at " << idx);
/* two 32 bit integers, and possible padding */
offsets_idx = idx;
num_messages = chunk_size / 8;
break;
case 0x69:
i18n_log("Found messages at " << idx);
messages_idx = idx;
break;
default:
i18n_log("Found unsupported chunk type: " << chunk_type);
break;
}
const unsigned char* data = reinterpret_cast<const unsigned char*>(contents.c_str());
size_t datalen = contents.size();
size_t idx = 0;
i18n_log("Translations file size: " << datalen);
idx += chunk_size;
}
if (offsets_idx == (uint32_t)-1) {
i18n_log("No offsets chunk found");
return -1;
}
if (messages_idx == (uint32_t)-1) {
i18n_log("No messages chunk found");
return -1;
}
std::string translation, source, context;
for (uint32_t m = 0; m < num_messages; ++m) {
be32(data+offsets_idx+m*8); // unused
idx = be32(data+offsets_idx+m*8+4);
idx += messages_idx;
if (idx > datalen || idx + 1 > datalen) {
i18n_log("Bad translations file format: " << filename);
return -1;
}
while (1) {
if (idx + 5 > datalen) {
/* Format of the QM file (AFAICT):
* 16 bytes magic
* chunk list: N instances of chunks:
* 1 byte: chunk type (0x42: offsets, 0x69: messages)
* 4 bytes: chunk length, big endian
* D bytes: "chunk length" bytes of data
*
* 0x42 chunk: N instances of subchunks:
* 1 byte: subchunk type
* 0x01: end, no data
* 0x02: unsupported
* 0x03: translation
* 4 bytes: string length, big endian
* N bytes: string data, UTF-16 (or UCS2-BE ?)
* 0x04: unsupported
* 0x05: obsolete, unsupported
* 0x06: source text
* 0x07: context
* 0x08: obsolete, unsupported
* other: unsupported
* 4 bytes: subchunk length, big endian - except for 0x01, which has none
* S bytes: "chunk length" bytes of data
* 0x69 chunk:
* string data indexed by the 0x42 chunk data
*/
if (datalen < sizeof(qm_magic) || memcmp(data, qm_magic, sizeof(qm_magic))) {
i18n_log("Bad translations file format: " << filename);
return -1;
}
chunk_type = data[idx++];
chunk_size = 0;
if (chunk_type == 0x01) {
i18n_entries[context + "\0"s + source] = translation;
context.clear();
source.clear();
translation.clear();
break;
}
chunk_size = be32(data+idx);
idx += 4;
i18n_log("Found " << chunk_type << " of " << chunk_size << " bytes");
if (chunk_size >= datalen || idx > datalen - chunk_size) {
i18n_log("Bad translations file format: " << filename);
return -1;
}
switch (chunk_type) {
case 0x03: // translation, UTF-16
translation = utf16(data+idx, chunk_size);
i18n_log("Found translation: " << translation);
break;
case 0x06: // source, UTF-8
source = utf8(data+idx, chunk_size);
i18n_log("Found source: " << source);
break;
case 0x07: // context, UTF-8
context = utf8(data+idx, chunk_size);
i18n_log("Found context: " << context);
break;
}
idx += chunk_size;
}
}
idx += sizeof(qm_magic);
return 0;
unsigned char chunk_type;
uint32_t chunk_size;
uint32_t num_messages = (uint32_t)-1;
uint32_t messages_idx = (uint32_t)-1;
uint32_t offsets_idx = (uint32_t)-1;
while (idx < datalen) {
if (idx + 5 > datalen) {
i18n_log("Bad translations file format: " << filename);
return -1;
}
chunk_type = data[idx++];
chunk_size = be32(data + idx);
idx += 4;
i18n_log("Found " << chunk_type << " of " << chunk_size << " bytes");
if (chunk_size >= datalen || idx > datalen - chunk_size) {
i18n_log("Bad translations file format: " << filename);
return -1;
}
switch (chunk_type) {
case 0x42:
i18n_log("Found offsets at " << idx);
/* two 32 bit integers, and possible padding */
offsets_idx = idx;
num_messages = chunk_size / 8;
break;
case 0x69:
i18n_log("Found messages at " << idx);
messages_idx = idx;
break;
default: i18n_log("Found unsupported chunk type: " << chunk_type); break;
}
idx += chunk_size;
}
if (offsets_idx == (uint32_t)-1) {
i18n_log("No offsets chunk found");
return -1;
}
if (messages_idx == (uint32_t)-1) {
i18n_log("No messages chunk found");
return -1;
}
std::string translation, source, context;
for (uint32_t m = 0; m < num_messages; ++m) {
be32(data + offsets_idx + m * 8); // unused
idx = be32(data + offsets_idx + m * 8 + 4);
idx += messages_idx;
if (idx > datalen || idx + 1 > datalen) {
i18n_log("Bad translations file format: " << filename);
return -1;
}
while (1) {
if (idx + 5 > datalen) {
i18n_log("Bad translations file format: " << filename);
return -1;
}
chunk_type = data[idx++];
chunk_size = 0;
if (chunk_type == 0x01) {
i18n_entries[context + "\0"s + source] = translation;
context.clear();
source.clear();
translation.clear();
break;
}
chunk_size = be32(data + idx);
idx += 4;
i18n_log("Found " << chunk_type << " of " << chunk_size << " bytes");
if (chunk_size >= datalen || idx > datalen - chunk_size) {
i18n_log("Bad translations file format: " << filename);
return -1;
}
switch (chunk_type) {
case 0x03: // translation, UTF-16
translation = utf16(data + idx, chunk_size);
i18n_log("Found translation: " << translation);
break;
case 0x06: // source, UTF-8
source = utf8(data + idx, chunk_size);
i18n_log("Found source: " << source);
break;
case 0x07: // context, UTF-8
context = utf8(data + idx, chunk_size);
i18n_log("Found context: " << context);
break;
}
idx += chunk_size;
}
}
return 0;
}
/* The entries is constant by that time */
const char *i18n_translate(const char *s, const std::string &context)
{
const std::string key = context + "\0"s + s;
std::map<std::string,std::string>::const_iterator i = i18n_entries.find(key);
if (i == i18n_entries.end())
return s;
return (*i).second.c_str();
const char* i18n_translate(const char* s, const std::string& context) {
const std::string key = context + "\0"s + s;
std::map<std::string, std::string>::const_iterator i = i18n_entries.find(key);
if (i == i18n_entries.end())
return s;
return (*i).second.c_str();
}

View File

@ -1,21 +1,21 @@
// Copyright (c) 2014-2019, The Monero 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
@ -29,10 +29,14 @@
#pragma once
#include <string>
#define QT_TRANSLATE_NOOP(context,str) i18n_translate(str,context)
#define QT_TRANSLATE_NOOP(context, str) i18n_translate(str, context)
std::string i18n_get_language();
int i18n_set_language(const char *directory, const char *base, std::string language = std::string());
const char *i18n_translate(const char *str, const std::string &context);
inline const char *tr(const char *str) { return i18n_translate(str, std::string{}); }
bool find_embedded_file(const std::string &name, std::string &data); // In the generated translation_files.cpp
int i18n_set_language(
const char* directory, const char* base, std::string language = std::string());
const char* i18n_translate(const char* str, const std::string& context);
inline const char* tr(const char* str) {
return i18n_translate(str, std::string{});
}
bool find_embedded_file(
const std::string& name, std::string& data); // In the generated translation_files.cpp

View File

@ -1,42 +1,46 @@
#include "json_binary_proxy.h"
#include <oxenc/hex.h>
#include <oxenc/base64.h>
#include <oxenc/hex.h>
namespace tools {
void load_binary_parameter_impl(std::string_view bytes, size_t raw_size, bool allow_raw, uint8_t* val_data) {
void load_binary_parameter_impl(
std::string_view bytes, size_t raw_size, bool allow_raw, uint8_t* val_data) {
if (allow_raw && bytes.size() == raw_size) {
std::memcpy(val_data, bytes.data(), bytes.size());
return;
} else if (bytes.size() == raw_size * 2) {
if (oxenc::is_hex(bytes)) {
oxenc::from_hex(bytes.begin(), bytes.end(), val_data);
std::memcpy(val_data, bytes.data(), bytes.size());
return;
}
} else {
const size_t b64_padded = (raw_size + 2) / 3 * 4;
const size_t b64_padding = raw_size % 3 == 1 ? 2 : raw_size % 3 == 2 ? 1 : 0;
const size_t b64_unpadded = b64_padded - b64_padding;
const std::string_view b64_padding_string = b64_padding == 2 ? "=="sv : b64_padding == 1 ? "="sv : ""sv;
if (bytes.size() == b64_unpadded ||
(b64_padding > 0 && bytes.size() == b64_padded && bytes.substr(b64_unpadded) == b64_padding_string)) {
if (oxenc::is_base64(bytes)) {
oxenc::from_base64(bytes.begin(), bytes.end(), val_data);
return;
} else if (bytes.size() == raw_size * 2) {
if (oxenc::is_hex(bytes)) {
oxenc::from_hex(bytes.begin(), bytes.end(), val_data);
return;
}
} else {
const size_t b64_padded = (raw_size + 2) / 3 * 4;
const size_t b64_padding = raw_size % 3 == 1 ? 2 : raw_size % 3 == 2 ? 1 : 0;
const size_t b64_unpadded = b64_padded - b64_padding;
const std::string_view b64_padding_string = b64_padding == 2 ? "=="sv
: b64_padding == 1 ? "="sv
: ""sv;
if (bytes.size() == b64_unpadded || (b64_padding > 0 && bytes.size() == b64_padded &&
bytes.substr(b64_unpadded) == b64_padding_string)) {
if (oxenc::is_base64(bytes)) {
oxenc::from_base64(bytes.begin(), bytes.end(), val_data);
return;
}
}
}
}
throw std::runtime_error{"Invalid binary value: unexpected size and/or encoding"};
}
}
nlohmann::json& json_binary_proxy::operator=(std::string_view binary_data) {
nlohmann::json& json_binary_proxy::operator=(std::string_view binary_data) {
switch (format) {
case fmt::bt: return e = binary_data;
case fmt::hex: return e = oxenc::to_hex(binary_data);
case fmt::base64: return e = oxenc::to_base64(binary_data);
case fmt::bt: return e = binary_data;
case fmt::hex: return e = oxenc::to_hex(binary_data);
case fmt::base64: return e = oxenc::to_base64(binary_data);
}
throw std::runtime_error{"Internal error: invalid binary encoding"};
}
}
} // namespace tools

View File

@ -1,57 +1,67 @@
#pragma once
#include "ringct/rctTypes.h"
#include "crypto/crypto.h"
#include <string_view>
#include <nlohmann/json.hpp>
#include <string_view>
#include <unordered_set>
#include "crypto/crypto.h"
#include "ringct/rctTypes.h"
using namespace std::literals;
namespace tools {
// Binary types that we support for rpc input/output. For json, these must be specified as hex or
// base64; for bt-encoded requests these can be accepted as binary, hex, or base64.
template <typename T>
inline constexpr bool json_is_binary = false;
template <> inline constexpr bool json_is_binary<crypto::hash> = true;
template <> inline constexpr bool json_is_binary<crypto::public_key> = true;
template <> inline constexpr bool json_is_binary<crypto::ed25519_public_key> = true;
template <> inline constexpr bool json_is_binary<crypto::x25519_public_key> = true;
template <> inline constexpr bool json_is_binary<crypto::key_image> = true;
template <> inline constexpr bool json_is_binary<rct::key> = true;
// Binary types that we support for rpc input/output. For json, these must be specified as hex or
// base64; for bt-encoded requests these can be accepted as binary, hex, or base64.
template <typename T>
inline constexpr bool json_is_binary = false;
template <>
inline constexpr bool json_is_binary<crypto::hash> = true;
template <>
inline constexpr bool json_is_binary<crypto::public_key> = true;
template <>
inline constexpr bool json_is_binary<crypto::ed25519_public_key> = true;
template <>
inline constexpr bool json_is_binary<crypto::x25519_public_key> = true;
template <>
inline constexpr bool json_is_binary<crypto::key_image> = true;
template <>
inline constexpr bool json_is_binary<rct::key> = true;
template <typename T>
inline constexpr bool json_is_binary_container = false;
template <typename T>
inline constexpr bool json_is_binary_container<std::vector<T>> = json_is_binary<T>;
template <typename T>
inline constexpr bool json_is_binary_container<std::unordered_set<T>> = json_is_binary<T>;
template <typename T>
inline constexpr bool json_is_binary_container = false;
template <typename T>
inline constexpr bool json_is_binary_container<std::vector<T>> = json_is_binary<T>;
template <typename T>
inline constexpr bool json_is_binary_container<std::unordered_set<T>> = json_is_binary<T>;
// De-referencing wrappers around the above:
template <typename T> inline constexpr bool json_is_binary<const T&> = json_is_binary<T>;
template <typename T> inline constexpr bool json_is_binary<T&&> = json_is_binary<T>;
template <typename T> inline constexpr bool json_is_binary_container<const T&> = json_is_binary_container<T>;
template <typename T> inline constexpr bool json_is_binary_container<T&&> = json_is_binary_container<T>;
// De-referencing wrappers around the above:
template <typename T>
inline constexpr bool json_is_binary<const T&> = json_is_binary<T>;
template <typename T>
inline constexpr bool json_is_binary<T&&> = json_is_binary<T>;
template <typename T>
inline constexpr bool json_is_binary_container<const T&> = json_is_binary_container<T>;
template <typename T>
inline constexpr bool json_is_binary_container<T&&> = json_is_binary_container<T>;
void load_binary_parameter_impl(
std::string_view bytes, size_t raw_size, bool allow_raw, uint8_t* val_data);
void load_binary_parameter_impl(std::string_view bytes, size_t raw_size, bool allow_raw, uint8_t* val_data);
// Loads a binary value from a string_view which may contain hex, base64, and (optionally) raw
// bytes.
template <typename T, typename = std::enable_if_t<json_is_binary<T>>>
void load_binary_parameter(std::string_view bytes, bool allow_raw, T& val) {
// Loads a binary value from a string_view which may contain hex, base64, and (optionally) raw
// bytes.
template <typename T, typename = std::enable_if_t<json_is_binary<T>>>
void load_binary_parameter(std::string_view bytes, bool allow_raw, T& val) {
load_binary_parameter_impl(bytes, sizeof(T), allow_raw, reinterpret_cast<uint8_t*>(&val));
}
}
// Wrapper around a nlohmann::json that assigns a binary value either as binary (for bt-encoding);
// or as hex or base64 (for json-encoding).
class json_binary_proxy {
public:
// Wrapper around a nlohmann::json that assigns a binary value either as binary (for bt-encoding);
// or as hex or base64 (for json-encoding).
class json_binary_proxy {
public:
nlohmann::json& e;
enum class fmt { bt, hex, base64 } format;
explicit json_binary_proxy(nlohmann::json& elem, fmt format)
: e{elem}, format{format} {}
explicit json_binary_proxy(nlohmann::json& elem, fmt format) : e{elem}, format{format} {}
json_binary_proxy() = delete;
json_binary_proxy(const json_binary_proxy&) = default;
@ -64,7 +74,7 @@ namespace tools {
/// Descends into the json object, returning a new binary value proxy around the child element.
template <typename T>
json_binary_proxy operator[](T&& key) {
return json_binary_proxy{e[std::forward<T>(key)], format};
return json_binary_proxy{e[std::forward<T>(key)], format};
}
/// Returns a binary value proxy around the first/last element (requires an underlying list)
@ -76,63 +86,65 @@ namespace tools {
/// Assigns binary data from a string_view over a 1-byte, non-char type (e.g. unsigned char or
/// uint8_t).
template <typename Char, std::enable_if_t<sizeof(Char) == 1 && !std::is_same_v<Char, char>, int> = 0>
template <
typename Char,
std::enable_if_t<sizeof(Char) == 1 && !std::is_same_v<Char, char>, int> = 0>
nlohmann::json& operator=(std::basic_string_view<Char> binary_data) {
return *this = std::string_view{reinterpret_cast<const char*>(binary_data.data()), binary_data.size()};
return *this = std::string_view{
reinterpret_cast<const char*>(binary_data.data()), binary_data.size()};
}
/// Takes a trivial, no-padding data structure (e.g. a crypto::hash) as the value and dumps its
/// contents as the binary value.
template <typename T, std::enable_if_t<json_is_binary<T>, int> = 0>
nlohmann::json& operator=(const T& val) {
return *this = std::string_view{reinterpret_cast<const char*>(&val), sizeof(val)};
return *this = std::string_view{reinterpret_cast<const char*>(&val), sizeof(val)};
}
/// Takes a vector of some json_binary_proxy-assignable type and builds an array by assigning
/// each one into a new array of binary values.
template <typename T, std::enable_if_t<json_is_binary_container<T>, int> = 0>
nlohmann::json& operator=(const T& vals) {
auto a = nlohmann::json::array();
for (auto& val : vals)
json_binary_proxy{a.emplace_back(), format} = val;
return e = std::move(a);
auto a = nlohmann::json::array();
for (auto& val : vals)
json_binary_proxy{a.emplace_back(), format} = val;
return e = std::move(a);
}
/// Emplaces a new nlohman::json to the end of an underlying list and returns a
/// json_binary_proxy wrapping it.
///
/// Example:
///
/// auto child = wrappedelem.emplace_back({"key1": 1}, {"key2": 2});
/// child["binary-key"] = some_binary_thing;
template <typename... Args>
json_binary_proxy emplace_back(Args&&... args) {
return json_binary_proxy{e.emplace_back(std::forward<Args>(args)...), format};
}
/// Emplaces a new nlohman::json to the end of an underlying list and returns a
/// json_binary_proxy wrapping it.
///
/// Example:
///
/// auto child = wrappedelem.emplace_back({"key1": 1}, {"key2": 2});
/// child["binary-key"] = some_binary_thing;
template <typename... Args>
json_binary_proxy emplace_back(Args&&... args) {
return json_binary_proxy{e.emplace_back(std::forward<Args>(args)...), format};
}
/// Adds an element to an underlying list, then copies or moves the given argument onto it via
/// json_binary_proxy assignment.
template <typename T>
void push_back(T&& val) {
emplace_back() = std::forward<T>(val);
}
};
}
/// Adds an element to an underlying list, then copies or moves the given argument onto it via
/// json_binary_proxy assignment.
template <typename T>
void push_back(T&& val) {
emplace_back() = std::forward<T>(val);
}
};
} // namespace tools
// Specializations of binary types for deserialization; when receiving these from json we expect
// them encoded in hex or base64. These may *not* be used for serialization, and will throw if so
// invoked; for serialization you need to use RPC_COMMAND::response_hex (or _b64) instead.
namespace nlohmann {
template <typename T>
struct adl_serializer<T, std::enable_if_t<tools::json_is_binary<T>>> {
template <typename T>
struct adl_serializer<T, std::enable_if_t<tools::json_is_binary<T>>> {
static_assert(std::is_trivially_copyable_v<T> && std::has_unique_object_representations_v<T>);
static void to_json(json& j, const T&) {
throw std::logic_error{"Internal error: binary types are not directly serializable"};
throw std::logic_error{"Internal error: binary types are not directly serializable"};
}
static void from_json(const json& j, T& val) {
tools::load_binary_parameter(j.get<std::string_view>(), false /*no raw*/, val);
tools::load_binary_parameter(j.get<std::string_view>(), false /*no raw*/, val);
}
};
}
};
} // namespace nlohmann

View File

@ -36,11 +36,10 @@ namespace tools {
/// Takes any number of lockable objects, locks them atomically, and returns a tuple of
/// std::unique_lock holding the individual locks.
template <typename... T>
[[nodiscard]]
std::tuple<std::unique_lock<T>...> unique_locks(T& ...lockables) {
[[nodiscard]] std::tuple<std::unique_lock<T>...> unique_locks(T&... lockables) {
std::lock(lockables...);
auto locks = std::make_tuple(std::unique_lock<T>(lockables, std::adopt_lock)...);
return locks;
}
}
} // namespace tools

View File

@ -5,29 +5,29 @@
namespace tools {
// Calculate the median element (the middle element, if an odd size, and the mean of the two
// middle elements if even). Pass first=true if you don't care about the mean of the middle two,
// in which case you'll get back the value of lower of the two middle elements.
// This leaves the given range in an indeterminant (partially sorted) order.
template <typename RandomAccessIter>
auto median(RandomAccessIter begin, RandomAccessIter end, bool first=false) {
// Calculate the median element (the middle element, if an odd size, and the mean of the two
// middle elements if even). Pass first=true if you don't care about the mean of the middle two,
// in which case you'll get back the value of lower of the two middle elements.
// This leaves the given range in an indeterminant (partially sorted) order.
template <typename RandomAccessIter>
auto median(RandomAccessIter begin, RandomAccessIter end, bool first = false) {
std::size_t size = end - begin;
if (size == 0)
return std::decay_t<decltype(*begin)>{};
return std::decay_t<decltype(*begin)>{};
auto mid = begin + (size - 1) / 2;
std::nth_element(begin, mid, end);
if (first || size % 2)
return *mid;
return *mid;
auto mid2 = std::min_element(mid + 1, end);
return (*mid + *mid2) / 2;
}
// Same as above, but takes a vector by value or move for convenience.
template <typename T>
T median(std::vector<T> v, bool first=false) {
return median(v.begin(), v.end(), first);
}
}
// Same as above, but takes a vector by value or move for convenience.
template <typename T>
T median(std::vector<T> v, bool first = false) {
return median(v.begin(), v.end(), first);
}
} // namespace tools

View File

@ -1,10 +1,12 @@
#pragma once
#include <string>
#include <oxenc/variant.h>
#include <array>
#include <string>
#include <typeinfo>
#ifdef __GNUG__
#include <cxxabi.h>
#include <cstdlib>
#endif
@ -12,24 +14,28 @@ namespace tools {
namespace detail {
template <typename T, typename T1, typename... Ts>
constexpr size_t template_index_impl_inner() {
if constexpr (std::is_same_v<T, T1>) return 0;
else {
static_assert(sizeof...(Ts) > 0, "Type not found");
return 1 + template_index_impl_inner<T, Ts...>();
template <typename T, typename T1, typename... Ts>
constexpr size_t template_index_impl_inner() {
if constexpr (std::is_same_v<T, T1>)
return 0;
else {
static_assert(sizeof...(Ts) > 0, "Type not found");
return 1 + template_index_impl_inner<T, Ts...>();
}
}
}
template <typename T, typename C> struct template_index_impl {};
template <typename T, typename C>
struct template_index_impl {};
template <typename T, template<typename...> typename C, typename... Ts>
struct template_index_impl<T, C<Ts...>> : std::integral_constant<size_t, template_index_impl_inner<T, Ts...>()> {};
template <typename T, template <typename...> typename C, typename... Ts>
struct template_index_impl<T, C<Ts...>>
: std::integral_constant<size_t, template_index_impl_inner<T, Ts...>()> {};
} // namespace detail
} // namespace detail
/// Type wrapper that contains an arbitrary list of types.
template <typename...> struct type_list {};
template <typename...>
struct type_list {};
/// Accesses the index of the first T within a template type's type list. E.g.
///
@ -70,6 +76,8 @@ inline std::string type_name(const std::type_info& ti) {
/// Same as above, but uses a templated type instead of a type_info argument.
template <typename T>
inline std::string type_name() { return type_name(typeid(T)); }
inline std::string type_name() {
return type_name(typeid(T));
}
} // namespace tools
} // namespace tools

View File

@ -1,21 +1,21 @@
// Copyright (c) 2019, The Monero 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
@ -26,52 +26,51 @@
// 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.
#include "string_util.h"
#include "notify.h"
#include "epee/misc_log_ex.h"
#include "logging/oxen_logger.h"
#include "spawn.h"
#include "notify.h"
#include "string_util.h"
namespace tools
{
namespace tools {
static auto logcat = log::Cat("notify");
static auto logcat = log::Cat("notify");
/*
TODO:
TODO:
- Improve tokenization to handle paths containing whitespaces, quotes, etc.
- Windows unicode support (implies implementing unicode command line parsing code)
*/
Notify::Notify(std::string_view spec)
{
CHECK_AND_ASSERT_THROW_MES(!spec.empty(), "Empty spec");
Notify::Notify(std::string_view spec) {
CHECK_AND_ASSERT_THROW_MES(!spec.empty(), "Empty spec");
auto pieces = tools::split_any(spec, " \t", true);
CHECK_AND_ASSERT_THROW_MES(pieces.size() > 0, "Failed to parse spec");
filename = fs::u8path(pieces[0]);
CHECK_AND_ASSERT_THROW_MES(fs::exists(filename), "File not found: " << filename);
auto pieces = tools::split_any(spec, " \t", true);
CHECK_AND_ASSERT_THROW_MES(pieces.size() > 0, "Failed to parse spec");
filename = fs::u8path(pieces[0]);
CHECK_AND_ASSERT_THROW_MES(fs::exists(filename), "File not found: " << filename);
args.reserve(pieces.size());
for (const auto& piece : pieces)
args.emplace_back(piece);
args.reserve(pieces.size());
for (const auto& piece : pieces)
args.emplace_back(piece);
}
void Notify::replace_tag(std::vector<std::string>& margs, std::string_view tag, std::string_view value)
{
if (tag.empty())
return;
// Skip margs[0], it's the binary name
for (size_t i = 1; i < margs.size(); i++) {
size_t pos = 0;
while ((pos = margs[i].find(tag, pos)) != std::string::npos) {
margs[i].replace(pos, tag.size(), value);
pos += value.size();
void Notify::replace_tag(
std::vector<std::string>& margs, std::string_view tag, std::string_view value) {
if (tag.empty())
return;
// Skip margs[0], it's the binary name
for (size_t i = 1; i < margs.size(); i++) {
size_t pos = 0;
while ((pos = margs[i].find(tag, pos)) != std::string::npos) {
margs[i].replace(pos, tag.size(), value);
pos += value.size();
}
}
}
}
int Notify::spawn(const std::vector<std::string>& margs) const {
return tools::spawn(filename, margs, false);
}
}
} // namespace tools

View File

@ -1,21 +1,21 @@
// Copyright (c) 2019, The Monero 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
@ -26,44 +26,49 @@
// 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.
#pragma once
#pragma once
#include <fmt/core.h>
#include <string>
#include <string_view>
#include <vector>
#include <fmt/core.h>
#include "common/format.h"
#include "fs.h"
namespace tools
{
namespace tools {
class Notify
{
public:
explicit Notify(std::string_view spec);
class Notify {
public:
explicit Notify(std::string_view spec);
template <typename T, typename... MoreTags>
int notify(std::string_view tag, const T& value, MoreTags&&... more) const {
std::vector<std::string> margs{args};
replace_tags(margs, tag, value, std::forward<MoreTags>(more)...);
return spawn(margs);
}
template <typename T, typename... MoreTags>
int notify(std::string_view tag, const T& value, MoreTags&&... more) const {
std::vector<std::string> margs{args};
replace_tags(margs, tag, value, std::forward<MoreTags>(more)...);
return spawn(margs);
}
private:
fs::path filename;
std::vector<std::string> args;
private:
fs::path filename;
std::vector<std::string> args;
int spawn(const std::vector<std::string>& margs) const;
int spawn(const std::vector<std::string>& margs) const;
template <typename T, typename... MoreTags>
static void replace_tags(std::vector<std::string>& margs, std::string_view tag, const T& value, MoreTags&&... more) {
replace_tag(margs, tag, "{}"_format(value));
if constexpr (sizeof...(MoreTags) > 0)
replace_tags(margs, std::forward<MoreTags>(more)...);
}
template <typename T, typename... MoreTags>
static void replace_tags(
std::vector<std::string>& margs,
std::string_view tag,
const T& value,
MoreTags&&... more) {
replace_tag(margs, tag, "{}"_format(value));
if constexpr (sizeof...(MoreTags) > 0)
replace_tags(margs, std::forward<MoreTags>(more)...);
}
static void replace_tag(std::vector<std::string>& margs, std::string_view tag, std::string_view value);
static void replace_tag(
std::vector<std::string>& margs, std::string_view tag, std::string_view value);
};
}
} // namespace tools

View File

@ -18,14 +18,15 @@
/* Specification. */
#include <limits>
#include <cfloat>
#include <cmath>
#include <limits>
// TODO(oxen): This is temporary until we switch to integer math for calculating
// block rewards. We provide the specific implementation to minimise the risk of
// different results from math functions across different std libraries.
static_assert(std::numeric_limits<double>::is_iec559, "We require IEEE standard compliant doubles.");
static_assert(
std::numeric_limits<double>::is_iec559, "We require IEEE standard compliant doubles.");
/* Best possible approximation of log(2) as a 'double'. */
#define LOG2 0.693147180559945309417232121458176568075
@ -39,360 +40,352 @@ static_assert(std::numeric_limits<double>::is_iec559, "We require IEEE standard
/* Best possible approximation of 256/log(2) as a 'double'. */
#define LOG2_BY_256_INVERSE 369.329930467574632284140718336484387181
double
oxen::exp2(double x)
{
/* exp2(x) = exp(x*log(2)).
If we would compute it like this, there would be rounding errors for
integer or near-integer values of x. To avoid these, we inline the
algorithm for exp(), and the multiplication with log(2) cancels a
division by log(2). */
double oxen::exp2(double x) {
/* exp2(x) = exp(x*log(2)).
If we would compute it like this, there would be rounding errors for
integer or near-integer values of x. To avoid these, we inline the
algorithm for exp(), and the multiplication with log(2) cancels a
division by log(2). */
// if (isnand (x)) // unnecessary for us
// return x;
// if (isnand (x)) // unnecessary for us
// return x;
if (x > (double) DBL_MAX_EXP)
/* x > DBL_MAX_EXP
hence exp2(x) > 2^DBL_MAX_EXP, overflows to Infinity. */
return HUGE_VAL;
if (x > (double)DBL_MAX_EXP)
/* x > DBL_MAX_EXP
hence exp2(x) > 2^DBL_MAX_EXP, overflows to Infinity. */
return HUGE_VAL;
if (x < (double) (DBL_MIN_EXP - 1 - DBL_MANT_DIG))
/* x < (DBL_MIN_EXP - 1 - DBL_MANT_DIG)
hence exp2(x) < 2^(DBL_MIN_EXP-1-DBL_MANT_DIG),
underflows to zero. */
return 0.0;
if (x < (double)(DBL_MIN_EXP - 1 - DBL_MANT_DIG))
/* x < (DBL_MIN_EXP - 1 - DBL_MANT_DIG)
hence exp2(x) < 2^(DBL_MIN_EXP-1-DBL_MANT_DIG),
underflows to zero. */
return 0.0;
/* Decompose x into
x = n + m/256 + y/log(2)
where
n is an integer,
m is an integer, -128 <= m <= 128,
y is a number, |y| <= log(2)/512 + epsilon = 0.00135...
Then
exp2(x) = 2^n * exp(m * log(2)/256) * exp(y)
The first factor is an ldexpl() call.
The second factor is a table lookup.
The third factor is computed
- either as sinh(y) + cosh(y)
where sinh(y) is computed through the power series:
sinh(y) = y + y^3/3! + y^5/5! + ...
and cosh(y) is computed as hypot(1, sinh(y)),
- or as exp(2*z) = (1 + tanh(z)) / (1 - tanh(z))
where z = y/2
and tanh(z) is computed through its power series:
tanh(z) = z
- 1/3 * z^3
+ 2/15 * z^5
- 17/315 * z^7
+ 62/2835 * z^9
- 1382/155925 * z^11
+ 21844/6081075 * z^13
- 929569/638512875 * z^15
+ ...
Since |z| <= log(2)/1024 < 0.0007, the relative contribution of the
z^7 term is < 0.0007^6 < 2^-60 <= 2^-DBL_MANT_DIG, therefore we can
truncate the series after the z^5 term. */
/* Decompose x into
x = n + m/256 + y/log(2)
where
n is an integer,
m is an integer, -128 <= m <= 128,
y is a number, |y| <= log(2)/512 + epsilon = 0.00135...
Then
exp2(x) = 2^n * exp(m * log(2)/256) * exp(y)
The first factor is an ldexpl() call.
The second factor is a table lookup.
The third factor is computed
- either as sinh(y) + cosh(y)
where sinh(y) is computed through the power series:
sinh(y) = y + y^3/3! + y^5/5! + ...
and cosh(y) is computed as hypot(1, sinh(y)),
- or as exp(2*z) = (1 + tanh(z)) / (1 - tanh(z))
where z = y/2
and tanh(z) is computed through its power series:
tanh(z) = z
- 1/3 * z^3
+ 2/15 * z^5
- 17/315 * z^7
+ 62/2835 * z^9
- 1382/155925 * z^11
+ 21844/6081075 * z^13
- 929569/638512875 * z^15
+ ...
Since |z| <= log(2)/1024 < 0.0007, the relative contribution of the
z^7 term is < 0.0007^6 < 2^-60 <= 2^-DBL_MANT_DIG, therefore we can
truncate the series after the z^5 term. */
{
double nm = oxen::round (x * 256.0); /* = 256 * n + m */
double z = (x * 256.0 - nm) * (LOG2_BY_256 * 0.5);
{
double nm = oxen::round(x * 256.0); /* = 256 * n + m */
double z = (x * 256.0 - nm) * (LOG2_BY_256 * 0.5);
/* Coefficients of the power series for tanh(z). */
#define TANH_COEFF_1 1.0
#define TANH_COEFF_3 -0.333333333333333333333333333333333333334
#define TANH_COEFF_5 0.133333333333333333333333333333333333334
#define TANH_COEFF_7 -0.053968253968253968253968253968253968254
#define TANH_COEFF_9 0.0218694885361552028218694885361552028218
#define TANH_COEFF_1 1.0
#define TANH_COEFF_3 -0.333333333333333333333333333333333333334
#define TANH_COEFF_5 0.133333333333333333333333333333333333334
#define TANH_COEFF_7 -0.053968253968253968253968253968253968254
#define TANH_COEFF_9 0.0218694885361552028218694885361552028218
#define TANH_COEFF_11 -0.00886323552990219656886323552990219656886
#define TANH_COEFF_13 0.00359212803657248101692546136990581435026
#define TANH_COEFF_13 0.00359212803657248101692546136990581435026
#define TANH_COEFF_15 -0.00145583438705131826824948518070211191904
double z2 = z * z;
double tanh_z =
((TANH_COEFF_5
* z2 + TANH_COEFF_3)
* z2 + TANH_COEFF_1)
* z;
double z2 = z * z;
double tanh_z = ((TANH_COEFF_5 * z2 + TANH_COEFF_3) * z2 + TANH_COEFF_1) * z;
double exp_y = (1.0 + tanh_z) / (1.0 - tanh_z);
double exp_y = (1.0 + tanh_z) / (1.0 - tanh_z);
int n = (int) oxen::round (nm * (1.0 / 256.0));
int m = (int) nm - 256 * n;
int n = (int)oxen::round(nm * (1.0 / 256.0));
int m = (int)nm - 256 * n;
/* exp_table[i] = exp((i - 128) * log(2)/256).
Computed in GNU clisp through
(setf (long-float-digits) 128)
(setq a 0L0)
(setf (long-float-digits) 256)
(dotimes (i 257)
(format t " ~D,~%"
(float (exp (* (/ (- i 128) 256) (log 2L0))) a))) */
static const double exp_table[257] =
{
0.707106781186547524400844362104849039284,
0.709023942160207598920563322257676190836,
0.710946301084582779904674297352120049962,
0.71287387205274715340350157671438300618,
0.714806669195985005617532889137569953044,
0.71674470668389442125974978427737336719,
0.71868799872449116280161304224785251353,
0.720636559564312831364255957304947586072,
0.72259040348852331001850312073583545284,
0.724549544821017490259402705487111270714,
0.726513997924526282423036245842287293786,
0.728483777200721910815451524818606761737,
0.730458897090323494325651445155310766577,
0.732439372073202913296664682112279175616,
0.734425216668490963430822513132890712652,
0.736416445434683797507470506133110286942,
0.738413072969749655693453740187024961962,
0.740415113911235885228829945155951253966,
0.742422582936376250272386395864403155277,
0.744435494762198532693663597314273242753,
0.746453864145632424600321765743336770838,
0.748477705883617713391824861712720862423,
0.750507034813212760132561481529764324813,
0.752541865811703272039672277899716132493,
0.75458221379671136988300977551659676571,
0.756628093726304951096818488157633113612,
0.75867952059910734940489114658718937343,
0.760736509454407291763130627098242426467,
0.762799075372269153425626844758470477304,
0.76486723347364351194254345936342587308,
0.766940998920478000900300751753859329456,
0.769020386915828464216738479594307884331,
0.771105412703970411806145931045367420652,
0.773196091570510777431255778146135325272,
0.77529243884249997956151370535341912283,
0.777394469888544286059157168801667390437,
0.779502200118918483516864044737428940745,
0.781615644985678852072965367573877941354,
0.783734819982776446532455855478222575498,
0.78585974064617068462428149076570281356,
0.787990422553943243227635080090952504452,
0.790126881326412263402248482007960521995,
0.79226913262624686505993407346567890838,
0.794417192158581972116898048814333564685,
0.796571075671133448968624321559534367934,
0.798730798954313549131410147104316569576,
0.800896377841346676896923120795476813684,
0.803067828208385462848443946517563571584,
0.805245165974627154089760333678700291728,
0.807428407102430320039984581575729114268,
0.809617567597431874649880866726368203972,
0.81181266350866441589760797777344082227,
0.814013710928673883424109261007007338614,
0.816220725993637535170713864466769240053,
0.818433724883482243883852017078007231025,
0.82065272382200311435413206848451310067,
0.822877739076982422259378362362911222833,
0.825108786960308875483586738272485101678,
0.827345883828097198786118571797909120834,
0.829589046080808042697824787210781231927,
0.831838290163368217523168228488195222638,
0.834093632565291253329796170708536192903,
0.836355089820798286809404612069230711295,
0.83862267850893927589613232455870870518,
0.84089641525371454303112547623321489504,
0.84317631672419664796432298771385230143,
0.84546239963465259098692866759361830709,
0.84775468074466634749045860363936420312,
0.850053176859261734750681286748751167545,
0.852357904829025611837203530384718316326,
0.854668881550231413551897437515331498025,
0.856986123964963019301812477839166009452,
0.859309649061238957814672188228156252257,
0.861639473873136948607517116872358729753,
0.863975615480918781121524414614366207052,
0.866318091011155532438509953514163469652,
0.868666917636853124497101040936083380124,
0.871022112577578221729056715595464682243,
0.873383693099584470038708278290226842228,
0.875751676515939078050995142767930296012,
0.878126080186649741556080309687656610647,
0.880506921518791912081045787323636256171,
0.882894217966636410521691124969260937028,
0.885287987031777386769987907431242017412,
0.88768824626326062627527960009966160388,
0.89009501325771220447985955243623523504,
0.892508305659467490072110281986409916153,
0.8949281411607004980029443898876582985,
0.897354537501553593213851621063890907178,
0.899787512470267546027427696662514569756,
0.902227083903311940153838631655504844215,
0.904673269685515934269259325789226871994,
0.907126087750199378124917300181170171233,
0.909585556079304284147971563828178746372,
0.91205169270352665549806275316460097744,
0.914524515702448671545983912696158354092,
0.91700404320467123174354159479414442804,
0.919490293387946858856304371174663918816,
0.921983284479312962533570386670938449637,
0.92448303475522546419252726694739603678,
0.92698956254169278419622653516884831976,
0.929502886214410192307650717745572682403,
0.932023024198894522404814545597236289343,
0.934549994970619252444512104439799143264,
0.93708381705514995066499947497722326722,
0.93962450902828008902058735120448448827,
0.942172089516167224843810351983745154882,
0.944726577195469551733539267378681531548,
0.947287990793482820670109326713462307376,
0.949856349088277632361251759806996099924,
0.952431670908837101825337466217860725517,
0.955013975135194896221170529572799135168,
0.957603280698573646936305635147915443924,
0.960199606581523736948607188887070611744,
0.962802971818062464478519115091191368377,
0.965413395493813583952272948264534783197,
0.968030896746147225299027952283345762418,
0.970655494764320192607710617437589705184,
0.973287208789616643172102023321302921373,
0.97592605811548914795551023340047499377,
0.978572062087700134509161125813435745597,
0.981225240104463713381244885057070325016,
0.983885611616587889056366801238014683926,
0.98655319612761715646797006813220671315,
0.989228013193975484129124959065583667775,
0.99191008242510968492991311132615581644,
0.994599423483633175652477686222166314457,
0.997296056085470126257659913847922601123,
1.0,
1.00271127505020248543074558845036204047,
1.0054299011128028213513839559347998147,
1.008155898118417515783094890817201039276,
1.01088928605170046002040979056186052439,
1.013630084951489438840258929063939929597,
1.01637831491095303794049311378629406276,
1.0191339960777379496848780958207928794,
1.02189714865411667823448013478329943978,
1.02466779289713564514828907627081492763,
1.0274459491187636965388611939222137815,
1.030231637686041012871707902453904567093,
1.033024879021228422500108283970460918086,
1.035825693601957120029983209018081371844,
1.03863410196137879061243669795463973258,
1.04145012468831614126454607901189312648,
1.044273782427413840321966478739929008784,
1.04710509587928986612990725022711224056,
1.04994408580068726608203812651590790906,
1.05279077300462632711989120298074630319,
1.05564517836055715880834132515293865216,
1.058507322794512690105772109683716645074,
1.061377227289262080950567678003883726294,
1.06425491288446454978861125700158022068,
1.06714040067682361816952112099280916261,
1.0700337118202417735424119367576235685,
1.072934867525975551385035450873827585343,
1.075843889062791037803228648476057074063,
1.07876079775711979374068003743848295849,
1.081685614993215201942115594422531125643,
1.08461836221330923781610517190661434161,
1.087559060917769665346797830944039707867,
1.09050773266525765920701065576070797899,
1.09346439907288585422822014625044716208,
1.096429081816376823386138295859248481766,
1.09940180263022198546369696823882990404,
1.10238258330784094355641420942564685751,
1.10537144570174125558827469625695031104,
1.108368411723678638009423649426619850137,
1.111373503344817603850149254228916637444,
1.1143867425958925363088129569196030678,
1.11740815156736919905457996308578026665,
1.12043775240960668442900387986631301277,
1.123475567333019800733729739775321431954,
1.12652161860824189979479864378703477763,
1.129575928566288145997264988840249825907,
1.13263851959871922798707372367762308438,
1.13570941415780551424039033067611701343,
1.13878863475669165370383028384151125472,
1.14187620396956162271229760828788093894,
1.14497214443180421939441388822291589579,
1.14807647884017900677879966269734268003,
1.15118922995298270581775963520198253612,
1.154310420590216039548221528724806960684,
1.157440073633751029613085766293796821106,
1.16057821202749874636945947257609098625,
1.16372485877757751381357359909218531234,
1.166880036952481570555516298414089287834,
1.170043769683250188080259035792738573,
1.17321608016363724753480435451324538889,
1.176396991650281276284645728483848641054,
1.17958652746287594548610056676944051898,
1.182784710984341029924457204693850757966,
1.18599156566099383137126564953421556374,
1.18920711500272106671749997056047591529,
1.19243138258315122214272755814543101148,
1.195664392039827374583837049865451975705,
1.19890616707438048177030255797630020695,
1.202156731452703142096396957497765876003,
1.205416109005123825604211432558411335666,
1.208684323626581577354792255889216998484,
1.21196139927680119446816891773249304545,
1.215247359980468878116520251338798457624,
1.218542229827408361758207148117394510724,
1.221846032972757516903891841911570785836,
1.225158793637145437709464594384845353707,
1.22848053610687000569400895779278184036,
1.2318112847340759358845566532127948166,
1.235151063936933305692912507415415760294,
1.238499898199816567833368865859612431545,
1.24185781207348404859367746872659560551,
1.24522483017525793277520496748615267417,
1.24860097718920473662176609730249554519,
1.25198627786631627006020603178920359732,
1.255380757024691089579390657442301194595,
1.25878443954971644307786044181516261876,
1.26219735039425070801401025851841645967,
1.265619514578806324196273999873453036296,
1.26905095719173322255441908103233800472,
1.27249170338940275123669204418460217677,
1.27594177839639210038120243475928938891,
1.27940120750566922691358797002785254596,
1.28287001607877828072666978102151405111,
1.286348229546025533601482208069738348355,
1.28983587340666581223274729549155218968,
1.293332973229089436725559789048704304684,
1.296839554651009665933754117792451159835,
1.30035564337965065101414056707091779129,
1.30388126519193589857452364895199736833,
1.30741644593467724479715157747196172848,
1.310961211524764341922991786330755849366,
1.314515587949354658485983613383997794965,
1.318079601266063994690185647066116617664,
1.32165327760315751432651181233060922616,
1.32523664315974129462953709549872167411,
1.32882972420595439547865089632866510792,
1.33243254708316144935164337949073577407,
1.33604513820414577344262790437186975929,
1.33966752405330300536003066972435257602,
1.34329973118683526382421714618163087542,
1.346941786232945835788173713229537282075,
1.35059371589203439140852219606013396004,
1.35425554693689272829801474014070280434,
1.357927306212901046494536695671766697446,
1.36160902063822475558553593883194147464,
1.36530071720401181543069836033754285543,
1.36900242297459061192960113298219283217,
1.37271416508766836928499785714471721579,
1.37643597075453010021632280551868696026,
1.380167867260238095581945274358283464697,
1.383909881963831954872659527265192818,
1.387662042298529159042861017950775988896,
1.39142437577192618714983552956624344668,
1.395196909966200178275574599249220994716,
1.398979672538311140209528136715194969206,
1.40277269122020470637471352433337881711,
1.40657599381901544248361973255451684411,
1.410389608217270704414375128268675481145,
1.41421356237309504880168872420969807857
};
/* exp_table[i] = exp((i - 128) * log(2)/256).
Computed in GNU clisp through
(setf (long-float-digits) 128)
(setq a 0L0)
(setf (long-float-digits) 256)
(dotimes (i 257)
(format t " ~D,~%"
(float (exp (* (/ (- i 128) 256) (log 2L0))) a))) */
static const double exp_table[257] = {
0.707106781186547524400844362104849039284,
0.709023942160207598920563322257676190836,
0.710946301084582779904674297352120049962,
0.71287387205274715340350157671438300618,
0.714806669195985005617532889137569953044,
0.71674470668389442125974978427737336719,
0.71868799872449116280161304224785251353,
0.720636559564312831364255957304947586072,
0.72259040348852331001850312073583545284,
0.724549544821017490259402705487111270714,
0.726513997924526282423036245842287293786,
0.728483777200721910815451524818606761737,
0.730458897090323494325651445155310766577,
0.732439372073202913296664682112279175616,
0.734425216668490963430822513132890712652,
0.736416445434683797507470506133110286942,
0.738413072969749655693453740187024961962,
0.740415113911235885228829945155951253966,
0.742422582936376250272386395864403155277,
0.744435494762198532693663597314273242753,
0.746453864145632424600321765743336770838,
0.748477705883617713391824861712720862423,
0.750507034813212760132561481529764324813,
0.752541865811703272039672277899716132493,
0.75458221379671136988300977551659676571,
0.756628093726304951096818488157633113612,
0.75867952059910734940489114658718937343,
0.760736509454407291763130627098242426467,
0.762799075372269153425626844758470477304,
0.76486723347364351194254345936342587308,
0.766940998920478000900300751753859329456,
0.769020386915828464216738479594307884331,
0.771105412703970411806145931045367420652,
0.773196091570510777431255778146135325272,
0.77529243884249997956151370535341912283,
0.777394469888544286059157168801667390437,
0.779502200118918483516864044737428940745,
0.781615644985678852072965367573877941354,
0.783734819982776446532455855478222575498,
0.78585974064617068462428149076570281356,
0.787990422553943243227635080090952504452,
0.790126881326412263402248482007960521995,
0.79226913262624686505993407346567890838,
0.794417192158581972116898048814333564685,
0.796571075671133448968624321559534367934,
0.798730798954313549131410147104316569576,
0.800896377841346676896923120795476813684,
0.803067828208385462848443946517563571584,
0.805245165974627154089760333678700291728,
0.807428407102430320039984581575729114268,
0.809617567597431874649880866726368203972,
0.81181266350866441589760797777344082227,
0.814013710928673883424109261007007338614,
0.816220725993637535170713864466769240053,
0.818433724883482243883852017078007231025,
0.82065272382200311435413206848451310067,
0.822877739076982422259378362362911222833,
0.825108786960308875483586738272485101678,
0.827345883828097198786118571797909120834,
0.829589046080808042697824787210781231927,
0.831838290163368217523168228488195222638,
0.834093632565291253329796170708536192903,
0.836355089820798286809404612069230711295,
0.83862267850893927589613232455870870518,
0.84089641525371454303112547623321489504,
0.84317631672419664796432298771385230143,
0.84546239963465259098692866759361830709,
0.84775468074466634749045860363936420312,
0.850053176859261734750681286748751167545,
0.852357904829025611837203530384718316326,
0.854668881550231413551897437515331498025,
0.856986123964963019301812477839166009452,
0.859309649061238957814672188228156252257,
0.861639473873136948607517116872358729753,
0.863975615480918781121524414614366207052,
0.866318091011155532438509953514163469652,
0.868666917636853124497101040936083380124,
0.871022112577578221729056715595464682243,
0.873383693099584470038708278290226842228,
0.875751676515939078050995142767930296012,
0.878126080186649741556080309687656610647,
0.880506921518791912081045787323636256171,
0.882894217966636410521691124969260937028,
0.885287987031777386769987907431242017412,
0.88768824626326062627527960009966160388,
0.89009501325771220447985955243623523504,
0.892508305659467490072110281986409916153,
0.8949281411607004980029443898876582985,
0.897354537501553593213851621063890907178,
0.899787512470267546027427696662514569756,
0.902227083903311940153838631655504844215,
0.904673269685515934269259325789226871994,
0.907126087750199378124917300181170171233,
0.909585556079304284147971563828178746372,
0.91205169270352665549806275316460097744,
0.914524515702448671545983912696158354092,
0.91700404320467123174354159479414442804,
0.919490293387946858856304371174663918816,
0.921983284479312962533570386670938449637,
0.92448303475522546419252726694739603678,
0.92698956254169278419622653516884831976,
0.929502886214410192307650717745572682403,
0.932023024198894522404814545597236289343,
0.934549994970619252444512104439799143264,
0.93708381705514995066499947497722326722,
0.93962450902828008902058735120448448827,
0.942172089516167224843810351983745154882,
0.944726577195469551733539267378681531548,
0.947287990793482820670109326713462307376,
0.949856349088277632361251759806996099924,
0.952431670908837101825337466217860725517,
0.955013975135194896221170529572799135168,
0.957603280698573646936305635147915443924,
0.960199606581523736948607188887070611744,
0.962802971818062464478519115091191368377,
0.965413395493813583952272948264534783197,
0.968030896746147225299027952283345762418,
0.970655494764320192607710617437589705184,
0.973287208789616643172102023321302921373,
0.97592605811548914795551023340047499377,
0.978572062087700134509161125813435745597,
0.981225240104463713381244885057070325016,
0.983885611616587889056366801238014683926,
0.98655319612761715646797006813220671315,
0.989228013193975484129124959065583667775,
0.99191008242510968492991311132615581644,
0.994599423483633175652477686222166314457,
0.997296056085470126257659913847922601123,
1.0,
1.00271127505020248543074558845036204047,
1.0054299011128028213513839559347998147,
1.008155898118417515783094890817201039276,
1.01088928605170046002040979056186052439,
1.013630084951489438840258929063939929597,
1.01637831491095303794049311378629406276,
1.0191339960777379496848780958207928794,
1.02189714865411667823448013478329943978,
1.02466779289713564514828907627081492763,
1.0274459491187636965388611939222137815,
1.030231637686041012871707902453904567093,
1.033024879021228422500108283970460918086,
1.035825693601957120029983209018081371844,
1.03863410196137879061243669795463973258,
1.04145012468831614126454607901189312648,
1.044273782427413840321966478739929008784,
1.04710509587928986612990725022711224056,
1.04994408580068726608203812651590790906,
1.05279077300462632711989120298074630319,
1.05564517836055715880834132515293865216,
1.058507322794512690105772109683716645074,
1.061377227289262080950567678003883726294,
1.06425491288446454978861125700158022068,
1.06714040067682361816952112099280916261,
1.0700337118202417735424119367576235685,
1.072934867525975551385035450873827585343,
1.075843889062791037803228648476057074063,
1.07876079775711979374068003743848295849,
1.081685614993215201942115594422531125643,
1.08461836221330923781610517190661434161,
1.087559060917769665346797830944039707867,
1.09050773266525765920701065576070797899,
1.09346439907288585422822014625044716208,
1.096429081816376823386138295859248481766,
1.09940180263022198546369696823882990404,
1.10238258330784094355641420942564685751,
1.10537144570174125558827469625695031104,
1.108368411723678638009423649426619850137,
1.111373503344817603850149254228916637444,
1.1143867425958925363088129569196030678,
1.11740815156736919905457996308578026665,
1.12043775240960668442900387986631301277,
1.123475567333019800733729739775321431954,
1.12652161860824189979479864378703477763,
1.129575928566288145997264988840249825907,
1.13263851959871922798707372367762308438,
1.13570941415780551424039033067611701343,
1.13878863475669165370383028384151125472,
1.14187620396956162271229760828788093894,
1.14497214443180421939441388822291589579,
1.14807647884017900677879966269734268003,
1.15118922995298270581775963520198253612,
1.154310420590216039548221528724806960684,
1.157440073633751029613085766293796821106,
1.16057821202749874636945947257609098625,
1.16372485877757751381357359909218531234,
1.166880036952481570555516298414089287834,
1.170043769683250188080259035792738573,
1.17321608016363724753480435451324538889,
1.176396991650281276284645728483848641054,
1.17958652746287594548610056676944051898,
1.182784710984341029924457204693850757966,
1.18599156566099383137126564953421556374,
1.18920711500272106671749997056047591529,
1.19243138258315122214272755814543101148,
1.195664392039827374583837049865451975705,
1.19890616707438048177030255797630020695,
1.202156731452703142096396957497765876003,
1.205416109005123825604211432558411335666,
1.208684323626581577354792255889216998484,
1.21196139927680119446816891773249304545,
1.215247359980468878116520251338798457624,
1.218542229827408361758207148117394510724,
1.221846032972757516903891841911570785836,
1.225158793637145437709464594384845353707,
1.22848053610687000569400895779278184036,
1.2318112847340759358845566532127948166,
1.235151063936933305692912507415415760294,
1.238499898199816567833368865859612431545,
1.24185781207348404859367746872659560551,
1.24522483017525793277520496748615267417,
1.24860097718920473662176609730249554519,
1.25198627786631627006020603178920359732,
1.255380757024691089579390657442301194595,
1.25878443954971644307786044181516261876,
1.26219735039425070801401025851841645967,
1.265619514578806324196273999873453036296,
1.26905095719173322255441908103233800472,
1.27249170338940275123669204418460217677,
1.27594177839639210038120243475928938891,
1.27940120750566922691358797002785254596,
1.28287001607877828072666978102151405111,
1.286348229546025533601482208069738348355,
1.28983587340666581223274729549155218968,
1.293332973229089436725559789048704304684,
1.296839554651009665933754117792451159835,
1.30035564337965065101414056707091779129,
1.30388126519193589857452364895199736833,
1.30741644593467724479715157747196172848,
1.310961211524764341922991786330755849366,
1.314515587949354658485983613383997794965,
1.318079601266063994690185647066116617664,
1.32165327760315751432651181233060922616,
1.32523664315974129462953709549872167411,
1.32882972420595439547865089632866510792,
1.33243254708316144935164337949073577407,
1.33604513820414577344262790437186975929,
1.33966752405330300536003066972435257602,
1.34329973118683526382421714618163087542,
1.346941786232945835788173713229537282075,
1.35059371589203439140852219606013396004,
1.35425554693689272829801474014070280434,
1.357927306212901046494536695671766697446,
1.36160902063822475558553593883194147464,
1.36530071720401181543069836033754285543,
1.36900242297459061192960113298219283217,
1.37271416508766836928499785714471721579,
1.37643597075453010021632280551868696026,
1.380167867260238095581945274358283464697,
1.383909881963831954872659527265192818,
1.387662042298529159042861017950775988896,
1.39142437577192618714983552956624344668,
1.395196909966200178275574599249220994716,
1.398979672538311140209528136715194969206,
1.40277269122020470637471352433337881711,
1.40657599381901544248361973255451684411,
1.410389608217270704414375128268675481145,
1.41421356237309504880168872420969807857};
double ret = exp_table[128 + m] * exp_y;
for (int i = 0; i < n; i++)
ret *= 2;
return ret;
}
double ret = exp_table[128 + m] * exp_y;
for (int i = 0; i < n; i++)
ret *= 2;
return ret;
}
}
/* Round toward nearest, breaking ties away from zero.
@ -416,84 +409,76 @@ oxen::exp2(double x)
/* Specification. */
#include <cstdint>
#include <cfloat>
#include <cstdint>
#include <limits>
/* -0.0. See minus-zero.h. */
#if defined __hpux || defined __sgi || defined __ICC
# define MINUS_ZERO (-DBL_MIN * DBL_MIN)
#define MINUS_ZERO (-DBL_MIN * DBL_MIN)
#else
# define MINUS_ZERO -0.0
#define MINUS_ZERO -0.0
#endif
/* MSVC with option -fp:strict refuses to compile constant initializers that
contain floating-point operations. Pacify this compiler. */
#ifdef _MSC_VER
# pragma fenv_access (off)
#pragma fenv_access(off)
#endif
double
oxen::round (double x)
{
/* 2^(DBL_MANT_DIG-1). */
static const double TWO_MANT_DIG =
/* Assume DBL_MANT_DIG <= 5 * 31.
Use the identity
n = floor(n/5) + floor((n+1)/5) + ... + floor((n+4)/5). */
(double) (1U << ((DBL_MANT_DIG - 1) / 5))
* (double) (1U << ((DBL_MANT_DIG - 1 + 1) / 5))
* (double) (1U << ((DBL_MANT_DIG - 1 + 2) / 5))
* (double) (1U << ((DBL_MANT_DIG - 1 + 3) / 5))
* (double) (1U << ((DBL_MANT_DIG - 1 + 4) / 5));
double oxen::round(double x) {
/* 2^(DBL_MANT_DIG-1). */
static const double TWO_MANT_DIG =
/* Assume DBL_MANT_DIG <= 5 * 31.
Use the identity
n = floor(n/5) + floor((n+1)/5) + ... + floor((n+4)/5). */
(double)(1U << ((DBL_MANT_DIG - 1) / 5)) *
(double)(1U << ((DBL_MANT_DIG - 1 + 1) / 5)) *
(double)(1U << ((DBL_MANT_DIG - 1 + 2) / 5)) *
(double)(1U << ((DBL_MANT_DIG - 1 + 3) / 5)) *
(double)(1U << ((DBL_MANT_DIG - 1 + 4) / 5));
/* The use of 'volatile' guarantees that excess precision bits are dropped at
each addition step and before the following comparison at the caller's
site. It is necessary on x86 systems where double-floats are not IEEE
compliant by default, to avoid that the results become platform and
compiler option dependent. 'volatile' is a portable alternative to gcc's
-ffloat-store option. */
volatile double y = x;
volatile double z = y;
/* The use of 'volatile' guarantees that excess precision bits are dropped at
each addition step and before the following comparison at the caller's
site. It is necessary on x86 systems where double-floats are not IEEE
compliant by default, to avoid that the results become platform and
compiler option dependent. 'volatile' is a portable alternative to gcc's
-ffloat-store option. */
volatile double y = x;
volatile double z = y;
if (z > 0.0)
{
/* Avoid rounding error for x = 0.5 - 2^(-DBL_MANT_DIG-1). */
if (z < 0.5)
z = 0.0;
/* Avoid rounding errors for values near 2^k, where k >= DBL_MANT_DIG-1. */
else if (z < TWO_MANT_DIG)
{
/* Add 0.5 to the absolute value. */
y = z += 0.5;
/* Round to the next integer (nearest or up or down, doesn't
matter). */
z += TWO_MANT_DIG;
z -= TWO_MANT_DIG;
/* Enforce rounding down. */
if (z > y)
z -= 1.0;
if (z > 0.0) {
/* Avoid rounding error for x = 0.5 - 2^(-DBL_MANT_DIG-1). */
if (z < 0.5)
z = 0.0;
/* Avoid rounding errors for values near 2^k, where k >= DBL_MANT_DIG-1. */
else if (z < TWO_MANT_DIG) {
/* Add 0.5 to the absolute value. */
y = z += 0.5;
/* Round to the next integer (nearest or up or down, doesn't
matter). */
z += TWO_MANT_DIG;
z -= TWO_MANT_DIG;
/* Enforce rounding down. */
if (z > y)
z -= 1.0;
}
} else if (z < 0.0) {
/* Avoid rounding error for x = -(0.5 - 2^(-DBL_MANT_DIG-1)). */
if (z > -0.5)
z = MINUS_ZERO;
/* Avoid rounding errors for values near -2^k, where k >= DBL_MANT_DIG-1. */
else if (z > -TWO_MANT_DIG) {
/* Add 0.5 to the absolute value. */
y = z -= 0.5;
/* Round to the next integer (nearest or up or down, doesn't
matter). */
z -= TWO_MANT_DIG;
z += TWO_MANT_DIG;
/* Enforce rounding up. */
if (z < y)
z += 1.0;
}
}
else if (z < 0.0)
{
/* Avoid rounding error for x = -(0.5 - 2^(-DBL_MANT_DIG-1)). */
if (z > - 0.5)
z = MINUS_ZERO;
/* Avoid rounding errors for values near -2^k, where k >= DBL_MANT_DIG-1. */
else if (z > -TWO_MANT_DIG)
{
/* Add 0.5 to the absolute value. */
y = z -= 0.5;
/* Round to the next integer (nearest or up or down, doesn't
matter). */
z -= TWO_MANT_DIG;
z += TWO_MANT_DIG;
/* Enforce rounding up. */
if (z < y)
z += 1.0;
}
}
return z;
return z;
}

View File

@ -1,21 +1,21 @@
// 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
@ -33,49 +33,68 @@
#include <utility>
#define OXEN_RPC_DOC_INTROSPECT
namespace oxen
{
double round (double);
double exp2 (double);
namespace oxen {
double round(double);
double exp2(double);
template <typename lambda_t>
struct deferred
{
private:
lambda_t lambda;
bool cancelled = false;
public:
deferred(lambda_t lambda) : lambda(lambda) {}
void invoke() { lambda(); cancelled = true; } // Invoke early instead of at destruction
void cancel() { cancelled = true; } // Cancel invocation at destruction
~deferred() { if (!cancelled) lambda(); }
struct deferred {
private:
lambda_t lambda;
bool cancelled = false;
deferred(deferred<lambda_t>&& d) : lambda{std::move(d.lambda)}, cancelled{d.cancelled} { d.cancel(); }
deferred& operator=(deferred<lambda_t>&& d) { lambda = std::move(d.lambda); cancelled = d.cancelled; d.cancel(); return *this; }
deferred(const deferred<lambda_t>&) = delete;
deferred& operator=(const deferred<lambda_t>&) = delete;
public:
deferred(lambda_t lambda) : lambda(lambda) {}
void invoke() {
lambda();
cancelled = true;
} // Invoke early instead of at destruction
void cancel() { cancelled = true; } // Cancel invocation at destruction
~deferred() {
if (!cancelled)
lambda();
}
deferred(deferred<lambda_t>&& d) : lambda{std::move(d.lambda)}, cancelled{d.cancelled} {
d.cancel();
}
deferred& operator=(deferred<lambda_t>&& d) {
lambda = std::move(d.lambda);
cancelled = d.cancelled;
d.cancel();
return *this;
}
deferred(const deferred<lambda_t>&) = delete;
deferred& operator=(const deferred<lambda_t>&) = delete;
};
template <typename lambda_t>
[[nodiscard]]
deferred<lambda_t> defer(lambda_t lambda) { return lambda; }
[[nodiscard]] deferred<lambda_t> defer(lambda_t lambda) {
return lambda;
}
struct defer_helper
{
template <typename lambda_t>
deferred<lambda_t> operator+(lambda_t lambda) { return lambda; }
struct defer_helper {
template <typename lambda_t>
deferred<lambda_t> operator+(lambda_t lambda) {
return lambda;
}
};
#define OXEN_TOKEN_COMBINE2(x, y) x ## y
#define OXEN_TOKEN_COMBINE2(x, y) x##y
#define OXEN_TOKEN_COMBINE(x, y) OXEN_TOKEN_COMBINE2(x, y)
#define OXEN_DEFER auto const OXEN_TOKEN_COMBINE(oxen_defer_, __LINE__) = oxen::defer_helper() + [&]()
#define OXEN_DEFER \
auto const OXEN_TOKEN_COMBINE(oxen_defer_, __LINE__) = oxen::defer_helper() + [&]()
template <typename T, size_t N>
constexpr size_t array_count(T (&)[N]) { return N; }
constexpr size_t array_count(T (&)[N]) {
return N;
}
template <typename T, size_t N>
constexpr size_t char_count(T (&)[N]) { return N - 1; }
constexpr size_t char_count(T (&)[N]) {
return N - 1;
}
}; // namespace Oxen
}; // namespace oxen
#endif // OXEN_H
#endif // OXEN_H

View File

@ -1,21 +1,21 @@
// Copyright (c) 2014-2019, The Monero 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
@ -25,15 +25,15 @@
// 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 "password.h"
#include <iostream>
#include <cstdio>
#include <utility>
#include <iostream>
#include <type_traits>
#include <utility>
#if defined(_WIN32)
#include <io.h>
@ -45,16 +45,13 @@
#define EOT 0x4
namespace
{
namespace {
#if defined(_WIN32)
bool is_cin_tty() noexcept
{
bool is_cin_tty() noexcept {
return 0 != _isatty(_fileno(stdin));
}
}
bool read_from_tty(epee::wipeable_string& pass, bool hide_input)
{
bool read_from_tty(epee::wipeable_string& pass, bool hide_input) {
HANDLE h_cin = ::GetStdHandle(STD_INPUT_HANDLE);
DWORD mode_old;
@ -66,60 +63,52 @@ namespace
pass.reserve(tools::password_container::max_password_size);
std::vector<int> chlen;
chlen.reserve(tools::password_container::max_password_size);
while (pass.size() < tools::password_container::max_password_size)
{
DWORD read;
wchar_t ucs2_ch;
r = (TRUE == ::ReadConsoleW(h_cin, &ucs2_ch, 1, &read, NULL));
r &= (1 == read);
while (pass.size() < tools::password_container::max_password_size) {
DWORD read;
wchar_t ucs2_ch;
r = (TRUE == ::ReadConsoleW(h_cin, &ucs2_ch, 1, &read, NULL));
r &= (1 == read);
if (!r)
{
break;
}
else if (ucs2_ch == L'\r')
{
std::cout << std::endl;
break;
}
else if (ucs2_ch == L'\b')
{
if (!pass.empty())
{
int len = chlen.back();
chlen.pop_back();
while(len-- > 0)
pass.pop_back();
if (!r) {
break;
} else if (ucs2_ch == L'\r') {
std::cout << std::endl;
break;
} else if (ucs2_ch == L'\b') {
if (!pass.empty()) {
int len = chlen.back();
chlen.pop_back();
while (len-- > 0)
pass.pop_back();
}
continue;
}
continue;
}
char utf8_ch[8] = {0};
int len;
if((len = WideCharToMultiByte(CP_UTF8, 0, &ucs2_ch, 1, utf8_ch, sizeof(utf8_ch), NULL, NULL)) <= 0)
break;
if(pass.size() + len >= tools::password_container::max_password_size)
break;
char utf8_ch[8] = {0};
int len;
if ((len = WideCharToMultiByte(
CP_UTF8, 0, &ucs2_ch, 1, utf8_ch, sizeof(utf8_ch), NULL, NULL)) <= 0)
break;
chlen.push_back(len);
pass += utf8_ch;
if (pass.size() + len >= tools::password_container::max_password_size)
break;
chlen.push_back(len);
pass += utf8_ch;
}
::SetConsoleMode(h_cin, mode_old);
return r;
}
}
#else // end WIN32
#else // end WIN32
bool is_cin_tty() noexcept
{
bool is_cin_tty() noexcept {
return 0 != isatty(fileno(stdin));
}
}
int getch(bool hide_input) noexcept
{
int getch(bool hide_input) noexcept {
struct termios tty_old;
tcgetattr(STDIN_FILENO, &tty_old);
@ -133,150 +122,127 @@ namespace
tcsetattr(STDIN_FILENO, TCSANOW, &tty_old);
return ch;
}
}
bool read_from_tty(epee::wipeable_string& aPass, bool hide_input)
{
bool read_from_tty(epee::wipeable_string& aPass, bool hide_input) {
static constexpr const char BACKSPACE = 127;
aPass.reserve(tools::password_container::max_password_size);
while (aPass.size() < tools::password_container::max_password_size)
{
int ch = getch(hide_input);
if (EOF == ch || ch == EOT)
{
return false;
}
else if (ch == '\n' || ch == '\r')
{
std::cout << std::endl;
break;
}
else if (ch == BACKSPACE)
{
if (!aPass.empty())
{
aPass.pop_back();
if (!hide_input)
std::cout << "\b\b\b \b\b\b" << std::flush;
while (aPass.size() < tools::password_container::max_password_size) {
int ch = getch(hide_input);
if (EOF == ch || ch == EOT) {
return false;
} else if (ch == '\n' || ch == '\r') {
std::cout << std::endl;
break;
} else if (ch == BACKSPACE) {
if (!aPass.empty()) {
aPass.pop_back();
if (!hide_input)
std::cout << "\b\b\b \b\b\b" << std::flush;
} else {
if (!hide_input)
std::cout << "\b\b \b\b" << std::flush;
}
} else {
aPass.push_back(ch);
}
else
{
if (!hide_input)
std::cout << "\b\b \b\b" << std::flush;
}
}
else
{
aPass.push_back(ch);
}
}
return true;
}
}
#endif // end !WIN32
#endif // end !WIN32
bool read_from_tty(const bool verify, const char *message, bool hide_input, epee::wipeable_string& pass1, epee::wipeable_string& pass2)
{
while (true)
{
if (message)
std::cout << message <<": " << std::flush;
if (!read_from_tty(pass1, hide_input))
return false;
if (verify)
{
std::cout << "Confirm password: ";
if (!read_from_tty(pass2, hide_input))
return false;
if(pass1!=pass2)
{
std::cout << "Passwords do not match! Please try again." << std::endl;
pass1.clear();
pass2.clear();
}
else //new password matches
return true;
}
else
return true;
//No need to verify password entered at this point in the code
bool read_from_tty(
const bool verify,
const char* message,
bool hide_input,
epee::wipeable_string& pass1,
epee::wipeable_string& pass2) {
while (true) {
if (message)
std::cout << message << ": " << std::flush;
if (!read_from_tty(pass1, hide_input))
return false;
if (verify) {
std::cout << "Confirm password: ";
if (!read_from_tty(pass2, hide_input))
return false;
if (pass1 != pass2) {
std::cout << "Passwords do not match! Please try again." << std::endl;
pass1.clear();
pass2.clear();
} else // new password matches
return true;
} else
return true;
// No need to verify password entered at this point in the code
}
return false;
}
}
bool read_from_file(epee::wipeable_string& pass)
{
bool read_from_file(epee::wipeable_string& pass) {
pass.reserve(tools::password_container::max_password_size);
for (size_t i = 0; i < tools::password_container::max_password_size; ++i)
{
char ch = static_cast<char>(std::cin.get());
if (std::cin.eof() || ch == '\n' || ch == '\r')
{
break;
}
else if (std::cin.fail())
{
return false;
}
else
{
pass.push_back(ch);
}
for (size_t i = 0; i < tools::password_container::max_password_size; ++i) {
char ch = static_cast<char>(std::cin.get());
if (std::cin.eof() || ch == '\n' || ch == '\r') {
break;
} else if (std::cin.fail()) {
return false;
} else {
pass.push_back(ch);
}
}
return true;
}
}
} // anonymous namespace
} // anonymous namespace
namespace tools
{
// deleted via private member
password_container::password_container() noexcept : m_password() {}
password_container::password_container(epee::wipeable_string password) noexcept
: m_password{std::move(password)}
{
}
namespace tools {
// deleted via private member
password_container::password_container() noexcept : m_password() {}
password_container::password_container(epee::wipeable_string password) noexcept :
m_password{std::move(password)} {}
std::atomic<bool> password_container::is_prompting(false);
std::atomic<bool> password_container::is_prompting(false);
std::optional<password_container> password_container::prompt(const bool verify, const char *message, bool hide_input)
{
std::optional<password_container> password_container::prompt(
const bool verify, const char* message, bool hide_input) {
is_prompting = true;
password_container pass1{};
password_container pass2{};
if (is_cin_tty() ? read_from_tty(verify, message, hide_input, pass1.m_password, pass2.m_password) : read_from_file(pass1.m_password))
{
is_prompting = false;
return {std::move(pass1)};
if (is_cin_tty()
? read_from_tty(verify, message, hide_input, pass1.m_password, pass2.m_password)
: read_from_file(pass1.m_password)) {
is_prompting = false;
return {std::move(pass1)};
}
is_prompting = false;
return std::nullopt;
}
}
std::optional<login> login::parse(std::string&& userpass, bool verify, const std::function<std::optional<password_container>(bool)> &prompt)
{
std::optional<login> login::parse(
std::string&& userpass,
bool verify,
const std::function<std::optional<password_container>(bool)>& prompt) {
login out{};
const auto loc = userpass.find(':');
if (loc == std::string::npos)
{
auto result = prompt(verify);
if (!result)
return std::nullopt;
if (loc == std::string::npos) {
auto result = prompt(verify);
if (!result)
return std::nullopt;
out.password = std::move(*result);
}
else
{
out.password = password_container{userpass.substr(loc + 1)};
out.password = std::move(*result);
} else {
out.password = password_container{userpass.substr(loc + 1)};
}
out.username = userpass.substr(0, loc);
password_container wipe{std::move(userpass)};
return {std::move(out)};
}
}
}
} // namespace tools

View File

@ -1,21 +1,21 @@
// Copyright (c) 2014-2019, The Monero 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
@ -25,22 +25,21 @@
// 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
#pragma once
#include <string>
#include <atomic>
#include <optional>
#include <functional>
#include <cstddef>
#include <functional>
#include <optional>
#include <string>
#include "epee/wipeable_string.h"
namespace tools
{
class password_container
{
namespace tools {
class password_container {
public:
static constexpr const size_t max_password_size = 1024;
@ -51,22 +50,22 @@ namespace tools
password_container(epee::wipeable_string password) noexcept;
//! \return A password from stdin TTY prompt or `std::cin` pipe.
static std::optional<password_container> prompt(bool verify, const char *mesage = "Password", bool hide_input = true);
static std::optional<password_container> prompt(
bool verify, const char* mesage = "Password", bool hide_input = true);
static std::atomic<bool> is_prompting;
const epee::wipeable_string &password() const noexcept { return m_password; }
const epee::wipeable_string& password() const noexcept { return m_password; }
private:
epee::wipeable_string m_password;
};
};
struct login
{
struct login {
login() = default;
/// Constructs a login from a username/password. Does not prompt.
login(std::string_view user, epee::wipeable_string pass)
: username{user}, password{std::move(pass)} {}
login(std::string_view user, epee::wipeable_string pass) :
username{user}, password{std::move(pass)} {}
/*!
Extracts username and password from the format `username:password`. A
@ -81,9 +80,12 @@ namespace tools
\return The username and password, or std::nullopt if
`password_container::prompt` fails.
*/
static std::optional<login> parse(std::string&& userpass, bool verify, const std::function<std::optional<password_container>(bool)> &prompt);
static std::optional<login> parse(
std::string&& userpass,
bool verify,
const std::function<std::optional<password_container>(bool)>& prompt);
std::string username;
password_container password;
};
}
};
} // namespace tools

View File

@ -1,51 +1,51 @@
#pragma once
#include <chrono>
#include <atomic>
#include <chrono>
#include "crypto/crypto.h"
namespace tools
{
namespace tools {
// Periodic timer that gatekeeps calling of a job to a minimum interval after the previous job
// finished. Only the reset() call is thread-safe; everything else should be confined to the
// owning thread.
class periodic_task
{
class periodic_task {
public:
explicit periodic_task(std::chrono::microseconds interval,
bool start_immediately = true,
std::pair<int, int> random_delay_interval = {})
: m_interval{interval}
, m_last_worked_time{std::chrono::steady_clock::now()}
, m_trigger_now{start_immediately}
, m_random_delay_interval{random_delay_interval}
, m_next_delay{std::chrono::microseconds(crypto::rand_range(m_random_delay_interval.first, m_random_delay_interval.second))}
{}
explicit periodic_task(
std::chrono::microseconds interval,
bool start_immediately = true,
std::pair<int, int> random_delay_interval = {}) :
m_interval{interval},
m_last_worked_time{std::chrono::steady_clock::now()},
m_trigger_now{start_immediately},
m_random_delay_interval{random_delay_interval},
m_next_delay{std::chrono::microseconds(crypto::rand_range(
m_random_delay_interval.first, m_random_delay_interval.second))} {}
template <class functor_t>
void do_call(functor_t functr)
{
if (m_trigger_now || std::chrono::steady_clock::now() - m_last_worked_time > (m_interval + m_next_delay))
{
functr();
m_last_worked_time = std::chrono::steady_clock::now();
m_trigger_now = false;
m_next_delay = std::chrono::microseconds(crypto::rand_range(m_random_delay_interval.first, m_random_delay_interval.second));
template <class functor_t>
void do_call(functor_t functr) {
if (m_trigger_now ||
std::chrono::steady_clock::now() - m_last_worked_time > (m_interval + m_next_delay)) {
functr();
m_last_worked_time = std::chrono::steady_clock::now();
m_trigger_now = false;
m_next_delay = std::chrono::microseconds(crypto::rand_range(
m_random_delay_interval.first, m_random_delay_interval.second));
}
}
}
// Makes the next task attempt run the job, regardless of the time since the last job. Atomic.
void reset() { m_trigger_now = true; }
// Returns the current interval
std::chrono::microseconds interval() const { return m_interval; }
// Changes the current interval
void interval(std::chrono::microseconds us) { m_interval = us; }
// Makes the next task attempt run the job, regardless of the time since the last job. Atomic.
void reset() { m_trigger_now = true; }
// Returns the current interval
std::chrono::microseconds interval() const { return m_interval; }
// Changes the current interval
void interval(std::chrono::microseconds us) { m_interval = us; }
private:
std::chrono::microseconds m_interval;
std::chrono::steady_clock::time_point m_last_worked_time;
std::atomic<bool> m_trigger_now;
std::pair<int, int> m_random_delay_interval;
std::chrono::microseconds m_next_delay;
};
private:
std::chrono::microseconds m_interval;
std::chrono::steady_clock::time_point m_last_worked_time;
std::atomic<bool> m_trigger_now;
std::pair<int, int> m_random_delay_interval;
std::chrono::microseconds m_next_delay;
};
}; // namespace tools

View File

@ -1,21 +1,21 @@
// Copyright (c) 2019, The Monero 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
@ -26,93 +26,94 @@
// 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.
#include "cryptonote_config.h"
#include "epee/misc_log_ex.h"
#include "crypto/crypto.h"
#include "pruning.h"
namespace tools
{
#include "crypto/crypto.h"
#include "cryptonote_config.h"
#include "epee/misc_log_ex.h"
namespace tools {
using namespace cryptonote;
uint32_t make_pruning_seed(uint32_t stripe, uint32_t log_stripes)
{
CHECK_AND_ASSERT_THROW_MES(log_stripes <= PRUNING_SEED_LOG_STRIPES_MASK, "log_stripes out of range");
CHECK_AND_ASSERT_THROW_MES(stripe > 0 && stripe <= (1ul << log_stripes), "stripe out of range");
return (log_stripes << PRUNING_SEED_LOG_STRIPES_SHIFT) | ((stripe - 1) << PRUNING_SEED_STRIPE_SHIFT);
uint32_t make_pruning_seed(uint32_t stripe, uint32_t log_stripes) {
CHECK_AND_ASSERT_THROW_MES(
log_stripes <= PRUNING_SEED_LOG_STRIPES_MASK, "log_stripes out of range");
CHECK_AND_ASSERT_THROW_MES(stripe > 0 && stripe <= (1ul << log_stripes), "stripe out of range");
return (log_stripes << PRUNING_SEED_LOG_STRIPES_SHIFT) |
((stripe - 1) << PRUNING_SEED_STRIPE_SHIFT);
}
bool has_unpruned_block(uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed)
{
const uint32_t stripe = get_pruning_stripe(pruning_seed);
if (stripe == 0)
return true;
const uint32_t log_stripes = get_pruning_log_stripes(pruning_seed);
uint32_t block_stripe = get_pruning_stripe(block_height, blockchain_height, log_stripes);
return block_stripe == 0 || block_stripe == stripe;
bool has_unpruned_block(uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed) {
const uint32_t stripe = get_pruning_stripe(pruning_seed);
if (stripe == 0)
return true;
const uint32_t log_stripes = get_pruning_log_stripes(pruning_seed);
uint32_t block_stripe = get_pruning_stripe(block_height, blockchain_height, log_stripes);
return block_stripe == 0 || block_stripe == stripe;
}
uint32_t get_pruning_stripe(uint64_t block_height, uint64_t blockchain_height, uint32_t log_stripes)
{
if (block_height + PRUNING_TIP_BLOCKS >= blockchain_height)
return 0;
return ((block_height / PRUNING_STRIPE_SIZE) & (uint64_t)((1ul << log_stripes) - 1)) + 1;
uint32_t get_pruning_stripe(
uint64_t block_height, uint64_t blockchain_height, uint32_t log_stripes) {
if (block_height + PRUNING_TIP_BLOCKS >= blockchain_height)
return 0;
return ((block_height / PRUNING_STRIPE_SIZE) & (uint64_t)((1ul << log_stripes) - 1)) + 1;
}
uint32_t get_pruning_seed(uint64_t block_height, uint64_t blockchain_height, uint32_t log_stripes)
{
const uint32_t stripe = get_pruning_stripe(block_height, blockchain_height, log_stripes);
if (stripe == 0)
return 0;
return make_pruning_seed(stripe, log_stripes);
uint32_t get_pruning_seed(uint64_t block_height, uint64_t blockchain_height, uint32_t log_stripes) {
const uint32_t stripe = get_pruning_stripe(block_height, blockchain_height, log_stripes);
if (stripe == 0)
return 0;
return make_pruning_seed(stripe, log_stripes);
}
uint64_t get_next_unpruned_block_height(uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed)
{
CHECK_AND_ASSERT_MES(block_height <= MAX_BLOCK_NUMBER+1, block_height, "block_height too large");
CHECK_AND_ASSERT_MES(blockchain_height <= MAX_BLOCK_NUMBER+1, block_height, "blockchain_height too large");
const uint32_t stripe = get_pruning_stripe(pruning_seed);
if (stripe == 0)
return block_height;
if (block_height + PRUNING_TIP_BLOCKS >= blockchain_height)
return block_height;
const uint32_t seed_log_stripes = get_pruning_log_stripes(pruning_seed);
const uint64_t log_stripes = seed_log_stripes ? seed_log_stripes : PRUNING_LOG_STRIPES;
const uint64_t mask = (1ul << log_stripes) - 1;
const uint32_t block_pruning_stripe = ((block_height / PRUNING_STRIPE_SIZE) & mask) + 1;
if (block_pruning_stripe == stripe)
return block_height;
const uint64_t cycles = ((block_height / PRUNING_STRIPE_SIZE) >> log_stripes);
const uint64_t cycle_start = cycles + ((stripe > block_pruning_stripe) ? 0 : 1);
const uint64_t h = cycle_start * (PRUNING_STRIPE_SIZE << log_stripes) + (stripe - 1) * PRUNING_STRIPE_SIZE;
if (h + PRUNING_TIP_BLOCKS > blockchain_height)
return blockchain_height < PRUNING_TIP_BLOCKS ? 0 : blockchain_height - PRUNING_TIP_BLOCKS;
CHECK_AND_ASSERT_MES(h >= block_height, block_height, "h < block_height, unexpected");
return h;
uint64_t get_next_unpruned_block_height(
uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed) {
CHECK_AND_ASSERT_MES(
block_height <= MAX_BLOCK_NUMBER + 1, block_height, "block_height too large");
CHECK_AND_ASSERT_MES(
blockchain_height <= MAX_BLOCK_NUMBER + 1, block_height, "blockchain_height too large");
const uint32_t stripe = get_pruning_stripe(pruning_seed);
if (stripe == 0)
return block_height;
if (block_height + PRUNING_TIP_BLOCKS >= blockchain_height)
return block_height;
const uint32_t seed_log_stripes = get_pruning_log_stripes(pruning_seed);
const uint64_t log_stripes = seed_log_stripes ? seed_log_stripes : PRUNING_LOG_STRIPES;
const uint64_t mask = (1ul << log_stripes) - 1;
const uint32_t block_pruning_stripe = ((block_height / PRUNING_STRIPE_SIZE) & mask) + 1;
if (block_pruning_stripe == stripe)
return block_height;
const uint64_t cycles = ((block_height / PRUNING_STRIPE_SIZE) >> log_stripes);
const uint64_t cycle_start = cycles + ((stripe > block_pruning_stripe) ? 0 : 1);
const uint64_t h =
cycle_start * (PRUNING_STRIPE_SIZE << log_stripes) + (stripe - 1) * PRUNING_STRIPE_SIZE;
if (h + PRUNING_TIP_BLOCKS > blockchain_height)
return blockchain_height < PRUNING_TIP_BLOCKS ? 0 : blockchain_height - PRUNING_TIP_BLOCKS;
CHECK_AND_ASSERT_MES(h >= block_height, block_height, "h < block_height, unexpected");
return h;
}
uint64_t get_next_pruned_block_height(uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed)
{
const uint32_t stripe = get_pruning_stripe(pruning_seed);
if (stripe == 0)
return blockchain_height;
if (block_height + PRUNING_TIP_BLOCKS >= blockchain_height)
return blockchain_height;
const uint32_t seed_log_stripes = get_pruning_log_stripes(pruning_seed);
const uint64_t log_stripes = seed_log_stripes ? seed_log_stripes : PRUNING_LOG_STRIPES;
const uint64_t mask = (1ul << log_stripes) - 1;
const uint32_t block_pruning_seed = ((block_height / PRUNING_STRIPE_SIZE) & mask) + 1;
if (block_pruning_seed != stripe)
return block_height;
const uint32_t next_stripe = 1 + (block_pruning_seed & mask);
return get_next_unpruned_block_height(block_height, blockchain_height, tools::make_pruning_seed(next_stripe, log_stripes));
}
uint32_t get_random_stripe()
{
return 1 + crypto::rand<uint8_t>() % (1ul << PRUNING_LOG_STRIPES);
uint64_t get_next_pruned_block_height(
uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed) {
const uint32_t stripe = get_pruning_stripe(pruning_seed);
if (stripe == 0)
return blockchain_height;
if (block_height + PRUNING_TIP_BLOCKS >= blockchain_height)
return blockchain_height;
const uint32_t seed_log_stripes = get_pruning_log_stripes(pruning_seed);
const uint64_t log_stripes = seed_log_stripes ? seed_log_stripes : PRUNING_LOG_STRIPES;
const uint64_t mask = (1ul << log_stripes) - 1;
const uint32_t block_pruning_seed = ((block_height / PRUNING_STRIPE_SIZE) & mask) + 1;
if (block_pruning_seed != stripe)
return block_height;
const uint32_t next_stripe = 1 + (block_pruning_seed & mask);
return get_next_unpruned_block_height(
block_height, blockchain_height, tools::make_pruning_seed(next_stripe, log_stripes));
}
uint32_t get_random_stripe() {
return 1 + crypto::rand<uint8_t>() % (1ul << PRUNING_LOG_STRIPES);
}
} // namespace tools

View File

@ -1,21 +1,21 @@
// Copyright (c) 2019, The Monero 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
@ -30,29 +30,30 @@
#include <stdint.h>
namespace tools
{
inline constexpr uint32_t PRUNING_SEED_LOG_STRIPES_SHIFT = 7;
inline constexpr uint32_t PRUNING_SEED_LOG_STRIPES_MASK = 0x7;
inline constexpr uint32_t PRUNING_SEED_STRIPE_SHIFT = 0;
inline constexpr uint32_t PRUNING_SEED_STRIPE_MASK = 0x7f;
namespace tools {
inline constexpr uint32_t PRUNING_SEED_LOG_STRIPES_SHIFT = 7;
inline constexpr uint32_t PRUNING_SEED_LOG_STRIPES_MASK = 0x7;
inline constexpr uint32_t PRUNING_SEED_STRIPE_SHIFT = 0;
inline constexpr uint32_t PRUNING_SEED_STRIPE_MASK = 0x7f;
inline constexpr uint32_t get_pruning_log_stripes(uint32_t pruning_seed) {
inline constexpr uint32_t get_pruning_log_stripes(uint32_t pruning_seed) {
return (pruning_seed >> PRUNING_SEED_LOG_STRIPES_SHIFT) & PRUNING_SEED_LOG_STRIPES_MASK;
}
inline constexpr uint32_t get_pruning_stripe(uint32_t pruning_seed) {
}
inline constexpr uint32_t get_pruning_stripe(uint32_t pruning_seed) {
if (pruning_seed == 0)
return 0;
return 0;
return 1 + ((pruning_seed >> PRUNING_SEED_STRIPE_SHIFT) & PRUNING_SEED_STRIPE_MASK);
}
uint32_t make_pruning_seed(uint32_t stripe, uint32_t log_stripes);
bool has_unpruned_block(uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed);
uint32_t get_pruning_stripe(uint64_t block_height, uint64_t blockchain_height, uint32_t log_stripes);
uint32_t get_pruning_seed(uint64_t block_height, uint64_t blockchain_height, uint32_t log_stripes);
uint64_t get_next_unpruned_block_height(uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed);
uint64_t get_next_pruned_block_height(uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed);
uint32_t get_random_stripe();
}
uint32_t make_pruning_seed(uint32_t stripe, uint32_t log_stripes);
bool has_unpruned_block(uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed);
uint32_t get_pruning_stripe(
uint64_t block_height, uint64_t blockchain_height, uint32_t log_stripes);
uint32_t get_pruning_seed(uint64_t block_height, uint64_t blockchain_height, uint32_t log_stripes);
uint64_t get_next_unpruned_block_height(
uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed);
uint64_t get_next_pruned_block_height(
uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed);
uint32_t get_random_stripe();
} // namespace tools

View File

@ -27,19 +27,21 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "random.h"
#include <cassert>
namespace tools {
thread_local std::mt19937_64 rng{std::random_device{}()};
uint64_t uniform_distribution_portable(std::mt19937_64& rng, const uint64_t n)
{
assert(n > 0);
const uint64_t secureMax = rng.max() - rng.max() % n;
uint64_t x;
do x = rng(); while (x >= secureMax);
return x / (secureMax / n);
uint64_t uniform_distribution_portable(std::mt19937_64& rng, const uint64_t n) {
assert(n > 0);
const uint64_t secureMax = rng.max() - rng.max() % n;
uint64_t x;
do
x = rng();
while (x >= secureMax);
return x / (secureMax / n);
}
}
} // namespace tools

View File

@ -43,32 +43,34 @@ uint64_t uniform_distribution_portable(std::mt19937_64& rng, uint64_t n);
/// Uniformly shuffles all the elements in [begin, end) in a deterministic method so that, given the
/// same seed, this will always produce the same result on any platform/compiler/etc.
template<typename RandomIt>
void shuffle_portable(RandomIt begin, RandomIt end, std::mt19937_64 &rng)
{
if (end <= begin + 1) return;
const size_t size = std::distance(begin, end);
for (size_t i = 1; i < size; i++)
{
size_t j = (size_t)uniform_distribution_portable(rng, i+1);
using std::swap;
swap(begin[i], begin[j]);
}
template <typename RandomIt>
void shuffle_portable(RandomIt begin, RandomIt end, std::mt19937_64& rng) {
if (end <= begin + 1)
return;
const size_t size = std::distance(begin, end);
for (size_t i = 1; i < size; i++) {
size_t j = (size_t)uniform_distribution_portable(rng, i + 1);
using std::swap;
swap(begin[i], begin[j]);
}
}
/// Returns a random element between the elements in [begin, end) will use the random number generator provided as g
template<typename Iter, typename RandomGenerator>
/// Returns a random element between the elements in [begin, end) will use the random number
/// generator provided as g
template <typename Iter, typename RandomGenerator>
Iter select_randomly(Iter begin, Iter end, RandomGenerator& g) {
auto dist = std::distance(begin, end);
if (dist <= 1) return begin; // Handles both begin=end case and single-element case
std::advance(begin, std::uniform_int_distribution<>{0, static_cast<int>(dist-1)}(g));
return begin;
auto dist = std::distance(begin, end);
if (dist <= 1)
return begin; // Handles both begin=end case and single-element case
std::advance(begin, std::uniform_int_distribution<>{0, static_cast<int>(dist - 1)}(g));
return begin;
}
/// Returns a random element same as above but defaults to the seeded random number generator defined in this file
template<typename Iter>
/// Returns a random element same as above but defaults to the seeded random number generator
/// defined in this file
template <typename Iter>
Iter select_randomly(Iter begin, Iter end) {
return select_randomly(begin, end, rng);
return select_randomly(begin, end, rng);
}
};
}; // namespace tools

View File

@ -28,40 +28,33 @@
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include <ctime>
#include <cstdint>
#include "cryptonote_config.h"
#include <ctime>
#include "common/util.h"
#include "cryptonote_config.h"
namespace cryptonote
{
namespace cryptonote { namespace rules {
namespace rules
{
bool is_output_unlocked(uint64_t unlock_time, uint64_t height) {
if (unlock_time < MAX_BLOCK_NUMBER) {
// ND: Instead of calling get_current_blockchain_height(), call m_db->height()
// directly as get_current_blockchain_height() locks the recursive mutex.
if (height - 1 + LOCKED_TX_ALLOWED_DELTA_BLOCKS >= unlock_time)
return true;
else
return false;
} else {
// interpret as time
uint64_t current_time = static_cast<uint64_t>(time(NULL));
if (current_time +
tools::to_seconds(LOCKED_TX_ALLOWED_DELTA_BLOCKS * TARGET_BLOCK_TIME) >=
unlock_time)
return true;
else
return false;
}
return false;
}
bool is_output_unlocked(uint64_t unlock_time, uint64_t height)
{
if(unlock_time < MAX_BLOCK_NUMBER)
{
// ND: Instead of calling get_current_blockchain_height(), call m_db->height()
// directly as get_current_blockchain_height() locks the recursive mutex.
if(height - 1 + LOCKED_TX_ALLOWED_DELTA_BLOCKS >= unlock_time)
return true;
else
return false;
}
else
{
//interpret as time
uint64_t current_time = static_cast<uint64_t>(time(NULL));
if(current_time + tools::to_seconds(LOCKED_TX_ALLOWED_DELTA_BLOCKS * TARGET_BLOCK_TIME) >= unlock_time)
return true;
else
return false;
}
return false;
}
} // namespace rules
} // namespace cryptonote
}} // namespace cryptonote::rules

View File

@ -30,9 +30,8 @@
#include <cstdint>
namespace cryptonote::rules
{
namespace cryptonote::rules {
bool is_output_unlocked(uint64_t unlock_time, uint64_t height);
} // namespace cryptonote::rules
} // namespace cryptonote::rules

View File

@ -1,30 +1,27 @@
#include "scoped_message_writer.h"
#include "common/format.h"
namespace tools {
static auto logcat = log::Cat("msgwriter");
scoped_message_writer& scoped_message_writer::flush()
{
if (!m_content.empty())
{
logcat->log(m_log_level, "{}{}", m_prefix, m_content);
scoped_message_writer& scoped_message_writer::flush() {
if (!m_content.empty()) {
logcat->log(m_log_level, "{}{}", m_prefix, m_content);
if (m_color) {
rdln::suspend_readline pause_readline;
fmt::print(fg(*m_color), "{}{}\n", m_prefix, m_content);
if (m_color) {
rdln::suspend_readline pause_readline;
fmt::print(fg(*m_color), "{}{}\n", m_prefix, m_content);
} else
fmt::print("{}{}\n", m_prefix, m_content);
m_content.clear();
}
else
fmt::print("{}{}\n", m_prefix, m_content);
m_content.clear();
}
return *this;
return *this;
}
scoped_message_writer::~scoped_message_writer()
{
flush();
scoped_message_writer::~scoped_message_writer() {
flush();
}
}
} // namespace tools

View File

@ -1,21 +1,21 @@
// Copyright (c) 2014-2019, The Monero 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
@ -28,91 +28,89 @@
#pragma once
#include "epee/readline_suspend.h"
#include "epee/misc_log_ex.h"
#include <iostream>
#include "logging/oxen_logger.h"
#include <fmt/color.h>
namespace tools
{
#include <iostream>
#include "epee/misc_log_ex.h"
#include "epee/readline_suspend.h"
#include "logging/oxen_logger.h"
namespace tools {
/************************************************************************/
/* */
/************************************************************************/
class scoped_message_writer
{
private:
std::string m_prefix;
std::string m_content;
std::optional<fmt::terminal_color> m_color;
oxen::log::Level m_log_level;
public:
explicit scoped_message_writer(
std::optional<fmt::terminal_color> color = std::nullopt,
std::string prefix = "",
log::Level log_level = log::Level::info
)
: m_color{color}, m_log_level{log_level}, m_prefix{std::move(prefix)}
{}
class scoped_message_writer {
private:
std::string m_prefix;
std::string m_content;
std::optional<fmt::terminal_color> m_color;
oxen::log::Level m_log_level;
scoped_message_writer(scoped_message_writer&& o)
: m_prefix{std::move(o.m_prefix)},
m_content{std::move(o.m_content)},
m_color{o.m_color},
m_log_level{o.m_log_level}
{
o.m_content.clear();
}
public:
explicit scoped_message_writer(
std::optional<fmt::terminal_color> color = std::nullopt,
std::string prefix = "",
log::Level log_level = log::Level::info) :
m_color{color}, m_log_level{log_level}, m_prefix{std::move(prefix)} {}
scoped_message_writer(const scoped_message_writer& rhs) = delete;
scoped_message_writer& operator=(const scoped_message_writer& rhs) = delete;
scoped_message_writer& operator=(scoped_message_writer&& rhs) = delete;
scoped_message_writer(scoped_message_writer&& o) :
m_prefix{std::move(o.m_prefix)},
m_content{std::move(o.m_content)},
m_color{o.m_color},
m_log_level{o.m_log_level} {
o.m_content.clear();
}
/// Appends a message and returns *this (so that it can be chained). If called with more than 1
/// argument then the first argument is fmt::format'ed with the remaining arguments.
template <typename... T>
scoped_message_writer& append(std::string_view msg, T&&... args)
{
if constexpr (sizeof...(T))
fmt::format_to(std::back_inserter(m_content), msg, std::forward<T>(args)...);
else
m_content.append(msg);
return *this;
}
scoped_message_writer(const scoped_message_writer& rhs) = delete;
scoped_message_writer& operator=(const scoped_message_writer& rhs) = delete;
scoped_message_writer& operator=(scoped_message_writer&& rhs) = delete;
/// Same as .append(msg). (Doesn't format, just like the single-argument .append(msg)).
scoped_message_writer& operator+=(std::string_view msg) { return append(msg); }
/// Appends a message and returns *this (so that it can be chained). If called with more than 1
/// argument then the first argument is fmt::format'ed with the remaining arguments.
template <typename... T>
scoped_message_writer& append(std::string_view msg, T&&... args) {
if constexpr (sizeof...(T))
fmt::format_to(std::back_inserter(m_content), msg, std::forward<T>(args)...);
else
m_content.append(msg);
return *this;
}
/// Essentially the same as +=, but can only be used on an rvalue instance of the object, so that
/// you can do things like: `scoped_message_writer{} + "abc"`, which feels more natural than
/// `scoped_message_writer{} += "abc"`.
scoped_message_writer&& operator+(std::string_view msg) && { append(msg); return std::move(*this); }
/// Same as .append(msg). (Doesn't format, just like the single-argument .append(msg)).
scoped_message_writer& operator+=(std::string_view msg) { return append(msg); }
/// Flushes the current message to output and resets it. This is normally not called explicitly
/// but rather implicitly when the object is destroyed.
scoped_message_writer& flush();
/// Essentially the same as +=, but can only be used on an rvalue instance of the object, so
/// that you can do things like: `scoped_message_writer{} + "abc"`, which feels more natural
/// than `scoped_message_writer{} += "abc"`.
scoped_message_writer&& operator+(std::string_view msg) && {
append(msg);
return std::move(*this);
}
/// Prints the complete message on destruction.
~scoped_message_writer();
/// Flushes the current message to output and resets it. This is normally not called explicitly
/// but rather implicitly when the object is destroyed.
scoped_message_writer& flush();
/// Prints the complete message on destruction.
~scoped_message_writer();
};
template <typename... T>
scoped_message_writer msg_writer(std::optional<fmt::terminal_color> color = std::nullopt, T&&... args)
{
scoped_message_writer writer{color};
if constexpr (sizeof...(T))
writer.append(std::forward<T>(args)...);
return writer;
scoped_message_writer msg_writer(
std::optional<fmt::terminal_color> color = std::nullopt, T&&... args) {
scoped_message_writer writer{color};
if constexpr (sizeof...(T))
writer.append(std::forward<T>(args)...);
return writer;
}
template <typename... T>
scoped_message_writer msg_writer(std::string_view msg, T&&... args)
{
return msg_writer(std::nullopt, msg, std::forward<T>(args)...);
scoped_message_writer msg_writer(std::string_view msg, T&&... args) {
return msg_writer(std::nullopt, msg, std::forward<T>(args)...);
}
constexpr std::optional<fmt::terminal_color> success_color{fmt::terminal_color::green};
constexpr std::optional<fmt::terminal_color> fail_color{fmt::terminal_color::red};
@ -122,24 +120,21 @@ constexpr std::optional<fmt::terminal_color> fail_color{fmt::terminal_color::red
///
/// (We deduce the Bool argument here to avoid implicit conversion to bool from non-bool values).
template <typename Bool, typename... T, std::enable_if_t<std::is_same_v<Bool, bool>, int> = 0>
scoped_message_writer success_msg_writer(Bool color, T&&... args)
{
auto writer = msg_writer(color ? success_color : std::nullopt);
if constexpr (sizeof...(T))
writer.append(std::forward<T>(args)...);
return writer;
scoped_message_writer success_msg_writer(Bool color, T&&... args) {
auto writer = msg_writer(color ? success_color : std::nullopt);
if constexpr (sizeof...(T))
writer.append(std::forward<T>(args)...);
return writer;
}
inline scoped_message_writer success_msg_writer()
{
return success_msg_writer(true);
inline scoped_message_writer success_msg_writer() {
return success_msg_writer(true);
}
/// Same as above, but for calling without just a message (with a bool). Color will be true.
template <typename... T>
scoped_message_writer success_msg_writer(std::string_view msg, T&&... args)
{
return success_msg_writer(true, msg, std::forward<T>(args)...);
scoped_message_writer success_msg_writer(std::string_view msg, T&&... args) {
return success_msg_writer(true, msg, std::forward<T>(args)...);
}
/// Constructs and returns a scoped_message_writer for a typical error message. Color will be
@ -147,12 +142,11 @@ scoped_message_writer success_msg_writer(std::string_view msg, T&&... args)
/// .append() and so can specify either a single unformatted string, or a format string + format
/// arguments.
template <typename... T>
scoped_message_writer fail_msg_writer(T&&... args)
{
scoped_message_writer writer{fail_color, "Error: ", spdlog::level::err};
if constexpr (sizeof...(T))
writer.append(std::forward<T>(args)...);
return writer;
scoped_message_writer fail_msg_writer(T&&... args) {
scoped_message_writer writer{fail_color, "Error: ", spdlog::level::err};
if constexpr (sizeof...(T))
writer.append(std::forward<T>(args)...);
return writer;
}
} // namespace tools
} // namespace tools

View File

@ -1,30 +1,29 @@
#include "sha256sum.h"
#include <sodium/crypto_hash_sha256.h>
#include <fstream>
#include "crypto/hash.h"
#include "fs.h"
#include <sodium/crypto_hash_sha256.h>
namespace tools {
bool sha256sum_str(std::string_view data, crypto::hash &hash)
{
bool sha256sum_str(std::string_view data, crypto::hash& hash) {
crypto_hash_sha256(
hash.data(),
reinterpret_cast<const unsigned char*>(data.data()),
data.size());
hash.data(), reinterpret_cast<const unsigned char*>(data.data()), data.size());
return true;
}
}
bool sha256sum_file(const fs::path& filename, crypto::hash& hash)
{
bool sha256sum_file(const fs::path& filename, crypto::hash& hash) {
if (std::error_code ec; !fs::exists(filename, ec) || ec)
return false;
return false;
fs::ifstream f;
f.exceptions(std::ifstream::failbit | std::ifstream::badbit);
f.open(filename, std::ios_base::binary | std::ios_base::in | std::ios::ate);
if (!f)
return false;
return false;
std::ifstream::pos_type file_size = f.tellg();
crypto_hash_sha256_state st;
crypto_hash_sha256_init(&st);
@ -32,18 +31,17 @@ namespace tools {
f.seekg(0, std::ios::beg);
std::array<unsigned char, 16384> buf;
while (size_left)
{
auto read_size = std::min(size_left, buf.size());
f.read(reinterpret_cast<char*>(buf.data()), read_size);
if (!f || !f.good())
return false;
crypto_hash_sha256_update(&st, buf.data(), read_size);
size_left -= read_size;
while (size_left) {
auto read_size = std::min(size_left, buf.size());
f.read(reinterpret_cast<char*>(buf.data()), read_size);
if (!f || !f.good())
return false;
crypto_hash_sha256_update(&st, buf.data(), read_size);
size_left -= read_size;
}
f.close();
crypto_hash_sha256_final(&st, hash.data());
return true;
}
}
} // namespace tools

View File

@ -1,33 +1,37 @@
#pragma once
#include <type_traits>
#include <string>
#include <string_view>
#include <type_traits>
#include "fs.h"
namespace crypto { struct hash; }
namespace crypto {
struct hash;
}
namespace tools {
// Calculates sha256 checksum of the given data
bool sha256sum_str(std::string_view str, crypto::hash& hash);
// Calculates sha256 checksum of the given data, for non-char string_view (e.g.
// basic_string_view<unsigned char> or basic_string_view<uint8_t>).
template <typename Char, std::enable_if_t<sizeof(Char) == 1 && !std::is_same_v<Char, char>, int> = 0>
bool sha256sum_str(std::basic_string_view<Char> str, crypto::hash& hash)
{
return sha256sum_str(std::string_view{reinterpret_cast<const char*>(str.data()), str.size()}, hash);
}
// Calculates sha256 checksum of the given byte data given any arbitrary size-1 value pointer and
// byte length.
template <typename Char, std::enable_if_t<sizeof(Char) == 1, int> = 0>
bool sha256sum_str(const Char* data, size_t len, crypto::hash& hash)
{
return sha256sum_str(std::string_view{reinterpret_cast<const char*>(data), len}, hash);
}
// Opens the given file and calculates a sha256sum of its contents
bool sha256sum_file(const fs::path& filename, crypto::hash& hash);
// Calculates sha256 checksum of the given data
bool sha256sum_str(std::string_view str, crypto::hash& hash);
// Calculates sha256 checksum of the given data, for non-char string_view (e.g.
// basic_string_view<unsigned char> or basic_string_view<uint8_t>).
template <
typename Char,
std::enable_if_t<sizeof(Char) == 1 && !std::is_same_v<Char, char>, int> = 0>
bool sha256sum_str(std::basic_string_view<Char> str, crypto::hash& hash) {
return sha256sum_str(
std::string_view{reinterpret_cast<const char*>(str.data()), str.size()}, hash);
}
// Calculates sha256 checksum of the given byte data given any arbitrary size-1 value pointer and
// byte length.
template <typename Char, std::enable_if_t<sizeof(Char) == 1, int> = 0>
bool sha256sum_str(const Char* data, size_t len, crypto::hash& hash) {
return sha256sum_str(std::string_view{reinterpret_cast<const char*>(data), len}, hash);
}
// Opens the given file and calculates a sha256sum of its contents
bool sha256sum_file(const fs::path& filename, crypto::hash& hash);
} // namespace tools

View File

@ -1,80 +1,75 @@
#pragma once
#include <cstring>
#include <csignal>
#include <cstring>
#include <functional>
#include <mutex>
#include "logging/oxen_logger.h"
#ifdef _WIN32
#include "windows.h"
#endif
namespace tools {
/*! \brief Defines a singleton signal handler for win32 and *nix
*/
class signal_handler
{
/*! \brief Defines a singleton signal handler for win32 and *nix
*/
class signal_handler {
public:
/*! \brief installs a signal handler */
template<typename T>
static bool install(T t)
{
template <typename T>
static bool install(T t) {
#ifdef _WIN32
bool r = TRUE == ::SetConsoleCtrlHandler(&win_handler, TRUE);
if (r)
{
m_handler = t;
}
return r;
bool r = TRUE == ::SetConsoleCtrlHandler(&win_handler, TRUE);
if (r) {
m_handler = t;
}
return r;
#else
static struct sigaction sa;
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = posix_handler;
sa.sa_flags = 0;
/* Only blocks SIGINT, SIGTERM and SIGPIPE */
sigaction(SIGINT, &sa, NULL);
signal(SIGTERM, posix_handler);
signal(SIGPIPE, SIG_IGN);
m_handler = t;
return true;
static struct sigaction sa;
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = posix_handler;
sa.sa_flags = 0;
/* Only blocks SIGINT, SIGTERM and SIGPIPE */
sigaction(SIGINT, &sa, NULL);
signal(SIGTERM, posix_handler);
signal(SIGPIPE, SIG_IGN);
m_handler = t;
return true;
#endif
}
private:
#if defined(WIN32)
/*! \brief Handler for win */
static BOOL WINAPI win_handler(DWORD type)
{
if (CTRL_C_EVENT == type || CTRL_BREAK_EVENT == type)
{
handle_signal(type);
}
else
{
log::info(globallogcat, fg(fmt::terminal_color::red), "Got control signal {}. Exiting without saving...", type);
return FALSE;
}
return TRUE;
static BOOL WINAPI win_handler(DWORD type) {
if (CTRL_C_EVENT == type || CTRL_BREAK_EVENT == type) {
handle_signal(type);
} else {
log::info(
globallogcat,
fg(fmt::terminal_color::red),
"Got control signal {}. Exiting without saving...",
type);
return FALSE;
}
return TRUE;
}
#else
/*! \brief handler for NIX */
static void posix_handler(int type)
{
handle_signal(type);
static void posix_handler(int type) {
handle_signal(type);
}
#endif
/*! \brief calles m_handler */
static void handle_signal(int type)
{
static std::mutex m_mutex;
std::unique_lock lock{m_mutex};
m_handler(type);
static void handle_signal(int type) {
static std::mutex m_mutex;
std::unique_lock lock{m_mutex};
m_handler(type);
}
/*! \brief where the installed handler is stored */
static inline std::function<void(int)> m_handler;
};
};
}
} // namespace tools

View File

@ -1,21 +1,21 @@
// Copyright (c) 2019, The Monero 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
@ -27,148 +27,146 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/wait.h>
#include <signal.h>
#include <sys/wait.h>
#endif
#include "epee/misc_log_ex.h"
#include "logging/oxen_logger.h"
#include "util.h"
#include "spawn.h"
#include "oxen.h"
#include "spawn.h"
#include "string_util.h"
#include "util.h"
namespace tools
{
namespace tools {
static auto logcat = log::Cat("spawn");
static auto logcat = log::Cat("spawn");
#ifndef _WIN32
static void closefrom(int fd)
{
static void closefrom(int fd) {
#if defined __FreeBSD__ || defined __OpenBSD__ || defined __NetBSD__ || defined __DragonFly__
::closefrom(fd);
::closefrom(fd);
#else
#if defined __GLIBC__
const int sc_open_max = sysconf(_SC_OPEN_MAX);
const int MAX_FDS = std::min(65536, sc_open_max);
const int sc_open_max = sysconf(_SC_OPEN_MAX);
const int MAX_FDS = std::min(65536, sc_open_max);
#else
const int MAX_FDS = 65536;
const int MAX_FDS = 65536;
#endif
while (fd < MAX_FDS)
{
close(fd);
++fd;
}
while (fd < MAX_FDS) {
close(fd);
++fd;
}
#endif
}
#endif
int spawn(const fs::path& filename, const std::vector<std::string>& args, bool wait)
{
int spawn(const fs::path& filename, const std::vector<std::string>& args, bool wait) {
#ifdef _WIN32
std::string joined = tools::join(" ", args);
char *commandLine = !joined.empty() ? &joined[0] : nullptr;
STARTUPINFOA si = {};
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
// This .string() is wrong for non-ascii paths, but if we switch to CreateProcessW and use
// .c_str() directly our commandLine argument will not be accepted (because it then has to be a
// wchar_t* but out input is utf-8). Shame on you for this garbage API, Windows.
if (!CreateProcessA(filename.string().c_str(), commandLine, nullptr, nullptr, false, 0, nullptr, nullptr, &si, &pi))
{
log::error(logcat, "CreateProcess failed. Error code {}", GetLastError());
return -1;
}
OXEN_DEFER {
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
};
if (!wait)
{
return 0;
}
DWORD result = WaitForSingleObject(pi.hProcess, INFINITE);
if (result != WAIT_OBJECT_0)
{
log::error(logcat, "WaitForSingleObject failed. Result {}, error code {}", result, GetLastError());
return -1;
}
DWORD exitCode;
if (!GetExitCodeProcess(pi.hProcess, &exitCode))
{
log::error(logcat, "GetExitCodeProcess failed. Error code {}", GetLastError());
return -1;
}
log::info(logcat, "Child exited with {}", exitCode);
return static_cast<int>(exitCode);
#else
std::vector<char*> argv(args.size() + 1);
for (size_t n = 0; n < args.size(); ++n)
argv[n] = (char*)args[n].c_str();
argv[args.size()] = NULL;
pid_t pid = fork();
if (pid < 0)
{
log::error(logcat, "Error forking: {}", strerror(errno));
return -1;
}
// child
if (pid == 0)
{
tools::closefrom(3);
close(0);
char *envp[] = {NULL};
execve(filename.c_str(), argv.data(), envp);
log::error(logcat, "Failed to execve: {}", strerror(errno));
return -1;
}
// parent
if (pid > 0)
{
if (!wait)
{
signal(SIGCHLD, SIG_IGN);
return 0;
}
while (1)
{
int wstatus = 0;
pid_t w = waitpid(pid, &wstatus, WUNTRACED | WCONTINUED);
if (w < 0) {
log::error(logcat, "Error waiting for child: {}", strerror(errno));
std::string joined = tools::join(" ", args);
char* commandLine = !joined.empty() ? &joined[0] : nullptr;
STARTUPINFOA si = {};
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
// This .string() is wrong for non-ascii paths, but if we switch to CreateProcessW and use
// .c_str() directly our commandLine argument will not be accepted (because it then has to be a
// wchar_t* but out input is utf-8). Shame on you for this garbage API, Windows.
if (!CreateProcessA(
filename.string().c_str(),
commandLine,
nullptr,
nullptr,
false,
0,
nullptr,
nullptr,
&si,
&pi)) {
log::error(logcat, "CreateProcess failed. Error code {}", GetLastError());
return -1;
}
if (WIFEXITED(wstatus))
{
log::info(logcat, "Child exited with {}", WEXITSTATUS(wstatus));
return WEXITSTATUS(wstatus);
}
if (WIFSIGNALED(wstatus))
{
log::info(logcat, "Child killed by {}", WEXITSTATUS(wstatus));
return WEXITSTATUS(wstatus);
}
}
}
log::error(logcat, "Secret passage found");
return -1;
OXEN_DEFER {
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
};
if (!wait) {
return 0;
}
DWORD result = WaitForSingleObject(pi.hProcess, INFINITE);
if (result != WAIT_OBJECT_0) {
log::error(
logcat,
"WaitForSingleObject failed. Result {}, error code {}",
result,
GetLastError());
return -1;
}
DWORD exitCode;
if (!GetExitCodeProcess(pi.hProcess, &exitCode)) {
log::error(logcat, "GetExitCodeProcess failed. Error code {}", GetLastError());
return -1;
}
log::info(logcat, "Child exited with {}", exitCode);
return static_cast<int>(exitCode);
#else
std::vector<char*> argv(args.size() + 1);
for (size_t n = 0; n < args.size(); ++n)
argv[n] = (char*)args[n].c_str();
argv[args.size()] = NULL;
pid_t pid = fork();
if (pid < 0) {
log::error(logcat, "Error forking: {}", strerror(errno));
return -1;
}
// child
if (pid == 0) {
tools::closefrom(3);
close(0);
char* envp[] = {NULL};
execve(filename.c_str(), argv.data(), envp);
log::error(logcat, "Failed to execve: {}", strerror(errno));
return -1;
}
// parent
if (pid > 0) {
if (!wait) {
signal(SIGCHLD, SIG_IGN);
return 0;
}
while (1) {
int wstatus = 0;
pid_t w = waitpid(pid, &wstatus, WUNTRACED | WCONTINUED);
if (w < 0) {
log::error(logcat, "Error waiting for child: {}", strerror(errno));
return -1;
}
if (WIFEXITED(wstatus)) {
log::info(logcat, "Child exited with {}", WEXITSTATUS(wstatus));
return WEXITSTATUS(wstatus);
}
if (WIFSIGNALED(wstatus)) {
log::info(logcat, "Child killed by {}", WEXITSTATUS(wstatus));
return WEXITSTATUS(wstatus);
}
}
}
log::error(logcat, "Secret passage found");
return -1;
#endif
}
}
} // namespace tools

View File

@ -1,21 +1,21 @@
// Copyright (c) 2019, The Monero 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
@ -26,13 +26,13 @@
// 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.
#pragma once
#include <vector>
#pragma once
#include <string>
#include <vector>
#include "common/fs.h"
namespace tools
{
namespace tools {
int spawn(const fs::path& filename, const std::vector<std::string>& args, bool wait);

View File

@ -1,117 +1,114 @@
#include "string_util.h"
#include <fmt/core.h>
#include <cassert>
namespace tools {
using namespace std::literals;
std::vector<std::string_view> split(std::string_view str, const std::string_view delim, bool trim)
{
std::vector<std::string_view> results;
// Special case for empty delimiter: splits on each character boundary:
if (delim.empty())
{
results.reserve(str.size());
for (size_t i = 0; i < str.size(); i++)
results.emplace_back(str.data() + i, 1);
return results;
}
std::vector<std::string_view> split(std::string_view str, const std::string_view delim, bool trim) {
std::vector<std::string_view> results;
// Special case for empty delimiter: splits on each character boundary:
if (delim.empty()) {
results.reserve(str.size());
for (size_t i = 0; i < str.size(); i++)
results.emplace_back(str.data() + i, 1);
return results;
}
for (size_t pos = str.find(delim); pos != std::string_view::npos; pos = str.find(delim))
{
if (!trim || !results.empty() || pos > 0)
results.push_back(str.substr(0, pos));
str.remove_prefix(pos + delim.size());
}
if (!trim || str.size())
results.push_back(str);
else
while (!results.empty() && results.back().empty())
results.pop_back();
return results;
}
std::vector<std::string_view> split_any(std::string_view str, const std::string_view delims, bool trim)
{
if (delims.empty())
return split(str, delims, trim);
std::vector<std::string_view> results;
for (size_t pos = str.find_first_of(delims); pos != std::string_view::npos; pos = str.find_first_of(delims))
{
if (!trim || !results.empty() || pos > 0)
results.push_back(str.substr(0, pos));
size_t until = str.find_first_not_of(delims, pos+1);
if (until == std::string_view::npos)
str.remove_prefix(str.size());
for (size_t pos = str.find(delim); pos != std::string_view::npos; pos = str.find(delim)) {
if (!trim || !results.empty() || pos > 0)
results.push_back(str.substr(0, pos));
str.remove_prefix(pos + delim.size());
}
if (!trim || str.size())
results.push_back(str);
else
str.remove_prefix(until);
}
if (!trim || str.size())
results.push_back(str);
else
while (!results.empty() && results.back().empty())
results.pop_back();
return results;
while (!results.empty() && results.back().empty())
results.pop_back();
return results;
}
void trim(std::string_view& s)
{
constexpr auto simple_whitespace = " \t\r\n"sv;
auto pos = s.find_first_not_of(simple_whitespace);
if (pos == std::string_view::npos) { // whole string is whitespace
s.remove_prefix(s.size());
return;
}
s.remove_prefix(pos);
pos = s.find_last_not_of(simple_whitespace);
assert(pos != std::string_view::npos);
s.remove_suffix(s.size() - (pos + 1));
std::vector<std::string_view> split_any(
std::string_view str, const std::string_view delims, bool trim) {
if (delims.empty())
return split(str, delims, trim);
std::vector<std::string_view> results;
for (size_t pos = str.find_first_of(delims); pos != std::string_view::npos;
pos = str.find_first_of(delims)) {
if (!trim || !results.empty() || pos > 0)
results.push_back(str.substr(0, pos));
size_t until = str.find_first_not_of(delims, pos + 1);
if (until == std::string_view::npos)
str.remove_prefix(str.size());
else
str.remove_prefix(until);
}
if (!trim || str.size())
results.push_back(str);
else
while (!results.empty() && results.back().empty())
results.pop_back();
return results;
}
std::string lowercase_ascii_string(std::string_view src)
{
std::string result;
result.reserve(src.size());
for (char ch : src)
result += ch >= 'A' && ch <= 'Z' ? ch + ('a' - 'A') : ch;
return result;
void trim(std::string_view& s) {
constexpr auto simple_whitespace = " \t\r\n"sv;
auto pos = s.find_first_not_of(simple_whitespace);
if (pos == std::string_view::npos) { // whole string is whitespace
s.remove_prefix(s.size());
return;
}
s.remove_prefix(pos);
pos = s.find_last_not_of(simple_whitespace);
assert(pos != std::string_view::npos);
s.remove_suffix(s.size() - (pos + 1));
}
std::string lowercase_ascii_string(std::string_view src) {
std::string result;
result.reserve(src.size());
for (char ch : src)
result += ch >= 'A' && ch <= 'Z' ? ch + ('a' - 'A') : ch;
return result;
}
std::string friendly_duration(std::chrono::nanoseconds dur) {
std::string friendly;
auto append = std::back_inserter(friendly);
bool some = false;
if (dur >= 24h) {
fmt::format_to(append, "{}d", dur / 24h);
dur %= 24h;
some = true;
}
if (dur >= 1h || some) {
fmt::format_to(append, "{}h", dur / 1h);
dur %= 1h;
some = true;
}
if (dur >= 1min || some) {
fmt::format_to(append, "{}m", dur / 1min);
dur %= 1min;
some = true;
}
if (some || dur == 0s) {
// If we have >= minutes or its exactly 0 seconds then don't bother with fractional seconds
fmt::format_to(append, "{}s", dur / 1s);
} else {
double seconds = std::chrono::duration<double>(dur).count();
if (dur >= 1s)
fmt::format_to(append, "{:.3f}s", seconds);
else if (dur >= 1ms)
fmt::format_to(append, "{:.3f}ms", seconds * 1000);
else if (dur >= 1us)
fmt::format_to(append, "{:.3f}µs", seconds * 1'000'000);
else
fmt::format_to(append, "{:.0f}ns", seconds * 1'000'000'000);
}
return friendly;
std::string friendly;
auto append = std::back_inserter(friendly);
bool some = false;
if (dur >= 24h) {
fmt::format_to(append, "{}d", dur / 24h);
dur %= 24h;
some = true;
}
if (dur >= 1h || some) {
fmt::format_to(append, "{}h", dur / 1h);
dur %= 1h;
some = true;
}
if (dur >= 1min || some) {
fmt::format_to(append, "{}m", dur / 1min);
dur %= 1min;
some = true;
}
if (some || dur == 0s) {
// If we have >= minutes or its exactly 0 seconds then don't bother with fractional seconds
fmt::format_to(append, "{}s", dur / 1s);
} else {
double seconds = std::chrono::duration<double>(dur).count();
if (dur >= 1s)
fmt::format_to(append, "{:.3f}s", seconds);
else if (dur >= 1ms)
fmt::format_to(append, "{:.3f}ms", seconds * 1000);
else if (dur >= 1us)
fmt::format_to(append, "{:.3f}µs", seconds * 1'000'000);
else
fmt::format_to(append, "{:.0f}ns", seconds * 1'000'000'000);
}
return friendly;
}
}
} // namespace tools

View File

@ -1,13 +1,15 @@
#pragma once
#include <string_view>
#include <vector>
#include <cstring>
#include <iterator>
#include <fmt/format.h>
#include <cassert>
#include <charconv>
#include <chrono>
#include <cassert>
#include <fmt/format.h>
#include "epee/span.h" // epee
#include <cstring>
#include <iterator>
#include <string_view>
#include <vector>
#include "epee/span.h" // epee
namespace tools {
@ -15,25 +17,27 @@ using namespace std::literals;
/// Returns true if the first string is equal to the second string, compared case-insensitively.
inline bool string_iequal(std::string_view s1, std::string_view s2) {
return std::equal(s1.begin(), s1.end(), s2.begin(), s2.end(), [](char a, char b) {
return std::tolower(static_cast<unsigned char>(a)) == std::tolower(static_cast<unsigned char>(b)); });
return std::equal(s1.begin(), s1.end(), s2.begin(), s2.end(), [](char a, char b) {
return std::tolower(static_cast<unsigned char>(a)) ==
std::tolower(static_cast<unsigned char>(b));
});
}
/// Returns true if the first string matches any of the given strings case-insensitively. Arguments
/// must be string literals, std::string, or std::string_views
template <typename S1, typename... S>
bool string_iequal_any(const S1& s1, const S&... s) {
return (... || string_iequal(s1, s));
return (... || string_iequal(s1, s));
}
/// Returns true if the first argument begins with the second argument
inline bool starts_with(std::string_view str, std::string_view prefix) {
return str.substr(0, prefix.size()) == prefix;
return str.substr(0, prefix.size()) == prefix;
}
/// Returns true if the first argument ends with the second argument
inline bool ends_with(std::string_view str, std::string_view suffix) {
return str.size() >= suffix.size() && str.substr(str.size() - suffix.size()) == suffix;
return str.size() >= suffix.size() && str.substr(str.size() - suffix.size()) == suffix;
}
/// Splits a string on some delimiter string and returns a vector of string_view's pointing into the
@ -49,7 +53,8 @@ inline bool ends_with(std::string_view str, std::string_view suffix) {
/// auto v = split("-a--b--", "-"); // v is {"", "a", "", "b", "", ""}
/// auto v = split("-a--b--", "-", true); // v is {"a", "", "b"}
///
std::vector<std::string_view> split(std::string_view str, std::string_view delim, bool trim = false);
std::vector<std::string_view> split(
std::string_view str, std::string_view delim, bool trim = false);
/// Splits a string on any 1 or more of the given delimiter characters and returns a vector of
/// string_view's pointing into the pieces of the original string. If delims is empty this works
@ -57,56 +62,58 @@ std::vector<std::string_view> split(std::string_view str, std::string_view delim
/// pieces).
///
/// auto v = split_any("abcdedf", "dcx"); // v is {"ab", "e", "f"}
std::vector<std::string_view> split_any(std::string_view str, std::string_view delims, bool trim = false);
std::vector<std::string_view> split_any(
std::string_view str, std::string_view delims, bool trim = false);
/// Joins [begin, end) with a delimiter and returns the resulting string. Elements can be anything
/// that can be formatted. Semi-deprecated: this just uses fmt to join.
template <typename It>
std::string join(std::string_view delimiter, It begin, It end) {
return fmt::format("{}", fmt::join(begin, end, delimiter));
return fmt::format("{}", fmt::join(begin, end, delimiter));
}
/// Same as the above, but works on a container. Just use fmt::join.
template <typename Container>
std::string join(std::string_view delimiter, const Container& c) {
return fmt::format("{}", fmt::join(c, delimiter));
return fmt::format("{}", fmt::join(c, delimiter));
}
/// Similar to join(), but first applies a transformation to each element.
template <typename It, typename UnaryOperation>
std::string join_transform(std::string_view delimiter, It begin, It end, UnaryOperation transform) {
std::string result;
auto append = std::back_inserter(result);
if (begin != end)
result = fmt::format("{}", transform(*begin++));
while (begin != end)
fmt::format_to(append, "{}{}", delimiter, transform(*begin++));
return result;
std::string result;
auto append = std::back_inserter(result);
if (begin != end)
result = fmt::format("{}", transform(*begin++));
while (begin != end)
fmt::format_to(append, "{}{}", delimiter, transform(*begin++));
return result;
}
/// Wrapper around the above that takes a container and passes c.begin(), c.end().
template <typename Container, typename UnaryOperation>
std::string join_transform(std::string_view delimiter, const Container& c, UnaryOperation&& transform) {
return join_transform(delimiter, c.begin(), c.end(), std::forward<UnaryOperation>(transform));
std::string join_transform(
std::string_view delimiter, const Container& c, UnaryOperation&& transform) {
return join_transform(delimiter, c.begin(), c.end(), std::forward<UnaryOperation>(transform));
}
/// Concatenates a bunch of random values together with delim as a separator via fmt::format.
/// Returns the result as a string.
template <typename T, typename... Ts>
std::string join_stuff(std::string_view delim, T&& first, Ts&&... stuff) {
std::string result = fmt::format(std::forward<T>(first));
auto append = std::back_inserter(result);
(fmt::format_to(append, "{}{}", delim, std::forward<Ts>(stuff)), ...);
return result;
std::string result = fmt::format(std::forward<T>(first));
auto append = std::back_inserter(result);
(fmt::format_to(append, "{}{}", delim, std::forward<Ts>(stuff)), ...);
return result;
}
/// Concatenates arguments via fmt::format operator, returns as a string.
template <typename... T>
std::string concat(T&&... stuff) {
std::string result;
auto append = std::back_inserter(result);
(fmt::format_to(append, "{}", std::forward<T>(stuff)), ...);
return result;
std::string result;
auto append = std::back_inserter(result);
(fmt::format_to(append, "{}", std::forward<T>(stuff)), ...);
return result;
}
/// Simple version of whitespace trimming: mutates the given string view to remove leading
@ -118,13 +125,13 @@ void trim(std::string_view& s);
/// string was consumed.
template <typename T>
bool parse_int(const std::string_view str, T& value, int base = 10) {
T tmp;
auto* strend = str.data() + str.size();
auto [p, ec] = std::from_chars(str.data(), strend, tmp, base);
if (ec != std::errc() || p != strend)
return false;
value = tmp;
return true;
T tmp;
auto* strend = str.data() + str.size();
auto [p, ec] = std::from_chars(str.data(), strend, tmp, base);
if (ec != std::errc() || p != strend)
return false;
value = tmp;
return true;
}
/// Returns a string_view that views the data of the given object; this is not something you want to
@ -133,24 +140,26 @@ bool parse_int(const std::string_view str, T& value, int base = 10) {
/// that aren't C++-trivial but are still designed to be accessed this way.
template <typename T>
std::string_view view_guts(const T& val) {
static_assert((std::is_standard_layout_v<T> && std::has_unique_object_representations_v<T>)
|| epee::is_byte_spannable<T>,
"cannot safely access non-trivial class as string_view");
return {reinterpret_cast<const char *>(&val), sizeof(val)};
static_assert(
(std::is_standard_layout_v<T> && std::has_unique_object_representations_v<T>) ||
epee::is_byte_spannable<T>,
"cannot safely access non-trivial class as string_view");
return {reinterpret_cast<const char*>(&val), sizeof(val)};
}
/// Convenience wrapper around the above that also copies the result into a new string
template <typename T>
std::string copy_guts(const T& val) {
return std::string{view_guts(val)};
return std::string{view_guts(val)};
}
/// Function to reverse the above view_guts
template <typename T>
T make_from_guts(std::string_view s) {
static_assert((std::is_standard_layout_v<T> && std::has_unique_object_representations_v<T>)
|| epee::is_byte_spannable<T>,
"cannot safely reconstitute a non-trivial class from data");
static_assert(
(std::is_standard_layout_v<T> && std::has_unique_object_representations_v<T>) ||
epee::is_byte_spannable<T>,
"cannot safely reconstitute a non-trivial class from data");
if (s.size() != sizeof(T))
throw std::runtime_error("Cannot reconstitute type: wrong type content size");
T x;
@ -166,13 +175,13 @@ std::string friendly_duration(std::chrono::nanoseconds dur);
/// Converts a duration into a shorter, single-unit fractional display such as `42.3min`
std::string short_duration(std::chrono::duration<double> dur);
/// Given an array of string arguments, look for strings of the format <prefix><value> and return <value>
/// Returns empty string view if not found.
/// Given an array of string arguments, look for strings of the format <prefix><value> and return
/// <value> Returns empty string view if not found.
template <typename It>
std::string_view find_prefixed_value(It begin, It end, std::string_view prefix)
{
auto it = std::find_if(begin, end, [&](const auto& s) { return starts_with(s, prefix); });
if (it == end) return {};
return std::string_view{*it}.substr(prefix.size());
}
std::string_view find_prefixed_value(It begin, It end, std::string_view prefix) {
auto it = std::find_if(begin, end, [&](const auto& s) { return starts_with(s, prefix); });
if (it == end)
return {};
return std::string_view{*it}.substr(prefix.size());
}
} // namespace tools

View File

@ -25,154 +25,148 @@
// 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.
#include "epee/misc_log_ex.h"
#include "logging/oxen_logger.h"
#include "common/threadpool.h"
#include "cryptonote_config.h"
#include "common/util.h"
#include "cryptonote_config.h"
#include "epee/misc_log_ex.h"
#include "logging/oxen_logger.h"
static thread_local int depth = 0;
static thread_local bool is_leaf = false;
namespace tools
{
namespace tools {
threadpool::threadpool(unsigned int max_threads) : running(true), active(0) {
create(max_threads);
create(max_threads);
}
threadpool::~threadpool() {
destroy();
destroy();
}
void threadpool::destroy() {
try
{
const std::unique_lock lock{mutex};
running = false;
has_work.notify_all();
}
catch (...)
{
// if the lock throws, we're just do it without a lock and hope,
// since the alternative is terminate
running = false;
has_work.notify_all();
}
for (size_t i = 0; i<threads.size(); i++) {
try { threads[i].join(); }
catch (...) { /* ignore */ }
}
threads.clear();
try {
const std::unique_lock lock{mutex};
running = false;
has_work.notify_all();
} catch (...) {
// if the lock throws, we're just do it without a lock and hope,
// since the alternative is terminate
running = false;
has_work.notify_all();
}
for (size_t i = 0; i < threads.size(); i++) {
try {
threads[i].join();
} catch (...) { /* ignore */
}
}
threads.clear();
}
void threadpool::recycle() {
destroy();
create(max);
destroy();
create(max);
}
void threadpool::create(unsigned int max_threads) {
const std::unique_lock lock{mutex};
max = max_threads ? max_threads : tools::get_max_concurrency();
running = true;
for (size_t i = max ? max : 1; i > 0; i--) {
threads.emplace_back([this] { run(false); });
}
const std::unique_lock lock{mutex};
max = max_threads ? max_threads : tools::get_max_concurrency();
running = true;
for (size_t i = max ? max : 1; i > 0; i--) {
threads.emplace_back([this] { run(false); });
}
}
void threadpool::submit(waiter *obj, std::function<void()> f, bool leaf) {
CHECK_AND_ASSERT_THROW_MES(!is_leaf, "A leaf routine is using a thread pool");
std::unique_lock lock{mutex};
if (!leaf && ((active == max && !queue.empty()) || depth > 0)) {
// if all available threads are already running
// and there's work waiting, just run in current thread
lock.unlock();
++depth;
is_leaf = leaf;
f();
--depth;
is_leaf = false;
} else {
if (obj)
obj->inc();
if (leaf)
queue.push_front({obj, f, leaf});
else
queue.push_back({obj, f, leaf});
has_work.notify_one();
}
void threadpool::submit(waiter* obj, std::function<void()> f, bool leaf) {
CHECK_AND_ASSERT_THROW_MES(!is_leaf, "A leaf routine is using a thread pool");
std::unique_lock lock{mutex};
if (!leaf && ((active == max && !queue.empty()) || depth > 0)) {
// if all available threads are already running
// and there's work waiting, just run in current thread
lock.unlock();
++depth;
is_leaf = leaf;
f();
--depth;
is_leaf = false;
} else {
if (obj)
obj->inc();
if (leaf)
queue.push_front({obj, f, leaf});
else
queue.push_back({obj, f, leaf});
has_work.notify_one();
}
}
unsigned int threadpool::get_max_concurrency() const {
return max;
return max;
}
threadpool::waiter::~waiter()
{
try
{
threadpool::waiter::~waiter() {
try {
std::unique_lock lock{mt};
if (num)
log::error(
globallogcat, "wait should have been called before waiter dtor - waiting now");
} catch (...) { /* ignore */
}
try {
wait(NULL);
} catch (const std::exception& e) {
/* ignored */
}
}
void threadpool::waiter::wait(threadpool* tpool) {
if (tpool)
tpool->run(true);
std::unique_lock lock{mt};
if (num)
log::error(globallogcat, "wait should have been called before waiter dtor - waiting now");
}
catch (...) { /* ignore */ }
try
{
wait(NULL);
}
catch (const std::exception &e)
{
/* ignored */
}
}
void threadpool::waiter::wait(threadpool *tpool) {
if (tpool)
tpool->run(true);
std::unique_lock lock{mt};
while(num)
cv.wait(lock);
while (num)
cv.wait(lock);
}
void threadpool::waiter::inc() {
const std::unique_lock lock{mt};
num++;
const std::unique_lock lock{mt};
num++;
}
void threadpool::waiter::dec() {
const std::unique_lock lock{mt};
num--;
if (!num)
cv.notify_all();
const std::unique_lock lock{mt};
num--;
if (!num)
cv.notify_all();
}
void threadpool::run(bool flush) {
std::unique_lock lock{mutex};
while (running) {
entry e;
while(queue.empty() && running)
{
if (flush)
return;
has_work.wait(lock);
std::unique_lock lock{mutex};
while (running) {
entry e;
while (queue.empty() && running) {
if (flush)
return;
has_work.wait(lock);
}
if (!running)
break;
active++;
e = std::move(queue.front());
queue.pop_front();
lock.unlock();
++depth;
is_leaf = e.leaf;
e.f();
--depth;
is_leaf = false;
if (e.wo)
e.wo->dec();
lock.lock();
active--;
}
if (!running) break;
active++;
e = std::move(queue.front());
queue.pop_front();
lock.unlock();
++depth;
is_leaf = e.leaf;
e.f();
--depth;
is_leaf = false;
if (e.wo)
e.wo->dec();
lock.lock();
active--;
}
}
}
} // namespace tools

View File

@ -27,66 +27,65 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <condition_variable>
#include <cstddef>
#include <functional>
#include <utility>
#include <vector>
#include <deque>
#include <functional>
#include <mutex>
#include <stdexcept>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <utility>
#include <vector>
namespace tools
{
namespace tools {
//! A global thread pool
class threadpool
{
public:
static threadpool& getInstance() {
static threadpool instance;
return instance;
}
static threadpool *getNewForUnitTests(unsigned max_threads = 0) {
return new threadpool(max_threads);
}
class threadpool {
public:
static threadpool& getInstance() {
static threadpool instance;
return instance;
}
static threadpool* getNewForUnitTests(unsigned max_threads = 0) {
return new threadpool(max_threads);
}
// The waiter lets the caller know when all of its
// tasks are completed.
class waiter {
std::mutex mt;
std::condition_variable cv;
int num;
public:
void inc();
void dec();
void wait(threadpool *tpool); //! Wait for a set of tasks to finish.
waiter() : num(0){}
~waiter();
};
// The waiter lets the caller know when all of its
// tasks are completed.
class waiter {
std::mutex mt;
std::condition_variable cv;
int num;
// Submit a task to the pool. The waiter pointer may be
// NULL if the caller doesn't care to wait for the
// task to finish.
void submit(waiter *waiter, std::function<void()> f, bool leaf = false);
public:
void inc();
void dec();
void wait(threadpool* tpool); //! Wait for a set of tasks to finish.
waiter() : num(0) {}
~waiter();
};
// destroy and recreate threads
void recycle();
// Submit a task to the pool. The waiter pointer may be
// NULL if the caller doesn't care to wait for the
// task to finish.
void submit(waiter* waiter, std::function<void()> f, bool leaf = false);
unsigned int get_max_concurrency() const;
// destroy and recreate threads
void recycle();
~threadpool();
void stop();
void start(unsigned int max_threads = 0);
unsigned int get_max_concurrency() const;
~threadpool();
void stop();
void start(unsigned int max_threads = 0);
private:
threadpool(unsigned int max_threads = 0);
void destroy();
void create(unsigned int max_threads);
typedef struct entry {
waiter *wo;
std::function<void()> f;
bool leaf;
waiter* wo;
std::function<void()> f;
bool leaf;
} entry;
std::deque<entry> queue;
std::condition_variable has_work;
@ -98,4 +97,4 @@ public:
void run(bool flush = false);
};
}
} // namespace tools

View File

@ -1,21 +1,21 @@
// Copyright (c) 2014-2019, The Monero 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
@ -25,7 +25,7 @@
// 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
#pragma once
@ -34,110 +34,111 @@
#include <unordered_map>
#include <unordered_set>
namespace boost
{
namespace serialization
{
namespace boost { namespace serialization {
template <class Archive, class h_key, class hval>
inline void save(Archive &a, const std::unordered_map<h_key, hval> &x, const boost::serialization::version_type ver)
{
size_t s = x.size();
a << s;
for(auto& v: x)
{
a << v.first;
a << v.second;
}
inline void save(
Archive& a,
const std::unordered_map<h_key, hval>& x,
const boost::serialization::version_type ver) {
size_t s = x.size();
a << s;
for (auto& v : x) {
a << v.first;
a << v.second;
}
}
template <class Archive, class h_key, class hval>
inline void load(Archive &a, std::unordered_map<h_key, hval> &x, const boost::serialization::version_type ver)
{
x.clear();
size_t s = 0;
a >> s;
for(size_t i = 0; i != s; i++)
{
h_key k;
hval v;
a >> k;
a >> v;
x.insert(std::pair<h_key, hval>(k, v));
}
}
template <class Archive, class h_key, class hval>
inline void save(Archive &a, const std::unordered_multimap<h_key, hval> &x, const boost::serialization::version_type ver)
{
size_t s = x.size();
a << s;
for(auto& v: x)
{
a << v.first;
a << v.second;
}
inline void load(
Archive& a,
std::unordered_map<h_key, hval>& x,
const boost::serialization::version_type ver) {
x.clear();
size_t s = 0;
a >> s;
for (size_t i = 0; i != s; i++) {
h_key k;
hval v;
a >> k;
a >> v;
x.insert(std::pair<h_key, hval>(k, v));
}
}
template <class Archive, class h_key, class hval>
inline void load(Archive &a, std::unordered_multimap<h_key, hval> &x, const boost::serialization::version_type ver)
{
x.clear();
size_t s = 0;
a >> s;
for(size_t i = 0; i != s; i++)
{
h_key k;
hval v;
a >> k;
a >> v;
x.emplace(k, v);
}
inline void save(
Archive& a,
const std::unordered_multimap<h_key, hval>& x,
const boost::serialization::version_type ver) {
size_t s = x.size();
a << s;
for (auto& v : x) {
a << v.first;
a << v.second;
}
}
template <class Archive, class hval>
inline void save(Archive &a, const std::unordered_set<hval> &x, const boost::serialization::version_type ver)
{
size_t s = x.size();
a << s;
for(auto& v: x)
{
a << v;
}
template <class Archive, class h_key, class hval>
inline void load(
Archive& a,
std::unordered_multimap<h_key, hval>& x,
const boost::serialization::version_type ver) {
x.clear();
size_t s = 0;
a >> s;
for (size_t i = 0; i != s; i++) {
h_key k;
hval v;
a >> k;
a >> v;
x.emplace(k, v);
}
}
template <class Archive, class hval>
inline void load(Archive &a, std::unordered_set<hval> &x, const boost::serialization::version_type ver)
{
x.clear();
size_t s = 0;
a >> s;
for(size_t i = 0; i != s; i++)
{
hval v;
a >> v;
x.insert(v);
}
}
template <class Archive, class h_key, class hval>
inline void serialize(Archive &a, std::unordered_map<h_key, hval> &x, const boost::serialization::version_type ver)
{
split_free(a, x, ver);
}
template <class Archive, class h_key, class hval>
inline void serialize(Archive &a, std::unordered_multimap<h_key, hval> &x, const boost::serialization::version_type ver)
{
split_free(a, x, ver);
inline void save(
Archive& a,
const std::unordered_set<hval>& x,
const boost::serialization::version_type ver) {
size_t s = x.size();
a << s;
for (auto& v : x) {
a << v;
}
}
template <class Archive, class hval>
inline void serialize(Archive &a, std::unordered_set<hval> &x, const boost::serialization::version_type ver)
{
split_free(a, x, ver);
inline void load(
Archive& a, std::unordered_set<hval>& x, const boost::serialization::version_type ver) {
x.clear();
size_t s = 0;
a >> s;
for (size_t i = 0; i != s; i++) {
hval v;
a >> v;
x.insert(v);
}
}
}
}
template <class Archive, class h_key, class hval>
inline void serialize(
Archive& a,
std::unordered_map<h_key, hval>& x,
const boost::serialization::version_type ver) {
split_free(a, x, ver);
}
template <class Archive, class h_key, class hval>
inline void serialize(
Archive& a,
std::unordered_multimap<h_key, hval>& x,
const boost::serialization::version_type ver) {
split_free(a, x, ver);
}
template <class Archive, class hval>
inline void serialize(
Archive& a, std::unordered_set<hval>& x, const boost::serialization::version_type ver) {
split_free(a, x, ver);
}
}} // namespace boost::serialization

View File

@ -1,22 +1,22 @@
// Copyright (c) 2018, The Loki Project
// Copyright (c) 2014-2019, The Monero 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
@ -26,149 +26,147 @@
// 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 <chrono>
#include <string>
#include <iomanip>
#include <thread>
#include "util.h"
#include <fmt/chrono.h>
#include <fmt/color.h>
#include <chrono>
#include <iomanip>
#include <string>
#include <thread>
#include "crypto/crypto.h"
#include "epee/misc_log_ex.h"
#include "epee/readline_buffer.h"
#include "epee/string_tools.h"
#include "epee/wipeable_string.h"
#include "crypto/crypto.h"
#include "util.h"
#include "epee/readline_buffer.h"
#include "epee/misc_log_ex.h"
#include "i18n.h"
#include "logging/oxen_logger.h"
#include "string_util.h"
#include "i18n.h"
#ifdef __GLIBC__
#include <sys/resource.h>
#include <gnu/libc-version.h>
#include <sys/resource.h>
#endif
namespace tools
{
static auto logcat = log::Cat("util");
namespace tools {
static auto logcat = log::Cat("util");
bool disable_core_dumps()
{
bool disable_core_dumps() {
#ifdef __GLIBC__
// disable core dumps in release mode
struct rlimit rlimit;
rlimit.rlim_cur = rlimit.rlim_max = 0;
if (setrlimit(RLIMIT_CORE, &rlimit))
{
log::warning(logcat, "Failed to disable core dumps");
return false;
if (setrlimit(RLIMIT_CORE, &rlimit)) {
log::warning(logcat, "Failed to disable core dumps");
return false;
}
#endif
return true;
}
}
ssize_t get_lockable_memory()
{
ssize_t get_lockable_memory() {
#ifdef __GLIBC__
struct rlimit rlim;
if (getrlimit(RLIMIT_MEMLOCK, &rlim) < 0)
{
log::error(logcat, "Failed to determine the lockable memory limit");
return -1;
if (getrlimit(RLIMIT_MEMLOCK, &rlim) < 0) {
log::error(logcat, "Failed to determine the lockable memory limit");
return -1;
}
return rlim.rlim_cur;
#else
return -1;
#endif
}
}
bool on_startup()
{
bool on_startup() {
#ifdef __GLIBC__
const char *ver = ::gnu_get_libc_version();
const char* ver = ::gnu_get_libc_version();
if (!strcmp(ver, "2.25"))
log::warning(logcat, fg(fmt::terminal_color::red), "Running with glibc {}, hangs may occur - change glibc version if possible", ver);
log::warning(
logcat,
fg(fmt::terminal_color::red),
"Running with glibc {}, hangs may occur - change glibc version if possible",
ver);
#endif
return true;
}
namespace
{
}
namespace {
std::mutex max_concurrency_lock;
unsigned max_concurrency = std::thread::hardware_concurrency();
}
} // namespace
void set_max_concurrency(unsigned n)
{
void set_max_concurrency(unsigned n) {
if (n < 1)
n = std::thread::hardware_concurrency();
n = std::thread::hardware_concurrency();
unsigned hwc = std::thread::hardware_concurrency();
if (n > hwc)
n = hwc;
n = hwc;
std::lock_guard lock{max_concurrency_lock};
max_concurrency = n;
}
}
unsigned get_max_concurrency()
{
unsigned get_max_concurrency() {
std::lock_guard lock{max_concurrency_lock};
return max_concurrency;
}
}
bool is_local_address(const std::string &address)
{
return address == "localhost"sv
|| (tools::starts_with(address, "127."sv) && address.find_first_not_of("0123456789."sv) == std::string::npos)
|| address == "::1"sv
|| address == "[::1]"sv; // There are other uncommon ways to specify localhost (e.g. 0::1, ::0001) but don't worry about them.
}
bool is_local_address(const std::string& address) {
return address == "localhost"sv ||
(tools::starts_with(address, "127."sv) &&
address.find_first_not_of("0123456789."sv) == std::string::npos) ||
address == "::1"sv ||
address == "[::1]"sv; // There are other uncommon ways to specify localhost (e.g. 0::1,
// ::0001) but don't worry about them.
}
int vercmp(std::string_view v0, std::string_view v1)
{
int vercmp(std::string_view v0, std::string_view v1) {
auto f0 = tools::split_any(v0, ".-");
auto f1 = tools::split_any(v1, ".-");
const auto max = std::max(f0.size(), f1.size());
for (size_t i = 0; i < max; ++i) {
if (i >= f0.size())
return -1;
if (i >= f1.size())
return 1;
int f0i = 0, f1i = 0;
tools::parse_int(f0[i], f0i);
tools::parse_int(f1[i], f1i);
int n = f0i - f1i;
if (n)
return n;
if (i >= f0.size())
return -1;
if (i >= f1.size())
return 1;
int f0i = 0, f1i = 0;
tools::parse_int(f0[i], f0i);
tools::parse_int(f1[i], f1i);
int n = f0i - f1i;
if (n)
return n;
}
return 0;
}
}
std::optional<std::pair<uint32_t, uint32_t>> parse_subaddress_lookahead(const std::string& str)
{
std::optional<std::pair<uint32_t, uint32_t>> parse_subaddress_lookahead(const std::string& str) {
auto pos = str.find(":");
bool r = pos != std::string::npos;
uint32_t major;
r = r && epee::string_tools::get_xtype_from_string(major, str.substr(0, pos));
uint32_t minor;
r = r && epee::string_tools::get_xtype_from_string(minor, str.substr(pos + 1));
if (r)
{
return std::make_pair(major, minor);
if (r) {
return std::make_pair(major, minor);
} else {
return std::nullopt;
}
else
{
return std::nullopt;
}
}
}
#ifdef _WIN32
std::string input_line_win()
{
HANDLE hConIn = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
std::string input_line_win() {
HANDLE hConIn = CreateFileW(
L"CONIN$",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr,
OPEN_EXISTING,
0,
nullptr);
DWORD oldMode;
FlushConsoleInputBuffer(hConIn);
@ -178,63 +176,61 @@ namespace tools
wchar_t buffer[1024];
DWORD read;
ReadConsoleW(hConIn, buffer, sizeof(buffer)/sizeof(wchar_t)-1, &read, nullptr);
ReadConsoleW(hConIn, buffer, sizeof(buffer) / sizeof(wchar_t) - 1, &read, nullptr);
buffer[read] = 0;
SetConsoleMode(hConIn, oldMode);
CloseHandle(hConIn);
int size_needed = WideCharToMultiByte(CP_UTF8, 0, buffer, -1, NULL, 0, NULL, NULL);
std::string buf(size_needed, '\0');
WideCharToMultiByte(CP_UTF8, 0, buffer, -1, &buf[0], size_needed, NULL, NULL);
buf.pop_back(); //size_needed includes null that we needed to have space for
buf.pop_back(); // size_needed includes null that we needed to have space for
return buf;
}
}
#endif
std::string get_human_readable_timestamp(std::time_t t)
{
std::string get_human_readable_timestamp(std::time_t t) {
if (t < 1234567890)
return "<unknown>";
return "<unknown>";
return "{:%Y-%m-%d %H:%M:%S} UTC"_format(fmt::gmtime(t));
}
}
std::string get_human_readable_timespan(std::chrono::seconds seconds)
{
std::string get_human_readable_timespan(std::chrono::seconds seconds) {
uint64_t ts = seconds.count();
if (ts < 60)
return std::to_string(ts) + tr(" seconds");
return std::to_string(ts) + tr(" seconds");
if (ts < 3600)
return std::to_string((uint64_t)(ts / 60)) + tr(" minutes");
return std::to_string((uint64_t)(ts / 60)) + tr(" minutes");
if (ts < 3600 * 24)
return std::to_string((uint64_t)(ts / 3600)) + tr(" hours");
return std::to_string((uint64_t)(ts / 3600)) + tr(" hours");
if (ts < 3600 * 24 * 30.5)
return std::to_string((uint64_t)(ts / (3600 * 24))) + tr(" days");
return std::to_string((uint64_t)(ts / (3600 * 24))) + tr(" days");
if (ts < 3600 * 24 * 365.25)
return std::to_string((uint64_t)(ts / (3600 * 24 * 30.5))) + tr(" months");
return std::to_string((uint64_t)(ts / (3600 * 24 * 30.5))) + tr(" months");
return tr("a long time");
}
}
std::string get_human_readable_bytes(uint64_t bytes)
{
if (bytes < 1000) return std::to_string(bytes) + " B";
std::string get_human_readable_bytes(uint64_t bytes) {
if (bytes < 1000)
return std::to_string(bytes) + " B";
constexpr std::array units{" kB", " MB", " GB", " TB"};
double b = bytes;
for (const auto& suffix : units) {
b /= 1000.;
if (b < 1000.) {
std::ostringstream o;
o << std::fixed << std::setprecision(2) << b;
return o.str() + suffix;
}
b /= 1000.;
if (b < 1000.) {
std::ostringstream o;
o << std::fixed << std::setprecision(2) << b;
return o.str() + suffix;
}
}
return std::to_string(std::lround(b)) + units.back();
}
}
// Calculate a "sync weight" over ranges of blocks in the blockchain, suitable for
// calculating sync time estimates
uint64_t cumulative_block_sync_weight(cryptonote::network_type nettype, uint64_t start_block, uint64_t num_blocks)
{
// Calculate a "sync weight" over ranges of blocks in the blockchain, suitable for
// calculating sync time estimates
uint64_t cumulative_block_sync_weight(
cryptonote::network_type nettype, uint64_t start_block, uint64_t num_blocks) {
// No detailed data available except for Mainnet: Give back the number of blocks
// as a very simple and non-varying block sync weight for ranges of Testnet and
// Devnet blocks
@ -309,5 +305,5 @@ namespace tools
}
return weight;
#endif
}
}
} // namespace tools

Some files were not shown because too many files have changed in this diff Show More