The blockchain prunes seven eighths of prunable tx data.
This saves about two thirds of the blockchain size, while
keeping the node useful as a sync source for an eighth
of the blockchain.

No other data is currently pruned.

There are three ways to prune a blockchain:

- run monerod with --prune-blockchain
- run "prune_blockchain" in the monerod console
- run the monero-blockchain-prune utility

The first two will prune in place. Due to how LMDB works, this
will not reduce the blockchain size on disk. Instead, it will
mark parts of the file as free, so that future data will use
that free space, causing the file to not grow until free space
grows scarce.

The third way will create a second database, a pruned copy of
the original one. Since this is a new file, this one will be
smaller than the original one.

Once the database is pruned, it will stay pruned as it syncs.
That is, there is no need to use --prune-blockchain again, etc.
release-v0.6.1.2
moneromooo-monero 6 years ago
parent 4e72384318
commit b750fb27b0
No known key found for this signature in database
GPG Key ID: 686F07454D6CEFC3

@ -85,6 +85,16 @@
#define MGINFO_MAGENTA(x) MCLOG_MAGENTA(el::Level::Info, "global",x) #define MGINFO_MAGENTA(x) MCLOG_MAGENTA(el::Level::Info, "global",x)
#define MGINFO_CYAN(x) MCLOG_CYAN(el::Level::Info, "global",x) #define MGINFO_CYAN(x) MCLOG_CYAN(el::Level::Info, "global",x)
#define IFLOG(level, cat, type, init, x) \
do { \
if (ELPP->vRegistry()->allowed(level, cat)) { \
init; \
el::base::Writer(level, __FILE__, __LINE__, ELPP_FUNC, type).construct(cat) << x; \
} \
} while(0)
#define MIDEBUG(init, x) IFLOG(el::Level::Debug, MONERO_DEFAULT_LOG_CATEGORY, el::base::DispatchAction::NormalLog, init, x)
#define LOG_ERROR(x) MERROR(x) #define LOG_ERROR(x) MERROR(x)
#define LOG_PRINT_L0(x) MWARNING(x) #define LOG_PRINT_L0(x) MWARNING(x)
#define LOG_PRINT_L1(x) MINFO(x) #define LOG_PRINT_L1(x) MINFO(x)

@ -295,6 +295,7 @@ PRAGMA_WARNING_DISABLE_VS(4355)
CRITICAL_REGION_LOCAL(m_throttle_speed_in_mutex); CRITICAL_REGION_LOCAL(m_throttle_speed_in_mutex);
m_throttle_speed_in.handle_trafic_exact(bytes_transferred); m_throttle_speed_in.handle_trafic_exact(bytes_transferred);
context.m_current_speed_down = m_throttle_speed_in.get_current_speed(); context.m_current_speed_down = m_throttle_speed_in.get_current_speed();
context.m_max_speed_down = std::max(context.m_max_speed_down, context.m_current_speed_down);
} }
{ {
@ -497,6 +498,7 @@ PRAGMA_WARNING_DISABLE_VS(4355)
CRITICAL_REGION_LOCAL(m_throttle_speed_out_mutex); CRITICAL_REGION_LOCAL(m_throttle_speed_out_mutex);
m_throttle_speed_out.handle_trafic_exact(cb); m_throttle_speed_out.handle_trafic_exact(cb);
context.m_current_speed_up = m_throttle_speed_out.get_current_speed(); context.m_current_speed_up = m_throttle_speed_out.get_current_speed();
context.m_max_speed_up = std::max(context.m_max_speed_up, context.m_current_speed_up);
} }
//_info("[sock " << socket_.native_handle() << "] SEND " << cb); //_info("[sock " << socket_.native_handle() << "] SEND " << cb);

