Merge pull request #4843

b750fb27 Pruning (moneromooo-monero)
pull/5102/head
Riccardo Spagni 5 years ago
commit 4a0e4c7d70
No known key found for this signature in database
GPG Key ID: 55432DF31CCD4FCD

@ -85,6 +85,16 @@
#define MGINFO_MAGENTA(x) MCLOG_MAGENTA(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_PRINT_L0(x) MWARNING(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);
m_throttle_speed_in.handle_trafic_exact(bytes_transferred);
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);
m_throttle_speed_out.handle_trafic_exact(cb);
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);

@ -228,6 +228,8 @@ namespace net_utils
uint64_t m_send_cnt;
double m_current_speed_down;
double m_current_speed_up;
double m_max_speed_down;
double m_max_speed_up;
connection_context_base(boost::uuids::uuid connection_id,
const network_address &remote_address, bool is_income,
@ -242,7 +244,9 @@ namespace net_utils
m_recv_cnt(recv_cnt),
m_send_cnt(send_cnt),
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(),
@ -254,7 +258,9 @@ namespace net_utils
m_recv_cnt(0),
m_send_cnt(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)

@ -206,6 +206,15 @@ POP_WARNINGS
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)
{

@ -267,7 +267,10 @@ void BlockchainDB::pop_block(block& blk, std::vector<transaction>& txs)
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(get_transaction_hash(blk.miner_tx));
@ -280,7 +283,7 @@ bool BlockchainDB::is_open() const
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)
{
@ -325,6 +328,17 @@ bool BlockchainDB::get_tx(const crypto::hash& h, cryptonote::transaction &tx) co
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 tx;
@ -333,6 +347,14 @@ transaction BlockchainDB::get_tx(const crypto::hash& h) const
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()
{
num_calls = 0;

@ -1125,6 +1125,17 @@ public:
*/
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
*
@ -1136,6 +1147,17 @@ public:
*/
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
*
@ -1164,6 +1186,21 @@ public:
*/
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
*
@ -1412,6 +1449,31 @@ public:
*/
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
*

@ -35,6 +35,7 @@
#include "string_tools.h"
#include "file_io_utils.h"
#include "common/util.h"
#include "common/pruning.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "crypto/crypto.h"
#include "profile_tools.h"
@ -130,14 +131,20 @@ private:
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;
}
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 *vb = (uint32_t*) b->mv_data;
@ -151,13 +158,18 @@ int compare_hash32(const MDB_val *a, const MDB_val *b)
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 *vb = (const char*) b->mv_data;
return strcmp(va, vb);
}
}
namespace
{
/* DB schema:
*
* 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_prunable txn ID prunable txn blob
* txs_prunable_hash txn ID prunable txn hash
* txs_prunable_tip txn ID height
* tx_indices txn hash {txn ID, metadata}
* 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_PRUNABLE = "txs_prunable";
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_OUTPUTS = "tx_outputs";
@ -279,11 +293,6 @@ typedef struct blk_height {
uint64_t bh_height;
} blk_height;
typedef struct txindex {
crypto::hash key;
tx_data_t data;
} txindex;
typedef struct pre_rct_outkey {
uint64_t amount_index;
uint64_t output_id;
@ -549,18 +558,18 @@ bool BlockchainLMDB::need_resize(uint64_t threshold_size) const
// additional size needed.
uint64_t size_used = mst.ms_psize * mei.me_last_pgno;
LOG_PRINT_L1("DB map size: " << mei.me_mapsize);
LOG_PRINT_L1("Space used: " << size_used);
LOG_PRINT_L1("Space remaining: " << mei.me_mapsize - size_used);
LOG_PRINT_L1("Size threshold: " << threshold_size);
MDEBUG("DB map size: " << mei.me_mapsize);
MDEBUG("Space used: " << size_used);
MDEBUG("Space remaining: " << mei.me_mapsize - size_used);
MDEBUG("Size threshold: " << threshold_size);
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 (mei.me_mapsize - size_used < threshold_size)
{
LOG_PRINT_L1("Threshold met (size-based)");
MINFO("Threshold met (size-based)");
return true;
}
else
@ -569,7 +578,7 @@ bool BlockchainLMDB::need_resize(uint64_t threshold_size) const
if ((double)size_used / mei.me_mapsize > resize_percent)
{
LOG_PRINT_L1("Threshold met (percent-based)");
MINFO("Threshold met (percent-based)");
return true;
}
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)
{
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);
uint64_t threshold_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_prunable)
CURSOR(txs_prunable_hash)
CURSOR(txs_prunable_tip)
CURSOR(tx_indices)
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)
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)
{
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_prunable)
CURSOR(txs_prunable_hash)
CURSOR(txs_prunable_tip)
CURSOR(tx_outputs)
MDB_val_set(val_h, tx_hash);
@ -898,11 +917,25 @@ void BlockchainLMDB::remove_transaction_data(const crypto::hash& tx_hash, const
if (result)
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()));
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()));
result = mdb_cursor_get(m_cur_txs_prunable_tip, &val_tx_id, NULL, MDB_SET);
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)
{
@ -1308,6 +1341,7 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags)
// open necessary databases, and set properties as needed
// 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_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_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_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_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_txs, 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_blob, compare_hash32);
@ -1502,6 +1542,8 @@ void BlockchainLMDB::reset()
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))
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))
throw0(DB_ERROR(lmdb_error("Failed to drop m_tx_indices: ", result).c_str()));
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;
}
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
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
@ -2428,6 +2754,36 @@ bool BlockchainLMDB::get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobd
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
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);

@ -40,6 +40,11 @@
namespace cryptonote
{
typedef struct txindex {
crypto::hash key;
tx_data_t data;
} txindex;
typedef struct mdb_txn_cursors
{
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_prunable;
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_outputs;
@ -62,6 +68,8 @@ typedef struct mdb_txn_cursors
MDB_cursor *m_txc_txpool_blob;
MDB_cursor *m_txc_hf_versions;
MDB_cursor *m_txc_properties;
} mdb_txn_cursors;
#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_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_tip m_cursors->m_txc_txs_prunable_tip
#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_spent_keys m_cursors->m_txc_spent_keys
#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_hf_versions m_cursors->m_txc_hf_versions
#define m_cur_properties m_cursors->m_txc_properties
typedef struct mdb_rflags
{
@ -92,12 +102,14 @@ typedef struct mdb_rflags
bool m_rf_txs_pruned;
bool m_rf_txs_prunable;
bool m_rf_txs_prunable_hash;
bool m_rf_txs_prunable_tip;
bool m_rf_tx_indices;
bool m_rf_tx_outputs;
bool m_rf_spent_keys;
bool m_rf_txpool_meta;
bool m_rf_txpool_blob;
bool m_rf_hf_versions;
bool m_rf_properties;
} mdb_rflags;
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_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 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_blob(const crypto::hash& txid, cryptonote::blobdata &bd) 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_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;
// helper functions
static int compare_uint64(const MDB_val *a, const MDB_val *b);
static int compare_hash32(const MDB_val *a, const MDB_val *b);
static int compare_string(const MDB_val *a, const MDB_val *b);
private:
void do_resize(uint64_t size_increase=0);
@ -361,6 +384,8 @@ private:
inline void check_open() const;
bool prune_worker(int mode, uint32_t pruning_seed);
virtual bool is_read_only() const;
virtual uint64_t get_database_size() const;
@ -393,6 +418,7 @@ private:
MDB_dbi m_txs_pruned;
MDB_dbi m_txs_prunable;
MDB_dbi m_txs_prunable_hash;
MDB_dbi m_txs_prunable_tip;
MDB_dbi m_tx_indices;
MDB_dbi m_tx_outputs;

@ -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
blockchain_ancestry.cpp
)
@ -298,3 +309,25 @@ set_property(TARGET blockchain_prune_known_spent_data
PROPERTY
OUTPUT_NAME "monero-blockchain-prune-known-spent-data")
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);
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");
LOG_PRINT_L0("Source blockchain storage initialized OK");
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
password.cpp
perf_timer.cpp
pruning.cpp
spawn.cpp
threadpool.cpp
updates.cpp
@ -69,6 +70,7 @@ set(common_private_headers
http_connection.h
notify.h
pod-class.h
pruning.h
rpc_client.h
scoped_message_writer.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>
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
#include <string>
#include "span.h"
namespace cryptonote
{
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
{
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
{
@ -59,6 +59,8 @@ namespace cryptonote
boost::posix_time::ptime m_last_request_time;
epee::copyable_atomic m_callback_request_count; //in debug purpose: problem with double callback rise
crypto::hash m_last_known_hash;
uint32_t m_pruning_seed;
bool m_anchor;
//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 size_t blob_size;
bool pruned;
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 &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(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); } pruned = t.pruned; return *this; }
virtual ~transaction();
void set_null();
void invalidate_hashes();
@ -232,7 +234,7 @@ namespace cryptonote
if (!signatures_not_expected && vin.size() != signatures.size())
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]);
if (signatures_not_expected)
@ -263,7 +265,7 @@ namespace cryptonote
bool r = rct_signatures.serialize_rctsig_base(ar, vin.size(), vout.size());
if (!r || !ar.stream().good()) return false;
ar.end_object();
if (rct_signatures.type != rct::RCTTypeNull)
if (!pruned && rct_signatures.type != rct::RCTTypeNull)
{
ar.tag("rctsig_prunable");
ar.begin_object();
@ -274,6 +276,8 @@ namespace cryptonote
}
}
}
if (!typename Archive<W>::is_saving())
pruned = false;
END_SERIALIZE()
template<bool W, template <bool> class Archive>
@ -295,6 +299,8 @@ namespace cryptonote
ar.end_object();
}
}
if (!typename Archive<W>::is_saving())
pruned = true;
return true;
}
@ -322,6 +328,7 @@ namespace cryptonote
rct_signatures.type = rct::RCTTypeNull;
set_hash_valid(false);
set_blob_size_valid(false);
pruned = false;
}
inline

@ -196,6 +196,7 @@ namespace cryptonote
bool r = tx.serialize_base(ba);
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");
tx.invalidate_hashes();
return true;
}
//---------------------------------------------------------------
@ -225,6 +226,22 @@ namespace cryptonote
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)
{
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);
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>
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 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
namespace config
{

@ -53,6 +53,8 @@
#include "ringct/rctSigs.h"
#include "common/perf_timer.h"
#include "common/notify.h"
#include "common/varint.h"
#include "common/pruning.h"
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "blockchain"
@ -646,8 +648,14 @@ block Blockchain::pop_block_from_blockchain()
m_hardfork->on_block_popped(1);
// return transactions from popped block to the tx_pool
size_t pruned = 0;
for (transaction& tx : popped_txs)
{
if (tx.pruned)
{
++pruned;
continue;
}
if (!is_coinbase(tx))
{
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_scan_table.clear();
@ -2044,6 +2054,51 @@ bool Blockchain::get_transactions_blobs(const t_ids_container& txs_ids, t_tx_con
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>
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);
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;
hashes.reserve(std::max((size_t)(current_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++)
hashes.reserve(std::min((size_t)(stop_height - start_height), (size_t)BLOCKS_IDS_SYNCHRONIZING_DEFAULT_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));
}
@ -3369,7 +3427,7 @@ leave:
{
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;
goto leave;
}
@ -3635,6 +3693,35 @@ leave:
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()
{
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();
m_tx_pool.unlock();
update_blockchain_pruning();
return success;
}
@ -4621,4 +4710,5 @@ void Blockchain::cache_block_template(const block &b, const cryptonote::account_
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_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>
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>
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;
//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() 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);
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 unlock();

@ -105,6 +105,11 @@ namespace cryptonote
"disable-dns-checkpoints"
, "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 = {
"test-drop-download"
@ -175,6 +180,11 @@ namespace cryptonote
, "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):
@ -285,9 +295,11 @@ namespace cryptonote
command_line::add_arg(desc, arg_test_dbg_lock_sleep);
command_line::add_arg(desc, arg_offline);
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_pad_transactions);
command_line::add_arg(desc, arg_block_notify);
command_line::add_arg(desc, arg_prune_blockchain);
miner::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);
}
//-----------------------------------------------------------------------------------------------
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
{
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);
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);
bool prune_blockchain = command_line::get_arg(vm, arg_prune_blockchain);
boost::filesystem::path folder(m_config_folder);
if (m_nettype == FAKECHAIN)
@ -607,6 +625,14 @@ namespace cryptonote
r = m_miner.init(vm, m_nettype);
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();
}
//-----------------------------------------------------------------------------------------------
@ -1501,6 +1527,7 @@ namespace cryptonote
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_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_mempool.on_idle();
return true;
@ -1736,6 +1763,16 @@ namespace cryptonote
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)
{
m_target_blockchain_height = target_blockchain_height;
@ -1758,6 +1795,16 @@ namespace cryptonote
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
{
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<difficulty_type> arg_fixed_difficulty;
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;
/**
* @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
*
@ -783,6 +791,36 @@ namespace cryptonote
*/
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:
/**
@ -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*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<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?

@ -34,6 +34,7 @@
#include <boost/uuid/uuid_io.hpp>
#include "string_tools.h"
#include "cryptonote_protocol_defs.h"
#include "common/pruning.h"
#include "block_queue.h"
#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)
{
for (const crypto::hash &h: hashes)
{
requested_hashes.insert(h);
have_blocks.insert(h);
}
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");
for (const crypto::hash &h: j->hashes)
{
requested_hashes.erase(h);
have_blocks.erase(h);
}
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);
block_map::iterator i = blocks.begin();
if (i != blocks.end() && is_blockchain_placeholder(*i))
++i;
while (i != blocks.end())
{
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);
}
@ -152,23 +157,56 @@ uint64_t block_queue::get_max_block_height() const
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
{
boost::unique_lock<boost::recursive_mutex> lock(mutex);
MDEBUG("Block queue has " << blocks.size() << " spans");
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);
if (blocks.empty())
return "[]";
block_map::const_iterator i = blocks.begin();
std::string s = std::string("[") + std::to_string(i->start_block_height + i->nblocks - 1) + ":";
while (++i != blocks.end())
s += i->blocks.empty() ? "." : "o";
std::string s = std::string("[");
uint64_t expected = blockchain_height;
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 += "]";
return s;
}
@ -184,16 +222,31 @@ bool block_queue::requested(const crypto::hash &hash) const
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);
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)
{
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);
}
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;
std::vector<crypto::hash>::const_iterator i = block_hashes.begin();
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;
++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;
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);
++i;
++span_length;
}
if (span_length == 0)
{
MDEBUG("span_length 0, cannot reserve");
return std::make_pair(0, 0);
}
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);
set_span_hashes(span_start_height, connection_id, hashes);
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
{
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))
++i;
if (i == blocks.end())
return std::make_pair(0, 0);
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);
}
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)
{
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())
return false;
block_map::const_iterator i = blocks.begin();
if (is_blockchain_placeholder(*i))
++i;
for (; i != blocks.end(); ++i)
{
if (!filled || !i->blocks.empty())
@ -299,19 +362,34 @@ bool block_queue::get_next_span(uint64_t &height, std::vector<cryptonote::block_
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);
if (blocks.empty())
return false;
block_map::const_iterator i = blocks.begin();
if (is_blockchain_placeholder(*i))
++i;
if (i == blocks.end())
return false;
if (i->connection_id != connection_id)
return false;
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;
}
@ -331,8 +409,6 @@ size_t block_queue::get_num_filled_spans_prefix() const
if (blocks.empty())
return 0;
block_map::const_iterator i = blocks.begin();
if (is_blockchain_placeholder(*i))
++i;
size_t size = 0;
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;
}
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);
block_map::const_iterator i = blocks.begin();
if (!include_blockchain_placeholder && i != blocks.end() && is_blockchain_placeholder(*i))
++i;
while (i != blocks.end())
if (!f(*i++))
return false;

@ -76,22 +76,26 @@ namespace cryptonote
void remove_spans(const boost::uuids::uuid &connection_id, uint64_t start_block_height);
uint64_t get_max_block_height() const;
void print() const;
std::string get_overview() 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 is_blockchain_placeholder(const span &span) const;
std::pair<uint64_t, uint64_t> get_start_gap_span() const;
std::string get_overview(uint64_t blockchain_height) const;
bool has_unpruned_height(uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed) 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());
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;
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);
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_num_filled_spans_prefix() const;
size_t get_num_filled_spans() const;
crypto::hash get_last_known_hash(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;
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 have(const crypto::hash &hash) const;
private:
void erase_block(block_map::iterator j);
@ -101,5 +105,6 @@ namespace cryptonote
block_map blocks;
mutable boost::recursive_mutex mutex;
std::unordered_set<crypto::hash> requested_hashes;
std::unordered_set<crypto::hash> have_blocks;
};
}

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

@ -43,6 +43,7 @@
#include "cryptonote_protocol_defs.h"
#include "cryptonote_protocol_handler_common.h"
#include "block_queue.h"
#include "common/perf_timer.h"
#include "cryptonote_basic/connection_context.h"
#include "cryptonote_basic/cryptonote_stat_info.h"
#include <boost/circular_buffer.hpp>
@ -109,6 +110,10 @@ namespace cryptonote
const block_queue &get_block_queue() const { return m_block_queue; }
void stop();
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:
//----------------- commands handlers ----------------------------------------------
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);
//----------------------------------------------------------------------------------
//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);
size_t get_synchronizing_connections_count();
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);
bool kick_idle_peers();
bool check_standby_peers();
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;
@ -145,6 +153,12 @@ namespace cryptonote
block_queue m_block_queue;
epee::math_helper::once_a_time_seconds<30> m_idle_peer_kicker;
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;
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;
}
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

@ -142,6 +142,10 @@ public:
bool pop_blocks(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

@ -292,6 +292,16 @@ t_command_server::t_command_server(
, std::bind(&t_command_parser_executor::version, &m_parser, p::_1)
, "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)

@ -31,6 +31,7 @@
#include "string_tools.h"
#include "common/password.h"
#include "common/scoped_message_writer.h"
#include "common/pruning.h"
#include "daemon/rpc_command_executor.h"
#include "rpc/core_rpc_server_commands_defs.h"
#include "cryptonote_core/cryptonote_core.h"
@ -60,7 +61,8 @@ namespace {
peer_id_str >> id_str;
epee::string_tools::xtype_to_string(peer.port, 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)
@ -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.decode_as_json = false;
req.split = true;
req.prune = false;
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)
tools::success_msg_writer() << "Found in pool";
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 &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
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
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;
cryptonote::transaction tx;
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";
}
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
{
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)
current_download += p.info.current_download;
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";
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);
uint64_t nblocks = 0, size = 0;
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;
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;
for (const auto &s: res.spans)
total_size += s.size;
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)
{
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)
{
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
{
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;
}
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

@ -154,6 +154,10 @@ public:
bool sync_info();
bool pop_blocks(uint64_t num_blocks);
bool prune_blockchain();
bool check_blockchain_pruning();
};
} // 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))
{
/*
if (tx.pruned)
std::cout << "Parsed pruned transaction:" << std::endl;
else
*/
std::cout << "Parsed transaction:" << 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
#pragma once
#include <array>
#include <boost/thread.hpp>
#include <boost/program_options/options_description.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 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 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:
const std::vector<std::string> m_seed_nodes_list =
{ "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 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 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_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;
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;
};

@ -41,6 +41,7 @@
#include "string_tools.h"
#include "common/util.h"
#include "common/dns_utils.h"
#include "common/pruning.h"
#include "net/net_helper.h"
#include "math_helper.h"
#include "p2p_protocol_defs.h"
@ -133,6 +134,28 @@ namespace nodetool
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
make_default_peer_id();
@ -729,7 +752,7 @@ namespace nodetool
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(),
[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();});
@ -762,7 +785,7 @@ namespace nodetool
}
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)
{
@ -770,11 +793,13 @@ namespace nodetool
hsh_result = false;
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");
}else
{
LOG_DEBUG_CC(context, " COMMAND_HANDSHAKE(AND CLOSE) INVOKED OK");
}
context_ = context;
}, P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT);
if(r)
@ -821,7 +846,7 @@ namespace nodetool
add_host_fail(context.m_remote_address);
}
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);
});
@ -939,6 +964,7 @@ namespace nodetool
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);
con.m_anchor = peer_type == anchor;
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()),
m_config.m_net_config.connection_timeout,
@ -978,6 +1004,7 @@ namespace nodetool
time_t last_seen;
time(&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);
//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>();
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()),
epee::string_tools::num_to_string_fast(ipv4.port()),
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)
{
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)) {
_note("Peer is used");
@ -1089,11 +1117,7 @@ namespace nodetool
template<class t_payload_net_handler>
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();
if(!local_peers_count)
return false;//no peers
size_t max_random_index = std::min<uint64_t>(local_peers_count -1, 20);
size_t max_random_index = 0;
std::set<size_t> tried_peers;
@ -1103,21 +1127,54 @@ namespace nodetool
{
++rand_count;
size_t random_index;
const uint32_t next_needed_pruning_stripe = m_payload_handler.get_next_needed_pruning_stripe().second;
if (use_white_list) {
local_peers_count = m_peerlist.get_white_peers_count();
if (!local_peers_count)
std::deque<size_t> filtered;
const size_t limit = use_white_list ? 20 : std::numeric_limits<size_t>::max();
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;
max_random_index = std::min<uint64_t>(local_peers_count -1, 20);
random_index = get_random_index_with_fixed_probability(max_random_index);
} else {
local_peers_count = m_peerlist.get_gray_peers_count();
if (!local_peers_count)
return false;
random_index = crypto::rand<size_t>() % local_peers_count;
if (next_needed_pruning_stripe == 0 || pe.pruning_seed == 0)
filtered.push_back(idx);
else if (next_needed_pruning_stripe == tools::get_pruning_stripe(pe.pruning_seed))
filtered.push_front(idx);
++idx;
return true;
});
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))
continue;
@ -1129,7 +1186,9 @@ namespace nodetool
++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)) {
_note("Peer is used");
@ -1143,6 +1202,7 @@ namespace nodetool
continue;
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)
<< "] 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;
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();
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)
{
//start from anchor list
if(!make_expected_connections_count(anchor, P2P_DEFAULT_ANCHOR_CONNECTIONS_COUNT))
return false;
while (get_outgoing_connections_count() < P2P_DEFAULT_ANCHOR_CONNECTIONS_COUNT
&& make_expected_connections_count(anchor, P2P_DEFAULT_ANCHOR_CONNECTIONS_COUNT));
//then do white list
if(!make_expected_connections_count(white, expected_white_connections))
return false;
while (get_outgoing_connections_count() < expected_white_connections
&& make_expected_connections_count(white, expected_white_connections));
//then do grey list
if(!make_expected_connections_count(gray, m_config.m_net_config.max_out_connection_count))
return false;
while (get_outgoing_connections_count() < m_config.m_net_config.max_out_connection_count
&& make_expected_connections_count(gray, m_config.m_net_config.max_out_connection_count));
}else
{
//start from grey list
if(!make_expected_connections_count(gray, m_config.m_net_config.max_out_connection_count))
return false;
while (get_outgoing_connections_count() < m_config.m_net_config.max_out_connection_count
&& make_expected_connections_count(gray, m_config.m_net_config.max_out_connection_count));
//and then do white list
if(!make_expected_connections_count(white, m_config.m_net_config.max_out_connection_count))
return false;
while (get_outgoing_connections_count() < m_config.m_net_config.max_out_connection_count
&& 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)
@ -1260,7 +1324,7 @@ namespace nodetool
bool node_server<t_payload_net_handler>::make_expected_connections_count(PeerType peer_type, size_t expected_connections)
{
if (m_offline)
return true;
return false;
std::vector<anchor_peerlist_entry> apl;
@ -1270,24 +1334,24 @@ namespace nodetool
size_t conn_count = get_outgoing_connections_count();
//add new connections from white peers
while(conn_count < expected_connections)
if(conn_count < expected_connections)
{
if(m_net_server.is_stop_signal_sent())
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)) {
break;
return false;
}
if (peer_type == white && !make_new_connection_from_peerlist(true)) {
break;
return false;
}
if (peer_type == gray && !make_new_connection_from_peerlist(false)) {
break;
return false;
}
conn_count = get_outgoing_connections_count();
}
return true;
}
@ -1390,6 +1454,9 @@ namespace nodetool
return false;
}
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;
}
@ -1750,6 +1817,7 @@ namespace nodetool
time(&last_seen);
pe.last_seen = static_cast<int64_t>(last_seen);
pe.id = peer_id_l;
pe.pruning_seed = context.m_pruning_seed;
this->m_peerlist.append_with_peer_white(pe);
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>
bool node_server<t_payload_net_handler>::set_max_out_peers(const boost::program_options::variables_map& vm, int64_t max)
{
if(max == -1) {
m_config.m_net_config.max_out_connection_count = P2P_DEFAULT_CONNECTIONS_COUNT;
return true;
}
if(max == -1)
max = P2P_DEFAULT_CONNECTIONS_COUNT;
m_config.m_net_config.max_out_connection_count = max;
m_payload_handler.set_max_out_peers(max);
return true;
}
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)
{
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;
return true;
}
@ -2010,6 +2073,7 @@ namespace nodetool
{
if (m_offline) 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);
@ -2030,13 +2094,49 @@ namespace nodetool
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));
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>
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 std::map<std::string, time_t> get_blocked_hosts()=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>
@ -114,5 +117,14 @@ namespace nodetool
{
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_white_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_gray(const peerlist_entry& pr);
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 is_host_allowed(const epee::net_utils::network_address &address);
bool get_random_gray_peer(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 remove_from_peer_anchor(const epee::net_utils::network_address& addr);
bool remove_from_peer_white(const peerlist_entry& pe);
private:
struct by_time{};
@ -356,8 +358,19 @@ namespace nodetool
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
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();
CRITICAL_REGION_LOCAL(m_peerlist_lock);
@ -366,6 +379,7 @@ namespace nodetool
ple.adr = addr;
ple.id = peer;
ple.last_seen = time(NULL);
ple.pruning_seed = pruning_seed;
return append_with_peer_white(ple);
CATCH_ENTRY_L0("peerlist_manager::set_peer_just_seen()", false);
}
@ -469,6 +483,24 @@ namespace nodetool
}
//--------------------------------------------------------------------------------------------------
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)
{
TRY_ENTRY();

@ -33,6 +33,10 @@
#include "net/net_utils_base.h"
#include "p2p/p2p_protocol_defs.h"
#ifdef CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED
#include "common/pruning.h"
#endif
namespace boost
{
namespace serialization
@ -77,6 +81,19 @@ namespace boost
a & pl.adr;
a & pl.id;
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>

@ -31,6 +31,7 @@
#pragma once
#include <boost/uuid/uuid.hpp>
#include <boost/serialization/version.hpp>
#include "serialization/keyvalue_serialization.h"
#include "net/net_utils_base.h"
#include "misc_language.h"
@ -72,11 +73,13 @@ namespace nodetool
AddressType adr;
peerid_type id;
int64_t last_seen;
uint32_t pruning_seed;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(adr)
KV_SERIALIZE(id)
KV_SERIALIZE(last_seen)
KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0)
END_KV_SERIALIZE_MAP()
};
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;
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();
}
@ -205,7 +208,7 @@ namespace nodetool
{
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>();
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
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;
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)
((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()
@ -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()));
}
std::vector<crypto::hash> missed_txs;
std::vector<transaction> txs;
bool r = m_core.get_transactions(vh, txs, missed_txs);
std::vector<std::tuple<crypto::hash, cryptonote::blobdata, crypto::hash, cryptonote::blobdata>> txs;
bool r = m_core.get_split_transactions_blobs(vh, txs, missed_txs);
if(!r)
{
res.status = "Failed";
@ -506,7 +506,7 @@ namespace cryptonote
if(r)
{
// 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;
unsigned txs_processed = 0;
for (const crypto::hash &h: vh)
@ -519,7 +519,7 @@ namespace cryptonote
return true;
}
// 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";
return true;
@ -535,7 +535,16 @@ namespace cryptonote
res.status = "Failed to parse and validate tx from blob";
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));
pool_tx_hashes.insert(h);
const std::string hash_string = epee::string_tools::pod_to_hex(h);
@ -564,11 +573,36 @@ namespace cryptonote
crypto::hash tx_hash = *vhi++;
e.tx_hash = *txhi++;
pruned_transaction pruned_tx{tx};
blobdata blob = req.prune ? t_serializable_object_to_blob(pruned_tx) : t_serializable_object_to_blob(tx);
e.as_hex = string_tools::buff_to_hex_nodelimer(blob);
if (req.decode_as_json)
e.as_json = req.prune ? obj_to_json_str(pruned_tx) : obj_to_json_str(tx);
e.prunable_hash = epee::string_tools::pod_to_hex(std::get<2>(tx));
if (req.split || req.prune || std::get<3>(tx).empty())
{
e.pruned_as_hex = string_tools::buff_to_hex_nodelimer(std::get<1>(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();
if (e.in_pool)
{
@ -869,9 +903,9 @@ namespace cryptonote
{
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(),
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
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());
@ -879,9 +913,9 @@ namespace cryptonote
{
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(),
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
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;
@ -2102,6 +2136,7 @@ namespace cryptonote
m_core.get_blockchain_top(res.height, top_hash);
++res.height; // turn top block height into 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())
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});
return true;
});
res.overview = block_queue.get_overview(res.height);
res.status = CORE_RPC_STATUS_OK;
return true;
@ -2215,6 +2251,29 @@ namespace cryptonote
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 = {

@ -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("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_IF("prune_blockchain", on_prune_blockchain, COMMAND_RPC_PRUNE_BLOCKCHAIN, !m_restricted)
END_JSON_RPC_MAP()
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_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_prune_blockchain(const COMMAND_RPC_PRUNE_BLOCKCHAIN::request& req, COMMAND_RPC_PRUNE_BLOCKCHAIN::response& res, epee::json_rpc::error& error_resp);
//-----------------------
private:

@ -84,7 +84,7 @@ namespace cryptonote
// advance which version they will stop working with
// Don't go over 32767 for any of these
#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 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;
bool decode_as_json;
bool prune;
bool split;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(txs_hashes)
KV_SERIALIZE(decode_as_json)
KV_SERIALIZE_OPT(prune, false)
KV_SERIALIZE_OPT(split, false)
END_KV_SERIALIZE_MAP()
};
@ -613,6 +615,9 @@ namespace cryptonote
{
std::string tx_hash;
std::string as_hex;
std::string pruned_as_hex;
std::string prunable_as_hex;
std::string prunable_hash;
std::string as_json;
bool in_pool;
bool double_spend_seen;
@ -623,6 +628,9 @@ namespace cryptonote
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash)
KV_SERIALIZE(as_hex)
KV_SERIALIZE(pruned_as_hex)
KV_SERIALIZE(prunable_as_hex)
KV_SERIALIZE(prunable_hash)
KV_SERIALIZE(as_json)
KV_SERIALIZE(in_pool)
KV_SERIALIZE(double_spend_seen)
@ -1311,14 +1319,15 @@ namespace cryptonote
uint32_t ip;
uint16_t port;
uint64_t last_seen;
uint32_t pruning_seed;
peer() = default;
peer(uint64_t id, const std::string &host, uint64_t last_seen)
: id(id), host(host), ip(0), port(0), last_seen(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), pruning_seed(pruning_seed)
{}
peer(uint64_t id, uint32_t ip, uint16_t port, uint64_t last_seen)
: id(id), host(std::to_string(ip)), ip(ip), port(port), last_seen(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), pruning_seed(pruning_seed)
{}
BEGIN_KV_SERIALIZE_MAP()
@ -1327,6 +1336,7 @@ namespace cryptonote
KV_SERIALIZE(ip)
KV_SERIALIZE(port)
KV_SERIALIZE(last_seen)
KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0)
END_KV_SERIALIZE_MAP()
};
@ -2238,15 +2248,19 @@ namespace cryptonote
std::string status;
uint64_t height;
uint64_t target_height;
uint32_t next_needed_pruning_seed;
std::list<peer> peers;
std::list<span> spans;
std::string overview;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(status)
KV_SERIALIZE(height)
KV_SERIALIZE(target_height)
KV_SERIALIZE(next_needed_pruning_seed)
KV_SERIALIZE(peers)
KV_SERIALIZE(spans)
KV_SERIALIZE(overview)
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;
uint16_t port;
uint64_t last_seen;
uint32_t pruning_seed;
};
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, port, peer.port);
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.port, port);
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)