@ -228,6 +228,8 @@ namespace net_utils
uint64_t m_send_cnt; uint64_t m_send_cnt;
double m_current_speed_down; double m_current_speed_down;
double m_current_speed_up; double m_current_speed_up;
double m_max_speed_down;
double m_max_speed_up;
connection_context_base(boost::uuids::uuid connection_id, connection_context_base(boost::uuids::uuid connection_id,
const network_address &remote_address, bool is_income, const network_address &remote_address, bool is_income,
@ -242,7 +244,9 @@ namespace net_utils
m_recv_cnt(recv_cnt), m_recv_cnt(recv_cnt),
m_send_cnt(send_cnt), m_send_cnt(send_cnt),
m_current_speed_down(0), m_current_speed_down(0),
m_current_speed_up(0) m_current_speed_up(0),
m_max_speed_down(0),
m_max_speed_up(0)
{} {}
connection_context_base(): m_connection_id(), connection_context_base(): m_connection_id(),
@ -254,7 +258,9 @@ namespace net_utils
m_recv_cnt(0), m_recv_cnt(0),
m_send_cnt(0), m_send_cnt(0),
m_current_speed_down(0), m_current_speed_down(0),
m_current_speed_up(0) m_current_speed_up(0),
m_max_speed_down(0),
m_max_speed_up(0)
{} {}
connection_context_base& operator=(const connection_context_base& a) connection_context_base& operator=(const connection_context_base& a)

@ -206,6 +206,15 @@ POP_WARNINGS
return boost::lexical_cast<std::string>(val); return boost::lexical_cast<std::string>(val);
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
inline std::string to_string_hex(uint32_t val)
{
std::stringstream ss;
ss << std::hex << val;
std::string s;
ss >> s;
return s;
}
//----------------------------------------------------------------------------
inline bool compare_no_case(const std::string& str1, const std::string& str2) inline bool compare_no_case(const std::string& str1, const std::string& str2)
{ {

@ -267,7 +267,10 @@ void BlockchainDB::pop_block(block& blk, std::vector<transaction>& txs)
for (const auto& h : boost::adaptors::reverse(blk.tx_hashes)) for (const auto& h : boost::adaptors::reverse(blk.tx_hashes))
{ {
txs.push_back(get_tx(h)); 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(h);
} }
remove_transaction(get_transaction_hash(blk.miner_tx)); remove_transaction(get_transaction_hash(blk.miner_tx));
@ -280,7 +283,7 @@ bool BlockchainDB::is_open() const
void BlockchainDB::remove_transaction(const crypto::hash& tx_hash) void BlockchainDB::remove_transaction(const crypto::hash& tx_hash)
{ {
transaction tx = get_tx(tx_hash); transaction tx = get_pruned_tx(tx_hash);
for (const txin_v& tx_input : tx.vin) for (const txin_v& tx_input : tx.vin)
{ {
@ -325,6 +328,17 @@ bool BlockchainDB::get_tx(const crypto::hash& h, cryptonote::transaction &tx) co
return true; return true;
} }
bool BlockchainDB::get_pruned_tx(const crypto::hash& h, cryptonote::transaction &tx) const
{
blobdata 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;
}
transaction BlockchainDB::get_tx(const crypto::hash& h) const transaction BlockchainDB::get_tx(const crypto::hash& h) const
{ {
transaction tx; transaction tx;
@ -333,6 +347,14 @@ transaction BlockchainDB::get_tx(const crypto::hash& h) const
return tx; return tx;
} }
transaction BlockchainDB::get_pruned_tx(const crypto::hash& h) const
{
transaction tx;
if (!get_pruned_tx(h, tx))
throw TX_DNE(std::string("pruned tx with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found in db").c_str());
return tx;
}
void BlockchainDB::reset_stats() void BlockchainDB::reset_stats()
{ {
num_calls = 0; num_calls = 0;

@ -1125,6 +1125,17 @@ public:
*/ */
virtual transaction get_tx(const crypto::hash& h) const; virtual transaction get_tx(const crypto::hash& h) const;
/**
* @brief fetches the transaction base with the given hash
*
* If the transaction does not exist, the subclass should throw TX_DNE.
*
* @param h the hash to look for
*
* @return the transaction with the given hash
*/
virtual transaction get_pruned_tx(const crypto::hash& h) const;
/** /**
* @brief fetches the transaction with the given hash * @brief fetches the transaction with the given hash
* *
@ -1136,6 +1147,17 @@ public:
*/ */
virtual bool get_tx(const crypto::hash& h, transaction &tx) const; virtual bool get_tx(const crypto::hash& h, transaction &tx) const;
/**
* @brief fetches the transaction base with the given hash
*
* If the transaction does not exist, the subclass should return false.
*
* @param h the hash to look for
*
* @return true iff the transaction was found
*/
virtual bool get_pruned_tx(const crypto::hash& h, transaction &tx) const;
/** /**
* @brief fetches the transaction blob with the given hash * @brief fetches the transaction blob with the given hash
* *
@ -1164,6 +1186,21 @@ public:
*/ */
virtual bool get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const = 0; virtual bool get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const = 0;
/**
* @brief fetches the prunable transaction blob with the given hash
*
* The subclass should return the prunable transaction stored which has the given
* hash.
*
* If the transaction does not exist, or if we do not have that prunable data,
* the subclass should return false.
*
* @param h the hash to look for
*
* @return true iff the transaction was found and we have its prunable data
*/
virtual bool get_prunable_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const = 0;
/** /**
* @brief fetches the prunable transaction hash * @brief fetches the prunable transaction hash
* *
@ -1412,6 +1449,31 @@ public:
*/ */
virtual void prune_outputs(uint64_t amount) = 0; virtual void prune_outputs(uint64_t amount) = 0;
/**
* @brief get the blockchain pruning seed
* @return the blockchain pruning seed
*/
virtual uint32_t get_blockchain_pruning_seed() const = 0;
/**
* @brief prunes the blockchain
* @param pruning_seed the seed to use, 0 for default (highly recommended)
* @return success iff true
*/
virtual bool prune_blockchain(uint32_t pruning_seed = 0) = 0;
/**
* @brief prunes recent blockchain changes as needed, iff pruning is enabled
* @return success iff true
*/
virtual bool update_pruning() = 0;
/**
* @brief checks pruning was done correctly, iff enabled
* @return success iff true
*/
virtual bool check_pruning() = 0;
/** /**
* @brief runs a function over all txpool transactions * @brief runs a function over all txpool transactions
* *

@ -35,6 +35,7 @@
#include "string_tools.h" #include "string_tools.h"
#include "file_io_utils.h" #include "file_io_utils.h"
#include "common/util.h" #include "common/util.h"
#include "common/pruning.h"
#include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/cryptonote_format_utils.h"
#include "crypto/crypto.h" #include "crypto/crypto.h"
#include "profile_tools.h" #include "profile_tools.h"
@ -130,14 +131,20 @@ private:
std::unique_ptr<char[]> data; std::unique_ptr<char[]> data;
}; };
int compare_uint64(const MDB_val *a, const MDB_val *b) }
namespace cryptonote
{ {
const uint64_t va = *(const uint64_t *)a->mv_data;
const uint64_t vb = *(const uint64_t *)b->mv_data; int BlockchainLMDB::compare_uint64(const MDB_val *a, const MDB_val *b)
{
uint64_t va, vb;
memcpy(&va, a->mv_data, sizeof(va));
memcpy(&vb, b->mv_data, sizeof(vb));
return (va < vb) ? -1 : va > vb; return (va < vb) ? -1 : va > vb;
} }
int compare_hash32(const MDB_val *a, const MDB_val *b) int BlockchainLMDB::compare_hash32(const MDB_val *a, const MDB_val *b)
{ {
uint32_t *va = (uint32_t*) a->mv_data; uint32_t *va = (uint32_t*) a->mv_data;
uint32_t *vb = (uint32_t*) b->mv_data; uint32_t *vb = (uint32_t*) b->mv_data;
@ -151,13 +158,18 @@ int compare_hash32(const MDB_val *a, const MDB_val *b)
return 0; return 0;
} }
int compare_string(const MDB_val *a, const MDB_val *b) int BlockchainLMDB::compare_string(const MDB_val *a, const MDB_val *b)
{ {
const char *va = (const char*) a->mv_data; const char *va = (const char*) a->mv_data;
const char *vb = (const char*) b->mv_data; const char *vb = (const char*) b->mv_data;
return strcmp(va, vb); return strcmp(va, vb);
} }
}
namespace
{
/* DB schema: /* DB schema:
* *
* Table Key Data * Table Key Data
@ -169,6 +181,7 @@ int compare_string(const MDB_val *a, const MDB_val *b)
* txs_pruned txn ID pruned txn blob * txs_pruned txn ID pruned txn blob
* txs_prunable txn ID prunable txn blob * txs_prunable txn ID prunable txn blob
* txs_prunable_hash txn ID prunable txn hash * txs_prunable_hash txn ID prunable txn hash
* txs_prunable_tip txn ID height
* tx_indices txn hash {txn ID, metadata} * tx_indices txn hash {txn ID, metadata}
* tx_outputs txn ID [txn amount output indices] * tx_outputs txn ID [txn amount output indices]
* *
@ -196,6 +209,7 @@ const char* const LMDB_TXS = "txs";
const char* const LMDB_TXS_PRUNED = "txs_pruned"; const char* const LMDB_TXS_PRUNED = "txs_pruned";
const char* const LMDB_TXS_PRUNABLE = "txs_prunable"; const char* const LMDB_TXS_PRUNABLE = "txs_prunable";
const char* const LMDB_TXS_PRUNABLE_HASH = "txs_prunable_hash"; const char* const LMDB_TXS_PRUNABLE_HASH = "txs_prunable_hash";
const char* const LMDB_TXS_PRUNABLE_TIP = "txs_prunable_tip";
const char* const LMDB_TX_INDICES = "tx_indices"; const char* const LMDB_TX_INDICES = "tx_indices";
const char* const LMDB_TX_OUTPUTS = "tx_outputs"; const char* const LMDB_TX_OUTPUTS = "tx_outputs";
@ -279,11 +293,6 @@ typedef struct blk_height {
uint64_t bh_height; uint64_t bh_height;
} blk_height; } blk_height;
typedef struct txindex {
crypto::hash key;
tx_data_t data;
} txindex;
typedef struct pre_rct_outkey { typedef struct pre_rct_outkey {
uint64_t amount_index; uint64_t amount_index;
uint64_t output_id; uint64_t output_id;
@ -549,18 +558,18 @@ bool BlockchainLMDB::need_resize(uint64_t threshold_size) const
// additional size needed. // additional size needed.
uint64_t size_used = mst.ms_psize * mei.me_last_pgno; uint64_t size_used = mst.ms_psize * mei.me_last_pgno;
LOG_PRINT_L1("DB map size: " << mei.me_mapsize); MDEBUG("DB map size: " << mei.me_mapsize);
LOG_PRINT_L1("Space used: " << size_used); MDEBUG("Space used: " << size_used);
LOG_PRINT_L1("Space remaining: " << mei.me_mapsize - size_used); MDEBUG("Space remaining: " << mei.me_mapsize - size_used);
LOG_PRINT_L1("Size threshold: " << threshold_size); MDEBUG("Size threshold: " << threshold_size);
float resize_percent = RESIZE_PERCENT; float resize_percent = RESIZE_PERCENT;
LOG_PRINT_L1(boost::format("Percent used: %.04f Percent threshold: %.04f") % ((double)size_used/mei.me_mapsize) % resize_percent); MDEBUG(boost::format("Percent used: %.04f Percent threshold: %.04f") % ((double)size_used/mei.me_mapsize) % resize_percent);
if (threshold_size > 0) if (threshold_size > 0)
{ {
if (mei.me_mapsize - size_used < threshold_size) if (mei.me_mapsize - size_used < threshold_size)
{ {
LOG_PRINT_L1("Threshold met (size-based)"); MINFO("Threshold met (size-based)");
return true; return true;
} }
else else
@ -569,7 +578,7 @@ bool BlockchainLMDB::need_resize(uint64_t threshold_size) const
if ((double)size_used / mei.me_mapsize > resize_percent) if ((double)size_used / mei.me_mapsize > resize_percent)
{ {
LOG_PRINT_L1("Threshold met (percent-based)"); MINFO("Threshold met (percent-based)");
return true; return true;
} }
return false; return false;
@ -581,7 +590,7 @@ bool BlockchainLMDB::need_resize(uint64_t threshold_size) const
void BlockchainLMDB::check_and_resize_for_batch(uint64_t batch_num_blocks, uint64_t batch_bytes) void BlockchainLMDB::check_and_resize_for_batch(uint64_t batch_num_blocks, uint64_t batch_bytes)
{ {
LOG_PRINT_L3("BlockchainLMDB::" << __func__); LOG_PRINT_L3("BlockchainLMDB::" << __func__);
LOG_PRINT_L1("[" << __func__ << "] " << "checking DB size"); MTRACE("[" << __func__ << "] " << "checking DB size");
const uint64_t min_increase_size = 512 * (1 << 20); const uint64_t min_increase_size = 512 * (1 << 20);
uint64_t threshold_size = 0; uint64_t threshold_size = 0;
uint64_t increase_size = 0; uint64_t increase_size = 0;
@ -811,6 +820,7 @@ uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, cons
CURSOR(txs_pruned) CURSOR(txs_pruned)
CURSOR(txs_prunable) CURSOR(txs_prunable)
CURSOR(txs_prunable_hash) CURSOR(txs_prunable_hash)
CURSOR(txs_prunable_tip)
CURSOR(tx_indices) CURSOR(tx_indices)
MDB_val_set(val_tx_id, tx_id); MDB_val_set(val_tx_id, tx_id);
@ -858,6 +868,14 @@ uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, cons
if (result) if (result)
throw0(DB_ERROR(lmdb_error("Failed to add prunable tx blob to db transaction: ", result).c_str())); throw0(DB_ERROR(lmdb_error("Failed to add prunable tx blob to db transaction: ", result).c_str()));
if (get_blockchain_pruning_seed())
{
MDB_val_set(val_height, m_height);
result = mdb_cursor_put(m_cur_txs_prunable_tip, &val_tx_id, &val_height, 0);
if (result)
throw0(DB_ERROR(lmdb_error("Failed to add prunable tx id to db transaction: ", result).c_str()));
}
if (tx.version > 1) if (tx.version > 1)
{ {
MDB_val_set(val_prunable_hash, tx_prunable_hash); MDB_val_set(val_prunable_hash, tx_prunable_hash);
@ -883,6 +901,7 @@ void BlockchainLMDB::remove_transaction_data(const crypto::hash& tx_hash, const
CURSOR(txs_pruned) CURSOR(txs_pruned)
CURSOR(txs_prunable) CURSOR(txs_prunable)
CURSOR(txs_prunable_hash) CURSOR(txs_prunable_hash)
CURSOR(txs_prunable_tip)
CURSOR(tx_outputs) CURSOR(tx_outputs)
MDB_val_set(val_h, tx_hash); MDB_val_set(val_h, tx_hash);
@ -898,11 +917,25 @@ void BlockchainLMDB::remove_transaction_data(const crypto::hash& tx_hash, const
if (result) if (result)
throw1(DB_ERROR(lmdb_error("Failed to add removal of pruned tx to db transaction: ", result).c_str())); throw1(DB_ERROR(lmdb_error("Failed to add removal of pruned tx to db transaction: ", result).c_str()));
if ((result = mdb_cursor_get(m_cur_txs_prunable, &val_tx_id, NULL, MDB_SET))) result = mdb_cursor_get(m_cur_txs_prunable, &val_tx_id, NULL, MDB_SET);
if (result == 0)
{
result = mdb_cursor_del(m_cur_txs_prunable, 0);
if (result)
throw1(DB_ERROR(lmdb_error("Failed to add removal of prunable tx to db transaction: ", result).c_str()));
}
else if (result != MDB_NOTFOUND)
throw1(DB_ERROR(lmdb_error("Failed to locate prunable tx for removal: ", result).c_str())); throw1(DB_ERROR(lmdb_error("Failed to locate prunable tx for removal: ", result).c_str()));
result = mdb_cursor_del(m_cur_txs_prunable, 0);
if (result) result = mdb_cursor_get(m_cur_txs_prunable_tip, &val_tx_id, NULL, MDB_SET);
throw1(DB_ERROR(lmdb_error("Failed to add removal of prunable tx to db transaction: ", result).c_str())); if (result && result != MDB_NOTFOUND)
throw1(DB_ERROR(lmdb_error("Failed to locate tx id for removal: ", result).c_str()));
if (result == 0)
{
result = mdb_cursor_del(m_cur_txs_prunable_tip, 0);
if (result)
throw1(DB_ERROR(lmdb_error("Error adding removal of tx id to db transaction", result).c_str()));
}
if (tx.version > 1) if (tx.version > 1)
{ {
@ -1308,6 +1341,7 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags)
// open necessary databases, and set properties as needed // open necessary databases, and set properties as needed
// uses macros to avoid having to change things too many places // uses macros to avoid having to change things too many places
// also change blockchain_prune.cpp to match
lmdb_db_open(txn, LMDB_BLOCKS, MDB_INTEGERKEY | MDB_CREATE, m_blocks, "Failed to open db handle for m_blocks"); lmdb_db_open(txn, LMDB_BLOCKS, MDB_INTEGERKEY | MDB_CREATE, m_blocks, "Failed to open db handle for m_blocks");
lmdb_db_open(txn, LMDB_BLOCK_INFO, MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_block_info, "Failed to open db handle for m_block_info"); lmdb_db_open(txn, LMDB_BLOCK_INFO, MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_block_info, "Failed to open db handle for m_block_info");
@ -1316,7 +1350,9 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags)
lmdb_db_open(txn, LMDB_TXS, MDB_INTEGERKEY | MDB_CREATE, m_txs, "Failed to open db handle for m_txs"); lmdb_db_open(txn, LMDB_TXS, MDB_INTEGERKEY | MDB_CREATE, m_txs, "Failed to open db handle for m_txs");
lmdb_db_open(txn, LMDB_TXS_PRUNED, MDB_INTEGERKEY | MDB_CREATE, m_txs_pruned, "Failed to open db handle for m_txs_pruned"); lmdb_db_open(txn, LMDB_TXS_PRUNED, MDB_INTEGERKEY | MDB_CREATE, m_txs_pruned, "Failed to open db handle for m_txs_pruned");
lmdb_db_open(txn, LMDB_TXS_PRUNABLE, MDB_INTEGERKEY | MDB_CREATE, m_txs_prunable, "Failed to open db handle for m_txs_prunable"); lmdb_db_open(txn, LMDB_TXS_PRUNABLE, MDB_INTEGERKEY | MDB_CREATE, m_txs_prunable, "Failed to open db handle for m_txs_prunable");
lmdb_db_open(txn, LMDB_TXS_PRUNABLE_HASH, MDB_INTEGERKEY | MDB_CREATE, m_txs_prunable_hash, "Failed to open db handle for m_txs_prunable_hash"); lmdb_db_open(txn, LMDB_TXS_PRUNABLE_HASH, MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED | MDB_CREATE, m_txs_prunable_hash, "Failed to open db handle for m_txs_prunable_hash");
if (!(mdb_flags & MDB_RDONLY))
lmdb_db_open(txn, LMDB_TXS_PRUNABLE_TIP, MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED | MDB_CREATE, m_txs_prunable_tip, "Failed to open db handle for m_txs_prunable_tip");
lmdb_db_open(txn, LMDB_TX_INDICES, MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_tx_indices, "Failed to open db handle for m_tx_indices"); lmdb_db_open(txn, LMDB_TX_INDICES, MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_tx_indices, "Failed to open db handle for m_tx_indices");
lmdb_db_open(txn, LMDB_TX_OUTPUTS, MDB_INTEGERKEY | MDB_CREATE, m_tx_outputs, "Failed to open db handle for m_tx_outputs"); lmdb_db_open(txn, LMDB_TX_OUTPUTS, MDB_INTEGERKEY | MDB_CREATE, m_tx_outputs, "Failed to open db handle for m_tx_outputs");
@ -1344,6 +1380,10 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags)
mdb_set_dupsort(txn, m_output_amounts, compare_uint64); mdb_set_dupsort(txn, m_output_amounts, compare_uint64);
mdb_set_dupsort(txn, m_output_txs, compare_uint64); mdb_set_dupsort(txn, m_output_txs, compare_uint64);
mdb_set_dupsort(txn, m_block_info, compare_uint64); mdb_set_dupsort(txn, m_block_info, compare_uint64);
if (!(mdb_flags & MDB_RDONLY))
mdb_set_dupsort(txn, m_txs_prunable_tip, compare_uint64);
mdb_set_compare(txn, m_txs_prunable, compare_uint64);
mdb_set_dupsort(txn, m_txs_prunable_hash, compare_uint64);
mdb_set_compare(txn, m_txpool_meta, compare_hash32); mdb_set_compare(txn, m_txpool_meta, compare_hash32);
mdb_set_compare(txn, m_txpool_blob, compare_hash32); mdb_set_compare(txn, m_txpool_blob, compare_hash32);
@ -1502,6 +1542,8 @@ void BlockchainLMDB::reset()
throw0(DB_ERROR(lmdb_error("Failed to drop m_txs_prunable: ", result).c_str())); throw0(DB_ERROR(lmdb_error("Failed to drop m_txs_prunable: ", result).c_str()));
if (auto result = mdb_drop(txn, m_txs_prunable_hash, 0)) if (auto result = mdb_drop(txn, m_txs_prunable_hash, 0))
throw0(DB_ERROR(lmdb_error("Failed to drop m_txs_prunable_hash: ", result).c_str())); throw0(DB_ERROR(lmdb_error("Failed to drop m_txs_prunable_hash: ", result).c_str()));
if (auto result = mdb_drop(txn, m_txs_prunable_tip, 0))
throw0(DB_ERROR(lmdb_error("Failed to drop m_txs_prunable_tip: ", result).c_str()));
if (auto result = mdb_drop(txn, m_tx_indices, 0)) if (auto result = mdb_drop(txn, m_tx_indices, 0))
throw0(DB_ERROR(lmdb_error("Failed to drop m_tx_indices: ", result).c_str())); throw0(DB_ERROR(lmdb_error("Failed to drop m_tx_indices: ", result).c_str()));
if (auto result = mdb_drop(txn, m_tx_outputs, 0)) if (auto result = mdb_drop(txn, m_tx_outputs, 0))
@ -1827,6 +1869,290 @@ cryptonote::blobdata BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid
return bd; return bd;
} }
uint32_t BlockchainLMDB::get_blockchain_pruning_seed() const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
TXN_PREFIX_RDONLY();
RCURSOR(properties)
MDB_val_str(k, "pruning_seed");
MDB_val v;
int result = mdb_cursor_get(m_cur_properties, &k, &v, MDB_SET);
if (result == MDB_NOTFOUND)
return 0;
if (result)
throw0(DB_ERROR(lmdb_error("Failed to retrieve pruning seed: ", result).c_str()));
if (v.mv_size != sizeof(uint32_t))
throw0(DB_ERROR("Failed to retrieve or create pruning seed: unexpected value size"));
uint32_t pruning_seed;
memcpy(&pruning_seed, v.mv_data, sizeof(pruning_seed));
TXN_POSTFIX_RDONLY();
return pruning_seed;
}
static bool is_v1_tx(MDB_cursor *c_txs_pruned, MDB_val *tx_id)
{
MDB_val v;
int ret = mdb_cursor_get(c_txs_pruned, tx_id, &v, MDB_SET);
if (ret)
throw0(DB_ERROR(lmdb_error("Failed to find transaction pruned data: ", ret).c_str()));
if (v.mv_size == 0)
throw0(DB_ERROR("Invalid transaction pruned data"));
return cryptonote::is_v1_tx(cryptonote::blobdata_ref{(const char*)v.mv_data, v.mv_size});
}
enum { prune_mode_prune, prune_mode_update, prune_mode_check };
bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed)
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
const uint32_t log_stripes = tools::get_pruning_log_stripes(pruning_seed);
if (log_stripes && log_stripes != CRYPTONOTE_PRUNING_LOG_STRIPES)
throw0(DB_ERROR("Pruning seed not in range"));
pruning_seed = tools::get_pruning_stripe(pruning_seed);;
if (pruning_seed > (1ul << CRYPTONOTE_PRUNING_LOG_STRIPES))
throw0(DB_ERROR("Pruning seed not in range"));
check_open();
TIME_MEASURE_START(t);
size_t n_total_records = 0, n_prunable_records = 0, n_pruned_records = 0;
uint64_t n_bytes = 0;
mdb_txn_safe txn;
auto result = mdb_txn_begin(m_env, NULL, 0, txn);
if (result)
throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str()));
MDB_stat db_stats;
if ((result = mdb_stat(txn, m_txs_prunable, &db_stats)))
throw0(DB_ERROR(lmdb_error("Failed to query m_txs_prunable: ", result).c_str()));
const size_t pages0 = db_stats.ms_branch_pages + db_stats.ms_leaf_pages + db_stats.ms_overflow_pages;
MDB_val_str(k, "pruning_seed");
MDB_val v;
result = mdb_get(txn, m_properties, &k, &v);
bool prune_tip_table = false;
if (result == MDB_NOTFOUND)
{
// not pruned yet
if (mode != prune_mode_prune)
{
txn.abort();
TIME_MEASURE_FINISH(t);
MDEBUG("Pruning not enabled, nothing to do");
return true;
}
if (pruning_seed == 0)
pruning_seed = tools::get_random_stripe();
pruning_seed = tools::make_pruning_seed(pruning_seed, CRYPTONOTE_PRUNING_LOG_STRIPES);
v.mv_data = &pruning_seed;
v.mv_size = sizeof(pruning_seed);
result = mdb_put(txn, m_properties, &k, &v, 0);
if (result)
throw0(DB_ERROR("Failed to save pruning seed"));
prune_tip_table = false;
}
else if (result == 0)
{
// pruned already
if (v.mv_size != sizeof(uint32_t))
throw0(DB_ERROR("Failed to retrieve or create pruning seed: unexpected value size"));
const uint32_t data = *(const uint32_t*)v.mv_data;
if (pruning_seed == 0)
pruning_seed = tools::get_pruning_stripe(data);
if (tools::get_pruning_stripe(data) != pruning_seed)
throw0(DB_ERROR("Blockchain already pruned with different seed"));
if (tools::get_pruning_log_stripes(data) != CRYPTONOTE_PRUNING_LOG_STRIPES)
throw0(DB_ERROR("Blockchain already pruned with different base"));
pruning_seed = tools::make_pruning_seed(pruning_seed, CRYPTONOTE_PRUNING_LOG_STRIPES);
prune_tip_table = (mode == prune_mode_update);
}
else
{
throw0(DB_ERROR(lmdb_error("Failed to retrieve or create pruning seed: ", result).c_str()));
}
if (mode == prune_mode_check)
MINFO("Checking blockchain pruning...");
else
MINFO("Pruning blockchain...");
MDB_cursor *c_txs_pruned, *c_txs_prunable, *c_txs_prunable_tip;
result = mdb_cursor_open(txn, m_txs_pruned, &c_txs_pruned);
if (result)
throw0(DB_ERROR(lmdb_error("Failed to open a cursor for txs_pruned: ", result).c_str()));
result = mdb_cursor_open(txn, m_txs_prunable, &c_txs_prunable);
if (result)
throw0(DB_ERROR(lmdb_error("Failed to open a cursor for txs_prunable: ", result).c_str()));
result = mdb_cursor_open(txn, m_txs_prunable_tip, &c_txs_prunable_tip);
if (result)
throw0(DB_ERROR(lmdb_error("Failed to open a cursor for txs_prunable_tip: ", result).c_str()));
const uint64_t blockchain_height = height();
if (prune_tip_table)
{
MDB_cursor_op op = MDB_FIRST;
while (1)
{
int ret = mdb_cursor_get(c_txs_prunable_tip, &k, &v, op);
op = MDB_NEXT;
if (ret == MDB_NOTFOUND)
break;
if (ret)
throw0(DB_ERROR(lmdb_error("Failed to enumerate transactions: ", ret).c_str()));
uint64_t block_height;
memcpy(&block_height, v.mv_data, sizeof(block_height));
if (block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS < blockchain_height)
{
++n_total_records;
if (!tools::has_unpruned_block(block_height, blockchain_height, pruning_seed) && !is_v1_tx(c_txs_pruned, &k))
{
++n_prunable_records;
result = mdb_cursor_get(c_txs_prunable, &k, &v, MDB_SET);
if (result == MDB_NOTFOUND)
MWARNING("Already pruned at height " << block_height << "/" << blockchain_height);
else if (result)
throw0(DB_ERROR(lmdb_error("Failed to find transaction prunable data: ", result).c_str()));
else
{
MDEBUG("Pruning at height " << block_height << "/" << blockchain_height);
++n_pruned_records;
n_bytes += k.mv_size + v.mv_size;
result = mdb_cursor_del(c_txs_prunable, 0);
if (result)
throw0(DB_ERROR(lmdb_error("Failed to delete transaction prunable data: ", result).c_str()));
}
}
result = mdb_cursor_del(c_txs_prunable_tip, 0);
if (result)
throw0(DB_ERROR(lmdb_error("Failed to delete transaction tip data: ", result).c_str()));
}
}
}
else
{
MDB_cursor *c_tx_indices;
result = mdb_cursor_open(txn, m_tx_indices, &c_tx_indices);
if (result)
throw0(DB_ERROR(lmdb_error("Failed to open a cursor for tx_indices: ", result).c_str()));
MDB_cursor_op op = MDB_FIRST;
while (1)
{
int ret = mdb_cursor_get(c_tx_indices, &k, &v, op);
op = MDB_NEXT;
if (ret == MDB_NOTFOUND)
break;
if (ret)
throw0(DB_ERROR(lmdb_error("Failed to enumerate transactions: ", ret).c_str()));
++n_total_records;
//const txindex *ti = (const txindex *)v.mv_data;
txindex ti;
memcpy(&ti, v.mv_data, sizeof(ti));
const uint64_t block_height = ti.data.block_id;
if (block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS >= blockchain_height)
{
MDB_val_set(kp, ti.data.tx_id);
MDB_val_set(vp, block_height);
if (mode == prune_mode_check)
{
result = mdb_cursor_get(c_txs_prunable_tip, &kp, &vp, MDB_SET);
if (result && result != MDB_NOTFOUND)
throw0(DB_ERROR(lmdb_error("Error looking for transaction prunable data: ", result).c_str()));
if (result == MDB_NOTFOUND)
MERROR("Transaction not found in prunable tip table for height " << block_height << "/" << blockchain_height <<
", seed " << epee::string_tools::to_string_hex(pruning_seed));
}
else
{
result = mdb_cursor_put(c_txs_prunable_tip, &kp, &vp, 0);
if (result && result != MDB_NOTFOUND)
throw0(DB_ERROR(lmdb_error("Error looking for transaction prunable data: ", result).c_str()));
}
}
MDB_val_set(kp, ti.data.tx_id);
if (!tools::has_unpruned_block(block_height, blockchain_height, pruning_seed) && !is_v1_tx(c_txs_pruned, &kp))
{
result = mdb_cursor_get(c_txs_prunable, &kp, &v, MDB_SET);
if (result && result != MDB_NOTFOUND)
throw0(DB_ERROR(lmdb_error("Error looking for transaction prunable data: ", result).c_str()));
if (mode == prune_mode_check)
{
if (result != MDB_NOTFOUND)
MERROR("Prunable data found for pruned height " << block_height << "/" << blockchain_height <<
", seed " << epee::string_tools::to_string_hex(pruning_seed));
}
else
{
++n_prunable_records;
if (result == MDB_NOTFOUND)
MWARNING("Already pruned at height " << block_height << "/" << blockchain_height);
else
{
MDEBUG("Pruning at height " << block_height << "/" << blockchain_height);
++n_pruned_records;
n_bytes += kp.mv_size + v.mv_size;
result = mdb_cursor_del(c_txs_prunable, 0);
if (result)
throw0(DB_ERROR(lmdb_error("Failed to delete transaction prunable data: ", result).c_str()));
}
}
}
else
{
if (mode == prune_mode_check)
{
MDB_val_set(kp, ti.data.tx_id);
result = mdb_cursor_get(c_txs_prunable, &kp, &v, MDB_SET);
if (result && result != MDB_NOTFOUND)
throw0(DB_ERROR(lmdb_error("Error looking for transaction prunable data: ", result).c_str()));
if (result == MDB_NOTFOUND)
MERROR("Prunable data not found for unpruned height " << block_height << "/" << blockchain_height <<
", seed " << epee::string_tools::to_string_hex(pruning_seed));
}
}
}
mdb_cursor_close(c_tx_indices);
}
if ((result = mdb_stat(txn, m_txs_prunable, &db_stats)))
throw0(DB_ERROR(lmdb_error("Failed to query m_txs_prunable: ", result).c_str()));
const size_t pages1 = db_stats.ms_branch_pages + db_stats.ms_leaf_pages + db_stats.ms_overflow_pages;
const size_t db_bytes = (pages0 - pages1) * db_stats.ms_psize;
mdb_cursor_close(c_txs_prunable_tip);
mdb_cursor_close(c_txs_prunable);
mdb_cursor_close(c_txs_pruned);
txn.commit();
TIME_MEASURE_FINISH(t);
MINFO((mode == prune_mode_check ? "Checked" : "Pruned") << " blockchain in " <<
t << " ms: " << (n_bytes/1024.0f/1024.0f) << " MB (" << db_bytes/1024.0f/1024.0f << " MB) pruned in " <<
n_pruned_records << " records (" << pages0 - pages1 << "/" << pages0 << " " << db_stats.ms_psize << " byte pages), " <<
n_prunable_records << "/" << n_total_records << " pruned records");
return true;
}
bool BlockchainLMDB::prune_blockchain(uint32_t pruning_seed)
{
return prune_worker(prune_mode_prune, pruning_seed);
}
bool BlockchainLMDB::update_pruning()
{
return prune_worker(prune_mode_update, 0);
}
bool BlockchainLMDB::check_pruning()
{
return prune_worker(prune_mode_check, 0);
}
bool BlockchainLMDB::for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob, bool include_unrelayed_txes) const bool BlockchainLMDB::for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob, bool include_unrelayed_txes) const
{ {
LOG_PRINT_L3("BlockchainLMDB::" << __func__); LOG_PRINT_L3("BlockchainLMDB::" << __func__);
@ -2428,6 +2754,36 @@ bool BlockchainLMDB::get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobd
return true; return true;
} }
bool BlockchainLMDB::get_prunable_tx_blob(const crypto::hash& h, cryptonote::blobdata &bd) const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
TXN_PREFIX_RDONLY();
RCURSOR(tx_indices);
RCURSOR(txs_prunable);
MDB_val_set(v, h);
MDB_val result;
auto get_result = mdb_cursor_get(m_cur_tx_indices, (MDB_val *)&zerokval, &v, MDB_GET_BOTH);
if (get_result == 0)
{
const txindex *tip = (const txindex *)v.mv_data;
MDB_val_set(val_tx_id, tip->data.tx_id);
get_result = mdb_cursor_get(m_cur_txs_prunable, &val_tx_id, &result, MDB_SET);
}
if (get_result == MDB_NOTFOUND)
return false;
else if (get_result)
throw0(DB_ERROR(lmdb_error("DB error attempting to fetch tx from hash", get_result).c_str()));
bd.assign(reinterpret_cast<char*>(result.mv_data), result.mv_size);
TXN_POSTFIX_RDONLY();
return true;
}
bool BlockchainLMDB::get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const bool BlockchainLMDB::get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const
{ {
LOG_PRINT_L3("BlockchainLMDB::" << __func__); LOG_PRINT_L3("BlockchainLMDB::" << __func__);

@ -40,6 +40,11 @@
namespace cryptonote namespace cryptonote
{ {
typedef struct txindex {
crypto::hash key;
tx_data_t data;
} txindex;
typedef struct mdb_txn_cursors typedef struct mdb_txn_cursors
{ {
MDB_cursor *m_txc_blocks; MDB_cursor *m_txc_blocks;
@ -53,6 +58,7 @@ typedef struct mdb_txn_cursors
MDB_cursor *m_txc_txs_pruned; MDB_cursor *m_txc_txs_pruned;
MDB_cursor *m_txc_txs_prunable; MDB_cursor *m_txc_txs_prunable;
MDB_cursor *m_txc_txs_prunable_hash; MDB_cursor *m_txc_txs_prunable_hash;
MDB_cursor *m_txc_txs_prunable_tip;
MDB_cursor *m_txc_tx_indices; MDB_cursor *m_txc_tx_indices;
MDB_cursor *m_txc_tx_outputs; MDB_cursor *m_txc_tx_outputs;
@ -62,6 +68,8 @@ typedef struct mdb_txn_cursors
MDB_cursor *m_txc_txpool_blob; MDB_cursor *m_txc_txpool_blob;
MDB_cursor *m_txc_hf_versions; MDB_cursor *m_txc_hf_versions;
MDB_cursor *m_txc_properties;
} mdb_txn_cursors; } mdb_txn_cursors;
#define m_cur_blocks m_cursors->m_txc_blocks #define m_cur_blocks m_cursors->m_txc_blocks
@ -73,12 +81,14 @@ typedef struct mdb_txn_cursors
#define m_cur_txs_pruned m_cursors->m_txc_txs_pruned #define m_cur_txs_pruned m_cursors->m_txc_txs_pruned
#define m_cur_txs_prunable m_cursors->m_txc_txs_prunable #define m_cur_txs_prunable m_cursors->m_txc_txs_prunable
#define m_cur_txs_prunable_hash m_cursors->m_txc_txs_prunable_hash #define m_cur_txs_prunable_hash m_cursors->m_txc_txs_prunable_hash
#define m_cur_txs_prunable_tip m_cursors->m_txc_txs_prunable_tip
#define m_cur_tx_indices m_cursors->m_txc_tx_indices #define m_cur_tx_indices m_cursors->m_txc_tx_indices
#define m_cur_tx_outputs m_cursors->m_txc_tx_outputs #define m_cur_tx_outputs m_cursors->m_txc_tx_outputs
#define m_cur_spent_keys m_cursors->m_txc_spent_keys #define m_cur_spent_keys m_cursors->m_txc_spent_keys
#define m_cur_txpool_meta m_cursors->m_txc_txpool_meta #define m_cur_txpool_meta m_cursors->m_txc_txpool_meta
#define m_cur_txpool_blob m_cursors->m_txc_txpool_blob #define m_cur_txpool_blob m_cursors->m_txc_txpool_blob
#define m_cur_hf_versions m_cursors->m_txc_hf_versions #define m_cur_hf_versions m_cursors->m_txc_hf_versions
#define m_cur_properties m_cursors->m_txc_properties
typedef struct mdb_rflags typedef struct mdb_rflags
{ {
@ -92,12 +102,14 @@ typedef struct mdb_rflags
bool m_rf_txs_pruned; bool m_rf_txs_pruned;
bool m_rf_txs_prunable; bool m_rf_txs_prunable;
bool m_rf_txs_prunable_hash; bool m_rf_txs_prunable_hash;
bool m_rf_txs_prunable_tip;
bool m_rf_tx_indices; bool m_rf_tx_indices;
bool m_rf_tx_outputs; bool m_rf_tx_outputs;
bool m_rf_spent_keys; bool m_rf_spent_keys;
bool m_rf_txpool_meta; bool m_rf_txpool_meta;
bool m_rf_txpool_blob; bool m_rf_txpool_blob;
bool m_rf_hf_versions; bool m_rf_hf_versions;
bool m_rf_properties;
} mdb_rflags; } mdb_rflags;
typedef struct mdb_threadinfo typedef struct mdb_threadinfo
@ -232,6 +244,7 @@ public:
virtual bool get_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const; virtual bool get_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const;
virtual bool get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const; virtual bool get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const;
virtual bool get_prunable_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const;
virtual bool get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const; virtual bool get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const;
virtual uint64_t get_tx_count() const; virtual uint64_t get_tx_count() const;
@ -264,6 +277,11 @@ public:
virtual bool get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t &meta) const; virtual bool get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t &meta) const;
virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const; virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const;
virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const; virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const;
virtual uint32_t get_blockchain_pruning_seed() const;
virtual bool prune_blockchain(uint32_t pruning_seed = 0);
virtual bool update_pruning();
virtual bool check_pruning();
virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob = false, bool include_unrelayed_txes = true) const; virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob = false, bool include_unrelayed_txes = true) const;
virtual bool for_all_key_images(std::function<bool(const crypto::key_image&)>) const; virtual bool for_all_key_images(std::function<bool(const crypto::key_image&)>) const;
@ -309,6 +327,11 @@ public:
bool get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t to_height, std::vector<uint64_t> &distribution, uint64_t &base) const; bool get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t to_height, std::vector<uint64_t> &distribution, uint64_t &base) const;
// 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: private:
void do_resize(uint64_t size_increase=0); void do_resize(uint64_t size_increase=0);
@ -361,6 +384,8 @@ private:
inline void check_open() const; inline void check_open() const;
bool prune_worker(int mode, uint32_t pruning_seed);
virtual bool is_read_only() const; virtual bool is_read_only() const;
virtual uint64_t get_database_size() const; virtual uint64_t get_database_size() const;
@ -393,6 +418,7 @@ private:
MDB_dbi m_txs_pruned; MDB_dbi m_txs_pruned;
MDB_dbi m_txs_prunable; MDB_dbi m_txs_prunable;
MDB_dbi m_txs_prunable_hash; MDB_dbi m_txs_prunable_hash;
MDB_dbi m_txs_prunable_tip;
MDB_dbi m_tx_indices; MDB_dbi m_tx_indices;
MDB_dbi m_tx_outputs; MDB_dbi m_tx_outputs;

@ -92,6 +92,17 @@ monero_private_headers(blockchain_prune_known_spent_data
set(blockchain_prune_sources
blockchain_prune.cpp
)
set(blockchain_prune_private_headers)
monero_private_headers(blockchain_prune
${blockchain_prune_private_headers})
set(blockchain_ancestry_sources set(blockchain_ancestry_sources
blockchain_ancestry.cpp blockchain_ancestry.cpp
) )
@ -298,3 +309,25 @@ set_property(TARGET blockchain_prune_known_spent_data
PROPERTY PROPERTY
OUTPUT_NAME "monero-blockchain-prune-known-spent-data") OUTPUT_NAME "monero-blockchain-prune-known-spent-data")
install(TARGETS blockchain_prune_known_spent_data DESTINATION bin) install(TARGETS blockchain_prune_known_spent_data DESTINATION bin)
monero_add_executable(blockchain_prune
${blockchain_prune_sources}
${blockchain_prune_private_headers})
set_property(TARGET blockchain_prune
PROPERTY
OUTPUT_NAME "monero-blockchain-prune")
install(TARGETS blockchain_prune DESTINATION bin)
target_link_libraries(blockchain_prune
PRIVATE
cryptonote_core
blockchain_db
p2p
version
epee
${Boost_FILESYSTEM_LIBRARY}
${Boost_SYSTEM_LIBRARY}
${Boost_THREAD_LIBRARY}
${CMAKE_THREAD_LIBS_INIT}
${EXTRA_LIBRARIES})

@ -177,6 +177,12 @@ int main(int argc, char* argv[])
} }
r = core_storage->init(db, opt_testnet ? cryptonote::TESTNET : opt_stagenet ? cryptonote::STAGENET : cryptonote::MAINNET); r = core_storage->init(db, opt_testnet ? cryptonote::TESTNET : opt_stagenet ? cryptonote::STAGENET : cryptonote::MAINNET);
if (core_storage->get_blockchain_pruning_seed())
{
LOG_PRINT_L0("Blockchain is pruned, cannot export");
return 1;
}
CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage"); CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage");
LOG_PRINT_L0("Source blockchain storage initialized OK"); LOG_PRINT_L0("Source blockchain storage initialized OK");
LOG_PRINT_L0("Exporting blockchain raw data..."); LOG_PRINT_L0("Exporting blockchain raw data...");

@ -0,0 +1,663 @@
// Copyright (c) 2018, 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
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <array>
#include <lmdb.h>
#include <boost/algorithm/string.hpp>
#include "common/command_line.h"
#include "common/pruning.h"
#include "cryptonote_core/cryptonote_core.h"
#include "cryptonote_core/blockchain.h"
#include "blockchain_db/blockchain_db.h"
#include "blockchain_db/lmdb/db_lmdb.h"
#include "blockchain_db/db_types.h"
#include "version.h"
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "bcutil"
#define MDB_val_set(var, val) MDB_val var = {sizeof(val), (void *)&val}
namespace po = boost::program_options;
using namespace epee;
using namespace cryptonote;
static std::string db_path;
// default to fast:1
static uint64_t records_per_sync = 128;
static const size_t slack = 512 * 1024 * 1024;
static std::error_code replace_file(const boost::filesystem::path& replacement_name, const boost::filesystem::path& replaced_name)
{
std::error_code ec = tools::replace_file(replacement_name.string(), replaced_name.string());
if (ec)
MERROR("Error renaming " << replacement_name << " to " << replaced_name << ": " << ec.message());
return ec;
}
static void open(MDB_env *&env, const boost::filesystem::path &path, uint64_t db_flags, bool readonly)
{
int dbr;
int flags = 0;
if (db_flags & DBF_FAST)
flags |= MDB_NOSYNC;
if (db_flags & DBF_FASTEST)
flags |= MDB_NOSYNC | MDB_WRITEMAP | MDB_MAPASYNC;
if (readonly)
flags |= MDB_RDONLY;
dbr = mdb_env_create(&env);
if (dbr) throw std::runtime_error("Failed to create LDMB environment: " + std::string(mdb_strerror(dbr)));
dbr = mdb_env_set_maxdbs(env, 32);
if (dbr) throw std::runtime_error("Failed to set max env dbs: " + std::string(mdb_strerror(dbr)));
dbr = mdb_env_open(env, path.string().c_str(), flags, 0664);
if (dbr) throw std::runtime_error("Failed to open database file '"
+ path.string() + "': " + std::string(mdb_strerror(dbr)));
}
static void close(MDB_env *env)
{
mdb_env_close(env);
}
static void add_size(MDB_env *env, uint64_t bytes)
{
try
{
boost::filesystem::path path(db_path);
boost::filesystem::space_info si = boost::filesystem::space(path);
if(si.available < bytes)
{
MERROR("!! WARNING: Insufficient free space to extend database !!: " <<
(si.available >> 20L) << " MB available, " << (bytes >> 20L) << " MB needed");
return;
}
}
catch(...)
{
// print something but proceed.
MWARNING("Unable to query free disk space.");
}
MDB_envinfo mei;
mdb_env_info(env, &mei);
MDB_stat mst;
mdb_env_stat(env, &mst);
uint64_t new_mapsize = (uint64_t)mei.me_mapsize + bytes;
new_mapsize += (new_mapsize % mst.ms_psize);
int result = mdb_env_set_mapsize(env, new_mapsize);
if (result)
throw std::runtime_error("Failed to set new mapsize to " + std::to_string(new_mapsize) + ": " + std::string(mdb_strerror(result)));
MGINFO("LMDB Mapsize increased." << " Old: " << mei.me_mapsize / (1024 * 1024) << "MiB" << ", New: " << new_mapsize / (1024 * 1024) << "MiB");
}
static void check_resize(MDB_env *env, size_t bytes)
{
MDB_envinfo mei;
MDB_stat mst;
mdb_env_info(env, &mei);
mdb_env_stat(env, &mst);
uint64_t size_used = mst.ms_psize * mei.me_last_pgno;
if (size_used + bytes + slack >= mei.me_mapsize)
add_size(env, size_used + bytes + 2 * slack - mei.me_mapsize);
}
static bool resize_point(size_t nrecords, MDB_env *env, MDB_txn **txn, size_t &bytes)
{
if (nrecords % records_per_sync && bytes <= slack / 2)
return false;
int dbr = mdb_txn_commit(*txn);
if (dbr) throw std::runtime_error("Failed to commit txn: " + std::string(mdb_strerror(dbr)));
check_resize(env, bytes);
dbr = mdb_txn_begin(env, NULL, 0, txn);
if (dbr) throw std::runtime_error("Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
bytes = 0;
return true;
}
static void copy_table(MDB_env *env0, MDB_env *env1, const char *table, unsigned int flags, unsigned int putflags, int (*cmp)(const MDB_val*, const MDB_val*)=0)
{
MDB_dbi dbi0, dbi1;
MDB_txn *txn0, *txn1;
MDB_cursor *cur0, *cur1;
bool tx_active0 = false, tx_active1 = false;
int dbr;
MINFO("Copying " << table);
epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){
if (tx_active1) mdb_txn_abort(txn1);
if (tx_active0) mdb_txn_abort(txn0);
});
dbr = mdb_txn_begin(env0, NULL, MDB_RDONLY, &txn0);
if (dbr) throw std::runtime_error("Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
tx_active0 = true;
dbr = mdb_txn_begin(env1, NULL, 0, &txn1);
if (dbr) throw std::runtime_error("Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
tx_active1 = true;
dbr = mdb_dbi_open(txn0, table, flags, &dbi0);
if (dbr) throw std::runtime_error("Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
if (cmp)
((flags & MDB_DUPSORT) ? mdb_set_dupsort : mdb_set_compare)(txn0, dbi0, cmp);
dbr = mdb_dbi_open(txn1, table, flags, &dbi1);
if (dbr) throw std::runtime_error("Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
if (cmp)
((flags & MDB_DUPSORT) ? mdb_set_dupsort : mdb_set_compare)(txn1, dbi1, cmp);
dbr = mdb_txn_commit(txn1);
if (dbr) throw std::runtime_error("Failed to commit txn: " + std::string(mdb_strerror(dbr)));
tx_active1 = false;
MDB_stat stats;
dbr = mdb_env_stat(env0, &stats);
if (dbr) throw std::runtime_error("Failed to stat " + std::string(table) + " LMDB table: " + std::string(mdb_strerror(dbr)));
check_resize(env1, (stats.ms_branch_pages + stats.ms_overflow_pages + stats.ms_leaf_pages) * stats.ms_psize);
dbr = mdb_txn_begin(env1, NULL, 0, &txn1);
if (dbr) throw std::runtime_error("Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
tx_active1 = true;
dbr = mdb_drop(txn1, dbi1, 0);
if (dbr) throw std::runtime_error("Failed to empty " + std::string(table) + " LMDB table: " + std::string(mdb_strerror(dbr)));
dbr = mdb_cursor_open(txn0, dbi0, &cur0);
if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr)));
dbr = mdb_cursor_open(txn1, dbi1, &cur1);
if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr)));
MDB_val k;
MDB_val v;
MDB_cursor_op op = MDB_FIRST;
size_t nrecords = 0, bytes = 0;
while (1)
{
int ret = mdb_cursor_get(cur0, &k, &v, op);
op = MDB_NEXT;
if (ret == MDB_NOTFOUND)
break;
if (ret)
throw std::runtime_error("Failed to enumerate " + std::string(table) + " records: " + std::string(mdb_strerror(ret)));
bytes += k.mv_size + v.mv_size;
if (resize_point(++nrecords, env1, &txn1, bytes))
{
dbr = mdb_cursor_open(txn1, dbi1, &cur1);
if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr)));
}
ret = mdb_cursor_put(cur1, &k, &v, putflags);
if (ret)
throw std::runtime_error("Failed to write " + std::string(table) + " record: " + std::string(mdb_strerror(ret)));
}
mdb_cursor_close(cur1);
mdb_cursor_close(cur0);
mdb_txn_commit(txn1);
tx_active1 = false;
mdb_txn_commit(txn0);
tx_active0 = false;
mdb_dbi_close(env1, dbi1);
mdb_dbi_close(env0, dbi0);
}
static bool is_v1_tx(MDB_cursor *c_txs_pruned, MDB_val *tx_id)
{
MDB_val v;
int ret = mdb_cursor_get(c_txs_pruned, tx_id, &v, MDB_SET);
if (ret)
throw std::runtime_error("Failed to find transaction pruned data: " + std::string(mdb_strerror(ret)));
if (v.mv_size == 0)
throw std::runtime_error("Invalid transaction pruned data");
return cryptonote::is_v1_tx(cryptonote::blobdata_ref{(const char*)v.mv_data, v.mv_size});
}
static void prune(MDB_env *env0, MDB_env *env1)
{
MDB_dbi dbi0_blocks, dbi0_txs_pruned, dbi0_txs_prunable, dbi0_tx_indices, dbi1_txs_prunable, dbi1_txs_prunable_tip, dbi1_properties;
MDB_txn *txn0, *txn1;
MDB_cursor *cur0_txs_pruned, *cur0_txs_prunable, *cur0_tx_indices, *cur1_txs_prunable, *cur1_txs_prunable_tip;
bool tx_active0 = false, tx_active1 = false;
int dbr;
MGINFO("Creating pruned txs_prunable");
epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){
if (tx_active1) mdb_txn_abort(txn1);
if (tx_active0) mdb_txn_abort(txn0);
});
dbr = mdb_txn_begin(env0, NULL, MDB_RDONLY, &txn0);
if (dbr) throw std::runtime_error("Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
tx_active0 = true;
dbr = mdb_txn_begin(env1, NULL, 0, &txn1);
if (dbr) throw std::runtime_error("Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
tx_active1 = true;
dbr = mdb_dbi_open(txn0, "txs_pruned", MDB_INTEGERKEY, &dbi0_txs_pruned);
if (dbr) throw std::runtime_error("Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
mdb_set_compare(txn0, dbi0_txs_pruned, BlockchainLMDB::compare_uint64);
dbr = mdb_cursor_open(txn0, dbi0_txs_pruned, &cur0_txs_pruned);
if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr)));
dbr = mdb_dbi_open(txn0, "txs_prunable", MDB_INTEGERKEY, &dbi0_txs_prunable);
if (dbr) throw std::runtime_error("Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
mdb_set_compare(txn0, dbi0_txs_prunable, BlockchainLMDB::compare_uint64);
dbr = mdb_cursor_open(txn0, dbi0_txs_prunable, &cur0_txs_prunable);
if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr)));
dbr = mdb_dbi_open(txn0, "tx_indices", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, &dbi0_tx_indices);
if (dbr) throw std::runtime_error("Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
mdb_set_dupsort(txn0, dbi0_tx_indices, BlockchainLMDB::compare_hash32);
dbr = mdb_cursor_open(txn0, dbi0_tx_indices, &cur0_tx_indices);
if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr)));
dbr = mdb_dbi_open(txn1, "txs_prunable", MDB_INTEGERKEY, &dbi1_txs_prunable);
if (dbr) throw std::runtime_error("Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
mdb_set_compare(txn1, dbi1_txs_prunable, BlockchainLMDB::compare_uint64);
dbr = mdb_cursor_open(txn1, dbi1_txs_prunable, &cur1_txs_prunable);
if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr)));
dbr = mdb_dbi_open(txn1, "txs_prunable_tip", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, &dbi1_txs_prunable_tip);
if (dbr) throw std::runtime_error("Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
mdb_set_dupsort(txn1, dbi1_txs_prunable_tip, BlockchainLMDB::compare_uint64);
dbr = mdb_cursor_open(txn1, dbi1_txs_prunable_tip, &cur1_txs_prunable_tip);
if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr)));
dbr = mdb_drop(txn1, dbi1_txs_prunable, 0);
if (dbr) throw std::runtime_error("Failed to empty LMDB table: " + std::string(mdb_strerror(dbr)));
dbr = mdb_drop(txn1, dbi1_txs_prunable_tip, 0);
if (dbr) throw std::runtime_error("Failed to empty LMDB table: " + std::string(mdb_strerror(dbr)));
dbr = mdb_dbi_open(txn1, "properties", 0, &dbi1_properties);
if (dbr) throw std::runtime_error("Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
MDB_val k, v;
uint32_t pruning_seed = tools::make_pruning_seed(tools::get_random_stripe(), CRYPTONOTE_PRUNING_LOG_STRIPES);
static char pruning_seed_key[] = "pruning_seed";
k.mv_data = pruning_seed_key;
k.mv_size = strlen("pruning_seed") + 1;
v.mv_data = (void*)&pruning_seed;
v.mv_size = sizeof(pruning_seed);
dbr = mdb_put(txn1, dbi1_properties, &k, &v, 0);
if (dbr) throw std::runtime_error("Failed to save pruning seed: " + std::string(mdb_strerror(dbr)));
MDB_stat stats;
dbr = mdb_dbi_open(txn0, "blocks", 0, &dbi0_blocks);
if (dbr) throw std::runtime_error("Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
dbr = mdb_stat(txn0, dbi0_blocks, &stats);
if (dbr) throw std::runtime_error("Failed to query size of blocks: " + std::string(mdb_strerror(dbr)));
mdb_dbi_close(env0, dbi0_blocks);
const uint64_t blockchain_height = stats.ms_entries;
size_t nrecords = 0, bytes = 0;
MDB_cursor_op op = MDB_FIRST;
while (1)
{
int ret = mdb_cursor_get(cur0_tx_indices, &k, &v, op);
op = MDB_NEXT;
if (ret == MDB_NOTFOUND)
break;
if (ret) throw std::runtime_error("Failed to enumerate records: " + std::string(mdb_strerror(ret)));
const txindex *ti = (const txindex*)v.mv_data;
const uint64_t block_height = ti->data.block_id;
MDB_val_set(kk, ti->data.tx_id);
if (block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS >= blockchain_height)
{
MDEBUG(block_height << "/" << blockchain_height << " is in tip");
MDB_val_set(vv, block_height);
dbr = mdb_cursor_put(cur1_txs_prunable_tip, &kk, &vv, 0);
if (dbr) throw std::runtime_error("Failed to write prunable tx tip data: " + std::string(mdb_strerror(dbr)));
bytes += kk.mv_size + vv.mv_size;
}
if (tools::has_unpruned_block(block_height, blockchain_height, pruning_seed) || is_v1_tx(cur0_txs_pruned, &kk))
{
MDB_val vv;
dbr = mdb_cursor_get(cur0_txs_prunable, &kk, &vv, MDB_SET);
if (dbr) throw std::runtime_error("Failed to read prunable tx data: " + std::string(mdb_strerror(dbr)));
bytes += kk.mv_size + vv.mv_size;
if (resize_point(++nrecords, env1, &txn1, bytes))
{
dbr = mdb_cursor_open(txn1, dbi1_txs_prunable, &cur1_txs_prunable);
if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr)));
dbr = mdb_cursor_open(txn1, dbi1_txs_prunable_tip, &cur1_txs_prunable_tip);
if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr)));
}
dbr = mdb_cursor_put(cur1_txs_prunable, &kk, &vv, 0);
if (dbr) throw std::runtime_error("Failed to write prunable tx data: " + std::string(mdb_strerror(dbr)));
}
else
{
MDEBUG("" << block_height << "/" << blockchain_height << " should be pruned, dropping");
}
}
mdb_cursor_close(cur1_txs_prunable_tip);
mdb_cursor_close(cur1_txs_prunable);
mdb_cursor_close(cur0_txs_prunable);
mdb_cursor_close(cur0_txs_pruned);
mdb_cursor_close(cur0_tx_indices);
mdb_txn_commit(txn1);
tx_active1 = false;
mdb_txn_commit(txn0);
tx_active0 = false;
mdb_dbi_close(env1, dbi1_properties);
mdb_dbi_close(env1, dbi1_txs_prunable_tip);
mdb_dbi_close(env1, dbi1_txs_prunable);
mdb_dbi_close(env0, dbi0_txs_prunable);
mdb_dbi_close(env0, dbi0_txs_pruned);
mdb_dbi_close(env0, dbi0_tx_indices);
}
static bool parse_db_sync_mode(std::string db_sync_mode, uint64_t &db_flags)
{
std::vector<std::string> options;
boost::trim(db_sync_mode);
boost::split(options, db_sync_mode, boost::is_any_of(" :"));
for(const auto &option : options)
MDEBUG("option: " << option);
// default to fast:async:1
uint64_t DEFAULT_FLAGS = DBF_FAST;
db_flags = 0;
if(options.size() == 0)
{
// default to fast:async:1
db_flags = DEFAULT_FLAGS;
}
bool safemode = false;
if(options.size() >= 1)
{
if(options[0] == "safe")
{
safemode = true;
db_flags = DBF_SAFE;
}
else if(options[0] == "fast")
{
db_flags = DBF_FAST;
}
else if(options[0] == "fastest")
{
db_flags = DBF_FASTEST;
records_per_sync = 1000; // default to fastest:async:1000
}
else
return false;
}
if(options.size() >= 2 && !safemode)
{
char *endptr;
uint64_t bps = strtoull(options[1].c_str(), &endptr, 0);
if (*endptr != '\0')
return false;
records_per_sync = bps;
}
return true;
}
int main(int argc, char* argv[])
{
TRY_ENTRY();
epee::string_tools::set_module_name_and_folder(argv[0]);
std::string default_db_type = "lmdb";
std::string available_dbs = cryptonote::blockchain_db_types(", ");
available_dbs = "available: " + available_dbs;
uint32_t log_level = 0;
tools::on_startup();
boost::filesystem::path output_file_path;
po::options_description desc_cmd_only("Command line options");
po::options_description desc_cmd_sett("Command line options and settings options");
const command_line::arg_descriptor<std::string> arg_log_level = {"log-level", "0-4 or categories", ""};
const command_line::arg_descriptor<std::string> arg_database = {
"database", available_dbs.c_str(), default_db_type
};
const command_line::arg_descriptor<std::string> arg_db_sync_mode = {
"db-sync-mode"
, "Specify sync option, using format [safe|fast|fastest]:[nrecords_per_sync]."
, "fast:1000"
};
const command_line::arg_descriptor<bool> arg_copy_pruned_database = {"copy-pruned-database", "Copy database anyway if already pruned"};
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_stagenet_on);
command_line::add_arg(desc_cmd_sett, arg_log_level);
command_line::add_arg(desc_cmd_sett, arg_database);
command_line::add_arg(desc_cmd_sett, arg_db_sync_mode);
command_line::add_arg(desc_cmd_sett, arg_copy_pruned_database);
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 << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL << ENDL;
std::cout << desc_options << std::endl;
return 1;
}
mlog_configure(mlog_get_default_log_path("monero-blockchain-prune.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());
MINFO("Starting...");
bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on);
bool opt_stagenet = command_line::get_arg(vm, cryptonote::arg_stagenet_on);
network_type net_type = opt_testnet ? TESTNET : opt_stagenet ? STAGENET : MAINNET;
bool opt_copy_pruned_database = command_line::get_arg(vm, arg_copy_pruned_database);
std::string data_dir = command_line::get_arg(vm, cryptonote::arg_data_dir);
while (boost::ends_with(data_dir, "/") || boost::ends_with(data_dir, "\\"))
data_dir.pop_back();
std::string db_type = command_line::get_arg(vm, arg_database);
if (!cryptonote::blockchain_valid_db_type(db_type))
{
MERROR("Invalid database type: " << db_type);
return 1;
}
if (db_type != "lmdb")
{
MERROR("Unsupported database type: " << db_type << ". Only lmdb is supported");
return 1;
}
std::string db_sync_mode = command_line::get_arg(vm, arg_db_sync_mode);
uint64_t db_flags = 0;
if (!parse_db_sync_mode(db_sync_mode, db_flags))
{
MERROR("Invalid db sync mode: " << db_sync_mode);
return 1;
}
// 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.
MINFO("Initializing source blockchain (BlockchainDB)");
std::array<std::unique_ptr<Blockchain>, 2> core_storage;
Blockchain *blockchain = NULL;
tx_memory_pool m_mempool(*blockchain);
boost::filesystem::path paths[2];
bool already_pruned = false;
for (size_t n = 0; n < core_storage.size(); ++n)
{
core_storage[n].reset(new Blockchain(m_mempool));
BlockchainDB* db = new_db(db_type);
if (db == NULL)
{
MERROR("Attempted to use non-existent database type: " << db_type);
throw std::runtime_error("Attempting to use non-existent database type");
}
MDEBUG("database: " << db_type);
if (n == 1)
{
paths[1] = boost::filesystem::path(data_dir) / (db->get_db_name() + "-pruned");
if (boost::filesystem::exists(paths[1]))
{
if (!boost::filesystem::is_directory(paths[1]))
{
MERROR("LMDB needs a directory path, but a file was passed: " << paths[1].string());
return 1;
}
}
else
{
if (!boost::filesystem::create_directories(paths[1]))
{
MERROR("Failed to create directory: " << paths[1].string());
return 1;
}
}
db_path = paths[1].string();
}
else
{
paths[0] = boost::filesystem::path(data_dir) / db->get_db_name();
}
MINFO("Loading blockchain from folder " << paths[n] << " ...");
try
{
db->open(paths[n].string(), n == 0 ? DBF_RDONLY : 0);
}
catch (const std::exception& e)
{
MERROR("Error opening database: " << e.what());
return 1;
}
r = core_storage[n]->init(db, net_type);
std::string source_dest = n == 0 ? "source" : "pruned";
CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize " << source_dest << " blockchain storage");
MINFO(source_dest << " blockchain storage initialized OK");
if (n == 0 && core_storage[0]->get_blockchain_pruning_seed())
{
if (!opt_copy_pruned_database)
{
MERROR("Blockchain is already pruned, use --" << arg_copy_pruned_database.name << " to copy it anyway");
return 1;
}
already_pruned = true;
}
}
core_storage[0]->deinit();
core_storage[0].reset(NULL);
core_storage[1]->deinit();
core_storage[1].reset(NULL);
MINFO("Pruning...");
MDB_env *env0 = NULL, *env1 = NULL;
open(env0, paths[0], db_flags, true);
open(env1, paths[1], db_flags, false);
copy_table(env0, env1, "blocks", MDB_INTEGERKEY, MDB_APPEND);
copy_table(env0, env1, "block_info", MDB_INTEGERKEY | MDB_DUPSORT| MDB_DUPFIXED, MDB_APPENDDUP, BlockchainLMDB::compare_uint64);
copy_table(env0, env1, "block_heights", MDB_INTEGERKEY | MDB_DUPSORT| MDB_DUPFIXED, 0, BlockchainLMDB::compare_hash32);
//copy_table(env0, env1, "txs", MDB_INTEGERKEY);
copy_table(env0, env1, "txs_pruned", MDB_INTEGERKEY, MDB_APPEND);
copy_table(env0, env1, "txs_prunable_hash", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, MDB_APPEND);
// not copied: prunable, prunable_tip
copy_table(env0, env1, "tx_indices", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, 0, BlockchainLMDB::compare_hash32);
copy_table(env0, env1, "tx_outputs", MDB_INTEGERKEY, MDB_APPEND);
copy_table(env0, env1, "output_txs", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, MDB_APPENDDUP, BlockchainLMDB::compare_uint64);
copy_table(env0, env1, "output_amounts", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, MDB_APPENDDUP, BlockchainLMDB::compare_uint64);
copy_table(env0, env1, "spent_keys", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, MDB_NODUPDATA, BlockchainLMDB::compare_hash32);
copy_table(env0, env1, "txpool_meta", 0, MDB_NODUPDATA, BlockchainLMDB::compare_hash32);
copy_table(env0, env1, "txpool_blob", 0, MDB_NODUPDATA, BlockchainLMDB::compare_hash32);
copy_table(env0, env1, "hf_versions", MDB_INTEGERKEY, MDB_APPEND);
copy_table(env0, env1, "properties", 0, 0, BlockchainLMDB::compare_string);
if (already_pruned)
{
copy_table(env0, env1, "txs_prunable", MDB_INTEGERKEY, MDB_APPEND, BlockchainLMDB::compare_uint64);
copy_table(env0, env1, "txs_prunable_tip", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, MDB_NODUPDATA, BlockchainLMDB::compare_uint64);
}
else
{
prune(env0, env1);
}
close(env1);
close(env0);
MINFO("Swapping databases, pre-pruning blockchain will be left in " << paths[0].string() + "-old and can be removed if desired");
if (replace_file(paths[0].string(), paths[0].string() + "-old") || replace_file(paths[1].string(), paths[0].string()))
{
MERROR("Blockchain pruned OK, but renaming failed");
return 1;
}
MINFO("Blockchain pruned OK");
return 0;
CATCH_ENTRY("Pruning error", 1);
}

@ -40,6 +40,7 @@ set(common_sources
notify.cpp notify.cpp
password.cpp password.cpp
perf_timer.cpp perf_timer.cpp
pruning.cpp
spawn.cpp spawn.cpp
threadpool.cpp threadpool.cpp
updates.cpp updates.cpp
@ -69,6 +70,7 @@ set(common_private_headers
http_connection.h http_connection.h
notify.h notify.h
pod-class.h pod-class.h
pruning.h
rpc_client.h rpc_client.h
scoped_message_writer.h scoped_message_writer.h
unordered_containers_boost_serialization.h unordered_containers_boost_serialization.h

@ -0,0 +1,116 @@
// Copyright (c) 2018, 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
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "cryptonote_config.h"
#include "misc_log_ex.h"
#include "crypto/crypto.h"
#include "pruning.h"
namespace tools
{
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;
}
uint32_t get_pruning_stripe(uint64_t block_height, uint64_t blockchain_height, uint32_t log_stripes)
{
if (block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS >= blockchain_height)
return 0;
return ((block_height / CRYPTONOTE_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);
}
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 <= CRYPTONOTE_MAX_BLOCK_NUMBER+1, block_height, "block_height too large");
CHECK_AND_ASSERT_MES(blockchain_height <= CRYPTONOTE_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 + CRYPTONOTE_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 : CRYPTONOTE_PRUNING_LOG_STRIPES;
const uint64_t mask = (1ul << log_stripes) - 1;
const uint32_t block_pruning_stripe = ((block_height / CRYPTONOTE_PRUNING_STRIPE_SIZE) & mask) + 1;
if (block_pruning_stripe == stripe)
return block_height;
const uint64_t cycles = ((block_height / CRYPTONOTE_PRUNING_STRIPE_SIZE) >> log_stripes);
const uint64_t cycle_start = cycles + ((stripe > block_pruning_stripe) ? 0 : 1);
const uint64_t h = cycle_start * (CRYPTONOTE_PRUNING_STRIPE_SIZE << log_stripes) + (stripe - 1) * CRYPTONOTE_PRUNING_STRIPE_SIZE;
if (h + CRYPTONOTE_PRUNING_TIP_BLOCKS > blockchain_height)
return blockchain_height < CRYPTONOTE_PRUNING_TIP_BLOCKS ? 0 : blockchain_height - CRYPTONOTE_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 + CRYPTONOTE_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 : CRYPTONOTE_PRUNING_LOG_STRIPES;
const uint64_t mask = (1ul << log_stripes) - 1;
const uint32_t block_pruning_seed = ((block_height / CRYPTONOTE_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 << CRYPTONOTE_PRUNING_LOG_STRIPES);
}
}

@ -0,0 +1,52 @@
// Copyright (c) 2018, 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
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <stdint.h>
namespace tools
{
static constexpr uint32_t PRUNING_SEED_LOG_STRIPES_SHIFT = 7;
static constexpr uint32_t PRUNING_SEED_LOG_STRIPES_MASK = 0x7;
static constexpr uint32_t PRUNING_SEED_STRIPE_SHIFT = 0;
static constexpr uint32_t PRUNING_SEED_STRIPE_MASK = 0x7f;
constexpr inline uint32_t get_pruning_log_stripes(uint32_t pruning_seed) { return (pruning_seed >> PRUNING_SEED_LOG_STRIPES_SHIFT) & PRUNING_SEED_LOG_STRIPES_MASK; }
inline uint32_t get_pruning_stripe(uint32_t pruning_seed) { if (pruning_seed == 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();
}

@ -123,6 +123,6 @@ namespace tools {
*/ */
template<typename InputIt, typename T> template<typename InputIt, typename T>
int read_varint(InputIt &&first, InputIt &&last, T &i) { int read_varint(InputIt &&first, InputIt &&last, T &i) {
return read_varint<std::numeric_limits<T>::digits, InputIt, T>(std::move(first), std::move(last), i); return read_varint<std::numeric_limits<T>::digits>(std::forward<InputIt>(first), std::forward<InputIt>(last), i);
} }
} }

@ -30,7 +30,11 @@
#pragma once #pragma once
#include <string>
#include "span.h"
namespace cryptonote namespace cryptonote
{ {
typedef std::string blobdata; typedef std::string blobdata;
typedef epee::span<const char> blobdata_ref;
} }

@ -40,7 +40,7 @@ namespace cryptonote
struct cryptonote_connection_context: public epee::net_utils::connection_context_base struct cryptonote_connection_context: public epee::net_utils::connection_context_base
{ {
cryptonote_connection_context(): m_state(state_before_handshake), m_remote_blockchain_height(0), m_last_response_height(0), cryptonote_connection_context(): m_state(state_before_handshake), m_remote_blockchain_height(0), m_last_response_height(0),
m_last_request_time(boost::posix_time::microsec_clock::universal_time()), m_callback_request_count(0), m_last_known_hash(crypto::null_hash) {} m_last_request_time(boost::date_time::not_a_date_time), m_callback_request_count(0), m_last_known_hash(crypto::null_hash), m_pruning_seed(0), m_anchor(false) {}
enum state enum state
{ {
@ -59,6 +59,8 @@ namespace cryptonote
boost::posix_time::ptime m_last_request_time; boost::posix_time::ptime m_last_request_time;
epee::copyable_atomic m_callback_request_count; //in debug purpose: problem with double callback rise epee::copyable_atomic m_callback_request_count; //in debug purpose: problem with double callback rise
crypto::hash m_last_known_hash; crypto::hash m_last_known_hash;
uint32_t m_pruning_seed;
bool m_anchor;
//size_t m_score; TODO: add score calculations //size_t m_score; TODO: add score calculations
}; };
@ -81,4 +83,23 @@ namespace cryptonote
} }
} }
inline char get_protocol_state_char(cryptonote_connection_context::state s)
{
switch (s)
{
case cryptonote_connection_context::state_before_handshake:
return 'h';
case cryptonote_connection_context::state_synchronizing:
return 's';
case cryptonote_connection_context::state_standby:
return 'w';
case cryptonote_connection_context::state_idle:
return 'i';
case cryptonote_connection_context::state_normal:
return 'n';
default:
return 'u';
}
}
} }

@ -201,9 +201,11 @@ namespace cryptonote
mutable crypto::hash hash; mutable crypto::hash hash;
mutable size_t blob_size; mutable size_t blob_size;
bool pruned;
transaction(); transaction();
transaction(const transaction &t): transaction_prefix(t), hash_valid(false), blob_size_valid(false), signatures(t.signatures), rct_signatures(t.rct_signatures) { if (t.is_hash_valid()) { hash = t.hash; set_hash_valid(true); } if (t.is_blob_size_valid()) { blob_size = t.blob_size; set_blob_size_valid(true); } } transaction(const transaction &t): transaction_prefix(t), hash_valid(false), blob_size_valid(false), signatures(t.signatures), rct_signatures(t.rct_signatures), pruned(t.pruned) { if (t.is_hash_valid()) { hash = t.hash; set_hash_valid(true); } if (t.is_blob_size_valid()) { blob_size = t.blob_size; set_blob_size_valid(true); } }
transaction &operator=(const transaction &t) { transaction_prefix::operator=(t); set_hash_valid(false); set_blob_size_valid(false); signatures = t.signatures; rct_signatures = t.rct_signatures; if (t.is_hash_valid()) { hash = t.hash; set_hash_valid(true); } if (t.is_blob_size_valid()) { blob_size = t.blob_size; set_blob_size_valid(true); } return *this; } transaction &operator=(const transaction &t) { transaction_prefix::operator=(t); set_hash_valid(false); set_blob_size_valid(false); signatures = t.signatures; rct_signatures = t.rct_signatures; if (t.is_hash_valid()) { hash = t.hash; set_hash_valid(true); } if (t.is_blob_size_valid()) { blob_size = t.blob_size; set_blob_size_valid(true); } pruned = t.pruned; return *this; }
virtual ~transaction(); virtual ~transaction();
void set_null(); void set_null();
void invalidate_hashes(); void invalidate_hashes();
@ -232,7 +234,7 @@ namespace cryptonote
if (!signatures_not_expected && vin.size() != signatures.size()) if (!signatures_not_expected && vin.size() != signatures.size())
return false; return false;
for (size_t i = 0; i < vin.size(); ++i) if (!pruned) for (size_t i = 0; i < vin.size(); ++i)
{ {
size_t signature_size = get_signature_size(vin[i]); size_t signature_size = get_signature_size(vin[i]);
if (signatures_not_expected) if (signatures_not_expected)
@ -263,7 +265,7 @@ namespace cryptonote
bool r = rct_signatures.serialize_rctsig_base(ar, vin.size(), vout.size()); bool r = rct_signatures.serialize_rctsig_base(ar, vin.size(), vout.size());
if (!r || !ar.stream().good()) return false; if (!r || !ar.stream().good()) return false;
ar.end_object(); ar.end_object();
if (rct_signatures.type != rct::RCTTypeNull) if (!pruned && rct_signatures.type != rct::RCTTypeNull)
{ {
ar.tag("rctsig_prunable"); ar.tag("rctsig_prunable");
ar.begin_object(); ar.begin_object();
@ -274,6 +276,8 @@ namespace cryptonote
} }
} }
} }
if (!typename Archive<W>::is_saving())
pruned = false;
END_SERIALIZE() END_SERIALIZE()
template<bool W, template <bool> class Archive> template<bool W, template <bool> class Archive>
@ -295,6 +299,8 @@ namespace cryptonote
ar.end_object(); ar.end_object();
} }
} }
if (!typename Archive<W>::is_saving())
pruned = true;
return true; return true;
} }
@ -322,6 +328,7 @@ namespace cryptonote
rct_signatures.type = rct::RCTTypeNull; rct_signatures.type = rct::RCTTypeNull;
set_hash_valid(false); set_hash_valid(false);
set_blob_size_valid(false); set_blob_size_valid(false);
pruned = false;
} }
inline inline

@ -196,6 +196,7 @@ namespace cryptonote
bool r = tx.serialize_base(ba); bool r = tx.serialize_base(ba);
CHECK_AND_ASSERT_MES(r, false, "Failed to parse transaction from blob"); CHECK_AND_ASSERT_MES(r, false, "Failed to parse transaction from blob");
CHECK_AND_ASSERT_MES(expand_transaction_1(tx, true), false, "Failed to expand transaction data"); CHECK_AND_ASSERT_MES(expand_transaction_1(tx, true), false, "Failed to expand transaction data");
tx.invalidate_hashes();
return true; return true;
} }
//--------------------------------------------------------------- //---------------------------------------------------------------
@ -225,6 +226,22 @@ namespace cryptonote
return true; return true;
} }
//--------------------------------------------------------------- //---------------------------------------------------------------
bool is_v1_tx(const blobdata_ref& tx_blob)
{
uint64_t version;
const char* begin = static_cast<const char*>(tx_blob.data());
const char* end = begin + tx_blob.size();
int read = tools::read_varint(begin, end, version);
if (read <= 0)
throw std::runtime_error("Internal error getting transaction version");
return version <= 1;
}
//---------------------------------------------------------------
bool is_v1_tx(const blobdata& tx_blob)
{
return is_v1_tx(blobdata_ref{tx_blob.data(), tx_blob.size()});
}
//---------------------------------------------------------------
bool generate_key_image_helper(const account_keys& ack, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key& tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki, hw::device &hwdev) bool generate_key_image_helper(const account_keys& ack, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key& tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki, hw::device &hwdev)
{ {
crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation);

@ -53,6 +53,8 @@ namespace cryptonote
bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash); bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash);
bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx); bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx);
bool parse_and_validate_tx_base_from_blob(const blobdata& tx_blob, transaction& tx); bool parse_and_validate_tx_base_from_blob(const blobdata& tx_blob, transaction& tx);
bool is_v1_tx(const blobdata_ref& tx_blob);
bool is_v1_tx(const blobdata& tx_blob);
template<typename T> template<typename T>
bool find_tx_extra_field_by_type(const std::vector<tx_extra_field>& tx_extra_fields, T& field, size_t index = 0) bool find_tx_extra_field_by_type(const std::vector<tx_extra_field>& tx_extra_fields, T& field, size_t index = 0)