@ -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);
}
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
@ -2588,7 +2625,7 @@ void wallet2::update_pool_state(bool refreshed)
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(p.first));
MDEBUG("asking for " << txids.size() << " transactions");
req.decode_as_json = false;
req.prune = false;
req.prune = true;
m_daemon_rpc_mutex.lock();
bool r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
@ -2603,11 +2640,10 @@ void wallet2::update_pool_state(bool refreshed)
{
cryptonote::transaction tx;
cryptonote::blobdata bd;
crypto::hash tx_hash, tx_prefix_hash;
if (epee::string_tools::parse_hexstr_to_binbuff(tx_entry.as_hex, bd))
crypto::hash tx_hash;
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(),
[tx_hash](const std::pair<crypto::hash, bool> &e) { return e.first == tx_hash; });
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");
}
}
else
{
LOG_PRINT_L0("failed to validate transaction from daemon");
}
}
else
{
@ -6814,11 +6845,12 @@ bool wallet2::find_and_save_rings(bool force)
MDEBUG("Found " << std::to_string(txs_hashes.size()) << " transactions");
// get those transactions from the daemon
auto it = txs_hashes.begin();
static const size_t SLICE_SIZE = 200;
for (size_t slice = 0; slice < txs_hashes.size(); slice += SLICE_SIZE)
{
req.decode_as_json = false;
req.prune = false;
req.prune = true;
req.txs_hashes.clear();
size_t ntxes = slice + SLICE_SIZE > txs_hashes.size() ? txs_hashes.size() - slice : SLICE_SIZE;
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");
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)
{
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");
THROW_WALLET_EXCEPTION_IF(tx_info.tx_hash != *it, error::wallet_internal_error, "Wrong txid received");
cryptonote::blobdata bd;
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");
cryptonote::transaction tx;
crypto::hash tx_hash, tx_prefix_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(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");
cryptonote::transaction tx;
crypto::hash tx_hash;
THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(tx_info, tx, tx_hash), error::wallet_internal_error,
"Failed to get transaction from daemon");
THROW_WALLET_EXCEPTION_IF(!(tx_hash == *it), error::wallet_internal_error, "Wrong txid received");
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);
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false;
req.prune = false;
req.prune = true;
COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res);
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,
"daemon returned wrong response for gettransactions, wrong txs count = " +
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;
crypto::hash tx_hash, tx_prefix_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");
crypto::hash tx_hash;
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");
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");
@ -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);
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false;
req.prune = false;
req.prune = true;
COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res);
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,
"daemon returned wrong response for gettransactions, wrong txs count = " +
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;
crypto::hash tx_hash, tx_prefix_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(tx_hash != txid, error::wallet_internal_error, "txid mismatch");
crypto::hash tx_hash;
THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(res.txs[0], tx, tx_hash), error::wallet_internal_error, "Failed to get tx from daemon");
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);
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false;
req.prune = false;
req.prune = true;
COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res);
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,
"daemon returned wrong response for gettransactions, wrong txs count = " +
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;
crypto::hash tx_hash, tx_prefix_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(tx_hash != txid, error::wallet_internal_error, "txid mismatch");
crypto::hash tx_hash;
THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(res.txs[0], tx, tx_hash), error::wallet_internal_error, "failed to get tx from daemon");
// check signature size
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;
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false;
req.prune = false;
req.prune = true;
m_daemon_rpc_mutex.lock();
bool ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client);
m_daemon_rpc_mutex.unlock();
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");
cryptonote::blobdata tx_data;
cryptonote::transaction tx;
crypto::hash tx_hash;
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
{
cryptonote::blobdata tx_data;
crypto::hash tx_prefix_hash;
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(!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;
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false;
req.prune = false;
req.prune = true;
m_daemon_rpc_mutex.lock();
bool ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client);
m_daemon_rpc_mutex.unlock();
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");
cryptonote::blobdata tx_data;
cryptonote::transaction tx;
crypto::hash tx_hash;
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
{
cryptonote::blobdata tx_data;
crypto::hash tx_prefix_hash;
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");
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;
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false;
req.prune = false;
req.prune = true;
m_daemon_rpc_mutex.lock();
bool ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client);
m_daemon_rpc_mutex.unlock();
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");
cryptonote::blobdata tx_data;
cryptonote::transaction tx;
crypto::hash tx_hash;
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
{
cryptonote::blobdata tx_data;
crypto::hash tx_prefix_hash;
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");
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)
gettx_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(proofs[i].txid));
gettx_req.decode_as_json = false;
gettx_req.prune = false;
gettx_req.prune = true;
m_daemon_rpc_mutex.lock();
bool ok = net_utils::invoke_http_json("/gettransactions", gettx_req, gettx_res, m_http_client);
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];
THROW_WALLET_EXCEPTION_IF(gettx_res.txs[i].in_pool, error::wallet_internal_error, "Tx is unconfirmed");
cryptonote::blobdata tx_data;
ok = string_tools::parse_hexstr_to_binbuff(gettx_res.txs[i].as_hex, tx_data);
cryptonote::transaction tx;
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");
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(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::response gettxs_res;
gettxs_req.decode_as_json = false;
gettxs_req.prune = false;
gettxs_req.prune = true;
gettxs_req.txs_hashes.reserve(spent_txids.size());
for (const crypto::hash& spent_txid : spent_txids)
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);
auto spent_txid = spent_txids.begin();
hw::device &hwdev = m_account.get_device();
auto it = spent_txids.begin();
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");
// 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;
crypto::hash spnet_txid_parsed, spent_txid_prefix;
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(*spent_txid != spnet_txid_parsed, error::wallet_internal_error, "parsed txid mismatch");
crypto::hash spnet_txid_parsed;
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(!(spnet_txid_parsed == *it), error::wallet_internal_error, "parsed txid mismatch");
++it;
// get received (change) amount
uint64_t tx_money_got_in_outs = 0;

@ -105,5 +105,7 @@ namespace tests
bool fluffy_blocks_enabled() const { return false; }
uint64_t prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes) { return 0; }
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
output_distribution.cpp
parse_amount.cpp
pruning.cpp
random.cpp
serialization.cpp
sha256.cpp

@ -84,6 +84,8 @@ public:
bool fluffy_blocks_enabled() const { return false; }
uint64_t prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes) { return 0; }
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() {}
};

@ -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