@ -150,6 +150,11 @@
#define BULLETPROOF_MAX_OUTPUTS 16 #define BULLETPROOF_MAX_OUTPUTS 16
#define CRYPTONOTE_PRUNING_STRIPE_SIZE 4096 // the smaller, the smoother the increase
#define CRYPTONOTE_PRUNING_LOG_STRIPES 3 // the higher, the more space saved
#define CRYPTONOTE_PRUNING_TIP_BLOCKS 5500 // the smaller, the more space saved
//#define CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED
// New constants are intended to go here // New constants are intended to go here
namespace config namespace config
{ {

@ -53,6 +53,8 @@
#include "ringct/rctSigs.h" #include "ringct/rctSigs.h"
#include "common/perf_timer.h" #include "common/perf_timer.h"
#include "common/notify.h" #include "common/notify.h"
#include "common/varint.h"
#include "common/pruning.h"
#undef MONERO_DEFAULT_LOG_CATEGORY #undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "blockchain" #define MONERO_DEFAULT_LOG_CATEGORY "blockchain"
@ -646,8 +648,14 @@ block Blockchain::pop_block_from_blockchain()
m_hardfork->on_block_popped(1); m_hardfork->on_block_popped(1);
// return transactions from popped block to the tx_pool // return transactions from popped block to the tx_pool
size_t pruned = 0;
for (transaction& tx : popped_txs) for (transaction& tx : popped_txs)
{ {
if (tx.pruned)
{
++pruned;
continue;
}
if (!is_coinbase(tx)) if (!is_coinbase(tx))
{ {
cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc);
@ -669,6 +677,8 @@ block Blockchain::pop_block_from_blockchain()
} }
} }
} }
if (pruned)
MWARNING(pruned << " pruned txes could not be added back to the txpool");
m_blocks_longhash_table.clear(); m_blocks_longhash_table.clear();
m_scan_table.clear(); m_scan_table.clear();
@ -2044,6 +2054,51 @@ bool Blockchain::get_transactions_blobs(const t_ids_container& txs_ids, t_tx_con
return true; return true;
} }
//------------------------------------------------------------------ //------------------------------------------------------------------
size_t get_transaction_version(const cryptonote::blobdata &bd)
{
size_t version;
const char* begin = static_cast<const char*>(bd.data());
const char* end = begin + bd.size();
int read = tools::read_varint(begin, end, version);
if (read <= 0)
throw std::runtime_error("Internal error getting transaction version");
return version;
}
//------------------------------------------------------------------
template<class t_ids_container, class t_tx_container, class t_missed_container>
bool Blockchain::get_split_transactions_blobs(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
reserve_container(txs, txs_ids.size());
for (const auto& tx_hash : txs_ids)
{
try
{
cryptonote::blobdata tx;
if (m_db->get_pruned_tx_blob(tx_hash, tx))
{
txs.push_back(std::make_tuple(tx_hash, std::move(tx), crypto::null_hash, cryptonote::blobdata()));
if (!is_v1_tx(std::get<1>(txs.back())) && !m_db->get_prunable_tx_hash(tx_hash, std::get<2>(txs.back())))
{
MERROR("Prunable data hash not found for " << tx_hash);
return false;
}
if (!m_db->get_prunable_tx_blob(tx_hash, std::get<3>(txs.back())))
std::get<3>(txs.back()).clear();
}
else
missed_txs.push_back(tx_hash);
}
catch (const std::exception& e)
{
return false;
}
}
return true;
}
//------------------------------------------------------------------
template<class t_ids_container, class t_tx_container, class t_missed_container> template<class t_ids_container, class t_tx_container, class t_missed_container>
bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const
{ {
@ -2092,9 +2147,12 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc
m_db->block_txn_start(true); m_db->block_txn_start(true);
current_height = get_current_blockchain_height(); current_height = get_current_blockchain_height();
const uint32_t pruning_seed = get_blockchain_pruning_seed();
start_height = tools::get_next_unpruned_block_height(start_height, current_height, pruning_seed);
uint64_t stop_height = tools::get_next_pruned_block_height(start_height, current_height, pruning_seed);
size_t count = 0; size_t count = 0;
hashes.reserve(std::max((size_t)(current_height - start_height), (size_t)BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT)); hashes.reserve(std::min((size_t)(stop_height - start_height), (size_t)BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT));
for(size_t i = start_height; i < current_height && count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT; i++, count++) for(size_t i = start_height; i < stop_height && count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT; i++, count++)
{ {
hashes.push_back(m_db->get_block_hash_from_height(i)); hashes.push_back(m_db->get_block_hash_from_height(i));
} }
@ -3369,7 +3427,7 @@ leave:
{ {
if (memcmp(&hash, &expected_hash, sizeof(hash)) != 0) if (memcmp(&hash, &expected_hash, sizeof(hash)) != 0)
{ {
MERROR_VER("Block with id is INVALID: " << id); MERROR_VER("Block with id is INVALID: " << id << ", expected " << expected_hash);
bvc.m_verifivation_failed = true; bvc.m_verifivation_failed = true;
goto leave; goto leave;
} }
@ -3635,6 +3693,35 @@ leave:
return true; return true;
} }
//------------------------------------------------------------------ //------------------------------------------------------------------
bool Blockchain::prune_blockchain(uint32_t pruning_seed)
{
uint8_t hf_version = m_hardfork->get_current_version();
if (hf_version < 10)
{
MERROR("Most of the network will only be ready for pruned blockchains from v10, not pruning");
return false;
}
return m_db->prune_blockchain(pruning_seed);
}
//------------------------------------------------------------------
bool Blockchain::update_blockchain_pruning()
{
m_tx_pool.lock();
epee::misc_utils::auto_scope_leave_caller unlocker = epee::misc_utils::create_scope_leave_handler([&](){m_tx_pool.unlock();});
CRITICAL_REGION_LOCAL(m_blockchain_lock);
return m_db->update_pruning();
}
//------------------------------------------------------------------
bool Blockchain::check_blockchain_pruning()
{
m_tx_pool.lock();
epee::misc_utils::auto_scope_leave_caller unlocker = epee::misc_utils::create_scope_leave_handler([&](){m_tx_pool.unlock();});
CRITICAL_REGION_LOCAL(m_blockchain_lock);
return m_db->check_pruning();
}
//------------------------------------------------------------------
bool Blockchain::update_next_cumulative_weight_limit() bool Blockchain::update_next_cumulative_weight_limit()
{ {
uint64_t full_reward_zone = get_min_block_weight(get_current_hard_fork_version()); uint64_t full_reward_zone = get_min_block_weight(get_current_hard_fork_version());
@ -3848,6 +3935,8 @@ bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync)
CRITICAL_REGION_END(); CRITICAL_REGION_END();
m_tx_pool.unlock(); m_tx_pool.unlock();
update_blockchain_pruning();
return success; return success;
} }
@ -4621,4 +4710,5 @@ void Blockchain::cache_block_template(const block &b, const cryptonote::account_
namespace cryptonote { namespace cryptonote {
template bool Blockchain::get_transactions(const std::vector<crypto::hash>&, std::vector<transaction>&, std::vector<crypto::hash>&) const; template bool Blockchain::get_transactions(const std::vector<crypto::hash>&, std::vector<transaction>&, std::vector<crypto::hash>&) const;
template bool Blockchain::get_transactions_blobs(const std::vector<crypto::hash>&, std::vector<cryptonote::blobdata>&, std::vector<crypto::hash>&, bool) const; template bool Blockchain::get_transactions_blobs(const std::vector<crypto::hash>&, std::vector<cryptonote::blobdata>&, std::vector<crypto::hash>&, bool) const;
template bool Blockchain::get_split_transactions_blobs(const std::vector<crypto::hash>&, std::vector<std::tuple<crypto::hash, cryptonote::blobdata, crypto::hash, cryptonote::blobdata>>&, std::vector<crypto::hash>&) const;
} }

@ -677,6 +677,8 @@ namespace cryptonote
template<class t_ids_container, class t_tx_container, class t_missed_container> template<class t_ids_container, class t_tx_container, class t_missed_container>
bool get_transactions_blobs(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs, bool pruned = false) const; bool get_transactions_blobs(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs, bool pruned = false) const;
template<class t_ids_container, class t_tx_container, class t_missed_container> template<class t_ids_container, class t_tx_container, class t_missed_container>
bool get_split_transactions_blobs(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const;
template<class t_ids_container, class t_tx_container, class t_missed_container>
bool get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const; bool get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const;
//debug functions //debug functions
@ -956,6 +958,10 @@ namespace cryptonote
bool is_within_compiled_block_hash_area(uint64_t height) const; bool is_within_compiled_block_hash_area(uint64_t height) const;
bool is_within_compiled_block_hash_area() const { return is_within_compiled_block_hash_area(m_db->height()); } bool is_within_compiled_block_hash_area() const { return is_within_compiled_block_hash_area(m_db->height()); }
uint64_t prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes); uint64_t prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes);
uint32_t get_blockchain_pruning_seed() const { return m_db->get_blockchain_pruning_seed(); }
bool prune_blockchain(uint32_t pruning_seed = 0);
bool update_blockchain_pruning();
bool check_blockchain_pruning();
void lock(); void lock();
void unlock(); void unlock();

@ -105,6 +105,11 @@ namespace cryptonote
"disable-dns-checkpoints" "disable-dns-checkpoints"
, "Do not retrieve checkpoints from DNS" , "Do not retrieve checkpoints from DNS"
}; };
const command_line::arg_descriptor<size_t> arg_block_download_max_size = {
"block-download-max-size"
, "Set maximum size of block download queue in bytes (0 for default)"
, 0
};
static const command_line::arg_descriptor<bool> arg_test_drop_download = { static const command_line::arg_descriptor<bool> arg_test_drop_download = {
"test-drop-download" "test-drop-download"
@ -175,6 +180,11 @@ namespace cryptonote
, "Run a program for each new block, '%s' will be replaced by the block hash" , "Run a program for each new block, '%s' will be replaced by the block hash"
, "" , ""
}; };
static const command_line::arg_descriptor<bool> arg_prune_blockchain = {
"prune-blockchain"
, "Prune blockchain"
, false
};
//----------------------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------------------
core::core(i_cryptonote_protocol* pprotocol): core::core(i_cryptonote_protocol* pprotocol):
@ -285,9 +295,11 @@ namespace cryptonote
command_line::add_arg(desc, arg_test_dbg_lock_sleep); command_line::add_arg(desc, arg_test_dbg_lock_sleep);
command_line::add_arg(desc, arg_offline); command_line::add_arg(desc, arg_offline);
command_line::add_arg(desc, arg_disable_dns_checkpoints); command_line::add_arg(desc, arg_disable_dns_checkpoints);
command_line::add_arg(desc, arg_block_download_max_size);
command_line::add_arg(desc, arg_max_txpool_weight); command_line::add_arg(desc, arg_max_txpool_weight);
command_line::add_arg(desc, arg_pad_transactions); command_line::add_arg(desc, arg_pad_transactions);
command_line::add_arg(desc, arg_block_notify); command_line::add_arg(desc, arg_block_notify);
command_line::add_arg(desc, arg_prune_blockchain);
miner::init_options(desc); miner::init_options(desc);
BlockchainDB::init_options(desc); BlockchainDB::init_options(desc);
@ -374,6 +386,11 @@ namespace cryptonote
return m_blockchain_storage.get_transactions_blobs(txs_ids, txs, missed_txs); return m_blockchain_storage.get_transactions_blobs(txs_ids, txs, missed_txs);
} }
//----------------------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------------------
bool core::get_split_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<std::tuple<crypto::hash, cryptonote::blobdata, crypto::hash, cryptonote::blobdata>>& txs, std::vector<crypto::hash>& missed_txs) const
{
return m_blockchain_storage.get_split_transactions_blobs(txs_ids, txs, missed_txs);
}
//-----------------------------------------------------------------------------------------------
bool core::get_txpool_backlog(std::vector<tx_backlog_entry>& backlog) const bool core::get_txpool_backlog(std::vector<tx_backlog_entry>& backlog) const
{ {
m_mempool.get_transaction_backlog(backlog); m_mempool.get_transaction_backlog(backlog);
@ -413,6 +430,7 @@ namespace cryptonote
uint64_t blocks_threads = command_line::get_arg(vm, arg_prep_blocks_threads); uint64_t blocks_threads = command_line::get_arg(vm, arg_prep_blocks_threads);
std::string check_updates_string = command_line::get_arg(vm, arg_check_updates); std::string check_updates_string = command_line::get_arg(vm, arg_check_updates);
size_t max_txpool_weight = command_line::get_arg(vm, arg_max_txpool_weight); size_t max_txpool_weight = command_line::get_arg(vm, arg_max_txpool_weight);
bool prune_blockchain = command_line::get_arg(vm, arg_prune_blockchain);
boost::filesystem::path folder(m_config_folder); boost::filesystem::path folder(m_config_folder);
if (m_nettype == FAKECHAIN) if (m_nettype == FAKECHAIN)
@ -607,6 +625,14 @@ namespace cryptonote
r = m_miner.init(vm, m_nettype); r = m_miner.init(vm, m_nettype);
CHECK_AND_ASSERT_MES(r, false, "Failed to initialize miner instance"); CHECK_AND_ASSERT_MES(r, false, "Failed to initialize miner instance");
if (prune_blockchain)
{
// display a message if the blockchain is not pruned yet
if (m_blockchain_storage.get_current_blockchain_height() > 1 && !m_blockchain_storage.get_blockchain_pruning_seed())
MGINFO("Pruning blockchain...");
CHECK_AND_ASSERT_MES(m_blockchain_storage.prune_blockchain(), false, "Failed to prune blockchain");
}
return load_state_data(); return load_state_data();
} }
//----------------------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------------------
@ -1501,6 +1527,7 @@ namespace cryptonote
m_check_updates_interval.do_call(boost::bind(&core::check_updates, this)); m_check_updates_interval.do_call(boost::bind(&core::check_updates, this));
m_check_disk_space_interval.do_call(boost::bind(&core::check_disk_space, this)); m_check_disk_space_interval.do_call(boost::bind(&core::check_disk_space, this));
m_block_rate_interval.do_call(boost::bind(&core::check_block_rate, this)); m_block_rate_interval.do_call(boost::bind(&core::check_block_rate, this));
m_blockchain_pruning_interval.do_call(boost::bind(&core::update_blockchain_pruning, this));
m_miner.on_idle(); m_miner.on_idle();
m_mempool.on_idle(); m_mempool.on_idle();
return true; return true;
@ -1736,6 +1763,16 @@ namespace cryptonote
return true; return true;
} }
//----------------------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------------------
bool core::update_blockchain_pruning()
{
return m_blockchain_storage.update_blockchain_pruning();
}
//-----------------------------------------------------------------------------------------------
bool core::check_blockchain_pruning()
{
return m_blockchain_storage.check_blockchain_pruning();
}
//-----------------------------------------------------------------------------------------------
void core::set_target_blockchain_height(uint64_t target_blockchain_height) void core::set_target_blockchain_height(uint64_t target_blockchain_height)
{ {
m_target_blockchain_height = target_blockchain_height; m_target_blockchain_height = target_blockchain_height;
@ -1758,6 +1795,16 @@ namespace cryptonote
return si.available; return si.available;
} }
//----------------------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------------------
uint32_t core::get_blockchain_pruning_seed() const
{
return get_blockchain_storage().get_blockchain_pruning_seed();
}
//-----------------------------------------------------------------------------------------------
bool core::prune_blockchain(uint32_t pruning_seed)
{
return get_blockchain_storage().prune_blockchain(pruning_seed);
}
//-----------------------------------------------------------------------------------------------
std::time_t core::get_start_time() const std::time_t core::get_start_time() const
{ {
return start_time; return start_time;

@ -62,6 +62,7 @@ namespace cryptonote
extern const command_line::arg_descriptor<bool, false> arg_regtest_on; extern const command_line::arg_descriptor<bool, false> arg_regtest_on;
extern const command_line::arg_descriptor<difficulty_type> arg_fixed_difficulty; extern const command_line::arg_descriptor<difficulty_type> arg_fixed_difficulty;
extern const command_line::arg_descriptor<bool> arg_offline; extern const command_line::arg_descriptor<bool> arg_offline;
extern const command_line::arg_descriptor<size_t> arg_block_download_max_size;
/************************************************************************/ /************************************************************************/
/* */ /* */
@ -354,6 +355,13 @@ namespace cryptonote
*/ */
bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<cryptonote::blobdata>& txs, std::vector<crypto::hash>& missed_txs) const; bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<cryptonote::blobdata>& txs, std::vector<crypto::hash>& missed_txs) const;
/**
* @copydoc Blockchain::get_transactions
*
* @note see Blockchain::get_transactions
*/
bool get_split_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<std::tuple<crypto::hash, cryptonote::blobdata, crypto::hash, cryptonote::blobdata>>& txs, std::vector<crypto::hash>& missed_txs) const;
/** /**
* @copydoc Blockchain::get_transactions * @copydoc Blockchain::get_transactions
* *
@ -783,6 +791,36 @@ namespace cryptonote
*/ */
bool offline() const { return m_offline; } bool offline() const { return m_offline; }
/**
* @brief get the blockchain pruning seed
*
* @return the blockchain pruning seed
*/
uint32_t get_blockchain_pruning_seed() const;
/**
* @brief prune the blockchain
*
* @param pruning_seed the seed to use to prune the chain (0 for default, highly recommended)
*
* @return true iff success
*/
bool prune_blockchain(uint32_t pruning_seed = 0);
/**
* @brief incrementally prunes blockchain
*
* @return true on success, false otherwise
*/
bool update_blockchain_pruning();
/**
* @brief checks the blockchain pruning if enabled
*
* @return true on success, false otherwise
*/
bool check_blockchain_pruning();
private: private:
/** /**
@ -985,6 +1023,7 @@ namespace cryptonote
epee::math_helper::once_a_time_seconds<60*60*12, true> m_check_updates_interval; //!< interval for checking for new versions epee::math_helper::once_a_time_seconds<60*60*12, true> m_check_updates_interval; //!< interval for checking for new versions
epee::math_helper::once_a_time_seconds<60*10, true> m_check_disk_space_interval; //!< interval for checking for disk space epee::math_helper::once_a_time_seconds<60*10, true> m_check_disk_space_interval; //!< interval for checking for disk space
epee::math_helper::once_a_time_seconds<90, false> m_block_rate_interval; //!< interval for checking block rate epee::math_helper::once_a_time_seconds<90, false> m_block_rate_interval; //!< interval for checking block rate
epee::math_helper::once_a_time_seconds<60*60*5, true> m_blockchain_pruning_interval; //!< interval for incremental blockchain pruning
std::atomic<bool> m_starter_message_showed; //!< has the "daemon will sync now" message been shown? std::atomic<bool> m_starter_message_showed; //!< has the "daemon will sync now" message been shown?

@ -34,6 +34,7 @@
#include <boost/uuid/uuid_io.hpp> #include <boost/uuid/uuid_io.hpp>
#include "string_tools.h" #include "string_tools.h"
#include "cryptonote_protocol_defs.h" #include "cryptonote_protocol_defs.h"
#include "common/pruning.h"
#include "block_queue.h" #include "block_queue.h"
#undef MONERO_DEFAULT_LOG_CATEGORY #undef MONERO_DEFAULT_LOG_CATEGORY
@ -60,7 +61,10 @@ void block_queue::add_blocks(uint64_t height, std::vector<cryptonote::block_comp
if (has_hashes) if (has_hashes)
{ {
for (const crypto::hash &h: hashes) for (const crypto::hash &h: hashes)
{
requested_hashes.insert(h); requested_hashes.insert(h);
have_blocks.insert(h);
}
set_span_hashes(height, connection_id, hashes); set_span_hashes(height, connection_id, hashes);
} }
} }
@ -90,7 +94,10 @@ void block_queue::erase_block(block_map::iterator j)
{ {
CHECK_AND_ASSERT_THROW_MES(j != blocks.end(), "Invalid iterator"); CHECK_AND_ASSERT_THROW_MES(j != blocks.end(), "Invalid iterator");
for (const crypto::hash &h: j->hashes) for (const crypto::hash &h: j->hashes)
{
requested_hashes.erase(h); requested_hashes.erase(h);
have_blocks.erase(h);
}
blocks.erase(j); blocks.erase(j);
} }
@ -98,12 +105,10 @@ void block_queue::flush_stale_spans(const std::set<boost::uuids::uuid> &live_con
{ {
boost::unique_lock<boost::recursive_mutex> lock(mutex); boost::unique_lock<boost::recursive_mutex> lock(mutex);
block_map::iterator i = blocks.begin(); block_map::iterator i = blocks.begin();
if (i != blocks.end() && is_blockchain_placeholder(*i))
++i;
while (i != blocks.end()) while (i != blocks.end())
{ {
block_map::iterator j = i++; block_map::iterator j = i++;
if (live_connections.find(j->connection_id) == live_connections.end() && j->blocks.size() == 0) if (j->blocks.empty() && live_connections.find(j->connection_id) == live_connections.end())
{ {
erase_block(j); erase_block(j);
} }
@ -152,23 +157,56 @@ uint64_t block_queue::get_max_block_height() const
return height; return height;
} }
uint64_t block_queue::get_next_needed_height(uint64_t blockchain_height) const
{
boost::unique_lock<boost::recursive_mutex> lock(mutex);
if (blocks.empty())
return blockchain_height;
uint64_t last_needed_height = blockchain_height;
bool first = true;
for (const auto &span: blocks)
{
if (span.start_block_height + span.nblocks - 1 < blockchain_height)
continue;
if (span.start_block_height != last_needed_height || (first && span.blocks.empty()))
return last_needed_height;
last_needed_height = span.start_block_height + span.nblocks;
first = false;
}
return last_needed_height;
}
void block_queue::print() const void block_queue::print() const
{ {
boost::unique_lock<boost::recursive_mutex> lock(mutex); boost::unique_lock<boost::recursive_mutex> lock(mutex);
MDEBUG("Block queue has " << blocks.size() << " spans"); MDEBUG("Block queue has " << blocks.size() << " spans");
for (const auto &span: blocks) for (const auto &span: blocks)
MDEBUG(" " << span.start_block_height << " - " << (span.start_block_height+span.nblocks-1) << " (" << span.nblocks << ") - " << (is_blockchain_placeholder(span) ? "blockchain" : span.blocks.empty() ? "scheduled" : "filled ") << " " << span.connection_id << " (" << ((unsigned)(span.rate*10/1024.f))/10.f << " kB/s)"); MDEBUG(" " << span.start_block_height << " - " << (span.start_block_height+span.nblocks-1) << " (" << span.nblocks << ") - " << (span.blocks.empty() ? "scheduled" : "filled ") << " " << span.connection_id << " (" << ((unsigned)(span.rate*10/1024.f))/10.f << " kB/s)");
} }
std::string block_queue::get_overview() const std::string block_queue::get_overview(uint64_t blockchain_height) const
{ {
boost::unique_lock<boost::recursive_mutex> lock(mutex); boost::unique_lock<boost::recursive_mutex> lock(mutex);
if (blocks.empty()) if (blocks.empty())
return "[]"; return "[]";
block_map::const_iterator i = blocks.begin(); block_map::const_iterator i = blocks.begin();
std::string s = std::string("[") + std::to_string(i->start_block_height + i->nblocks - 1) + ":"; std::string s = std::string("[");
while (++i != blocks.end()) uint64_t expected = blockchain_height;
s += i->blocks.empty() ? "." : "o"; while (i != blocks.end())
{
if (expected > i->start_block_height)
{
s += "<";
}
else
{
if (expected < i->start_block_height)
s += std::string(std::max((uint64_t)1, (i->start_block_height - expected) / (i->nblocks ? i->nblocks : 1)), '_');
s += i->blocks.empty() ? "." : i->start_block_height == blockchain_height ? "m" : "o";
expected = i->start_block_height + i->nblocks;
}
++i;
}
s += "]"; s += "]";
return s; return s;
} }
@ -184,16 +222,31 @@ bool block_queue::requested(const crypto::hash &hash) const
return requested_internal(hash); return requested_internal(hash);
} }
std::pair<uint64_t, uint64_t> block_queue::reserve_span(uint64_t first_block_height, uint64_t last_block_height, uint64_t max_blocks, const boost::uuids::uuid &connection_id, const std::vector<crypto::hash> &block_hashes, boost::posix_time::ptime time) bool block_queue::have(const crypto::hash &hash) const
{
boost::unique_lock<boost::recursive_mutex> lock(mutex);
return have_blocks.find(hash) != have_blocks.end();
}
std::pair<uint64_t, uint64_t> block_queue::reserve_span(uint64_t first_block_height, uint64_t last_block_height, uint64_t max_blocks, const boost::uuids::uuid &connection_id, uint32_t pruning_seed, uint64_t blockchain_height, const std::vector<crypto::hash> &block_hashes, boost::posix_time::ptime time)
{ {
boost::unique_lock<boost::recursive_mutex> lock(mutex); boost::unique_lock<boost::recursive_mutex> lock(mutex);
MDEBUG("reserve_span: first_block_height " << first_block_height << ", last_block_height " << last_block_height
<< ", max " << max_blocks << ", seed " << epee::string_tools::to_string_hex(pruning_seed) << ", blockchain_height " <<
blockchain_height << ", block hashes size " << block_hashes.size());
if (last_block_height < first_block_height || max_blocks == 0) if (last_block_height < first_block_height || max_blocks == 0)
{ {
MDEBUG("reserve_span: early out: first_block_height " << first_block_height << ", last_block_height " << last_block_height << ", max_blocks " << max_blocks); MDEBUG("reserve_span: early out: first_block_height " << first_block_height << ", last_block_height " << last_block_height << ", max_blocks " << max_blocks);
return std::make_pair(0, 0); return std::make_pair(0, 0);
} }
if (block_hashes.size() >= last_block_height)
{
MDEBUG("reserve_span: more block hashes than fit within last_block_height: " << block_hashes.size() << " and " << last_block_height);
return std::make_pair(0, 0);
}
// skip everything we've already requested
uint64_t span_start_height = last_block_height - block_hashes.size() + 1; uint64_t span_start_height = last_block_height - block_hashes.size() + 1;
std::vector<crypto::hash>::const_iterator i = block_hashes.begin(); std::vector<crypto::hash>::const_iterator i = block_hashes.begin();
while (i != block_hashes.end() && requested_internal(*i)) while (i != block_hashes.end() && requested_internal(*i))
@ -201,55 +254,57 @@ std::pair<uint64_t, uint64_t> block_queue::reserve_span(uint64_t first_block_hei
++i; ++i;
++span_start_height; ++span_start_height;
} }
// if the peer's pruned for the starting block and its unpruned stripe comes next, start downloading from there
const uint32_t next_unpruned_height = tools::get_next_unpruned_block_height(span_start_height, blockchain_height, pruning_seed);
MDEBUG("reserve_span: next_unpruned_height " << next_unpruned_height << " from " << span_start_height << " and seed "
<< epee::string_tools::to_string_hex(pruning_seed) << ", limit " << span_start_height + CRYPTONOTE_PRUNING_STRIPE_SIZE);
if (next_unpruned_height > span_start_height && next_unpruned_height < span_start_height + CRYPTONOTE_PRUNING_STRIPE_SIZE)
{
MDEBUG("We can download from next span: ideal height " << span_start_height << ", next unpruned height " << next_unpruned_height <<
"(+" << next_unpruned_height - span_start_height << "), current seed " << pruning_seed);
span_start_height = next_unpruned_height;
}
MDEBUG("span_start_height: " <<span_start_height);
const uint64_t block_hashes_start_height = last_block_height - block_hashes.size() + 1;
if (span_start_height >= block_hashes.size() + block_hashes_start_height)
{
MDEBUG("Out of hashes, cannot reserve");
return std::make_pair(0, 0);
}
i = block_hashes.begin() + span_start_height - block_hashes_start_height;
while (i != block_hashes.end() && requested_internal(*i))
{
++i;
++span_start_height;
}
uint64_t span_length = 0; uint64_t span_length = 0;
std::vector<crypto::hash> hashes; std::vector<crypto::hash> hashes;
while (i != block_hashes.end() && span_length < max_blocks) while (i != block_hashes.end() && span_length < max_blocks && tools::has_unpruned_block(span_start_height + span_length, blockchain_height, pruning_seed))
{ {
hashes.push_back(*i); hashes.push_back(*i);
++i; ++i;
++span_length; ++span_length;
} }
if (span_length == 0) if (span_length == 0)
{
MDEBUG("span_length 0, cannot reserve");
return std::make_pair(0, 0); return std::make_pair(0, 0);
}
MDEBUG("Reserving span " << span_start_height << " - " << (span_start_height + span_length - 1) << " for " << connection_id); MDEBUG("Reserving span " << span_start_height << " - " << (span_start_height + span_length - 1) << " for " << connection_id);
add_blocks(span_start_height, span_length, connection_id, time); add_blocks(span_start_height, span_length, connection_id, time);
set_span_hashes(span_start_height, connection_id, hashes); set_span_hashes(span_start_height, connection_id, hashes);
return std::make_pair(span_start_height, span_length); return std::make_pair(span_start_height, span_length);
} }
bool block_queue::is_blockchain_placeholder(const span &span) const
{
return span.connection_id == boost::uuids::nil_uuid();
}
std::pair<uint64_t, uint64_t> block_queue::get_start_gap_span() const
{
boost::unique_lock<boost::recursive_mutex> lock(mutex);
if (blocks.empty())
return std::make_pair(0, 0);
block_map::const_iterator i = blocks.begin();
if (!is_blockchain_placeholder(*i))
return std::make_pair(0, 0);
uint64_t current_height = i->start_block_height + i->nblocks - 1;
++i;
if (i == blocks.end())
return std::make_pair(0, 0);
uint64_t first_span_height = i->start_block_height;
if (first_span_height <= current_height + 1)
return std::make_pair(0, 0);
MDEBUG("Found gap at start of spans: last blockchain block height " << current_height << ", first span's block height " << first_span_height);
print();
return std::make_pair(current_height + 1, first_span_height - current_height - 1);
}
std::pair<uint64_t, uint64_t> block_queue::get_next_span_if_scheduled(std::vector<crypto::hash> &hashes, boost::uuids::uuid &connection_id, boost::posix_time::ptime &time) const std::pair<uint64_t, uint64_t> block_queue::get_next_span_if_scheduled(std::vector<crypto::hash> &hashes, boost::uuids::uuid &connection_id, boost::posix_time::ptime &time) const
{ {
boost::unique_lock<boost::recursive_mutex> lock(mutex); boost::unique_lock<boost::recursive_mutex> lock(mutex);
if (blocks.empty()) if (blocks.empty())
return std::make_pair(0, 0); return std::make_pair(0, 0);
block_map::const_iterator i = blocks.begin(); block_map::const_iterator i = blocks.begin();
if (is_blockchain_placeholder(*i))
++i;
if (i == blocks.end()) if (i == blocks.end())
return std::make_pair(0, 0); return std::make_pair(0, 0);
if (!i->blocks.empty()) if (!i->blocks.empty())
@ -260,6 +315,16 @@ std::pair<uint64_t, uint64_t> block_queue::get_next_span_if_scheduled(std::vecto
return std::make_pair(i->start_block_height, i->nblocks); return std::make_pair(i->start_block_height, i->nblocks);
} }
void block_queue::reset_next_span_time(boost::posix_time::ptime t)
{
boost::unique_lock<boost::recursive_mutex> lock(mutex);
CHECK_AND_ASSERT_THROW_MES(!blocks.empty(), "No next span to reset time");
block_map::iterator i = blocks.begin();
CHECK_AND_ASSERT_THROW_MES(i != blocks.end(), "No next span to reset time");
CHECK_AND_ASSERT_THROW_MES(i->blocks.empty(), "Next span is not empty");
(boost::posix_time::ptime&)i->time = t; // sod off, time doesn't influence sorting
}
void block_queue::set_span_hashes(uint64_t start_height, const boost::uuids::uuid &connection_id, std::vector<crypto::hash> hashes) void block_queue::set_span_hashes(uint64_t start_height, const boost::uuids::uuid &connection_id, std::vector<crypto::hash> hashes)
{ {
boost::unique_lock<boost::recursive_mutex> lock(mutex); boost::unique_lock<boost::recursive_mutex> lock(mutex);
@ -284,8 +349,6 @@ bool block_queue::get_next_span(uint64_t &height, std::vector<cryptonote::block_
if (blocks.empty()) if (blocks.empty())
return false; return false;
block_map::const_iterator i = blocks.begin(); block_map::const_iterator i = blocks.begin();
if (is_blockchain_placeholder(*i))
++i;
for (; i != blocks.end(); ++i) for (; i != blocks.end(); ++i)
{ {
if (!filled || !i->blocks.empty()) if (!filled || !i->blocks.empty())
@ -299,19 +362,34 @@ bool block_queue::get_next_span(uint64_t &height, std::vector<cryptonote::block_
return false; return false;
} }
bool block_queue::has_next_span(const boost::uuids::uuid &connection_id, bool &filled) const bool block_queue::has_next_span(const boost::uuids::uuid &connection_id, bool &filled, boost::posix_time::ptime &time) const
{ {
boost::unique_lock<boost::recursive_mutex> lock(mutex); boost::unique_lock<boost::recursive_mutex> lock(mutex);
if (blocks.empty()) if (blocks.empty())
return false; return false;
block_map::const_iterator i = blocks.begin(); block_map::const_iterator i = blocks.begin();
if (is_blockchain_placeholder(*i))
++i;
if (i == blocks.end()) if (i == blocks.end())
return false; return false;
if (i->connection_id != connection_id) if (i->connection_id != connection_id)
return false; return false;
filled = !i->blocks.empty(); filled = !i->blocks.empty();
time = i->time;
return true;
}
bool block_queue::has_next_span(uint64_t height, bool &filled, boost::posix_time::ptime &time, boost::uuids::uuid &connection_id) const
{
boost::unique_lock<boost::recursive_mutex> lock(mutex);
if (blocks.empty())
return false;
block_map::const_iterator i = blocks.begin();
if (i == blocks.end())
return false;
if (i->start_block_height > height)
return false;
filled = !i->blocks.empty();
time = i->time;
connection_id = i->connection_id;
return true; return true;
} }
@ -331,8 +409,6 @@ size_t block_queue::get_num_filled_spans_prefix() const
if (blocks.empty()) if (blocks.empty())
return 0; return 0;
block_map::const_iterator i = blocks.begin(); block_map::const_iterator i = blocks.begin();
if (is_blockchain_placeholder(*i))
++i;
size_t size = 0; size_t size = 0;
while (i != blocks.end() && !i->blocks.empty()) while (i != blocks.end() && !i->blocks.empty())
{ {
@ -417,12 +493,35 @@ float block_queue::get_speed(const boost::uuids::uuid &connection_id) const
return speed; return speed;
} }
bool block_queue::foreach(std::function<bool(const span&)> f, bool include_blockchain_placeholder) const float block_queue::get_download_rate(const boost::uuids::uuid &connection_id) const
{
boost::unique_lock<boost::recursive_mutex> lock(mutex);
float conn_rate = -1.f;
for (const auto &span: blocks)
{
if (span.blocks.empty())
continue;
if (span.connection_id != connection_id)
continue;
// note that the average below does not average over the whole set, but over the
// previous pseudo average and the latest rate: this gives much more importance
// to the latest measurements, which is fine here
if (conn_rate < 0.f)
conn_rate = span.rate;
else
conn_rate = (conn_rate + span.rate) / 2;
}
if (conn_rate < 0)
conn_rate = 0.0f;
MTRACE("Download rate for " << connection_id << ": " << conn_rate << " b/s");
return conn_rate;
}
bool block_queue::foreach(std::function<bool(const span&)> f) const
{ {
boost::unique_lock<boost::recursive_mutex> lock(mutex); boost::unique_lock<boost::recursive_mutex> lock(mutex);
block_map::const_iterator i = blocks.begin(); block_map::const_iterator i = blocks.begin();
if (!include_blockchain_placeholder && i != blocks.end() && is_blockchain_placeholder(*i))
++i;
while (i != blocks.end()) while (i != blocks.end())
if (!f(*i++)) if (!f(*i++))
return false; return false;

@ -76,22 +76,26 @@ namespace cryptonote
void remove_spans(const boost::uuids::uuid &connection_id, uint64_t start_block_height); void remove_spans(const boost::uuids::uuid &connection_id, uint64_t start_block_height);
uint64_t get_max_block_height() const; uint64_t get_max_block_height() const;
void print() const; void print() const;
std::string get_overview() const; std::string get_overview(uint64_t blockchain_height) const;
std::pair<uint64_t, uint64_t> reserve_span(uint64_t first_block_height, uint64_t last_block_height, uint64_t max_blocks, const boost::uuids::uuid &connection_id, const std::vector<crypto::hash> &block_hashes, boost::posix_time::ptime time = boost::posix_time::microsec_clock::universal_time()); bool has_unpruned_height(uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed) const;
bool is_blockchain_placeholder(const span &span) const; std::pair<uint64_t, uint64_t> reserve_span(uint64_t first_block_height, uint64_t last_block_height, uint64_t max_blocks, const boost::uuids::uuid &connection_id, uint32_t pruning_seed, uint64_t blockchain_height, const std::vector<crypto::hash> &block_hashes, boost::posix_time::ptime time = boost::posix_time::microsec_clock::universal_time());
std::pair<uint64_t, uint64_t> get_start_gap_span() const; uint64_t get_next_needed_height(uint64_t blockchain_height) const;
std::pair<uint64_t, uint64_t> get_next_span_if_scheduled(std::vector<crypto::hash> &hashes, boost::uuids::uuid &connection_id, boost::posix_time::ptime &time) const; std::pair<uint64_t, uint64_t> get_next_span_if_scheduled(std::vector<crypto::hash> &hashes, boost::uuids::uuid &connection_id, boost::posix_time::ptime &time) const;
void reset_next_span_time(boost::posix_time::ptime t = boost::posix_time::microsec_clock::universal_time());
void set_span_hashes(uint64_t start_height, const boost::uuids::uuid &connection_id, std::vector<crypto::hash> hashes); void set_span_hashes(uint64_t start_height, const boost::uuids::uuid &connection_id, std::vector<crypto::hash> hashes);
bool get_next_span(uint64_t &height, std::vector<cryptonote::block_complete_entry> &bcel, boost::uuids::uuid &connection_id, bool filled = true) const; bool get_next_span(uint64_t &height, std::vector<cryptonote::block_complete_entry> &bcel, boost::uuids::uuid &connection_id, bool filled = true) const;
bool has_next_span(const boost::uuids::uuid &connection_id, bool &filled) const; bool has_next_span(const boost::uuids::uuid &connection_id, bool &filled, boost::posix_time::ptime &time) const;
bool has_next_span(uint64_t height, bool &filled, boost::posix_time::ptime &time, boost::uuids::uuid &connection_id) const;
size_t get_data_size() const; size_t get_data_size() const;
size_t get_num_filled_spans_prefix() const; size_t get_num_filled_spans_prefix() const;
size_t get_num_filled_spans() const; size_t get_num_filled_spans() const;
crypto::hash get_last_known_hash(const boost::uuids::uuid &connection_id) const; crypto::hash get_last_known_hash(const boost::uuids::uuid &connection_id) const;
bool has_spans(const boost::uuids::uuid &connection_id) const; bool has_spans(const boost::uuids::uuid &connection_id) const;
float get_speed(const boost::uuids::uuid &connection_id) const; float get_speed(const boost::uuids::uuid &connection_id) const;
bool foreach(std::function<bool(const span&)> f, bool include_blockchain_placeholder = false) const; float get_download_rate(const boost::uuids::uuid &connection_id) const;
bool foreach(std::function<bool(const span&)> f) const;
bool requested(const crypto::hash &hash) const; bool requested(const crypto::hash &hash) const;
bool have(const crypto::hash &hash) const;
private: private:
void erase_block(block_map::iterator j); void erase_block(block_map::iterator j);
@ -101,5 +105,6 @@ namespace cryptonote
block_map blocks; block_map blocks;
mutable boost::recursive_mutex mutex; mutable boost::recursive_mutex mutex;
std::unordered_set<crypto::hash> requested_hashes; std::unordered_set<crypto::hash> requested_hashes;
std::unordered_set<crypto::hash> have_blocks;
}; };
} }

@ -78,6 +78,8 @@ namespace cryptonote
uint64_t height; uint64_t height;
uint32_t pruning_seed;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(incoming) KV_SERIALIZE(incoming)
KV_SERIALIZE(localhost) KV_SERIALIZE(localhost)
@ -100,6 +102,7 @@ namespace cryptonote
KV_SERIALIZE(support_flags) KV_SERIALIZE(support_flags)
KV_SERIALIZE(connection_id) KV_SERIALIZE(connection_id)
KV_SERIALIZE(height) KV_SERIALIZE(height)
KV_SERIALIZE(pruning_seed)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
@ -200,12 +203,14 @@ namespace cryptonote
uint64_t cumulative_difficulty; uint64_t cumulative_difficulty;
crypto::hash top_id; crypto::hash top_id;
uint8_t top_version; uint8_t top_version;
uint32_t pruning_seed;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(current_height) KV_SERIALIZE(current_height)
KV_SERIALIZE(cumulative_difficulty) KV_SERIALIZE(cumulative_difficulty)
KV_SERIALIZE_VAL_POD_AS_BLOB(top_id) KV_SERIALIZE_VAL_POD_AS_BLOB(top_id)
KV_SERIALIZE_OPT(top_version, (uint8_t)0) KV_SERIALIZE_OPT(top_version, (uint8_t)0)
KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };

@ -43,6 +43,7 @@
#include "cryptonote_protocol_defs.h" #include "cryptonote_protocol_defs.h"
#include "cryptonote_protocol_handler_common.h" #include "cryptonote_protocol_handler_common.h"
#include "block_queue.h" #include "block_queue.h"
#include "common/perf_timer.h"
#include "cryptonote_basic/connection_context.h" #include "cryptonote_basic/connection_context.h"
#include "cryptonote_basic/cryptonote_stat_info.h" #include "cryptonote_basic/cryptonote_stat_info.h"
#include <boost/circular_buffer.hpp> #include <boost/circular_buffer.hpp>
@ -109,6 +110,10 @@ namespace cryptonote
const block_queue &get_block_queue() const { return m_block_queue; } const block_queue &get_block_queue() const { return m_block_queue; }
void stop(); void stop();
void on_connection_close(cryptonote_connection_context &context); void on_connection_close(cryptonote_connection_context &context);
void set_max_out_peers(unsigned int max) { m_max_out_peers = max; }
std::string get_peers_overview() const;
std::pair<uint32_t, uint32_t> get_next_needed_pruning_stripe() const;
bool needs_new_sync_connections() const;
private: private:
//----------------- commands handlers ---------------------------------------------- //----------------- commands handlers ----------------------------------------------
int handle_notify_new_block(int command, NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& context); int handle_notify_new_block(int command, NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& context);
@ -125,14 +130,17 @@ namespace cryptonote
virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context); virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context);
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
//bool get_payload_sync_data(HANDSHAKE_DATA::request& hshd, cryptonote_connection_context& context); //bool get_payload_sync_data(HANDSHAKE_DATA::request& hshd, cryptonote_connection_context& context);
bool should_drop_connection(cryptonote_connection_context& context, uint32_t next_stripe);
bool request_missing_objects(cryptonote_connection_context& context, bool check_having_blocks, bool force_next_span = false); bool request_missing_objects(cryptonote_connection_context& context, bool check_having_blocks, bool force_next_span = false);
size_t get_synchronizing_connections_count(); size_t get_synchronizing_connections_count();
bool on_connection_synchronized(); bool on_connection_synchronized();
bool should_download_next_span(cryptonote_connection_context& context) const; bool should_download_next_span(cryptonote_connection_context& context, bool standby);
void drop_connection(cryptonote_connection_context &context, bool add_fail, bool flush_all_spans); void drop_connection(cryptonote_connection_context &context, bool add_fail, bool flush_all_spans);
bool kick_idle_peers(); bool kick_idle_peers();
bool check_standby_peers(); bool check_standby_peers();
int try_add_next_blocks(cryptonote_connection_context &context); int try_add_next_blocks(cryptonote_connection_context &context);
void notify_new_stripe(cryptonote_connection_context &context, uint32_t stripe);
void skip_unneeded_hashes(cryptonote_connection_context& context, bool check_block_queue) const;
t_core& m_core; t_core& m_core;
@ -145,6 +153,12 @@ namespace cryptonote
block_queue m_block_queue; block_queue m_block_queue;
epee::math_helper::once_a_time_seconds<30> m_idle_peer_kicker; epee::math_helper::once_a_time_seconds<30> m_idle_peer_kicker;
epee::math_helper::once_a_time_milliseconds<100> m_standby_checker; epee::math_helper::once_a_time_milliseconds<100> m_standby_checker;
std::atomic<unsigned int> m_max_out_peers;
tools::PerformanceTimer m_sync_timer, m_add_timer;
uint64_t m_last_add_end_time;
uint64_t m_sync_spans_downloaded, m_sync_old_spans_downloaded, m_sync_bad_spans_downloaded;
uint64_t m_sync_download_chain_size, m_sync_download_objects_size;
size_t m_block_download_max_size;
boost::mutex m_buffer_mutex; boost::mutex m_buffer_mutex;
double get_avg_block_size(); double get_avg_block_size();

File diff suppressed because it is too large Load Diff

@ -717,4 +717,27 @@ bool t_command_parser_executor::version(const std::vector<std::string>& args)
return true; return true;
} }
bool t_command_parser_executor::prune_blockchain(const std::vector<std::string>& args)
{
if (args.size() > 1) return false;
if (args.empty() || args[0] != "confirm")
{
std::cout << "Warning: pruning from within monerod will not shrink the database file size." << std::endl;
std::cout << "Instead, parts of the file will be marked as free, so the file will not grow" << std::endl;
std::cout << "until that newly free space is used up. If you want a smaller file size now," << std::endl;
std::cout << "exit monerod and run monero-blockchain-prune (you will temporarily need more" << std::endl;
std::cout << "disk space for the database conversion though). If you are OK with the database" << std::endl;
std::cout << "file keeping the same size, re-run this command with the \"confirm\" parameter." << std::endl;
return true;
}
return m_executor.prune_blockchain();
}
bool t_command_parser_executor::check_blockchain_pruning(const std::vector<std::string>& args)
{
return m_executor.check_blockchain_pruning();
}
} // namespace daemonize } // namespace daemonize

@ -142,6 +142,10 @@ public:
bool pop_blocks(const std::vector<std::string>& args); bool pop_blocks(const std::vector<std::string>& args);
bool version(const std::vector<std::string>& args); bool version(const std::vector<std::string>& args);
bool prune_blockchain(const std::vector<std::string>& args);
bool check_blockchain_pruning(const std::vector<std::string>& args);
}; };
} // namespace daemonize } // namespace daemonize

@ -292,6 +292,16 @@ t_command_server::t_command_server(
, std::bind(&t_command_parser_executor::version, &m_parser, p::_1) , std::bind(&t_command_parser_executor::version, &m_parser, p::_1)
, "Print version information." , "Print version information."
); );
m_command_lookup.set_handler(
"prune_blockchain"
, std::bind(&t_command_parser_executor::prune_blockchain, &m_parser, p::_1)
, "Prune the blockchain."
);
m_command_lookup.set_handler(
"check_blockchain_pruning"
, std::bind(&t_command_parser_executor::check_blockchain_pruning, &m_parser, p::_1)
, "Check the blockchain pruning."
);
} }
bool t_command_server::process_command_str(const std::string& cmd) bool t_command_server::process_command_str(const std::string& cmd)

@ -31,6 +31,7 @@
#include "string_tools.h" #include "string_tools.h"
#include "common/password.h" #include "common/password.h"
#include "common/scoped_message_writer.h" #include "common/scoped_message_writer.h"
#include "common/pruning.h"
#include "daemon/rpc_command_executor.h" #include "daemon/rpc_command_executor.h"
#include "rpc/core_rpc_server_commands_defs.h" #include "rpc/core_rpc_server_commands_defs.h"
#include "cryptonote_core/cryptonote_core.h" #include "cryptonote_core/cryptonote_core.h"
@ -60,7 +61,8 @@ namespace {
peer_id_str >> id_str; peer_id_str >> id_str;
epee::string_tools::xtype_to_string(peer.port, port_str); epee::string_tools::xtype_to_string(peer.port, port_str);
std::string addr_str = ip_str + ":" + port_str; std::string addr_str = ip_str + ":" + port_str;
tools::msg_writer() << boost::format("%-10s %-25s %-25s %s") % prefix % id_str % addr_str % elapsed; std::string pruning_seed = epee::string_tools::to_string_hex(peer.pruning_seed);
tools::msg_writer() << boost::format("%-10s %-25s %-25s %-4s %s") % prefix % id_str % addr_str % pruning_seed % elapsed;
} }
void print_block_header(cryptonote::block_header_response const & header) void print_block_header(cryptonote::block_header_response const & header)
@ -741,6 +743,7 @@ bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash,
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(transaction_hash)); req.txs_hashes.push_back(epee::string_tools::pod_to_hex(transaction_hash));
req.decode_as_json = false; req.decode_as_json = false;
req.split = true;
req.prune = false; req.prune = false;
if (m_is_rpc) if (m_is_rpc)
{ {
@ -766,13 +769,25 @@ bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash,
if (res.txs.front().in_pool) if (res.txs.front().in_pool)
tools::success_msg_writer() << "Found in pool"; tools::success_msg_writer() << "Found in pool";
else else
tools::success_msg_writer() << "Found in blockchain at height " << res.txs.front().block_height; tools::success_msg_writer() << "Found in blockchain at height " << res.txs.front().block_height << (res.txs.front().prunable_as_hex.empty() ? " (pruned)" : "");
} }
const std::string &as_hex = (1 == res.txs.size()) ? res.txs.front().as_hex : res.txs_as_hex.front(); const std::string &as_hex = (1 == res.txs.size()) ? res.txs.front().as_hex : res.txs_as_hex.front();
const std::string &pruned_as_hex = (1 == res.txs.size()) ? res.txs.front().pruned_as_hex : "";
const std::string &prunable_as_hex = (1 == res.txs.size()) ? res.txs.front().prunable_as_hex : "";
// Print raw hex if requested // Print raw hex if requested
if (include_hex) if (include_hex)
tools::success_msg_writer() << as_hex << std::endl; {
if (!as_hex.empty())
{
tools::success_msg_writer() << as_hex << std::endl;
}
else
{
std::string output = pruned_as_hex + prunable_as_hex;
tools::success_msg_writer() << output << std::endl;
}
}
// Print json if requested // Print json if requested
if (include_json) if (include_json)
@ -780,17 +795,27 @@ bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash,
crypto::hash tx_hash, tx_prefix_hash; crypto::hash tx_hash, tx_prefix_hash;
cryptonote::transaction tx; cryptonote::transaction tx;
cryptonote::blobdata blob; cryptonote::blobdata blob;
if (!string_tools::parse_hexstr_to_binbuff(as_hex, blob)) std::string source = as_hex.empty() ? pruned_as_hex + prunable_as_hex : as_hex;
bool pruned = !pruned_as_hex.empty() && prunable_as_hex.empty();
if (!string_tools::parse_hexstr_to_binbuff(source, blob))
{ {
tools::fail_msg_writer() << "Failed to parse tx to get json format"; tools::fail_msg_writer() << "Failed to parse tx to get json format";
} }
else if (!cryptonote::parse_and_validate_tx_from_blob(blob, tx, tx_hash, tx_prefix_hash))
{
tools::fail_msg_writer() << "Failed to parse tx blob to get json format";
}
else else
{ {
tools::success_msg_writer() << cryptonote::obj_to_json_str(tx) << std::endl; bool ret;
if (pruned)
ret = cryptonote::parse_and_validate_tx_base_from_blob(blob, tx);
else
ret = cryptonote::parse_and_validate_tx_from_blob(blob, tx);
if (!ret)
{
tools::fail_msg_writer() << "Failed to parse tx blob to get json format";
}
else
{
tools::success_msg_writer() << cryptonote::obj_to_json_str(tx) << std::endl;
}
} }
} }
} }
@ -1939,6 +1964,8 @@ bool t_rpc_command_executor::sync_info()
for (const auto &p: res.peers) for (const auto &p: res.peers)
current_download += p.info.current_download; current_download += p.info.current_download;
tools::success_msg_writer() << "Downloading at " << current_download << " kB/s"; tools::success_msg_writer() << "Downloading at " << current_download << " kB/s";
if (res.next_needed_pruning_seed)
tools::success_msg_writer() << "Next needed pruning seed: " << res.next_needed_pruning_seed;
tools::success_msg_writer() << std::to_string(res.peers.size()) << " peers"; tools::success_msg_writer() << std::to_string(res.peers.size()) << " peers";
for (const auto &p: res.peers) for (const auto &p: res.peers)
@ -1946,25 +1973,30 @@ bool t_rpc_command_executor::sync_info()
std::string address = epee::string_tools::pad_string(p.info.address, 24); std::string address = epee::string_tools::pad_string(p.info.address, 24);
uint64_t nblocks = 0, size = 0; uint64_t nblocks = 0, size = 0;
for (const auto &s: res.spans) for (const auto &s: res.spans)
if (s.rate > 0.0f && s.connection_id == p.info.connection_id) if (s.connection_id == p.info.connection_id)
nblocks += s.nblocks, size += s.size; nblocks += s.nblocks, size += s.size;
tools::success_msg_writer() << address << " " << epee::string_tools::pad_string(p.info.peer_id, 16, '0', true) << " " << epee::string_tools::pad_string(p.info.state, 16) << " " << p.info.height << " " << p.info.current_download << " kB/s, " << nblocks << " blocks / " << size/1e6 << " MB queued"; tools::success_msg_writer() << address << " " << epee::string_tools::pad_string(p.info.peer_id, 16, '0', true) << " " <<
epee::string_tools::pad_string(p.info.state, 16) << " " <<
epee::string_tools::pad_string(epee::string_tools::to_string_hex(p.info.pruning_seed), 8) << " " << p.info.height << " " <<
p.info.current_download << " kB/s, " << nblocks << " blocks / " << size/1e6 << " MB queued";
} }
uint64_t total_size = 0; uint64_t total_size = 0;
for (const auto &s: res.spans) for (const auto &s: res.spans)
total_size += s.size; total_size += s.size;
tools::success_msg_writer() << std::to_string(res.spans.size()) << " spans, " << total_size/1e6 << " MB"; tools::success_msg_writer() << std::to_string(res.spans.size()) << " spans, " << total_size/1e6 << " MB";
tools::success_msg_writer() << res.overview;
for (const auto &s: res.spans) for (const auto &s: res.spans)
{ {
std::string address = epee::string_tools::pad_string(s.remote_address, 24); std::string address = epee::string_tools::pad_string(s.remote_address, 24);
std::string pruning_seed = epee::string_tools::to_string_hex(tools::get_pruning_seed(s.start_block_height, std::numeric_limits<uint64_t>::max(), CRYPTONOTE_PRUNING_LOG_STRIPES));
if (s.size == 0) if (s.size == 0)
{ {
tools::success_msg_writer() << address << " " << s.nblocks << " (" << s.start_block_height << " - " << (s.start_block_height + s.nblocks - 1) << ") -"; tools::success_msg_writer() << address << " " << s.nblocks << "/" << pruning_seed << " (" << s.start_block_height << " - " << (s.start_block_height + s.nblocks - 1) << ") -";
} }
else else
{ {
tools::success_msg_writer() << address << " " << s.nblocks << " (" << s.start_block_height << " - " << (s.start_block_height + s.nblocks - 1) << ", " << (uint64_t)(s.size/1e3) << " kB) " << (unsigned)(s.rate/1e3) << " kB/s (" << s.speed/100.0f << ")"; tools::success_msg_writer() << address << " " << s.nblocks << "/" << pruning_seed << " (" << s.start_block_height << " - " << (s.start_block_height + s.nblocks - 1) << ", " << (uint64_t)(s.size/1e3) << " kB) " << (unsigned)(s.rate/1e3) << " kB/s (" << s.speed/100.0f << ")";
} }
} }
@ -1998,4 +2030,69 @@ bool t_rpc_command_executor::pop_blocks(uint64_t num_blocks)
return true; return true;
} }
bool t_rpc_command_executor::prune_blockchain()
{
cryptonote::COMMAND_RPC_PRUNE_BLOCKCHAIN::request req;
cryptonote::COMMAND_RPC_PRUNE_BLOCKCHAIN::response res;
std::string fail_message = "Unsuccessful";
epee::json_rpc::error error_resp;
req.check = false;
if (m_is_rpc)
{
if (!m_rpc_client->json_rpc_request(req, res, "prune_blockchain", fail_message.c_str()))
{
return true;
}
}
else
{
if (!m_rpc_server->on_prune_blockchain(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK)
{
tools::fail_msg_writer() << make_error(fail_message, res.status);
return true;
}
}
tools::success_msg_writer() << "Blockchain pruned: seed " << epee::string_tools::to_string_hex(res.pruning_seed);
return true;
}
bool t_rpc_command_executor::check_blockchain_pruning()
{
cryptonote::COMMAND_RPC_PRUNE_BLOCKCHAIN::request req;
cryptonote::COMMAND_RPC_PRUNE_BLOCKCHAIN::response res;
std::string fail_message = "Unsuccessful";
epee::json_rpc::error error_resp;
req.check = true;
if (m_is_rpc)
{
if (!m_rpc_client->json_rpc_request(req, res, "prune_blockchain", fail_message.c_str()))
{
return true;
}
}
else
{
if (!m_rpc_server->on_prune_blockchain(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK)
{
tools::fail_msg_writer() << make_error(fail_message, res.status);
return true;
}
}
if (res.pruning_seed)
{
tools::success_msg_writer() << "Blockchain pruning checked";
}
else
{
tools::success_msg_writer() << "Blockchain is not pruned";
}
return true;
}
}// namespace daemonize }// namespace daemonize

@ -154,6 +154,10 @@ public:
bool sync_info(); bool sync_info();
bool pop_blocks(uint64_t num_blocks); bool pop_blocks(uint64_t num_blocks);
bool prune_blockchain();
bool check_blockchain_pruning();
}; };
} // namespace daemonize } // namespace daemonize

@ -180,11 +180,9 @@ int main(int argc, char* argv[])
} }
else if (cryptonote::parse_and_validate_tx_from_blob(blob, tx) || cryptonote::parse_and_validate_tx_base_from_blob(blob, tx)) else if (cryptonote::parse_and_validate_tx_from_blob(blob, tx) || cryptonote::parse_and_validate_tx_base_from_blob(blob, tx))
{ {
/*
if (tx.pruned) if (tx.pruned)
std::cout << "Parsed pruned transaction:" << std::endl; std::cout << "Parsed pruned transaction:" << std::endl;
else else
*/
std::cout << "Parsed transaction:" << std::endl; std::cout << "Parsed transaction:" << std::endl;
std::cout << cryptonote::obj_to_json_str(tx) << std::endl; std::cout << cryptonote::obj_to_json_str(tx) << std::endl;

@ -29,6 +29,7 @@
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#pragma once #pragma once
#include <array>
#include <boost/thread.hpp> #include <boost/thread.hpp>
#include <boost/program_options/options_description.hpp> #include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp> #include <boost/program_options/variables_map.hpp>
@ -127,6 +128,11 @@ namespace nodetool
virtual bool block_host(const epee::net_utils::network_address &adress, time_t seconds = P2P_IP_BLOCKTIME); virtual bool block_host(const epee::net_utils::network_address &adress, time_t seconds = P2P_IP_BLOCKTIME);
virtual bool unblock_host(const epee::net_utils::network_address &address); virtual bool unblock_host(const epee::net_utils::network_address &address);
virtual std::map<std::string, time_t> get_blocked_hosts() { CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); return m_blocked_hosts; } virtual std::map<std::string, time_t> get_blocked_hosts() { CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); return m_blocked_hosts; }
virtual void add_used_stripe_peer(const typename t_payload_net_handler::connection_context &context);
virtual void remove_used_stripe_peer(const typename t_payload_net_handler::connection_context &context);
virtual void clear_used_stripe_peers();
private: private:
const std::vector<std::string> m_seed_nodes_list = const std::vector<std::string> m_seed_nodes_list =
{ "seeds.moneroseeds.se" { "seeds.moneroseeds.se"
@ -235,7 +241,13 @@ namespace nodetool
bool parse_peers_and_add_to_container(const boost::program_options::variables_map& vm, const command_line::arg_descriptor<std::vector<std::string> > & arg, Container& container); bool parse_peers_and_add_to_container(const boost::program_options::variables_map& vm, const command_line::arg_descriptor<std::vector<std::string> > & arg, Container& container);
bool set_max_out_peers(const boost::program_options::variables_map& vm, int64_t max); bool set_max_out_peers(const boost::program_options::variables_map& vm, int64_t max);
bool get_max_out_peers() const { return m_config.m_net_config.max_out_connection_count; }
bool get_current_out_peers() const { return m_current_number_of_out_peers; }
bool set_max_in_peers(const boost::program_options::variables_map& vm, int64_t max); bool set_max_in_peers(const boost::program_options::variables_map& vm, int64_t max);
bool get_max_in_peers() const { return m_config.m_net_config.max_in_connection_count; }
bool get_current_in_peers() const { return m_current_number_of_in_peers; }
bool set_tos_flag(const boost::program_options::variables_map& vm, int limit); bool set_tos_flag(const boost::program_options::variables_map& vm, int limit);
bool set_rate_up_limit(const boost::program_options::variables_map& vm, int64_t limit); bool set_rate_up_limit(const boost::program_options::variables_map& vm, int64_t limit);
@ -336,6 +348,9 @@ namespace nodetool
epee::critical_section m_host_fails_score_lock; epee::critical_section m_host_fails_score_lock;
std::map<std::string, uint64_t> m_host_fails_score; std::map<std::string, uint64_t> m_host_fails_score;
boost::mutex m_used_stripe_peers_mutex;
std::array<std::list<epee::net_utils::network_address>, 1 << CRYPTONOTE_PRUNING_LOG_STRIPES> m_used_stripe_peers;
cryptonote::network_type m_nettype; cryptonote::network_type m_nettype;
}; };

@ -41,6 +41,7 @@
#include "string_tools.h" #include "string_tools.h"
#include "common/util.h" #include "common/util.h"
#include "common/dns_utils.h" #include "common/dns_utils.h"
#include "common/pruning.h"
#include "net/net_helper.h" #include "net/net_helper.h"
#include "math_helper.h" #include "math_helper.h"
#include "p2p_protocol_defs.h" #include "p2p_protocol_defs.h"
@ -133,6 +134,28 @@ namespace nodetool
make_default_config(); make_default_config();
} }
#ifdef CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED
std::list<peerlist_entry> plw;
while (m_peerlist.get_white_peers_count())
{
plw.push_back(peerlist_entry());
m_peerlist.get_white_peer_by_index(plw.back(), 0);
m_peerlist.remove_from_peer_white(plw.back());
}
for (auto &e:plw)
m_peerlist.append_with_peer_white(e);
std::list<peerlist_entry> plg;
while (m_peerlist.get_gray_peers_count())
{
plg.push_back(peerlist_entry());
m_peerlist.get_gray_peer_by_index(plg.back(), 0);
m_peerlist.remove_from_peer_gray(plg.back());
}
for (auto &e:plg)
m_peerlist.append_with_peer_gray(e);
#endif
// always recreate a new peer id // always recreate a new peer id
make_default_peer_id(); make_default_peer_id();
@ -729,7 +752,7 @@ namespace nodetool
std::atomic<bool> hsh_result(false); std::atomic<bool> hsh_result(false);
bool r = epee::net_utils::async_invoke_remote_command2<typename COMMAND_HANDSHAKE::response>(context_.m_connection_id, COMMAND_HANDSHAKE::ID, arg, m_net_server.get_config_object(), bool r = epee::net_utils::async_invoke_remote_command2<typename COMMAND_HANDSHAKE::response>(context_.m_connection_id, COMMAND_HANDSHAKE::ID, arg, m_net_server.get_config_object(),
[this, &pi, &ev, &hsh_result, &just_take_peerlist](int code, const typename COMMAND_HANDSHAKE::response& rsp, p2p_connection_context& context) [this, &pi, &ev, &hsh_result, &just_take_peerlist, &context_](int code, const typename COMMAND_HANDSHAKE::response& rsp, p2p_connection_context& context)
{ {
epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ev.raise();}); epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ev.raise();});
@ -762,7 +785,7 @@ namespace nodetool
} }
pi = context.peer_id = rsp.node_data.peer_id; pi = context.peer_id = rsp.node_data.peer_id;
m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_address); m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_address, context.m_pruning_seed);
if(rsp.node_data.peer_id == m_config.m_peer_id) if(rsp.node_data.peer_id == m_config.m_peer_id)
{ {
@ -770,11 +793,13 @@ namespace nodetool
hsh_result = false; hsh_result = false;
return; return;
} }
LOG_INFO_CC(context, "New connection handshaked, pruning seed " << epee::string_tools::to_string_hex(context.m_pruning_seed));
LOG_DEBUG_CC(context, " COMMAND_HANDSHAKE INVOKED OK"); LOG_DEBUG_CC(context, " COMMAND_HANDSHAKE INVOKED OK");
}else }else
{ {
LOG_DEBUG_CC(context, " COMMAND_HANDSHAKE(AND CLOSE) INVOKED OK"); LOG_DEBUG_CC(context, " COMMAND_HANDSHAKE(AND CLOSE) INVOKED OK");
} }
context_ = context;
}, P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT); }, P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT);
if(r) if(r)
@ -821,7 +846,7 @@ namespace nodetool
add_host_fail(context.m_remote_address); add_host_fail(context.m_remote_address);
} }
if(!context.m_is_income) if(!context.m_is_income)
m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_address); m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_address, context.m_pruning_seed);
m_payload_handler.process_payload_sync_data(rsp.payload_data, context, false); m_payload_handler.process_payload_sync_data(rsp.payload_data, context, false);
}); });
@ -939,6 +964,7 @@ namespace nodetool
const epee::net_utils::ipv4_network_address &ipv4 = na.as<const epee::net_utils::ipv4_network_address>(); const epee::net_utils::ipv4_network_address &ipv4 = na.as<const epee::net_utils::ipv4_network_address>();
typename net_server::t_connection_context con = AUTO_VAL_INIT(con); typename net_server::t_connection_context con = AUTO_VAL_INIT(con);
con.m_anchor = peer_type == anchor;
bool res = m_net_server.connect(epee::string_tools::get_ip_string_from_int32(ipv4.ip()), bool res = m_net_server.connect(epee::string_tools::get_ip_string_from_int32(ipv4.ip()),
epee::string_tools::num_to_string_fast(ipv4.port()), epee::string_tools::num_to_string_fast(ipv4.port()),
m_config.m_net_config.connection_timeout, m_config.m_net_config.connection_timeout,
@ -978,6 +1004,7 @@ namespace nodetool
time_t last_seen; time_t last_seen;
time(&last_seen); time(&last_seen);
pe_local.last_seen = static_cast<int64_t>(last_seen); pe_local.last_seen = static_cast<int64_t>(last_seen);
pe_local.pruning_seed = con.m_pruning_seed;
m_peerlist.append_with_peer_white(pe_local); m_peerlist.append_with_peer_white(pe_local);
//update last seen and push it to peerlist manager //update last seen and push it to peerlist manager
@ -1004,6 +1031,7 @@ namespace nodetool
const epee::net_utils::ipv4_network_address &ipv4 = na.as<epee::net_utils::ipv4_network_address>(); const epee::net_utils::ipv4_network_address &ipv4 = na.as<epee::net_utils::ipv4_network_address>();
typename net_server::t_connection_context con = AUTO_VAL_INIT(con); typename net_server::t_connection_context con = AUTO_VAL_INIT(con);
con.m_anchor = false;
bool res = m_net_server.connect(epee::string_tools::get_ip_string_from_int32(ipv4.ip()), bool res = m_net_server.connect(epee::string_tools::get_ip_string_from_int32(ipv4.ip()),
epee::string_tools::num_to_string_fast(ipv4.port()), epee::string_tools::num_to_string_fast(ipv4.port()),
m_config.m_net_config.connection_timeout, m_config.m_net_config.connection_timeout,
@ -1056,7 +1084,7 @@ namespace nodetool
bool node_server<t_payload_net_handler>::make_new_connection_from_anchor_peerlist(const std::vector<anchor_peerlist_entry>& anchor_peerlist) bool node_server<t_payload_net_handler>::make_new_connection_from_anchor_peerlist(const std::vector<anchor_peerlist_entry>& anchor_peerlist)
{ {
for (const auto& pe: anchor_peerlist) { for (const auto& pe: anchor_peerlist) {
_note("Considering connecting (out) to peer: " << peerid_type(pe.id) << " " << pe.adr.str()); _note("Considering connecting (out) to anchor peer: " << peerid_type(pe.id) << " " << pe.adr.str());
if(is_peer_used(pe)) { if(is_peer_used(pe)) {
_note("Peer is used"); _note("Peer is used");
@ -1089,11 +1117,7 @@ namespace nodetool
template<class t_payload_net_handler> template<class t_payload_net_handler>
bool node_server<t_payload_net_handler>::make_new_connection_from_peerlist(bool use_white_list) bool node_server<t_payload_net_handler>::make_new_connection_from_peerlist(bool use_white_list)
{ {
size_t local_peers_count = use_white_list ? m_peerlist.get_white_peers_count():m_peerlist.get_gray_peers_count(); size_t max_random_index = 0;
if(!local_peers_count)
return false;//no peers
size_t max_random_index = std::min<uint64_t>(local_peers_count -1, 20);
std::set<size_t> tried_peers; std::set<size_t> tried_peers;
@ -1103,21 +1127,54 @@ namespace nodetool
{ {
++rand_count; ++rand_count;
size_t random_index; size_t random_index;
const uint32_t next_needed_pruning_stripe = m_payload_handler.get_next_needed_pruning_stripe().second;
if (use_white_list) { std::deque<size_t> filtered;
local_peers_count = m_peerlist.get_white_peers_count(); const size_t limit = use_white_list ? 20 : std::numeric_limits<size_t>::max();
if (!local_peers_count) size_t idx = 0;
m_peerlist.foreach (use_white_list, [&filtered, &idx, limit, next_needed_pruning_stripe](const peerlist_entry &pe){
if (filtered.size() >= limit)
return false; return false;
max_random_index = std::min<uint64_t>(local_peers_count -1, 20); if (next_needed_pruning_stripe == 0 || pe.pruning_seed == 0)
random_index = get_random_index_with_fixed_probability(max_random_index); filtered.push_back(idx);
} else { else if (next_needed_pruning_stripe == tools::get_pruning_stripe(pe.pruning_seed))
local_peers_count = m_peerlist.get_gray_peers_count(); filtered.push_front(idx);
if (!local_peers_count) ++idx;
return false; return true;
random_index = crypto::rand<size_t>() % local_peers_count; });
if (filtered.empty())
{
MDEBUG("No available peer in " << (use_white_list ? "white" : "gray") << " list filtered by " << next_needed_pruning_stripe);
return false;
} }
if (use_white_list)
{
// if using the white list, we first pick in the set of peers we've already been using earlier
random_index = get_random_index_with_fixed_probability(std::min<uint64_t>(filtered.size() - 1, 20));
CRITICAL_REGION_LOCAL(m_used_stripe_peers_mutex);
if (next_needed_pruning_stripe > 0 && next_needed_pruning_stripe <= (1ul << CRYPTONOTE_PRUNING_LOG_STRIPES) && !m_used_stripe_peers[next_needed_pruning_stripe-1].empty())
{
const epee::net_utils::network_address na = m_used_stripe_peers[next_needed_pruning_stripe-1].front();
m_used_stripe_peers[next_needed_pruning_stripe-1].pop_front();
for (size_t i = 0; i < filtered.size(); ++i)
{
peerlist_entry pe;
if (m_peerlist.get_white_peer_by_index(pe, filtered[i]) && pe.adr == na)
{
MDEBUG("Reusing stripe " << next_needed_pruning_stripe << " peer " << pe.adr.str());
random_index = i;
break;
}
}
}
}
else
random_index = crypto::rand<size_t>() % filtered.size();
CHECK_AND_ASSERT_MES(random_index < local_peers_count, false, "random_starter_index < peers_local.size() failed!!"); CHECK_AND_ASSERT_MES(random_index < filtered.size(), false, "random_index < filtered.size() failed!!");
random_index = filtered[random_index];
CHECK_AND_ASSERT_MES(random_index < (use_white_list ? m_peerlist.get_white_peers_count() : m_peerlist.get_gray_peers_count()),
false, "random_index < peers size failed!!");
if(tried_peers.count(random_index)) if(tried_peers.count(random_index))
continue; continue;
@ -1129,7 +1186,9 @@ namespace nodetool
++try_count; ++try_count;
_note("Considering connecting (out) to peer: " << peerid_to_string(pe.id) << " " << pe.adr.str()); _note("Considering connecting (out) to " << (use_white_list ? "white" : "gray") << " list peer: " <<
peerid_to_string(pe.id) << " " << pe.adr.str() << ", pruning seed " << epee::string_tools::to_string_hex(pe.pruning_seed) <<
" (stripe " << next_needed_pruning_stripe << " needed)");
if(is_peer_used(pe)) { if(is_peer_used(pe)) {
_note("Peer is used"); _note("Peer is used");
@ -1143,6 +1202,7 @@ namespace nodetool
continue; continue;
MDEBUG("Selected peer: " << peerid_to_string(pe.id) << " " << pe.adr.str() MDEBUG("Selected peer: " << peerid_to_string(pe.id) << " " << pe.adr.str()
<< ", pruning seed " << epee::string_tools::to_string_hex(pe.pruning_seed) << " "
<< "[peer_list=" << (use_white_list ? white : gray) << "[peer_list=" << (use_white_list ? white : gray)
<< "] last_seen: " << (pe.last_seen ? epee::misc_utils::get_time_interval_string(time(NULL) - pe.last_seen) : "never")); << "] last_seen: " << (pe.last_seen ? epee::misc_utils::get_time_interval_string(time(NULL) - pe.last_seen) : "never"));
@ -1219,31 +1279,35 @@ namespace nodetool
if (!connect_to_peerlist(m_priority_peers)) return false; if (!connect_to_peerlist(m_priority_peers)) return false;
size_t expected_white_connections = (m_config.m_net_config.max_out_connection_count*P2P_DEFAULT_WHITELIST_CONNECTIONS_PERCENT)/100; size_t base_expected_white_connections = (m_config.m_net_config.max_out_connection_count*P2P_DEFAULT_WHITELIST_CONNECTIONS_PERCENT)/100;
size_t conn_count = get_outgoing_connections_count(); size_t conn_count = get_outgoing_connections_count();
if(conn_count < m_config.m_net_config.max_out_connection_count) while(conn_count < m_config.m_net_config.max_out_connection_count)
{ {
const size_t expected_white_connections = m_payload_handler.get_next_needed_pruning_stripe().second ? m_config.m_net_config.max_out_connection_count : base_expected_white_connections;
if(conn_count < expected_white_connections) if(conn_count < expected_white_connections)
{ {
//start from anchor list //start from anchor list
if(!make_expected_connections_count(anchor, P2P_DEFAULT_ANCHOR_CONNECTIONS_COUNT)) while (get_outgoing_connections_count() < P2P_DEFAULT_ANCHOR_CONNECTIONS_COUNT
return false; && make_expected_connections_count(anchor, P2P_DEFAULT_ANCHOR_CONNECTIONS_COUNT));
//then do white list //then do white list
if(!make_expected_connections_count(white, expected_white_connections)) while (get_outgoing_connections_count() < expected_white_connections
return false; && make_expected_connections_count(white, expected_white_connections));
//then do grey list //then do grey list
if(!make_expected_connections_count(gray, m_config.m_net_config.max_out_connection_count)) while (get_outgoing_connections_count() < m_config.m_net_config.max_out_connection_count
return false; && make_expected_connections_count(gray, m_config.m_net_config.max_out_connection_count));
}else }else
{ {
//start from grey list //start from grey list
if(!make_expected_connections_count(gray, m_config.m_net_config.max_out_connection_count)) while (get_outgoing_connections_count() < m_config.m_net_config.max_out_connection_count
return false; && make_expected_connections_count(gray, m_config.m_net_config.max_out_connection_count));
//and then do white list //and then do white list
if(!make_expected_connections_count(white, m_config.m_net_config.max_out_connection_count)) while (get_outgoing_connections_count() < m_config.m_net_config.max_out_connection_count
return false; && make_expected_connections_count(white, m_config.m_net_config.max_out_connection_count));
} }
if(m_net_server.is_stop_signal_sent())
return false;
conn_count = get_outgoing_connections_count();
} }
if (start_conn_count == get_outgoing_connections_count() && start_conn_count < m_config.m_net_config.max_out_connection_count) if (start_conn_count == get_outgoing_connections_count() && start_conn_count < m_config.m_net_config.max_out_connection_count)
@ -1260,7 +1324,7 @@ namespace nodetool
bool node_server<t_payload_net_handler>::make_expected_connections_count(PeerType peer_type, size_t expected_connections) bool node_server<t_payload_net_handler>::make_expected_connections_count(PeerType peer_type, size_t expected_connections)
{ {
if (m_offline) if (m_offline)
return true; return false;
std::vector<anchor_peerlist_entry> apl; std::vector<anchor_peerlist_entry> apl;
@ -1270,24 +1334,24 @@ namespace nodetool
size_t conn_count = get_outgoing_connections_count(); size_t conn_count = get_outgoing_connections_count();
//add new connections from white peers //add new connections from white peers
while(conn_count < expected_connections) if(conn_count < expected_connections)
{ {
if(m_net_server.is_stop_signal_sent()) if(m_net_server.is_stop_signal_sent())
return false; return false;
MDEBUG("Making expected connection, type " << peer_type << ", " << conn_count << "/" << expected_connections << " connections");
if (peer_type == anchor && !make_new_connection_from_anchor_peerlist(apl)) { if (peer_type == anchor && !make_new_connection_from_anchor_peerlist(apl)) {
break; return false;
} }
if (peer_type == white && !make_new_connection_from_peerlist(true)) { if (peer_type == white && !make_new_connection_from_peerlist(true)) {
break; return false;
} }
if (peer_type == gray && !make_new_connection_from_peerlist(false)) { if (peer_type == gray && !make_new_connection_from_peerlist(false)) {
break; return false;
} }
conn_count = get_outgoing_connections_count();
} }
return true; return true;
} }
@ -1390,6 +1454,9 @@ namespace nodetool
return false; return false;
} }
be.last_seen += delta; be.last_seen += delta;
#ifdef CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED
be.pruning_seed = tools::make_pruning_seed(1 + (be.adr.as<epee::net_utils::ipv4_network_address>().ip()) % (1ul << CRYPTONOTE_PRUNING_LOG_STRIPES), CRYPTONOTE_PRUNING_LOG_STRIPES);
#endif
} }
return true; return true;
} }
@ -1750,6 +1817,7 @@ namespace nodetool
time(&last_seen); time(&last_seen);
pe.last_seen = static_cast<int64_t>(last_seen); pe.last_seen = static_cast<int64_t>(last_seen);
pe.id = peer_id_l; pe.id = peer_id_l;
pe.pruning_seed = context.m_pruning_seed;
this->m_peerlist.append_with_peer_white(pe); this->m_peerlist.append_with_peer_white(pe);
LOG_DEBUG_CC(context, "PING SUCCESS " << context.m_remote_address.host_str() << ":" << port_l); LOG_DEBUG_CC(context, "PING SUCCESS " << context.m_remote_address.host_str() << ":" << port_l);
}); });
@ -1885,21 +1953,16 @@ namespace nodetool
template<class t_payload_net_handler> template<class t_payload_net_handler>
bool node_server<t_payload_net_handler>::set_max_out_peers(const boost::program_options::variables_map& vm, int64_t max) bool node_server<t_payload_net_handler>::set_max_out_peers(const boost::program_options::variables_map& vm, int64_t max)
{ {
if(max == -1) { if(max == -1)
m_config.m_net_config.max_out_connection_count = P2P_DEFAULT_CONNECTIONS_COUNT; max = P2P_DEFAULT_CONNECTIONS_COUNT;
return true;
}
m_config.m_net_config.max_out_connection_count = max; m_config.m_net_config.max_out_connection_count = max;
m_payload_handler.set_max_out_peers(max);
return true; return true;
} }
template<class t_payload_net_handler> template<class t_payload_net_handler>
bool node_server<t_payload_net_handler>::set_max_in_peers(const boost::program_options::variables_map& vm, int64_t max) bool node_server<t_payload_net_handler>::set_max_in_peers(const boost::program_options::variables_map& vm, int64_t max)
{ {
if(max == -1) {
m_config.m_net_config.max_in_connection_count = -1;
return true;
}
m_config.m_net_config.max_in_connection_count = max; m_config.m_net_config.max_in_connection_count = max;
return true; return true;
} }
@ -2010,6 +2073,7 @@ namespace nodetool
{ {
if (m_offline) return true; if (m_offline) return true;
if (!m_exclusive_peers.empty()) return true; if (!m_exclusive_peers.empty()) return true;
if (m_payload_handler.needs_new_sync_connections()) return true;
peerlist_entry pe = AUTO_VAL_INIT(pe); peerlist_entry pe = AUTO_VAL_INIT(pe);
@ -2030,13 +2094,49 @@ namespace nodetool
return true; return true;
} }
m_peerlist.set_peer_just_seen(pe.id, pe.adr); m_peerlist.set_peer_just_seen(pe.id, pe.adr, pe.pruning_seed);
LOG_PRINT_L2("PEER PROMOTED TO WHITE PEER LIST IP address: " << pe.adr.host_str() << " Peer ID: " << peerid_type(pe.id)); LOG_PRINT_L2("PEER PROMOTED TO WHITE PEER LIST IP address: " << pe.adr.host_str() << " Peer ID: " << peerid_type(pe.id));
return true; return true;
} }
template<class t_payload_net_handler>
void node_server<t_payload_net_handler>::add_used_stripe_peer(const typename t_payload_net_handler::connection_context &context)
{
const uint32_t stripe = tools::get_pruning_stripe(context.m_pruning_seed);
if (stripe == 0 || stripe > (1ul << CRYPTONOTE_PRUNING_LOG_STRIPES))
return;
const uint32_t index = stripe - 1;
CRITICAL_REGION_LOCAL(m_used_stripe_peers_mutex);
MINFO("adding stripe " << stripe << " peer: " << context.m_remote_address.str());
std::remove_if(m_used_stripe_peers[index].begin(), m_used_stripe_peers[index].end(),
[&context](const epee::net_utils::network_address &na){ return context.m_remote_address == na; });
m_used_stripe_peers[index].push_back(context.m_remote_address);
}
template<class t_payload_net_handler>
void node_server<t_payload_net_handler>::remove_used_stripe_peer(const typename t_payload_net_handler::connection_context &context)
{
const uint32_t stripe = tools::get_pruning_stripe(context.m_pruning_seed);
if (stripe == 0 || stripe > (1ul << CRYPTONOTE_PRUNING_LOG_STRIPES))
return;
const uint32_t index = stripe - 1;
CRITICAL_REGION_LOCAL(m_used_stripe_peers_mutex);
MINFO("removing stripe " << stripe << " peer: " << context.m_remote_address.str());
std::remove_if(m_used_stripe_peers[index].begin(), m_used_stripe_peers[index].end(),
[&context](const epee::net_utils::network_address &na){ return context.m_remote_address == na; });
}
template<class t_payload_net_handler>
void node_server<t_payload_net_handler>::clear_used_stripe_peers()
{
CRITICAL_REGION_LOCAL(m_used_stripe_peers_mutex);
MINFO("clearing used stripe peers");
for (auto &e: m_used_stripe_peers)
e.clear();
}
template<class t_payload_net_handler> template<class t_payload_net_handler>
void node_server<t_payload_net_handler>::add_upnp_port_mapping(uint32_t port) void node_server<t_payload_net_handler>::add_upnp_port_mapping(uint32_t port)
{ {

@ -56,6 +56,9 @@ namespace nodetool
virtual bool unblock_host(const epee::net_utils::network_address &address)=0; virtual bool unblock_host(const epee::net_utils::network_address &address)=0;
virtual std::map<std::string, time_t> get_blocked_hosts()=0; virtual std::map<std::string, time_t> get_blocked_hosts()=0;
virtual bool add_host_fail(const epee::net_utils::network_address &address)=0; virtual bool add_host_fail(const epee::net_utils::network_address &address)=0;
virtual void add_used_stripe_peer(const t_connection_context &context)=0;
virtual void remove_used_stripe_peer(const t_connection_context &context)=0;
virtual void clear_used_stripe_peers()=0;
}; };
template<class t_connection_context> template<class t_connection_context>
@ -114,5 +117,14 @@ namespace nodetool
{ {
return true; return true;
} }
virtual void add_used_stripe_peer(const t_connection_context &context)
{
}
virtual void remove_used_stripe_peer(const t_connection_context &context)
{
}
virtual void clear_used_stripe_peers()
{
}
}; };
} }

@ -73,16 +73,18 @@ namespace nodetool
bool get_peerlist_full(std::vector<peerlist_entry>& pl_gray, std::vector<peerlist_entry>& pl_white); bool get_peerlist_full(std::vector<peerlist_entry>& pl_gray, std::vector<peerlist_entry>& pl_white);
bool get_white_peer_by_index(peerlist_entry& p, size_t i); bool get_white_peer_by_index(peerlist_entry& p, size_t i);
bool get_gray_peer_by_index(peerlist_entry& p, size_t i); bool get_gray_peer_by_index(peerlist_entry& p, size_t i);
template<typename F> bool foreach(bool white, const F &f);
bool append_with_peer_white(const peerlist_entry& pr); bool append_with_peer_white(const peerlist_entry& pr);
bool append_with_peer_gray(const peerlist_entry& pr); bool append_with_peer_gray(const peerlist_entry& pr);
bool append_with_peer_anchor(const anchor_peerlist_entry& ple); bool append_with_peer_anchor(const anchor_peerlist_entry& ple);
bool set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr); bool set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed);
bool set_peer_unreachable(const peerlist_entry& pr); bool set_peer_unreachable(const peerlist_entry& pr);
bool is_host_allowed(const epee::net_utils::network_address &address); bool is_host_allowed(const epee::net_utils::network_address &address);
bool get_random_gray_peer(peerlist_entry& pe); bool get_random_gray_peer(peerlist_entry& pe);
bool remove_from_peer_gray(const peerlist_entry& pe); bool remove_from_peer_gray(const peerlist_entry& pe);
bool get_and_empty_anchor_peerlist(std::vector<anchor_peerlist_entry>& apl); bool get_and_empty_anchor_peerlist(std::vector<anchor_peerlist_entry>& apl);
bool remove_from_peer_anchor(const epee::net_utils::network_address& addr); bool remove_from_peer_anchor(const epee::net_utils::network_address& addr);
bool remove_from_peer_white(const peerlist_entry& pe);
private: private:
struct by_time{}; struct by_time{};
@ -356,8 +358,19 @@ namespace nodetool
return true; return true;
} }
//-------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------
template<typename F> inline
bool peerlist_manager::foreach(bool white, const F &f)
{
CRITICAL_REGION_LOCAL(m_peerlist_lock);
peers_indexed::index<by_time>::type& by_time_index = white ? m_peers_white.get<by_time>() : m_peers_gray.get<by_time>();
for(const peers_indexed::value_type& vl: boost::adaptors::reverse(by_time_index))
if (!f(vl))
return false;
return true;
}
//--------------------------------------------------------------------------------------------------
inline inline
bool peerlist_manager::set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr) bool peerlist_manager::set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed)
{ {
TRY_ENTRY(); TRY_ENTRY();
CRITICAL_REGION_LOCAL(m_peerlist_lock); CRITICAL_REGION_LOCAL(m_peerlist_lock);
@ -366,6 +379,7 @@ namespace nodetool
ple.adr = addr; ple.adr = addr;
ple.id = peer; ple.id = peer;
ple.last_seen = time(NULL); ple.last_seen = time(NULL);
ple.pruning_seed = pruning_seed;
return append_with_peer_white(ple); return append_with_peer_white(ple);
CATCH_ENTRY_L0("peerlist_manager::set_peer_just_seen()", false); CATCH_ENTRY_L0("peerlist_manager::set_peer_just_seen()", false);
} }
@ -469,6 +483,24 @@ namespace nodetool
} }
//-------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------
inline inline
bool peerlist_manager::remove_from_peer_white(const peerlist_entry& pe)
{
TRY_ENTRY();
CRITICAL_REGION_LOCAL(m_peerlist_lock);
peers_indexed::index_iterator<by_addr>::type iterator = m_peers_white.get<by_addr>().find(pe.adr);
if (iterator != m_peers_white.get<by_addr>().end()) {
m_peers_white.erase(iterator);
}
return true;
CATCH_ENTRY_L0("peerlist_manager::remove_from_peer_white()", false);
}
//--------------------------------------------------------------------------------------------------
inline
bool peerlist_manager::remove_from_peer_gray(const peerlist_entry& pe) bool peerlist_manager::remove_from_peer_gray(const peerlist_entry& pe)
{ {
TRY_ENTRY(); TRY_ENTRY();

@ -33,6 +33,10 @@
#include "net/net_utils_base.h" #include "net/net_utils_base.h"
#include "p2p/p2p_protocol_defs.h" #include "p2p/p2p_protocol_defs.h"
#ifdef CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED
#include "common/pruning.h"
#endif
namespace boost namespace boost
{ {
namespace serialization namespace serialization
@ -77,6 +81,19 @@ namespace boost
a & pl.adr; a & pl.adr;
a & pl.id; a & pl.id;
a & pl.last_seen; a & pl.last_seen;
if (ver < 1)
{
if (!typename Archive::is_saving())
pl.pruning_seed = 0;
return;
}
a & pl.pruning_seed;
#ifdef CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED
if (!typename Archive::is_saving())
{
pl.pruning_seed = tools::make_pruning_seed(1+pl.adr.as<epee::net_utils::ipv4_network_address>().ip() % (1<<CRYPTONOTE_PRUNING_LOG_STRIPES), CRYPTONOTE_PRUNING_LOG_STRIPES);
}
#endif
} }
template <class Archive, class ver_type> template <class Archive, class ver_type>

@ -31,6 +31,7 @@
#pragma once #pragma once
#include <boost/uuid/uuid.hpp> #include <boost/uuid/uuid.hpp>
#include <boost/serialization/version.hpp>
#include "serialization/keyvalue_serialization.h" #include "serialization/keyvalue_serialization.h"
#include "net/net_utils_base.h" #include "net/net_utils_base.h"
#include "misc_language.h" #include "misc_language.h"
@ -72,11 +73,13 @@ namespace nodetool
AddressType adr; AddressType adr;
peerid_type id; peerid_type id;
int64_t last_seen; int64_t last_seen;
uint32_t pruning_seed;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(adr) KV_SERIALIZE(adr)
KV_SERIALIZE(id) KV_SERIALIZE(id)
KV_SERIALIZE(last_seen) KV_SERIALIZE(last_seen)
KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
typedef peerlist_entry_base<epee::net_utils::network_address> peerlist_entry; typedef peerlist_entry_base<epee::net_utils::network_address> peerlist_entry;
@ -122,7 +125,7 @@ namespace nodetool
ss << std::setfill ('0') << std::setw (8) << std::hex << std::noshowbase; ss << std::setfill ('0') << std::setw (8) << std::hex << std::noshowbase;
for(const peerlist_entry& pe: pl) for(const peerlist_entry& pe: pl)
{ {
ss << pe.id << "\t" << pe.adr.str() << " \tlast_seen: " << epee::misc_utils::get_time_interval_string(now_time - pe.last_seen) << std::endl; ss << pe.id << "\t" << pe.adr.str() << " \tpruning seed " << pe.pruning_seed << " \tlast_seen: " << epee::misc_utils::get_time_interval_string(now_time - pe.last_seen) << std::endl;
} }
return ss.str(); return ss.str();
} }
@ -205,7 +208,7 @@ namespace nodetool
{ {
const epee::net_utils::network_address &na = p.adr; const epee::net_utils::network_address &na = p.adr;
const epee::net_utils::ipv4_network_address &ipv4 = na.as<const epee::net_utils::ipv4_network_address>(); const epee::net_utils::ipv4_network_address &ipv4 = na.as<const epee::net_utils::ipv4_network_address>();
local_peerlist.push_back(peerlist_entry_base<network_address_old>({{ipv4.ip(), ipv4.port()}, p.id, p.last_seen})); local_peerlist.push_back(peerlist_entry_base<network_address_old>({{ipv4.ip(), ipv4.port()}, p.id, p.last_seen, p.pruning_seed}));
} }
else else
MDEBUG("Not including in legacy peer list: " << p.adr.str()); MDEBUG("Not including in legacy peer list: " << p.adr.str());
@ -220,7 +223,7 @@ namespace nodetool
std::vector<peerlist_entry_base<network_address_old>> local_peerlist; std::vector<peerlist_entry_base<network_address_old>> local_peerlist;
epee::serialization::selector<is_store>::serialize_stl_container_pod_val_as_blob(local_peerlist, stg, hparent_section, "local_peerlist"); epee::serialization::selector<is_store>::serialize_stl_container_pod_val_as_blob(local_peerlist, stg, hparent_section, "local_peerlist");
for (const auto &p: local_peerlist) for (const auto &p: local_peerlist)
((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen})); ((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen, p.pruning_seed}));
} }
} }
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
@ -463,5 +466,6 @@ namespace nodetool
} }
BOOST_CLASS_VERSION(nodetool::peerlist_entry, 1)

@ -485,8 +485,8 @@ namespace cryptonote
vh.push_back(*reinterpret_cast<const crypto::hash*>(b.data())); vh.push_back(*reinterpret_cast<const crypto::hash*>(b.data()));
} }
std::vector<crypto::hash> missed_txs; std::vector<crypto::hash> missed_txs;
std::vector<transaction> txs; std::vector<std::tuple<crypto::hash, cryptonote::blobdata, crypto::hash, cryptonote::blobdata>> txs;
bool r = m_core.get_transactions(vh, txs, missed_txs); bool r = m_core.get_split_transactions_blobs(vh, txs, missed_txs);
if(!r) if(!r)
{ {
res.status = "Failed"; res.status = "Failed";
@ -506,7 +506,7 @@ namespace cryptonote
if(r) if(r)
{ {
// sort to match original request // sort to match original request
std::vector<transaction> sorted_txs; std::vector<std::tuple<crypto::hash, cryptonote::blobdata, crypto::hash, cryptonote::blobdata>> sorted_txs;
std::vector<tx_info>::const_iterator i; std::vector<tx_info>::const_iterator i;
unsigned txs_processed = 0; unsigned txs_processed = 0;
for (const crypto::hash &h: vh) for (const crypto::hash &h: vh)
@ -519,7 +519,7 @@ namespace cryptonote
return true; return true;
} }
// core returns the ones it finds in the right order // core returns the ones it finds in the right order
if (get_transaction_hash(txs[txs_processed]) != h) if (std::get<0>(txs[txs_processed]) != h)
{ {
res.status = "Failed: tx hash mismatch"; res.status = "Failed: tx hash mismatch";
return true; return true;
@ -535,7 +535,16 @@ namespace cryptonote
res.status = "Failed to parse and validate tx from blob"; res.status = "Failed to parse and validate tx from blob";
return true; return true;
} }
sorted_txs.push_back(tx); std::stringstream ss;
binary_archive<true> ba(ss);
bool r = const_cast<cryptonote::transaction&>(tx).serialize_base(ba);
if (!r)
{
res.status = "Failed to serialize transaction base";
return true;
}
const cryptonote::blobdata pruned = ss.str();
sorted_txs.push_back(std::make_tuple(h, pruned, get_transaction_prunable_hash(tx), std::string(i->tx_blob, pruned.size())));
missed_txs.erase(std::find(missed_txs.begin(), missed_txs.end(), h)); missed_txs.erase(std::find(missed_txs.begin(), missed_txs.end(), h));
pool_tx_hashes.insert(h); pool_tx_hashes.insert(h);
const std::string hash_string = epee::string_tools::pod_to_hex(h); const std::string hash_string = epee::string_tools::pod_to_hex(h);
@ -564,11 +573,36 @@ namespace cryptonote
crypto::hash tx_hash = *vhi++; crypto::hash tx_hash = *vhi++;
e.tx_hash = *txhi++; e.tx_hash = *txhi++;
pruned_transaction pruned_tx{tx}; e.prunable_hash = epee::string_tools::pod_to_hex(std::get<2>(tx));
blobdata blob = req.prune ? t_serializable_object_to_blob(pruned_tx) : t_serializable_object_to_blob(tx); if (req.split || req.prune || std::get<3>(tx).empty())
e.as_hex = string_tools::buff_to_hex_nodelimer(blob); {
if (req.decode_as_json) e.pruned_as_hex = string_tools::buff_to_hex_nodelimer(std::get<1>(tx));
e.as_json = req.prune ? obj_to_json_str(pruned_tx) : obj_to_json_str(tx); if (!req.prune)
e.prunable_as_hex = string_tools::buff_to_hex_nodelimer(std::get<3>(tx));
}
else
{
cryptonote::blobdata tx_data;
if (req.prune)
tx_data = std::get<1>(tx);
else
tx_data = std::get<1>(tx) + std::get<3>(tx);
e.as_hex = string_tools::buff_to_hex_nodelimer(tx_data);
if (req.decode_as_json && !tx_data.empty())
{
cryptonote::transaction t;
if (cryptonote::parse_and_validate_tx_from_blob(tx_data, t))
{
if (req.prune)
{
pruned_transaction pruned_tx{t};
e.as_json = obj_to_json_str(pruned_tx);
}
else
e.as_json = obj_to_json_str(t);
}
}
}
e.in_pool = pool_tx_hashes.find(tx_hash) != pool_tx_hashes.end(); e.in_pool = pool_tx_hashes.find(tx_hash) != pool_tx_hashes.end();
if (e.in_pool) if (e.in_pool)
{ {
@ -869,9 +903,9 @@ namespace cryptonote
{ {
if (entry.adr.get_type_id() == epee::net_utils::ipv4_network_address::ID) if (entry.adr.get_type_id() == epee::net_utils::ipv4_network_address::ID)
res.white_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv4_network_address>().ip(), res.white_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv4_network_address>().ip(),
entry.adr.as<epee::net_utils::ipv4_network_address>().port(), entry.last_seen); entry.adr.as<epee::net_utils::ipv4_network_address>().port(), entry.last_seen, entry.pruning_seed);
else else
res.white_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen); res.white_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen, entry.pruning_seed);
} }
res.gray_list.reserve(gray_list.size()); res.gray_list.reserve(gray_list.size());
@ -879,9 +913,9 @@ namespace cryptonote
{ {
if (entry.adr.get_type_id() == epee::net_utils::ipv4_network_address::ID) if (entry.adr.get_type_id() == epee::net_utils::ipv4_network_address::ID)
res.gray_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv4_network_address>().ip(), res.gray_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv4_network_address>().ip(),
entry.adr.as<epee::net_utils::ipv4_network_address>().port(), entry.last_seen); entry.adr.as<epee::net_utils::ipv4_network_address>().port(), entry.last_seen, entry.pruning_seed);
else else
res.gray_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen); res.gray_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen, entry.pruning_seed);
} }
res.status = CORE_RPC_STATUS_OK; res.status = CORE_RPC_STATUS_OK;
@ -2102,6 +2136,7 @@ namespace cryptonote
m_core.get_blockchain_top(res.height, top_hash); m_core.get_blockchain_top(res.height, top_hash);
++res.height; // turn top block height into blockchain height ++res.height; // turn top block height into blockchain height
res.target_height = m_core.get_target_blockchain_height(); res.target_height = m_core.get_target_blockchain_height();
res.next_needed_pruning_seed = m_p2p.get_payload_object().get_next_needed_pruning_stripe().second;
for (const auto &c: m_p2p.get_payload_object().get_connections()) for (const auto &c: m_p2p.get_payload_object().get_connections())
res.peers.push_back({c}); res.peers.push_back({c});
@ -2116,6 +2151,7 @@ namespace cryptonote
res.spans.push_back({span.start_block_height, span.nblocks, span_connection_id, (uint32_t)(span.rate + 0.5f), speed, span.size, address}); res.spans.push_back({span.start_block_height, span.nblocks, span_connection_id, (uint32_t)(span.rate + 0.5f), speed, span.size, address});
return true; return true;
}); });
res.overview = block_queue.get_overview(res.height);
res.status = CORE_RPC_STATUS_OK; res.status = CORE_RPC_STATUS_OK;
return true; return true;
@ -2215,6 +2251,29 @@ namespace cryptonote
return true; return true;
} }
//------------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_prune_blockchain(const COMMAND_RPC_PRUNE_BLOCKCHAIN::request& req, COMMAND_RPC_PRUNE_BLOCKCHAIN::response& res, epee::json_rpc::error& error_resp)
{
try
{
if (!(req.check ? m_core.check_blockchain_pruning() : m_core.prune_blockchain()))
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = req.check ? "Failed to check blockchain pruning" : "Failed to prune blockchain";
return false;
}
res.pruning_seed = m_core.get_blockchain_pruning_seed();
}
catch (const std::exception &e)
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Failed to prune blockchain";
return false;
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
const command_line::arg_descriptor<std::string, false, true, 2> core_rpc_server::arg_rpc_bind_port = { const command_line::arg_descriptor<std::string, false, true, 2> core_rpc_server::arg_rpc_bind_port = {

@ -153,6 +153,7 @@ namespace cryptonote
MAP_JON_RPC_WE_IF("sync_info", on_sync_info, COMMAND_RPC_SYNC_INFO, !m_restricted) MAP_JON_RPC_WE_IF("sync_info", on_sync_info, COMMAND_RPC_SYNC_INFO, !m_restricted)
MAP_JON_RPC_WE("get_txpool_backlog", on_get_txpool_backlog, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG) MAP_JON_RPC_WE("get_txpool_backlog", on_get_txpool_backlog, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG)
MAP_JON_RPC_WE("get_output_distribution", on_get_output_distribution, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION) MAP_JON_RPC_WE("get_output_distribution", on_get_output_distribution, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION)
MAP_JON_RPC_WE_IF("prune_blockchain", on_prune_blockchain, COMMAND_RPC_PRUNE_BLOCKCHAIN, !m_restricted)
END_JSON_RPC_MAP() END_JSON_RPC_MAP()
END_URI_MAP2() END_URI_MAP2()
@ -217,6 +218,7 @@ namespace cryptonote
bool on_sync_info(const COMMAND_RPC_SYNC_INFO::request& req, COMMAND_RPC_SYNC_INFO::response& res, epee::json_rpc::error& error_resp); bool on_sync_info(const COMMAND_RPC_SYNC_INFO::request& req, COMMAND_RPC_SYNC_INFO::response& res, epee::json_rpc::error& error_resp);
bool on_get_txpool_backlog(const COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::response& res, epee::json_rpc::error& error_resp); bool on_get_txpool_backlog(const COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::response& res, epee::json_rpc::error& error_resp);
bool on_get_output_distribution(const COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request& req, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response& res, epee::json_rpc::error& error_resp); bool on_get_output_distribution(const COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request& req, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response& res, epee::json_rpc::error& error_resp);
bool on_prune_blockchain(const COMMAND_RPC_PRUNE_BLOCKCHAIN::request& req, COMMAND_RPC_PRUNE_BLOCKCHAIN::response& res, epee::json_rpc::error& error_resp);
//----------------------- //-----------------------
private: private:

@ -84,7 +84,7 @@ namespace cryptonote
// advance which version they will stop working with // advance which version they will stop working with
// Don't go over 32767 for any of these // Don't go over 32767 for any of these
#define CORE_RPC_VERSION_MAJOR 2 #define CORE_RPC_VERSION_MAJOR 2
#define CORE_RPC_VERSION_MINOR 2 #define CORE_RPC_VERSION_MINOR 3
#define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR)
@ -601,11 +601,13 @@ namespace cryptonote
std::vector<std::string> txs_hashes; std::vector<std::string> txs_hashes;
bool decode_as_json; bool decode_as_json;
bool prune; bool prune;
bool split;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(txs_hashes) KV_SERIALIZE(txs_hashes)
KV_SERIALIZE(decode_as_json) KV_SERIALIZE(decode_as_json)
KV_SERIALIZE_OPT(prune, false) KV_SERIALIZE_OPT(prune, false)
KV_SERIALIZE_OPT(split, false)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
@ -613,6 +615,9 @@ namespace cryptonote
{ {
std::string tx_hash; std::string tx_hash;
std::string as_hex; std::string as_hex;
std::string pruned_as_hex;
std::string prunable_as_hex;
std::string prunable_hash;
std::string as_json; std::string as_json;
bool in_pool; bool in_pool;
bool double_spend_seen; bool double_spend_seen;
@ -623,6 +628,9 @@ namespace cryptonote
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_hash)
KV_SERIALIZE(as_hex) KV_SERIALIZE(as_hex)
KV_SERIALIZE(pruned_as_hex)
KV_SERIALIZE(prunable_as_hex)
KV_SERIALIZE(prunable_hash)
KV_SERIALIZE(as_json) KV_SERIALIZE(as_json)
KV_SERIALIZE(in_pool) KV_SERIALIZE(in_pool)
KV_SERIALIZE(double_spend_seen) KV_SERIALIZE(double_spend_seen)
@ -1311,14 +1319,15 @@ namespace cryptonote
uint32_t ip; uint32_t ip;
uint16_t port; uint16_t port;
uint64_t last_seen; uint64_t last_seen;
uint32_t pruning_seed;
peer() = default; peer() = default;
peer(uint64_t id, const std::string &host, uint64_t last_seen) peer(uint64_t id, const std::string &host, uint64_t last_seen, uint32_t pruning_seed)
: id(id), host(host), ip(0), port(0), last_seen(last_seen) : id(id), host(host), ip(0), port(0), last_seen(last_seen), pruning_seed(pruning_seed)
{} {}
peer(uint64_t id, uint32_t ip, uint16_t port, uint64_t last_seen) peer(uint64_t id, uint32_t ip, uint16_t port, uint64_t last_seen, uint32_t pruning_seed)
: id(id), host(std::to_string(ip)), ip(ip), port(port), last_seen(last_seen) : id(id), host(std::to_string(ip)), ip(ip), port(port), last_seen(last_seen), pruning_seed(pruning_seed)
{} {}
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
@ -1327,6 +1336,7 @@ namespace cryptonote
KV_SERIALIZE(ip) KV_SERIALIZE(ip)
KV_SERIALIZE(port) KV_SERIALIZE(port)
KV_SERIALIZE(last_seen) KV_SERIALIZE(last_seen)
KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
@ -2238,15 +2248,19 @@ namespace cryptonote
std::string status; std::string status;
uint64_t height; uint64_t height;
uint64_t target_height; uint64_t target_height;
uint32_t next_needed_pruning_seed;
std::list<peer> peers; std::list<peer> peers;
std::list<span> spans; std::list<span> spans;
std::string overview;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(status) KV_SERIALIZE(status)
KV_SERIALIZE(height) KV_SERIALIZE(height)
KV_SERIALIZE(target_height) KV_SERIALIZE(target_height)
KV_SERIALIZE(next_needed_pruning_seed)
KV_SERIALIZE(peers) KV_SERIALIZE(peers)
KV_SERIALIZE(spans) KV_SERIALIZE(spans)
KV_SERIALIZE(overview)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
}; };
@ -2351,4 +2365,27 @@ namespace cryptonote
}; };
}; };
struct COMMAND_RPC_PRUNE_BLOCKCHAIN
{
struct request
{
bool check;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_OPT(check, false)
END_KV_SERIALIZE_MAP()
};
struct response
{
uint32_t pruning_seed;
std::string status;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(status)
KV_SERIALIZE(pruning_seed)
END_KV_SERIALIZE_MAP()
};
};
} }

@ -79,6 +79,7 @@ namespace rpc
uint32_t ip; uint32_t ip;
uint16_t port; uint16_t port;
uint64_t last_seen; uint64_t last_seen;
uint32_t pruning_seed;
}; };
struct tx_in_pool struct tx_in_pool

@ -734,6 +734,7 @@ void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::peer& peer, ra
INSERT_INTO_JSON_OBJECT(val, doc, ip, peer.ip); INSERT_INTO_JSON_OBJECT(val, doc, ip, peer.ip);
INSERT_INTO_JSON_OBJECT(val, doc, port, peer.port); INSERT_INTO_JSON_OBJECT(val, doc, port, peer.port);
INSERT_INTO_JSON_OBJECT(val, doc, last_seen, peer.last_seen); INSERT_INTO_JSON_OBJECT(val, doc, last_seen, peer.last_seen);
INSERT_INTO_JSON_OBJECT(val, doc, pruning_seed, peer.pruning_seed);
} }
@ -748,6 +749,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::peer& peer)
GET_FROM_JSON_OBJECT(val, peer.ip, ip); GET_FROM_JSON_OBJECT(val, peer.ip, ip);
GET_FROM_JSON_OBJECT(val, peer.port, port); GET_FROM_JSON_OBJECT(val, peer.port, port);
GET_FROM_JSON_OBJECT(val, peer.last_seen, last_seen); GET_FROM_JSON_OBJECT(val, peer.last_seen, last_seen);
GET_FROM_JSON_OBJECT(val, peer.pruning_seed, pruning_seed);
} }
void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::tx_in_pool& tx, rapidjson::Value& val) void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::tx_in_pool& tx, rapidjson::Value& val)

@ -814,6 +814,43 @@ static void setup_shim(hw::wallet_shim * shim, tools::wallet2 * wallet)
shim->get_tx_pub_key_from_received_outs = boost::bind(&tools::wallet2::get_tx_pub_key_from_received_outs, wallet, _1); shim->get_tx_pub_key_from_received_outs = boost::bind(&tools::wallet2::get_tx_pub_key_from_received_outs, wallet, _1);
} }
bool get_pruned_tx(const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry &entry, cryptonote::transaction &tx, crypto::hash &tx_hash)
{
cryptonote::blobdata bd;
// easy case if we have the whole tx
if (!entry.as_hex.empty() || (!entry.prunable_as_hex.empty() && !entry.pruned_as_hex.empty()))
{
CHECK_AND_ASSERT_MES(epee::string_tools::parse_hexstr_to_binbuff(entry.as_hex.empty() ? entry.pruned_as_hex + entry.prunable_as_hex : entry.as_hex, bd), false, "Failed to parse tx data");
CHECK_AND_ASSERT_MES(cryptonote::parse_and_validate_tx_from_blob(bd, tx), false, "Invalid tx data");
tx_hash = cryptonote::get_transaction_hash(tx);
// if the hash was given, check it matches
CHECK_AND_ASSERT_MES(entry.tx_hash.empty() || epee::string_tools::pod_to_hex(tx_hash) == entry.tx_hash, false,
"Response claims a different hash than the data yields");
return true;
}
// case of a pruned tx with its prunable data hash
if (!entry.pruned_as_hex.empty() && !entry.prunable_hash.empty())
{
crypto::hash ph;
CHECK_AND_ASSERT_MES(epee::string_tools::hex_to_pod(entry.prunable_hash, ph), false, "Failed to parse prunable hash");
CHECK_AND_ASSERT_MES(epee::string_tools::parse_hexstr_to_binbuff(entry.pruned_as_hex, bd), false, "Failed to parse pruned data");
CHECK_AND_ASSERT_MES(parse_and_validate_tx_base_from_blob(bd, tx), false, "Invalid base tx data");
// only v2 txes can calculate their txid after pruned
if (bd[0] > 1)
{
tx_hash = cryptonote::get_pruned_transaction_hash(tx, ph);
}
else
{
// for v1, we trust the dameon
CHECK_AND_ASSERT_MES(epee::string_tools::hex_to_pod(entry.tx_hash, tx_hash), false, "Failed to parse tx hash");
}
return true;
}
return false;
}
//----------------------------------------------------------------- //-----------------------------------------------------------------
} //namespace } //namespace
@ -2588,7 +2625,7 @@ void wallet2::update_pool_state(bool refreshed)
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(p.first)); req.txs_hashes.push_back(epee::string_tools::pod_to_hex(p.first));
MDEBUG("asking for " << txids.size() << " transactions"); MDEBUG("asking for " << txids.size() << " transactions");
req.decode_as_json = false; req.decode_as_json = false;
req.prune = false; req.prune = true;
m_daemon_rpc_mutex.lock(); m_daemon_rpc_mutex.lock();
bool r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); bool r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock(); m_daemon_rpc_mutex.unlock();
@ -2603,11 +2640,10 @@ void wallet2::update_pool_state(bool refreshed)
{ {
cryptonote::transaction tx; cryptonote::transaction tx;
cryptonote::blobdata bd; cryptonote::blobdata bd;
crypto::hash tx_hash, tx_prefix_hash; crypto::hash tx_hash;
if (epee::string_tools::parse_hexstr_to_binbuff(tx_entry.as_hex, bd))
if (get_pruned_tx(tx_entry, tx, tx_hash))
{ {
if (cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash))
{
const std::vector<std::pair<crypto::hash, bool>>::const_iterator i = std::find_if(txids.begin(), txids.end(), const std::vector<std::pair<crypto::hash, bool>>::const_iterator i = std::find_if(txids.begin(), txids.end(),
[tx_hash](const std::pair<crypto::hash, bool> &e) { return e.first == tx_hash; }); [tx_hash](const std::pair<crypto::hash, bool> &e) { return e.first == tx_hash; });
if (i != txids.end()) if (i != txids.end())
@ -2624,11 +2660,6 @@ void wallet2::update_pool_state(bool refreshed)
{ {
MERROR("Got txid " << tx_hash << " which we did not ask for"); MERROR("Got txid " << tx_hash << " which we did not ask for");
} }
}
else
{
LOG_PRINT_L0("failed to validate transaction from daemon");
}
} }
else else
{ {
@ -6814,11 +6845,12 @@ bool wallet2::find_and_save_rings(bool force)
MDEBUG("Found " << std::to_string(txs_hashes.size()) << " transactions"); MDEBUG("Found " << std::to_string(txs_hashes.size()) << " transactions");
// get those transactions from the daemon // get those transactions from the daemon
auto it = txs_hashes.begin();
static const size_t SLICE_SIZE = 200; static const size_t SLICE_SIZE = 200;
for (size_t slice = 0; slice < txs_hashes.size(); slice += SLICE_SIZE) for (size_t slice = 0; slice < txs_hashes.size(); slice += SLICE_SIZE)
{ {
req.decode_as_json = false; req.decode_as_json = false;
req.prune = false; req.prune = true;
req.txs_hashes.clear(); req.txs_hashes.clear();
size_t ntxes = slice + SLICE_SIZE > txs_hashes.size() ? txs_hashes.size() - slice : SLICE_SIZE; size_t ntxes = slice + SLICE_SIZE > txs_hashes.size() ? txs_hashes.size() - slice : SLICE_SIZE;
for (size_t s = slice; s < slice + ntxes; ++s) for (size_t s = slice; s < slice + ntxes; ++s)
@ -6837,19 +6869,15 @@ bool wallet2::find_and_save_rings(bool force)
MDEBUG("Scanning " << res.txs.size() << " transactions"); MDEBUG("Scanning " << res.txs.size() << " transactions");
THROW_WALLET_EXCEPTION_IF(slice + res.txs.size() > txs_hashes.size(), error::wallet_internal_error, "Unexpected tx array size"); THROW_WALLET_EXCEPTION_IF(slice + res.txs.size() > txs_hashes.size(), error::wallet_internal_error, "Unexpected tx array size");
auto it = req.txs_hashes.begin();
for (size_t i = 0; i < res.txs.size(); ++i, ++it) for (size_t i = 0; i < res.txs.size(); ++i, ++it)
{ {
const auto &tx_info = res.txs[i]; const auto &tx_info = res.txs[i];
THROW_WALLET_EXCEPTION_IF(tx_info.tx_hash != epee::string_tools::pod_to_hex(txs_hashes[slice + i]), error::wallet_internal_error, "Wrong txid received"); cryptonote::transaction tx;
THROW_WALLET_EXCEPTION_IF(tx_info.tx_hash != *it, error::wallet_internal_error, "Wrong txid received"); crypto::hash tx_hash;
cryptonote::blobdata bd; THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(tx_info, tx, tx_hash), error::wallet_internal_error,
THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(tx_info.as_hex, bd), error::wallet_internal_error, "failed to parse tx from hexstr"); "Failed to get transaction from daemon");
cryptonote::transaction tx; THROW_WALLET_EXCEPTION_IF(!(tx_hash == *it), error::wallet_internal_error, "Wrong txid received");
crypto::hash tx_hash, tx_prefix_hash; THROW_WALLET_EXCEPTION_IF(!add_rings(get_ringdb_key(), tx), error::wallet_internal_error, "Failed to save ring");
THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob");
THROW_WALLET_EXCEPTION_IF(epee::string_tools::pod_to_hex(tx_hash) != tx_info.tx_hash, error::wallet_internal_error, "txid mismatch");
THROW_WALLET_EXCEPTION_IF(!add_rings(get_ringdb_key(), tx), error::wallet_internal_error, "Failed to save ring");
} }
} }
@ -9782,7 +9810,7 @@ void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_
COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req);
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false; req.decode_as_json = false;
req.prune = false; req.prune = true;
COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res);
bool r; bool r;
{ {
@ -9795,11 +9823,10 @@ void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_
THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error,
"daemon returned wrong response for gettransactions, wrong txs count = " + "daemon returned wrong response for gettransactions, wrong txs count = " +
std::to_string(res.txs.size()) + ", expected 1"); std::to_string(res.txs.size()) + ", expected 1");
cryptonote::blobdata bd;
THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(res.txs[0].as_hex, bd), error::wallet_internal_error, "failed to parse tx from hexstr");
cryptonote::transaction tx; cryptonote::transaction tx;
crypto::hash tx_hash, tx_prefix_hash; crypto::hash tx_hash;
THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob"); THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(res.txs[0], tx, tx_hash), error::wallet_internal_error,
"Failed to get transaction from daemon");
THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "txid mismatch"); THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "txid mismatch");
std::vector<tx_extra_field> tx_extra_fields; std::vector<tx_extra_field> tx_extra_fields;
THROW_WALLET_EXCEPTION_IF(!parse_tx_extra(tx.extra, tx_extra_fields), error::wallet_internal_error, "Transaction extra has unsupported format"); THROW_WALLET_EXCEPTION_IF(!parse_tx_extra(tx.extra, tx_extra_fields), error::wallet_internal_error, "Transaction extra has unsupported format");
@ -9833,7 +9860,7 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string
COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req);
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false; req.decode_as_json = false;
req.prune = false; req.prune = true;
COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res);
bool r; bool r;
{ {
@ -9846,12 +9873,10 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string
THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error,
"daemon returned wrong response for gettransactions, wrong txs count = " + "daemon returned wrong response for gettransactions, wrong txs count = " +
std::to_string(res.txs.size()) + ", expected 1"); std::to_string(res.txs.size()) + ", expected 1");
cryptonote::blobdata bd;
THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(res.txs[0].as_hex, bd), error::wallet_internal_error, "failed to parse tx from hexstr");
cryptonote::transaction tx; cryptonote::transaction tx;
crypto::hash tx_hash, tx_prefix_hash; crypto::hash tx_hash;
THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob"); THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(res.txs[0], tx, tx_hash), error::wallet_internal_error, "Failed to get tx from daemon");
THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "txid mismatch");
std::vector<std::vector<crypto::signature>> signatures; std::vector<std::vector<crypto::signature>> signatures;
@ -9953,7 +9978,7 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes
COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req);
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false; req.decode_as_json = false;
req.prune = false; req.prune = true;
COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res);
bool r; bool r;
{ {
@ -9966,12 +9991,10 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes
THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error,
"daemon returned wrong response for gettransactions, wrong txs count = " + "daemon returned wrong response for gettransactions, wrong txs count = " +
std::to_string(res.txs.size()) + ", expected 1"); std::to_string(res.txs.size()) + ", expected 1");
cryptonote::blobdata bd;
THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(res.txs[0].as_hex, bd), error::wallet_internal_error, "failed to parse tx from hexstr");
cryptonote::transaction tx; cryptonote::transaction tx;
crypto::hash tx_hash, tx_prefix_hash; crypto::hash tx_hash;
THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob"); THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(res.txs[0], tx, tx_hash), error::wallet_internal_error, "failed to get tx from daemon");
THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "txid mismatch");
// check signature size // check signature size
size_t num_sigs = 0; size_t num_sigs = 0;
@ -10078,24 +10101,30 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de
COMMAND_RPC_GET_TRANSACTIONS::response res; COMMAND_RPC_GET_TRANSACTIONS::response res;
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false; req.decode_as_json = false;
req.prune = false; req.prune = true;
m_daemon_rpc_mutex.lock(); m_daemon_rpc_mutex.lock();
bool ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); bool ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client);
m_daemon_rpc_mutex.unlock(); m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1),
error::wallet_internal_error, "Failed to get transaction from daemon"); error::wallet_internal_error, "Failed to get transaction from daemon");
cryptonote::blobdata tx_data; cryptonote::transaction tx;
crypto::hash tx_hash;
if (res.txs.size() == 1) if (res.txs.size() == 1)
ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); {
ok = get_pruned_tx(res.txs.front(), tx, tx_hash);
THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
}
else else
{
cryptonote::blobdata tx_data;
crypto::hash tx_prefix_hash;
ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data);
THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash),
error::wallet_internal_error, "Failed to validate transaction from daemon");
}
crypto::hash tx_hash, tx_prefix_hash;
cryptonote::transaction tx;
THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error,
"Failed to validate transaction from daemon");
THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error,
"Failed to get the right transaction from daemon"); "Failed to get the right transaction from daemon");
THROW_WALLET_EXCEPTION_IF(!additional_derivations.empty() && additional_derivations.size() != tx.vout.size(), error::wallet_internal_error, THROW_WALLET_EXCEPTION_IF(!additional_derivations.empty() && additional_derivations.size() != tx.vout.size(), error::wallet_internal_error,
@ -10218,24 +10247,30 @@ std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::ac
COMMAND_RPC_GET_TRANSACTIONS::response res; COMMAND_RPC_GET_TRANSACTIONS::response res;
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false; req.decode_as_json = false;
req.prune = false; req.prune = true;
m_daemon_rpc_mutex.lock(); m_daemon_rpc_mutex.lock();
bool ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); bool ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client);
m_daemon_rpc_mutex.unlock(); m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1),
error::wallet_internal_error, "Failed to get transaction from daemon"); error::wallet_internal_error, "Failed to get transaction from daemon");
cryptonote::blobdata tx_data; cryptonote::transaction tx;
crypto::hash tx_hash;
if (res.txs.size() == 1) if (res.txs.size() == 1)
ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); {
ok = get_pruned_tx(res.txs.front(), tx, tx_hash);
THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
}
else else
{
cryptonote::blobdata tx_data;
crypto::hash tx_prefix_hash;
ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data);
THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash),
error::wallet_internal_error, "Failed to validate transaction from daemon");
}
crypto::hash tx_hash, tx_prefix_hash;
cryptonote::transaction tx;
THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error,
"Failed to validate transaction from daemon");
THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon");
crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx);
@ -10330,24 +10365,30 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account
COMMAND_RPC_GET_TRANSACTIONS::response res; COMMAND_RPC_GET_TRANSACTIONS::response res;
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false; req.decode_as_json = false;
req.prune = false; req.prune = true;
m_daemon_rpc_mutex.lock(); m_daemon_rpc_mutex.lock();
bool ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); bool ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client);
m_daemon_rpc_mutex.unlock(); m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1),
error::wallet_internal_error, "Failed to get transaction from daemon"); error::wallet_internal_error, "Failed to get transaction from daemon");
cryptonote::blobdata tx_data; cryptonote::transaction tx;
crypto::hash tx_hash;
if (res.txs.size() == 1) if (res.txs.size() == 1)
ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); {
ok = get_pruned_tx(res.txs.front(), tx, tx_hash);
THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
}
else else
{
cryptonote::blobdata tx_data;
crypto::hash tx_prefix_hash;
ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data);
THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash),
error::wallet_internal_error, "Failed to validate transaction from daemon");
}
crypto::hash tx_hash, tx_prefix_hash;
cryptonote::transaction tx;
THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error,
"Failed to validate transaction from daemon");
THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon");
crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx);
@ -10566,7 +10607,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr
for (size_t i = 0; i < proofs.size(); ++i) for (size_t i = 0; i < proofs.size(); ++i)
gettx_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(proofs[i].txid)); gettx_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(proofs[i].txid));
gettx_req.decode_as_json = false; gettx_req.decode_as_json = false;
gettx_req.prune = false; gettx_req.prune = true;
m_daemon_rpc_mutex.lock(); m_daemon_rpc_mutex.lock();
bool ok = net_utils::invoke_http_json("/gettransactions", gettx_req, gettx_res, m_http_client); bool ok = net_utils::invoke_http_json("/gettransactions", gettx_req, gettx_res, m_http_client);
m_daemon_rpc_mutex.unlock(); m_daemon_rpc_mutex.unlock();
@ -10590,14 +10631,11 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr
const reserve_proof_entry& proof = proofs[i]; const reserve_proof_entry& proof = proofs[i];
THROW_WALLET_EXCEPTION_IF(gettx_res.txs[i].in_pool, error::wallet_internal_error, "Tx is unconfirmed"); THROW_WALLET_EXCEPTION_IF(gettx_res.txs[i].in_pool, error::wallet_internal_error, "Tx is unconfirmed");
cryptonote::blobdata tx_data; cryptonote::transaction tx;
ok = string_tools::parse_hexstr_to_binbuff(gettx_res.txs[i].as_hex, tx_data); crypto::hash tx_hash;
ok = get_pruned_tx(gettx_res.txs[i], tx, tx_hash);
THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
crypto::hash tx_hash, tx_prefix_hash;
cryptonote::transaction tx;
THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error,
"Failed to validate transaction from daemon");
THROW_WALLET_EXCEPTION_IF(tx_hash != proof.txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); THROW_WALLET_EXCEPTION_IF(tx_hash != proof.txid, error::wallet_internal_error, "Failed to get the right transaction from daemon");
THROW_WALLET_EXCEPTION_IF(proof.index_in_tx >= tx.vout.size(), error::wallet_internal_error, "index_in_tx is out of bound"); THROW_WALLET_EXCEPTION_IF(proof.index_in_tx >= tx.vout.size(), error::wallet_internal_error, "index_in_tx is out of bound");
@ -11207,7 +11245,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
COMMAND_RPC_GET_TRANSACTIONS::request gettxs_req; COMMAND_RPC_GET_TRANSACTIONS::request gettxs_req;
COMMAND_RPC_GET_TRANSACTIONS::response gettxs_res; COMMAND_RPC_GET_TRANSACTIONS::response gettxs_res;
gettxs_req.decode_as_json = false; gettxs_req.decode_as_json = false;
gettxs_req.prune = false; gettxs_req.prune = true;
gettxs_req.txs_hashes.reserve(spent_txids.size()); gettxs_req.txs_hashes.reserve(spent_txids.size());
for (const crypto::hash& spent_txid : spent_txids) for (const crypto::hash& spent_txid : spent_txids)
gettxs_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(spent_txid)); gettxs_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(spent_txid));
@ -11227,17 +11265,16 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
PERF_TIMER_START(import_key_images_F); PERF_TIMER_START(import_key_images_F);
auto spent_txid = spent_txids.begin(); auto spent_txid = spent_txids.begin();
hw::device &hwdev = m_account.get_device(); hw::device &hwdev = m_account.get_device();
auto it = spent_txids.begin();
for (const COMMAND_RPC_GET_TRANSACTIONS::entry& e : gettxs_res.txs) for (const COMMAND_RPC_GET_TRANSACTIONS::entry& e : gettxs_res.txs)
{ {
THROW_WALLET_EXCEPTION_IF(e.in_pool, error::wallet_internal_error, "spent tx isn't supposed to be in txpool"); THROW_WALLET_EXCEPTION_IF(e.in_pool, error::wallet_internal_error, "spent tx isn't supposed to be in txpool");
// parse tx
cryptonote::blobdata bd;
THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(e.as_hex, bd), error::wallet_internal_error, "parse_hexstr_to_binbuff failed");
cryptonote::transaction spent_tx; cryptonote::transaction spent_tx;
crypto::hash spnet_txid_parsed, spent_txid_prefix; crypto::hash spnet_txid_parsed;
THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, spent_tx, spnet_txid_parsed, spent_txid_prefix), error::wallet_internal_error, "parse_and_validate_tx_from_blob failed"); THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(e, spent_tx, spnet_txid_parsed), error::wallet_internal_error, "Failed to get tx from daemon");
THROW_WALLET_EXCEPTION_IF(*spent_txid != spnet_txid_parsed, error::wallet_internal_error, "parsed txid mismatch"); THROW_WALLET_EXCEPTION_IF(!(spnet_txid_parsed == *it), error::wallet_internal_error, "parsed txid mismatch");
++it;
// get received (change) amount // get received (change) amount
uint64_t tx_money_got_in_outs = 0; uint64_t tx_money_got_in_outs = 0;

@ -105,5 +105,7 @@ namespace tests
bool fluffy_blocks_enabled() const { return false; } bool fluffy_blocks_enabled() const { return false; }
uint64_t prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes) { return 0; } uint64_t prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes) { return 0; }
bool pad_transactions() const { return false; } bool pad_transactions() const { return false; }
uint32_t get_blockchain_pruning_seed() const { return 0; }
bool prune_blockchain(uint32_t pruning_seed) const { return true; }
}; };
} }

@ -65,6 +65,7 @@ set(unit_tests_sources
notify.cpp notify.cpp
output_distribution.cpp output_distribution.cpp
parse_amount.cpp parse_amount.cpp
pruning.cpp
random.cpp random.cpp
serialization.cpp serialization.cpp
sha256.cpp sha256.cpp

@ -84,6 +84,8 @@ public:
bool fluffy_blocks_enabled() const { return false; } bool fluffy_blocks_enabled() const { return false; }
uint64_t prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes) { return 0; } uint64_t prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes) { return 0; }
bool pad_transactions() { return false; } bool pad_transactions() { return false; }
uint32_t get_blockchain_pruning_seed() const { return 0; }
bool prune_blockchain(uint32_t pruning_seed = 0) { return true; }
void stop() {} void stop() {}
}; };

@ -0,0 +1,240 @@
// Copyright (c) 2018, 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
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "gtest/gtest.h"
#include "misc_log_ex.h"
#include "cryptonote_config.h"
#include "common/pruning.h"
#define ASSERT_EX(x) do { bool ex = false; try { x; } catch(...) { ex = true; } ASSERT_TRUE(ex); } while(0)
TEST(pruning, parts)
{
ASSERT_EQ(tools::get_pruning_stripe(tools::make_pruning_seed(3, 2)), 3);
ASSERT_EQ(tools::get_pruning_stripe(tools::make_pruning_seed(1, 2)), 1);
ASSERT_EQ(tools::get_pruning_stripe(tools::make_pruning_seed(7, 7)), 7);
ASSERT_EQ(tools::get_pruning_log_stripes(tools::make_pruning_seed(1, 2)), 2);
ASSERT_EQ(tools::get_pruning_log_stripes(tools::make_pruning_seed(1, 0)), 0);
ASSERT_EQ(tools::get_pruning_log_stripes(tools::make_pruning_seed(1, 7)), 7);
ASSERT_EQ(tools::get_pruning_log_stripes(tools::make_pruning_seed(7, 7)), 7);
for (uint32_t log_stripes = 1; log_stripes <= tools::PRUNING_SEED_LOG_STRIPES_MASK; ++log_stripes)
{
for (uint32_t stripe = 1; stripe <= (1 << log_stripes); ++stripe)
{
ASSERT_EQ(tools::get_pruning_log_stripes(tools::make_pruning_seed(stripe, log_stripes)), log_stripes);
ASSERT_EQ(tools::get_pruning_stripe(tools::make_pruning_seed(stripe, log_stripes)), stripe);
}
}
}
TEST(pruning, out_of_range)
{
ASSERT_EX(tools::make_pruning_seed(5, 2));
ASSERT_EX(tools::make_pruning_seed(0, 2));
}
TEST(pruning, fits)
{
const uint32_t log_stripes = 3;
const uint32_t num_stripes = 1 << log_stripes;
for (uint32_t stripe = 1; stripe <= num_stripes; ++stripe)
{
const uint32_t seed = tools::make_pruning_seed(stripe, log_stripes);
ASSERT_NE(seed, 0);
ASSERT_EQ(tools::get_pruning_log_stripes(seed), log_stripes);
ASSERT_EQ(tools::get_pruning_stripe(seed), stripe);
}
}
TEST(pruning, tip)
{
static constexpr uint64_t H = CRYPTONOTE_PRUNING_TIP_BLOCKS + 1000;
static_assert(H >= CRYPTONOTE_PRUNING_TIP_BLOCKS, "H must be >= CRYPTONOTE_PRUNING_TIP_BLOCKS");
for (uint64_t h = H - CRYPTONOTE_PRUNING_TIP_BLOCKS; h < H; ++h)
{
uint32_t pruning_seed = tools::get_pruning_seed(h, H, CRYPTONOTE_PRUNING_LOG_STRIPES);
ASSERT_EQ(pruning_seed, 0);
for (pruning_seed = 0; pruning_seed <= (1 << CRYPTONOTE_PRUNING_LOG_STRIPES); ++pruning_seed)
ASSERT_TRUE(tools::has_unpruned_block(h, H, pruning_seed));
}
}
TEST(pruning, seed)
{
const uint64_t SS = CRYPTONOTE_PRUNING_STRIPE_SIZE;
const uint64_t NS = 1 << CRYPTONOTE_PRUNING_LOG_STRIPES;
const uint64_t TB = NS * SS;
for (uint64_t cycle = 0; cycle < 10; ++cycle)
{
const uint64_t O = TB * cycle;
ASSERT_EQ(tools::get_pruning_stripe(O + 0, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 1);
ASSERT_EQ(tools::get_pruning_stripe(O + 1, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 1);
ASSERT_EQ(tools::get_pruning_stripe(O + SS-1, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 1);
ASSERT_EQ(tools::get_pruning_stripe(O + SS, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 2);
ASSERT_EQ(tools::get_pruning_stripe(O + SS*2-1, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 2);
ASSERT_EQ(tools::get_pruning_stripe(O + SS*2, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 3);
ASSERT_EQ(tools::get_pruning_stripe(O + SS*NS-1, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), NS);
ASSERT_EQ(tools::get_pruning_stripe(O + SS*NS, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 1);
}
}
TEST(pruning, match)
{
static constexpr uint64_t H = CRYPTONOTE_PRUNING_TIP_BLOCKS + 1000;
static_assert(H >= CRYPTONOTE_PRUNING_TIP_BLOCKS, "H must be >= CRYPTONOTE_PRUNING_TIP_BLOCKS");
for (uint64_t h = 0; h < H - CRYPTONOTE_PRUNING_TIP_BLOCKS; ++h)
{
uint32_t pruning_seed = tools::get_pruning_seed(h, H, CRYPTONOTE_PRUNING_LOG_STRIPES);
uint32_t pruning_stripe = tools::get_pruning_stripe(pruning_seed);
ASSERT_TRUE(pruning_stripe > 0 && pruning_stripe <= (1 << CRYPTONOTE_PRUNING_LOG_STRIPES));
for (uint32_t other_pruning_stripe = 1; other_pruning_stripe <= (1 << CRYPTONOTE_PRUNING_LOG_STRIPES); ++other_pruning_stripe)
{
uint32_t other_pruning_seed = tools::make_pruning_seed(other_pruning_stripe, CRYPTONOTE_PRUNING_LOG_STRIPES);
ASSERT_TRUE(tools::has_unpruned_block(h, H, other_pruning_seed) == (other_pruning_seed == pruning_seed));
}
}
}
TEST(pruning, stripe_size)
{
static constexpr uint64_t H = CRYPTONOTE_PRUNING_TIP_BLOCKS + CRYPTONOTE_PRUNING_STRIPE_SIZE * (1 << CRYPTONOTE_PRUNING_LOG_STRIPES) + 1000;
static_assert(H >= CRYPTONOTE_PRUNING_TIP_BLOCKS + CRYPTONOTE_PRUNING_STRIPE_SIZE * (1 << CRYPTONOTE_PRUNING_LOG_STRIPES), "H must be >= that stuff in front");
for (uint32_t pruning_stripe = 1; pruning_stripe <= (1 << CRYPTONOTE_PRUNING_LOG_STRIPES); ++pruning_stripe)
{
uint32_t pruning_seed = tools::make_pruning_seed(pruning_stripe, CRYPTONOTE_PRUNING_LOG_STRIPES);
unsigned int current_run = 0, best_run = 0;
for (uint64_t h = 0; h < H - CRYPTONOTE_PRUNING_TIP_BLOCKS; ++h)
{
if (tools::has_unpruned_block(h, H, pruning_seed))
{
++current_run;
}
else if (current_run)
{
ASSERT_EQ(current_run, CRYPTONOTE_PRUNING_STRIPE_SIZE);
best_run = std::max(best_run, current_run);
current_run = 0;
}
}
ASSERT_EQ(best_run, CRYPTONOTE_PRUNING_STRIPE_SIZE);
}
}
TEST(pruning, next_unpruned)
{
static_assert((1 << CRYPTONOTE_PRUNING_LOG_STRIPES) >= 4, "CRYPTONOTE_PRUNING_LOG_STRIPES too low");
const uint64_t SS = CRYPTONOTE_PRUNING_STRIPE_SIZE;
const uint64_t NS = 1 << CRYPTONOTE_PRUNING_LOG_STRIPES;
const uint64_t TB = NS * SS;
for (uint64_t h = 0; h < 100; ++h)
ASSERT_EQ(tools::get_next_unpruned_block_height(h, 10000000, 0), h);
const uint32_t seed1 = tools::make_pruning_seed(1, CRYPTONOTE_PRUNING_LOG_STRIPES);
const uint32_t seed2 = tools::make_pruning_seed(2, CRYPTONOTE_PRUNING_LOG_STRIPES);
const uint32_t seed3 = tools::make_pruning_seed(3, CRYPTONOTE_PRUNING_LOG_STRIPES);
const uint32_t seed4 = tools::make_pruning_seed(4, CRYPTONOTE_PRUNING_LOG_STRIPES);
const uint32_t seedNS = tools::make_pruning_seed(NS, CRYPTONOTE_PRUNING_LOG_STRIPES);
ASSERT_EQ(tools::get_next_unpruned_block_height(0, 10000000, seed1), 0);
ASSERT_EQ(tools::get_next_unpruned_block_height(1, 10000000, seed1), 1);
ASSERT_EQ(tools::get_next_unpruned_block_height(SS-1, 10000000, seed1), SS-1);
ASSERT_EQ(tools::get_next_unpruned_block_height(SS, 10000000, seed1), TB);
ASSERT_EQ(tools::get_next_unpruned_block_height(TB, 10000000, seed1), TB);
ASSERT_EQ(tools::get_next_unpruned_block_height(TB-1, 10000000, seed1), TB);
ASSERT_EQ(tools::get_next_unpruned_block_height(0, 10000000, seed2), SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(1, 10000000, seed2), SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(SS-1, 10000000, seed2), SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(SS, 10000000, seed2), SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(2*SS-1, 10000000, seed2), 2*SS-1);
ASSERT_EQ(tools::get_next_unpruned_block_height(2*SS, 10000000, seed2), TB+SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(TB+2*SS,10000000, seed2), TB*2+SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(0, 10000000, seed3), SS*2);
ASSERT_EQ(tools::get_next_unpruned_block_height(SS, 10000000, seed3), SS*2);
ASSERT_EQ(tools::get_next_unpruned_block_height(2*SS, 10000000, seed3), SS*2);
ASSERT_EQ(tools::get_next_unpruned_block_height(3*SS-1, 10000000, seed3), SS*3-1);
ASSERT_EQ(tools::get_next_unpruned_block_height(3*SS, 10000000, seed3), TB+SS*2);
ASSERT_EQ(tools::get_next_unpruned_block_height(TB+3*SS,10000000, seed3), TB*2+SS*2);
ASSERT_EQ(tools::get_next_unpruned_block_height(SS, 10000000, seed4), 3*SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(4*SS-1, 10000000, seed4), 4*SS-1);
ASSERT_EQ(tools::get_next_unpruned_block_height(4*SS, 10000000, seed4), TB+3*SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(TB+4*SS,10000000, seed4), TB*2+3*SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(SS, 10000000, seedNS), (NS-1)*SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(NS*SS-1,10000000, seedNS), NS*SS-1);
ASSERT_EQ(tools::get_next_unpruned_block_height(NS*SS, 10000000, seedNS), TB+(NS-1)*SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(TB+NS*SS, 10000000, seedNS), TB*2+(NS-1)*SS);
}
TEST(pruning, next_pruned)
{
static_assert((1 << CRYPTONOTE_PRUNING_LOG_STRIPES) >= 4, "CRYPTONOTE_PRUNING_LOG_STRIPES too low");
const uint64_t SS = CRYPTONOTE_PRUNING_STRIPE_SIZE;
const uint64_t NS = 1 << CRYPTONOTE_PRUNING_LOG_STRIPES;
const uint64_t TB = NS * SS;
const uint32_t seed1 = tools::make_pruning_seed(1, CRYPTONOTE_PRUNING_LOG_STRIPES);
const uint32_t seed2 = tools::make_pruning_seed(2, CRYPTONOTE_PRUNING_LOG_STRIPES);
const uint32_t seedNS_1 = tools::make_pruning_seed(NS-1, CRYPTONOTE_PRUNING_LOG_STRIPES);
const uint32_t seedNS = tools::make_pruning_seed(NS, CRYPTONOTE_PRUNING_LOG_STRIPES);
for (uint64_t h = 0; h < 100; ++h)
ASSERT_EQ(tools::get_next_pruned_block_height(h, 10000000, 0), 10000000);
for (uint64_t h = 10000000 - 1 - CRYPTONOTE_PRUNING_TIP_BLOCKS; h < 10000000; ++h)
ASSERT_EQ(tools::get_next_pruned_block_height(h, 10000000, 0), 10000000);
ASSERT_EQ(tools::get_next_pruned_block_height(1, 10000000, seed1), SS);
ASSERT_EQ(tools::get_next_pruned_block_height(SS-1, 10000000, seed1), SS);
ASSERT_EQ(tools::get_next_pruned_block_height(SS, 10000000, seed1), SS);
ASSERT_EQ(tools::get_next_pruned_block_height(TB-1, 10000000, seed1), TB-1);
ASSERT_EQ(tools::get_next_pruned_block_height(TB, 10000000, seed1), TB+SS);
ASSERT_EQ(tools::get_next_pruned_block_height(1, 10000000, seed2), 1);
ASSERT_EQ(tools::get_next_pruned_block_height(SS-1, 10000000, seed2), SS-1);
ASSERT_EQ(tools::get_next_pruned_block_height(SS, 10000000, seed2), 2*SS);
ASSERT_EQ(tools::get_next_pruned_block_height(TB-1, 10000000, seed2), TB-1);
ASSERT_EQ(tools::get_next_pruned_block_height(1, 10000000, seedNS_1), 1);
ASSERT_EQ(tools::get_next_pruned_block_height(SS-1, 10000000, seedNS_1), SS-1);
ASSERT_EQ(tools::get_next_pruned_block_height(SS, 10000000, seedNS_1), SS);
ASSERT_EQ(tools::get_next_pruned_block_height(TB-1, 10000000, seedNS_1), TB-1);
ASSERT_EQ(tools::get_next_pruned_block_height(1, 10000000, seedNS), 1);
ASSERT_EQ(tools::get_next_pruned_block_height(SS-1, 10000000, seedNS), SS-1);
ASSERT_EQ(tools::get_next_pruned_block_height(SS, 10000000, seedNS), SS);
ASSERT_EQ(tools::get_next_pruned_block_height(TB-1, 10000000, seedNS), TB);
}
Loading…
Cancel
Save