aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRiccardo Spagni <ric@spagni.net>2019-01-28 13:55:49 +0200
committerRiccardo Spagni <ric@spagni.net>2019-01-28 13:55:50 +0200
commit4a0e4c7d706f33fb72fe809434676b1de7b4b4f7 (patch)
tree36fdd03f84ae78dbd217b5bd255bd86e33db1bcc
parentMerge pull request #5008 (diff)
parentPruning (diff)
downloadmonero-4a0e4c7d706f33fb72fe809434676b1de7b4b4f7.tar.xz
Merge pull request #4843
b750fb27 Pruning (moneromooo-monero)
-rw-r--r--contrib/epee/include/misc_log_ex.h10
-rw-r--r--contrib/epee/include/net/abstract_tcp_server2.inl2
-rw-r--r--contrib/epee/include/net/net_utils_base.h10
-rw-r--r--contrib/epee/include/string_tools.h9
-rw-r--r--src/blockchain_db/blockchain_db.cpp26
-rw-r--r--src/blockchain_db/blockchain_db.h62
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.cpp402
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.h26
-rw-r--r--src/blockchain_utilities/CMakeLists.txt33
-rw-r--r--src/blockchain_utilities/blockchain_export.cpp6
-rw-r--r--src/blockchain_utilities/blockchain_prune.cpp663
-rw-r--r--src/common/CMakeLists.txt2
-rw-r--r--src/common/pruning.cpp116
-rw-r--r--src/common/pruning.h52
-rw-r--r--src/common/varint.h2
-rw-r--r--src/cryptonote_basic/blobdatatype.h4
-rw-r--r--src/cryptonote_basic/connection_context.h23
-rw-r--r--src/cryptonote_basic/cryptonote_basic.h15
-rw-r--r--src/cryptonote_basic/cryptonote_format_utils.cpp17
-rw-r--r--src/cryptonote_basic/cryptonote_format_utils.h2
-rw-r--r--src/cryptonote_config.h5
-rw-r--r--src/cryptonote_core/blockchain.cpp96
-rw-r--r--src/cryptonote_core/blockchain.h6
-rw-r--r--src/cryptonote_core/cryptonote_core.cpp47
-rw-r--r--src/cryptonote_core/cryptonote_core.h39
-rw-r--r--src/cryptonote_protocol/block_queue.cpp193
-rw-r--r--src/cryptonote_protocol/block_queue.h17
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_defs.h5
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_handler.h16
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_handler.inl788
-rw-r--r--src/daemon/command_parser_executor.cpp23
-rw-r--r--src/daemon/command_parser_executor.h4
-rw-r--r--src/daemon/command_server.cpp10
-rw-r--r--src/daemon/rpc_command_executor.cpp123
-rw-r--r--src/daemon/rpc_command_executor.h4
-rw-r--r--src/debug_utilities/cn_deserialize.cpp2
-rw-r--r--src/p2p/net_node.h15
-rw-r--r--src/p2p/net_node.inl198
-rw-r--r--src/p2p/net_node_common.h12
-rw-r--r--src/p2p/net_peerlist.h36
-rw-r--r--src/p2p/net_peerlist_boost_serialization.h17
-rw-r--r--src/p2p/p2p_protocol_defs.h10
-rw-r--r--src/rpc/core_rpc_server.cpp87
-rw-r--r--src/rpc/core_rpc_server.h2
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h47
-rw-r--r--src/rpc/message_data_structs.h1
-rw-r--r--src/serialization/json_object.cpp2
-rw-r--r--src/wallet/wallet2.cpp189
-rw-r--r--tests/core_proxy/core_proxy.h2
-rw-r--r--tests/unit_tests/CMakeLists.txt1
-rw-r--r--tests/unit_tests/ban.cpp2
-rw-r--r--tests/unit_tests/pruning.cpp240
52 files changed, 3292 insertions, 429 deletions
diff --git a/contrib/epee/include/misc_log_ex.h b/contrib/epee/include/misc_log_ex.h
index 1ff9da3a7..602b6a371 100644
--- a/contrib/epee/include/misc_log_ex.h
+++ b/contrib/epee/include/misc_log_ex.h
@@ -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)
diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl
index d8779f372..457ee2dd4 100644
--- a/contrib/epee/include/net/abstract_tcp_server2.inl
+++ b/contrib/epee/include/net/abstract_tcp_server2.inl
@@ -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);
diff --git a/contrib/epee/include/net/net_utils_base.h b/contrib/epee/include/net/net_utils_base.h
index a133942fb..a9e458626 100644
--- a/contrib/epee/include/net/net_utils_base.h
+++ b/contrib/epee/include/net/net_utils_base.h
@@ -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)
diff --git a/contrib/epee/include/string_tools.h b/contrib/epee/include/string_tools.h
index f7539fb6c..2e65876e6 100644
--- a/contrib/epee/include/string_tools.h
+++ b/contrib/epee/include/string_tools.h
@@ -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)
{
diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp
index c25798c1e..d3eefef6e 100644
--- a/src/blockchain_db/blockchain_db.cpp
+++ b/src/blockchain_db/blockchain_db.cpp
@@ -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;
diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h
index fe61aabd8..137d5958a 100644
--- a/src/blockchain_db/blockchain_db.h
+++ b/src/blockchain_db/blockchain_db.h
@@ -1126,6 +1126,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
*
* If the transaction does not exist, the subclass should return false.
@@ -1137,6 +1148,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
*
* The subclass should return the transaction stored which has the given
@@ -1165,6 +1187,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
*
* The subclass should return the hash of the prunable transaction data.
@@ -1413,6 +1450,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
*
* The subclass should run the passed function for each txpool tx it has
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index 2b5fa7fd9..8a1303be9 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -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__);
diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h
index a60956ab1..c07ab8da5 100644
--- a/src/blockchain_db/lmdb/db_lmdb.h
+++ b/src/blockchain_db/lmdb/db_lmdb.h
@@ -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;
diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt
index ddf575c29..df74eb695 100644
--- a/src/blockchain_utilities/CMakeLists.txt
+++ b/src/blockchain_utilities/CMakeLists.txt
@@ -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})
diff --git a/src/blockchain_utilities/blockchain_export.cpp b/src/blockchain_utilities/blockchain_export.cpp
index 5a49f3478..5c5bc7f69 100644
--- a/src/blockchain_utilities/blockchain_export.cpp
+++ b/src/blockchain_utilities/blockchain_export.cpp
@@ -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...");
diff --git a/src/blockchain_utilities/blockchain_prune.cpp b/src/blockchain_utilities/blockchain_prune.cpp
new file mode 100644
index 000000000..8e13f2c04
--- /dev/null
+++ b/src/blockchain_utilities/blockchain_prune.cpp
@@ -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);
+}
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 3b1eb6d23..212a1891e 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -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
diff --git a/src/common/pruning.cpp b/src/common/pruning.cpp
new file mode 100644
index 000000000..442b24e4e
--- /dev/null
+++ b/src/common/pruning.cpp
@@ -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);
+}
+
+}
+
diff --git a/src/common/pruning.h b/src/common/pruning.h
new file mode 100644
index 000000000..3fac3c0fa
--- /dev/null
+++ b/src/common/pruning.h
@@ -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();
+}
+
diff --git a/src/common/varint.h b/src/common/varint.h
index 151d13dbf..904255afc 100644
--- a/src/common/varint.h
+++ b/src/common/varint.h
@@ -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);
}
}
diff --git a/src/cryptonote_basic/blobdatatype.h b/src/cryptonote_basic/blobdatatype.h
index 7d6ff0187..82484c0a8 100644
--- a/src/cryptonote_basic/blobdatatype.h
+++ b/src/cryptonote_basic/blobdatatype.h
@@ -30,7 +30,11 @@
#pragma once
+#include <string>
+#include "span.h"
+
namespace cryptonote
{
typedef std::string blobdata;
+ typedef epee::span<const char> blobdata_ref;
}
diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h
index eb73ab0ea..112c13049 100644
--- a/src/cryptonote_basic/connection_context.h
+++ b/src/cryptonote_basic/connection_context.h
@@ -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';
+ }
+ }
+
}
diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h
index 196b20e2a..c9c783a56 100644
--- a/src/cryptonote_basic/cryptonote_basic.h
+++ b/src/cryptonote_basic/cryptonote_basic.h
@@ -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
diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp
index 82428f196..f6daaab95 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.cpp
+++ b/src/cryptonote_basic/cryptonote_format_utils.cpp
@@ -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);
diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h
index 8d33b1ca4..994978c10 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.h
+++ b/src/cryptonote_basic/cryptonote_format_utils.h
@@ -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)
diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h
index 496678b5e..359e719ec 100644
--- a/src/cryptonote_config.h
+++ b/src/cryptonote_config.h
@@ -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
{
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index a108124a8..70f4c7a1b 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -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;
}
diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h
index 5a1c4b9ad..4952116ac 100644
--- a/src/cryptonote_core/blockchain.h
+++ b/src/cryptonote_core/blockchain.h
@@ -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();
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index 1fa6969a6..e3424bdbd 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -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;
diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h
index fe86f8d39..4810fc891 100644
--- a/src/cryptonote_core/cryptonote_core.h
+++ b/src/cryptonote_core/cryptonote_core.h
@@ -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;
/************************************************************************/
/* */
@@ -359,6 +360,13 @@ namespace cryptonote
*
* @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
+ *
+ * @note see Blockchain::get_transactions
+ */
bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<transaction>& txs, std::vector<crypto::hash>& missed_txs) const;
/**
@@ -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?
diff --git a/src/cryptonote_protocol/block_queue.cpp b/src/cryptonote_protocol/block_queue.cpp
index c1989f093..672696944 100644
--- a/src/cryptonote_protocol/block_queue.cpp
+++ b/src/cryptonote_protocol/block_queue.cpp
@@ -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;
diff --git a/src/cryptonote_protocol/block_queue.h b/src/cryptonote_protocol/block_queue.h
index 9cce95075..c7d3af7ac 100644
--- a/src/cryptonote_protocol/block_queue.h
+++ b/src/cryptonote_protocol/block_queue.h
@@ -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;
};
}
diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h
index d5bb50930..c49371d48 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_defs.h
+++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h
@@ -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()
};
diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h
index 3c5b22b4a..a1bd9171c 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_handler.h
+++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h
@@ -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();
diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
index 01f70cba1..61a211094 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl
+++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
@@ -42,17 +42,24 @@
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "profile_tools.h"
#include "net/network_throttle-detail.hpp"
+#include "common/pruning.h"
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "net.cn"
#define MLOG_P2P_MESSAGE(x) MCINFO("net.p2p.msg", context << x)
+#define MLOG_PEER_STATE(x) \
+ MCINFO(MONERO_DEFAULT_LOG_CATEGORY, context << "[" << epee::string_tools::to_string_hex(context.m_pruning_seed) << "] state: " << x << " in state " << cryptonote::get_protocol_state_string(context.m_state))
-#define BLOCK_QUEUE_NBLOCKS_THRESHOLD 10 // chunks of N blocks
+#define BLOCK_QUEUE_NSPANS_THRESHOLD 10 // chunks of N blocks
#define BLOCK_QUEUE_SIZE_THRESHOLD (100*1024*1024) // MB
-#define REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD (5 * 1000000) // microseconds
+#define BLOCK_QUEUE_FORCE_DOWNLOAD_NEAR_BLOCKS 1000
+#define REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD_STANDBY (5 * 1000000) // microseconds
+#define REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD (30 * 1000000) // microseconds
#define IDLE_PEER_KICK_TIME (600 * 1000000) // microseconds
#define PASSIVE_PEER_KICK_TIME (60 * 1000000) // microseconds
+#define DROP_ON_SYNC_WEDGE_THRESHOLD (30 * 1000000000ull) // nanoseconds
+#define LAST_ACTIVITY_STALL_THRESHOLD (2.0f) // seconds
namespace cryptonote
{
@@ -75,6 +82,19 @@ namespace cryptonote
template<class t_core>
bool t_cryptonote_protocol_handler<t_core>::init(const boost::program_options::variables_map& vm)
{
+ m_sync_timer.pause();
+ m_sync_timer.reset();
+ m_add_timer.pause();
+ m_add_timer.reset();
+ m_last_add_end_time = 0;
+ m_sync_spans_downloaded = 0;
+ m_sync_old_spans_downloaded = 0;
+ m_sync_bad_spans_downloaded = 0;
+ m_sync_download_chain_size = 0;
+ m_sync_download_objects_size = 0;
+
+ m_block_download_max_size = command_line::get_arg(vm, cryptonote::arg_block_download_max_size);
+
return true;
}
//------------------------------------------------------------------------------------------------------------------------
@@ -103,9 +123,12 @@ namespace cryptonote
if(context.m_state == cryptonote_connection_context::state_synchronizing)
{
NOTIFY_REQUEST_CHAIN::request r = boost::value_initialized<NOTIFY_REQUEST_CHAIN::request>();
+ context.m_needed_objects.clear();
m_core.get_short_chain_history(r.block_ids);
- LOG_PRINT_CCONTEXT_L2("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() );
+ handler_request_blocks_history( r.block_ids ); // change the limit(?), sleep(?)
+ MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() );
post_notify<NOTIFY_REQUEST_CHAIN>(r, context);
+ MLOG_PEER_STATE("requesting chain");
}
else if(context.m_state == cryptonote_connection_context::state_standby)
{
@@ -247,6 +270,7 @@ namespace cryptonote
cnx.connection_id = epee::string_tools::pod_to_hex(cntxt.m_connection_id);
cnx.height = cntxt.m_remote_blockchain_height;
+ cnx.pruning_seed = cntxt.m_pruning_seed;
connections.push_back(cnx);
@@ -279,7 +303,23 @@ namespace cryptonote
}
}
+ // reject weird pruning schemes
+ if (hshd.pruning_seed)
+ {
+ const uint32_t log_stripes = tools::get_pruning_log_stripes(hshd.pruning_seed);
+ if (log_stripes != CRYPTONOTE_PRUNING_LOG_STRIPES || tools::get_pruning_stripe(hshd.pruning_seed) > (1u << log_stripes))
+ {
+ MWARNING(context << " peer claim unexpected pruning seed " << epee::string_tools::to_string_hex(hshd.pruning_seed) << ", disconnecting");
+ return false;
+ }
+ }
+
context.m_remote_blockchain_height = hshd.current_height;
+ context.m_pruning_seed = hshd.pruning_seed;
+#ifdef CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED
+ context.m_pruning_seed = tools::make_pruning_seed(1 + (context.m_remote_address.as<epee::net_utils::ipv4_network_address>().ip()) % (1 << CRYPTONOTE_PRUNING_LOG_STRIPES), CRYPTONOTE_PRUNING_LOG_STRIPES);
+ LOG_INFO_CC(context, "New connection posing as pruning seed " << epee::string_tools::to_string_hex(context.m_pruning_seed) << ", seed address " << &context.m_pruning_seed);
+#endif
uint64_t target = m_core.get_target_blockchain_height();
if (target == 0)
@@ -298,7 +338,6 @@ namespace cryptonote
/* As I don't know if accessing hshd from core could be a good practice,
I prefer pushing target height to the core at the same time it is pushed to the user.
Nz. */
- m_core.set_target_blockchain_height((hshd.current_height));
int64_t diff = static_cast<int64_t>(hshd.current_height) - static_cast<int64_t>(m_core.get_current_blockchain_height());
uint64_t abs_diff = std::abs(diff);
uint64_t max_block_height = std::max(hshd.current_height,m_core.get_current_blockchain_height());
@@ -309,14 +348,31 @@ namespace cryptonote
<< (0 <= diff ? std::string("behind") : std::string("ahead"))
<< "] " << ENDL << "SYNCHRONIZATION started");
if (hshd.current_height >= m_core.get_current_blockchain_height() + 5) // don't switch to unsafe mode just for a few blocks
+ {
m_core.safesyncmode(false);
+ }
+ if (m_core.get_target_blockchain_height() == 0) // only when sync starts
+ {
+ m_sync_timer.resume();
+ m_sync_timer.reset();
+ m_add_timer.pause();
+ m_add_timer.reset();
+ m_last_add_end_time = 0;
+ m_sync_spans_downloaded = 0;
+ m_sync_old_spans_downloaded = 0;
+ m_sync_bad_spans_downloaded = 0;
+ m_sync_download_chain_size = 0;
+ m_sync_download_objects_size = 0;
+ }
+ m_core.set_target_blockchain_height((hshd.current_height));
}
- LOG_PRINT_L1("Remote blockchain height: " << hshd.current_height << ", id: " << hshd.top_id);
+ MINFO(context << "Remote blockchain height: " << hshd.current_height << ", id: " << hshd.top_id);
context.m_state = cryptonote_connection_context::state_synchronizing;
//let the socket to send response to handshake, but request callback, to let send request data after response
LOG_PRINT_CCONTEXT_L2("requesting callback");
++context.m_callback_request_count;
m_p2p->request_callback(context);
+ MLOG_PEER_STATE("requesting callback");
return true;
}
//------------------------------------------------------------------------------------------------------------------------
@@ -327,6 +383,7 @@ namespace cryptonote
hshd.top_version = m_core.get_ideal_hard_fork_version(hshd.current_height);
hshd.cumulative_difficulty = m_core.get_block_cumulative_difficulty(hshd.current_height);
hshd.current_height +=1;
+ hshd.pruning_seed = m_core.get_blockchain_pruning_seed();
return true;
}
//------------------------------------------------------------------------------------------------------------------------
@@ -389,11 +446,14 @@ namespace cryptonote
relay_block(arg, context);
}else if(bvc.m_marked_as_orphaned)
{
+ context.m_needed_objects.clear();
context.m_state = cryptonote_connection_context::state_synchronizing;
NOTIFY_REQUEST_CHAIN::request r = boost::value_initialized<NOTIFY_REQUEST_CHAIN::request>();
m_core.get_short_chain_history(r.block_ids);
- LOG_PRINT_CCONTEXT_L2("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() );
+ handler_request_blocks_history( r.block_ids ); // change the limit(?), sleep(?)
+ MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() );
post_notify<NOTIFY_REQUEST_CHAIN>(r, context);
+ MLOG_PEER_STATE("requesting chain");
}
return 1;
@@ -616,6 +676,7 @@ namespace cryptonote
missing_tx_req.missing_tx_indices = std::move(need_tx_indices);
m_core.resume_mine();
+ MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_FLUFFY_MISSING_TX: missing_tx_indices.size()=" << missing_tx_req.missing_tx_indices.size() );
post_notify<NOTIFY_REQUEST_FLUFFY_MISSING_TX>(missing_tx_req, context);
}
else // whoo-hoo we've got em all ..
@@ -656,11 +717,14 @@ namespace cryptonote
}
else if( bvc.m_marked_as_orphaned )
{
+ context.m_needed_objects.clear();
context.m_state = cryptonote_connection_context::state_synchronizing;
NOTIFY_REQUEST_CHAIN::request r = boost::value_initialized<NOTIFY_REQUEST_CHAIN::request>();
m_core.get_short_chain_history(r.block_ids);
- LOG_PRINT_CCONTEXT_L2("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() );
+ handler_request_blocks_history( r.block_ids ); // change the limit(?), sleep(?)
+ MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() );
post_notify<NOTIFY_REQUEST_CHAIN>(r, context);
+ MLOG_PEER_STATE("requesting chain");
}
}
}
@@ -747,7 +811,7 @@ namespace cryptonote
fluffy_response.b.txs.push_back(t_serializable_object_to_blob(tx));
}
- LOG_PRINT_CCONTEXT_L2
+ MLOG_P2P_MESSAGE
(
"-->>NOTIFY_RESPONSE_FLUFFY_MISSING_TX: "
<< ", txs.size()=" << fluffy_response.b.txs.size()
@@ -811,7 +875,7 @@ namespace cryptonote
drop_connection(context, false, false);
return 1;
}
- LOG_PRINT_CCONTEXT_L2("-->>NOTIFY_RESPONSE_GET_OBJECTS: blocks.size()=" << rsp.blocks.size() << ", txs.size()=" << rsp.txs.size()
+ MLOG_P2P_MESSAGE("-->>NOTIFY_RESPONSE_GET_OBJECTS: blocks.size()=" << rsp.blocks.size() << ", txs.size()=" << rsp.txs.size()
<< ", rsp.m_current_blockchain_height=" << rsp.current_blockchain_height << ", missed_ids.size()=" << rsp.missed_ids.size());
post_notify<NOTIFY_RESPONSE_GET_OBJECTS>(rsp, context);
//handler_response_blocks_now(sizeof(rsp)); // XXX
@@ -834,11 +898,14 @@ namespace cryptonote
return avg / m_avg_buffer.size();
}
-
template<class t_core>
int t_cryptonote_protocol_handler<t_core>::handle_response_get_objects(int command, NOTIFY_RESPONSE_GET_OBJECTS::request& arg, cryptonote_connection_context& context)
{
MLOG_P2P_MESSAGE("Received NOTIFY_RESPONSE_GET_OBJECTS (" << arg.blocks.size() << " blocks, " << arg.txs.size() << " txes)");
+ MLOG_PEER_STATE("received objects");
+
+ boost::posix_time::ptime request_time = context.m_last_request_time;
+ context.m_last_request_time = boost::date_time::not_a_date_time;
// calculate size of request
size_t size = 0;
@@ -860,6 +927,8 @@ namespace cryptonote
CRITICAL_REGION_LOCAL(m_buffer_mutex);
m_avg_buffer.push_back(size);
}
+ ++m_sync_spans_downloaded;
+ m_sync_download_objects_size += size;
MDEBUG(context << " downloaded " << size << " bytes worth of blocks");
/*using namespace boost::chrono;
@@ -874,6 +943,7 @@ namespace cryptonote
LOG_ERROR_CCONTEXT("sent wrong NOTIFY_HAVE_OBJECTS: arg.m_current_blockchain_height=" << arg.current_blockchain_height
<< " < m_last_response_height=" << context.m_last_response_height << ", dropping connection");
drop_connection(context, false, false);
+ ++m_sync_bad_spans_downloaded;
return 1;
}
@@ -898,6 +968,7 @@ namespace cryptonote
LOG_ERROR_CCONTEXT("sent wrong block: failed to parse and validate block: "
<< epee::string_tools::buff_to_hex_nodelimer(block_entry.block) << ", dropping connection");
drop_connection(context, false, false);
+ ++m_sync_bad_spans_downloaded;
return 1;
}
if (b.miner_tx.vin.size() != 1 || b.miner_tx.vin.front().type() != typeid(txin_gen))
@@ -905,6 +976,7 @@ namespace cryptonote
LOG_ERROR_CCONTEXT("sent wrong block: block: miner tx does not have exactly one txin_gen input"
<< epee::string_tools::buff_to_hex_nodelimer(block_entry.block) << ", dropping connection");
drop_connection(context, false, false);
+ ++m_sync_bad_spans_downloaded;
return 1;
}
if (start_height == std::numeric_limits<uint64_t>::max())
@@ -917,6 +989,7 @@ namespace cryptonote
LOG_ERROR_CCONTEXT("sent wrong NOTIFY_RESPONSE_GET_OBJECTS: block with id=" << epee::string_tools::pod_to_hex(get_blob_hash(block_entry.block))
<< " wasn't requested, dropping connection");
drop_connection(context, false, false);
+ ++m_sync_bad_spans_downloaded;
return 1;
}
if(b.tx_hashes.size() != block_entry.txs.size())
@@ -924,6 +997,7 @@ namespace cryptonote
LOG_ERROR_CCONTEXT("sent wrong NOTIFY_RESPONSE_GET_OBJECTS: block with id=" << epee::string_tools::pod_to_hex(get_blob_hash(block_entry.block))
<< ", tx_hashes.size()=" << b.tx_hashes.size() << " mismatch with block_complete_entry.m_txs.size()=" << block_entry.txs.size() << ", dropping connection");
drop_connection(context, false, false);
+ ++m_sync_bad_spans_downloaded;
return 1;
}
@@ -931,34 +1005,27 @@ namespace cryptonote
block_hashes.push_back(block_hash);
}
- if(context.m_requested_objects.size())
+ if(!context.m_requested_objects.empty())
{
- MERROR("returned not all requested objects (context.m_requested_objects.size()="
+ MERROR(context << "returned not all requested objects (context.m_requested_objects.size()="
<< context.m_requested_objects.size() << "), dropping connection");
drop_connection(context, false, false);
+ ++m_sync_bad_spans_downloaded;
return 1;
}
- // get the last parsed block, which should be the highest one
- const crypto::hash last_block_hash = cryptonote::get_block_hash(b);
- if(m_core.have_block(last_block_hash))
- {
- const uint64_t subchain_height = start_height + arg.blocks.size();
- LOG_DEBUG_CC(context, "These are old blocks, ignoring: blocks " << start_height << " - " << (subchain_height-1) << ", blockchain height " << m_core.get_current_blockchain_height());
- m_block_queue.remove_spans(context.m_connection_id, start_height);
- goto skip;
- }
-
{
MLOG_YELLOW(el::Level::Debug, context << " Got NEW BLOCKS inside of " << __FUNCTION__ << ": size: " << arg.blocks.size()
- << ", blocks: " << start_height << " - " << (start_height + arg.blocks.size() - 1));
+ << ", blocks: " << start_height << " - " << (start_height + arg.blocks.size() - 1) <<
+ " (pruning seed " << epee::string_tools::to_string_hex(context.m_pruning_seed) << ")");
// add that new span to the block queue
- const boost::posix_time::time_duration dt = now - context.m_last_request_time;
+ const boost::posix_time::time_duration dt = now - request_time;
const float rate = size * 1e6 / (dt.total_microseconds() + 1);
- MDEBUG(context << " adding span: " << arg.blocks.size() << " at height " << start_height << ", " << dt.total_microseconds()/1e6 << " seconds, " << (rate/1e3) << " kB/s, size now " << (m_block_queue.get_data_size() + blocks_size) / 1048576.f << " MB");
+ MDEBUG(context << " adding span: " << arg.blocks.size() << " at height " << start_height << ", " << dt.total_microseconds()/1e6 << " seconds, " << (rate/1024) << " kB/s, size now " << (m_block_queue.get_data_size() + blocks_size) / 1048576.f << " MB");
m_block_queue.add_blocks(start_height, arg.blocks, context.m_connection_id, rate, blocks_size);
+ const crypto::hash last_block_hash = cryptonote::get_block_hash(b);
context.m_last_known_hash = last_block_hash;
if (!m_core.get_test_drop_download() || !m_core.get_test_drop_download_height()) { // DISCARD BLOCKS for testing
@@ -966,7 +1033,6 @@ namespace cryptonote
}
}
-skip:
try_add_next_blocks(context);
return 1;
}
@@ -983,15 +1049,22 @@ skip:
const boost::unique_lock<boost::mutex> sync{m_sync_lock, boost::try_to_lock};
if (!sync.owns_lock())
{
- MINFO("Failed to lock m_sync_lock, going back to download");
+ MINFO(context << "Failed to lock m_sync_lock, going back to download");
goto skip;
}
MDEBUG(context << " lock m_sync_lock, adding blocks to chain...");
+ MLOG_PEER_STATE("adding blocks");
{
m_core.pause_mine();
- epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler(
- boost::bind(&t_core::resume_mine, &m_core));
+ m_add_timer.resume();
+ bool starting = true;
+ epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([this, &starting]() {
+ m_add_timer.pause();
+ m_core.resume_mine();
+ if (!starting)
+ m_last_add_end_time = tools::get_tick_count();
+ });
while (1)
{
@@ -1004,22 +1077,38 @@ skip:
MDEBUG(context << " no next span found, going back to download");
break;
}
- MDEBUG(context << " next span in the queue has blocks " << start_height << "-" << (start_height + blocks.size() - 1)
- << ", we need " << previous_height);
if (blocks.empty())
{
- MERROR("Next span has no blocks");
+ MERROR(context << "Next span has no blocks");
m_block_queue.remove_spans(span_connection_id, start_height);
- break;
+ continue;
}
+ MDEBUG(context << " next span in the queue has blocks " << start_height << "-" << (start_height + blocks.size() - 1)
+ << ", we need " << previous_height);
+
block new_block;
+ if (!parse_and_validate_block_from_blob(blocks.back().block, new_block))
+ {
+ MERROR(context << "Failed to parse block, but it should already have been parsed");
+ m_block_queue.remove_spans(span_connection_id, start_height);
+ continue;
+ }
+ const crypto::hash last_block_hash = cryptonote::get_block_hash(new_block);
+ if (m_core.have_block(last_block_hash))
+ {
+ const uint64_t subchain_height = start_height + blocks.size();
+ LOG_DEBUG_CC(context, "These are old blocks, ignoring: blocks " << start_height << " - " << (subchain_height-1) << ", blockchain height " << m_core.get_current_blockchain_height());
+ m_block_queue.remove_spans(span_connection_id, start_height);
+ ++m_sync_old_spans_downloaded;
+ continue;
+ }
if (!parse_and_validate_block_from_blob(blocks.front().block, new_block))
{
- MERROR("Failed to parse block, but it should already have been parsed");
+ MERROR(context << "Failed to parse block, but it should already have been parsed");
m_block_queue.remove_spans(span_connection_id, start_height);
- break;
+ continue;
}
bool parent_known = m_core.have_block(new_block.prev_id);
if (!parent_known)
@@ -1032,6 +1121,24 @@ skip:
bool parent_requested = m_block_queue.requested(new_block.prev_id);
if (!parent_requested)
{
+ // we might be able to ask for that block directly, as we now can request out of order,
+ // otherwise we continue out of order, unless this block is the one we need, in which
+ // case we request block hashes, though it might be safer to disconnect ?
+ if (start_height > previous_height)
+ {
+ if (should_drop_connection(context, get_next_needed_pruning_stripe().first))
+ {
+ MDEBUG(context << "Got block with unknown parent which was not requested, but peer does not have that block - dropping connection");
+ if (!context.m_is_income)
+ m_p2p->add_used_stripe_peer(context);
+ drop_connection(context, false, true);
+ return 1;
+ }
+ MDEBUG(context << "Got block with unknown parent which was not requested, but peer does not have that block - back to download");
+
+ goto skip;
+ }
+
// this can happen if a connection was sicced onto a late span, if it did not have those blocks,
// since we don't know that at the sic time
LOG_ERROR_CCONTEXT("Got block with unknown parent which was not requested - querying block hashes");
@@ -1047,7 +1154,17 @@ skip:
}
const boost::posix_time::ptime start = boost::posix_time::microsec_clock::universal_time();
- context.m_last_request_time = start;
+
+ if (starting)
+ {
+ starting = false;
+ if (m_last_add_end_time)
+ {
+ const uint64_t tnow = tools::get_tick_count();
+ const uint64_t ns = tools::ticks_to_ns(tnow - m_last_add_end_time);
+ MINFO("Restarting adding block after idle for " << ns/1e9 << " seconds");
+ }
+ }
m_core.prepare_handle_incoming_blocks(blocks);
@@ -1150,7 +1267,7 @@ skip:
} // each download block
- MCINFO("sync-info", "Block process time (" << blocks.size() << " blocks, " << num_txs << " txs): " << block_process_time_full + transactions_process_time_full << " (" << transactions_process_time_full << "/" << block_process_time_full << ") ms");
+ MDEBUG(context << "Block process time (" << blocks.size() << " blocks, " << num_txs << " txs): " << block_process_time_full + transactions_process_time_full << " (" << transactions_process_time_full << "/" << block_process_time_full << ") ms");
if (!m_core.cleanup_handle_incoming_blocks())
{
@@ -1160,40 +1277,54 @@ skip:
m_block_queue.remove_spans(span_connection_id, start_height);
- if (m_core.get_current_blockchain_height() > previous_height)
+ const uint64_t current_blockchain_height = m_core.get_current_blockchain_height();
+ if (current_blockchain_height > previous_height)
{
+ const uint64_t target_blockchain_height = m_core.get_target_blockchain_height();
const boost::posix_time::time_duration dt = boost::posix_time::microsec_clock::universal_time() - start;
+ std::string progress_message = "";
+ if (current_blockchain_height < target_blockchain_height)
+ {
+ uint64_t completion_percent = (current_blockchain_height * 100 / target_blockchain_height);
+ if (completion_percent == 100) // never show 100% if not actually up to date
+ completion_percent = 99;
+ progress_message = " (" + std::to_string(completion_percent) + "%, "
+ + std::to_string(target_blockchain_height - current_blockchain_height) + " left)";
+ }
+ const uint32_t previous_stripe = tools::get_pruning_stripe(previous_height, target_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES);
+ const uint32_t current_stripe = tools::get_pruning_stripe(current_blockchain_height, target_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES);
std::string timing_message = "";
if (ELPP->vRegistry()->allowed(el::Level::Info, "sync-info"))
timing_message = std::string(" (") + std::to_string(dt.total_microseconds()/1e6) + " sec, "
- + std::to_string((m_core.get_current_blockchain_height() - previous_height) * 1e6 / dt.total_microseconds())
- + " blocks/sec), " + std::to_string(m_block_queue.get_data_size() / 1048576.f) + " MB queued";
+ + std::to_string((current_blockchain_height - previous_height) * 1e6 / dt.total_microseconds())
+ + " blocks/sec), " + std::to_string(m_block_queue.get_data_size() / 1048576.f) + " MB queued in "
+ + std::to_string(m_block_queue.get_num_filled_spans()) + " spans, stripe "
+ + std::to_string(previous_stripe) + " -> " + std::to_string(current_stripe);
if (ELPP->vRegistry()->allowed(el::Level::Debug, "sync-info"))
- timing_message += std::string(": ") + m_block_queue.get_overview();
- if(m_core.get_target_blockchain_height() == 0){
- MGINFO_YELLOW(context << " Synced " << m_core.get_current_blockchain_height() << "/" << m_core.get_target_blockchain_height()
- << timing_message);
- } else {
- const int completition_percent = (m_core.get_current_blockchain_height() * 100 / m_core.get_target_blockchain_height());
- if(completition_percent < 99) {//printing completion percent only if % is < of 99 cause for 99 >= this is useless
- MGINFO_YELLOW(context << " Synced " << m_core.get_current_blockchain_height() << "/" << m_core.get_target_blockchain_height()
- << " (" << completition_percent << "% " << (m_core.get_target_blockchain_height() - m_core.get_current_blockchain_height())
- << " blocks remaining)" << timing_message);
- } else {
- MGINFO_YELLOW(context << " Synced " << m_core.get_current_blockchain_height() << "/" << m_core.get_target_blockchain_height()
- << timing_message);
- }
- }
+ timing_message += std::string(": ") + m_block_queue.get_overview(current_blockchain_height);
+ MGINFO_YELLOW("Synced " << current_blockchain_height << "/" << target_blockchain_height
+ << progress_message << timing_message);
+ if (previous_stripe != current_stripe)
+ notify_new_stripe(context, current_stripe);
}
}
}
- if (should_download_next_span(context))
+ MLOG_PEER_STATE("stopping adding blocks");
+
+ if (should_download_next_span(context, false))
{
- context.m_needed_objects.clear();
- context.m_last_response_height = 0;
force_next_span = true;
}
+ else if (should_drop_connection(context, get_next_needed_pruning_stripe().first))
+ {
+ if (!context.m_is_income)
+ {
+ m_p2p->add_used_stripe_peer(context);
+ drop_connection(context, false, false);
+ }
+ return 1;
+ }
}
skip:
@@ -1207,6 +1338,28 @@ skip:
}
//------------------------------------------------------------------------------------------------------------------------
template<class t_core>
+ void t_cryptonote_protocol_handler<t_core>::notify_new_stripe(cryptonote_connection_context& cntxt, uint32_t stripe)
+ {
+ m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)->bool
+ {
+ if (cntxt.m_connection_id == context.m_connection_id)
+ return true;
+ if (context.m_state == cryptonote_connection_context::state_normal)
+ {
+ const uint32_t peer_stripe = tools::get_pruning_stripe(context.m_pruning_seed);
+ if (stripe && peer_stripe && peer_stripe != stripe)
+ return true;
+ context.m_state = cryptonote_connection_context::state_synchronizing;
+ LOG_PRINT_CCONTEXT_L2("requesting callback");
+ ++context.m_callback_request_count;
+ m_p2p->request_callback(context);
+ MLOG_PEER_STATE("requesting callback");
+ }
+ return true;
+ });
+ }
+ //------------------------------------------------------------------------------------------------------------------------
+ template<class t_core>
bool t_cryptonote_protocol_handler<t_core>::on_idle()
{
m_idle_peer_kicker.do_call(boost::bind(&t_cryptonote_protocol_handler<t_core>::kick_idle_peers, this));
@@ -1218,30 +1371,24 @@ skip:
bool t_cryptonote_protocol_handler<t_core>::kick_idle_peers()
{
MTRACE("Checking for idle peers...");
- std::vector<boost::uuids::uuid> kick_connections;
m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)->bool
{
- if (context.m_state == cryptonote_connection_context::state_synchronizing || context.m_state == cryptonote_connection_context::state_before_handshake)
+ if (context.m_state == cryptonote_connection_context::state_synchronizing && context.m_last_request_time != boost::date_time::not_a_date_time)
{
- const bool passive = context.m_state == cryptonote_connection_context::state_before_handshake;
const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
const boost::posix_time::time_duration dt = now - context.m_last_request_time;
- const int64_t threshold = passive ? PASSIVE_PEER_KICK_TIME : IDLE_PEER_KICK_TIME;
- if (dt.total_microseconds() > threshold)
+ if (dt.total_microseconds() > IDLE_PEER_KICK_TIME)
{
- MINFO(context << " kicking " << (passive ? "passive" : "idle") << " peer");
- kick_connections.push_back(context.m_connection_id);
+ MINFO(context << " kicking idle peer, last update " << (dt.total_microseconds() / 1.e6) << " seconds ago");
+ LOG_PRINT_CCONTEXT_L2("requesting callback");
+ context.m_last_request_time = boost::date_time::not_a_date_time;
+ context.m_state = cryptonote_connection_context::state_standby; // we'll go back to adding, then (if we can't), download
+ ++context.m_callback_request_count;
+ m_p2p->request_callback(context);
}
}
return true;
});
- for (const boost::uuids::uuid &conn_id: kick_connections)
- {
- m_p2p->for_connection(conn_id, [this](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags) {
- drop_connection(context, false, false);
- return true;
- });
- }
return true;
}
//------------------------------------------------------------------------------------------------------------------------
@@ -1272,75 +1419,174 @@ skip:
drop_connection(context, false, false);
return 1;
}
- LOG_PRINT_CCONTEXT_L2("-->>NOTIFY_RESPONSE_CHAIN_ENTRY: m_start_height=" << r.start_height << ", m_total_height=" << r.total_height << ", m_block_ids.size()=" << r.m_block_ids.size());
+ MLOG_P2P_MESSAGE("-->>NOTIFY_RESPONSE_CHAIN_ENTRY: m_start_height=" << r.start_height << ", m_total_height=" << r.total_height << ", m_block_ids.size()=" << r.m_block_ids.size());
post_notify<NOTIFY_RESPONSE_CHAIN_ENTRY>(r, context);
return 1;
}
//------------------------------------------------------------------------------------------------------------------------
template<class t_core>
- bool t_cryptonote_protocol_handler<t_core>::should_download_next_span(cryptonote_connection_context& context) const
+ bool t_cryptonote_protocol_handler<t_core>::should_download_next_span(cryptonote_connection_context& context, bool standby)
{
std::vector<crypto::hash> hashes;
boost::uuids::uuid span_connection_id;
boost::posix_time::ptime request_time;
+ boost::uuids::uuid connection_id;
std::pair<uint64_t, uint64_t> span;
+ bool filled;
- span = m_block_queue.get_start_gap_span();
- if (span.second > 0)
- {
- MDEBUG(context << " we should download it as there is a gap");
- return true;
- }
-
- // if the next span is not scheduled (or there is none)
- span = m_block_queue.get_next_span_if_scheduled(hashes, span_connection_id, request_time);
- if (span.second == 0)
+ const uint64_t blockchain_height = m_core.get_current_blockchain_height();
+ if (context.m_remote_blockchain_height <= blockchain_height)
+ return false;
+ const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
+ const bool has_next_block = tools::has_unpruned_block(blockchain_height, context.m_remote_blockchain_height, context.m_pruning_seed);
+ if (has_next_block)
{
- // we might be in a weird case where there is a filled next span,
- // but it starts higher than the current height
- uint64_t height;
- std::vector<cryptonote::block_complete_entry> bcel;
- if (!m_block_queue.get_next_span(height, bcel, span_connection_id, true))
- return false;
- if (height > m_core.get_current_blockchain_height())
+ if (!m_block_queue.has_next_span(blockchain_height, filled, request_time, connection_id))
{
- MDEBUG(context << " we should download it as the next block isn't scheduled");
+ MDEBUG(context << " we should download it as no peer reserved it");
return true;
}
+ if (!filled)
+ {
+ const long dt = (now - request_time).total_microseconds();
+ if (dt >= REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD)
+ {
+ MDEBUG(context << " we should download it as it's not been received yet after " << dt/1e6);
+ return true;
+ }
+
+ // in standby, be ready to double download early since we're idling anyway
+ // let the fastest peer trigger first
+ long threshold;
+ const double dl_speed = context.m_max_speed_down;
+ if (standby && dt >= REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD_STANDBY && dl_speed > 0)
+ {
+ bool download = false;
+ if (m_p2p->for_connection(connection_id, [&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t f)->bool{
+ const time_t nowt = time(NULL);
+ const time_t time_since_last_recv = nowt - ctx.m_last_recv;
+ const float last_activity = std::min((float)time_since_last_recv, dt/1e6f);
+ const bool stalled = last_activity > LAST_ACTIVITY_STALL_THRESHOLD;
+ if (stalled)
+ {
+ MDEBUG(context << " we should download it as the downloading peer is stalling for " << nowt - ctx.m_last_recv << " seconds");
+ download = true;
+ return true;
+ }
+
+ // estimate the standby peer can give us 80% of its max speed
+ // and let it download if that speed is > N times as fast as the current one
+ // N starts at 10 after REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD_STANDBY,
+ // decreases to 1.25 at REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD,
+ // so that at times goes without the download being done, a retry becomes easier
+ const float max_multiplier = 10.f;
+ const float min_multiplier = 1.25f;
+ float multiplier = max_multiplier;
+ if (dt/1e6 >= REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD_STANDBY)
+ {
+ multiplier = max_multiplier - (dt/1e6-REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD_STANDBY) * (max_multiplier - min_multiplier) / (REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD - REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD_STANDBY);
+ multiplier = std::min(max_multiplier, std::max(min_multiplier, multiplier));
+ }
+ if (dl_speed * .8f > ctx.m_current_speed_down * multiplier)
+ {
+ MDEBUG(context << " we should download it as we are substantially faster (" << dl_speed << " vs "
+ << ctx.m_current_speed_down << ", multiplier " << multiplier << " after " << dt/1e6 << " seconds)");
+ download = true;
+ return true;
+ }
+ return true;
+ }))
+ {
+ if (download)
+ return true;
+ }
+ else
+ {
+ MWARNING(context << " we should download it as the downloading peer is unexpectedly not known to us");
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+ //------------------------------------------------------------------------------------------------------------------------
+ template<class t_core>
+ bool t_cryptonote_protocol_handler<t_core>::should_drop_connection(cryptonote_connection_context& context, uint32_t next_stripe)
+ {
+ if (context.m_anchor)
+ {
+ MDEBUG(context << "This is an anchor peer, not dropping");
return false;
}
- // if it was scheduled by this particular peer
- if (span_connection_id == context.m_connection_id)
+ if (context.m_pruning_seed == 0)
+ {
+ MDEBUG(context << "This peer is not striped, not dropping");
return false;
+ }
- float span_speed = m_block_queue.get_speed(span_connection_id);
- float speed = m_block_queue.get_speed(context.m_connection_id);
- MDEBUG(context << " next span is scheduled for " << span_connection_id << ", speed " << span_speed << ", ours " << speed);
-
- // we try for that span too if:
- // - we're substantially faster, or:
- // - we're the fastest and the other one isn't (avoids a peer being waaaay slow but yet unmeasured)
- // - the other one asked at least 5 seconds ago
- if (span_speed < .25 && speed > .75f)
+ const uint32_t peer_stripe = tools::get_pruning_stripe(context.m_pruning_seed);
+ if (next_stripe == peer_stripe)
{
- MDEBUG(context << " we should download it as we're substantially faster");
- return true;
+ MDEBUG(context << "This peer has needed stripe " << peer_stripe << ", not dropping");
+ return false;
}
- if (speed == 1.0f && span_speed != 1.0f)
+
+ if (!context.m_needed_objects.empty())
{
- MDEBUG(context << " we should download it as we're the fastest peer");
- return true;
+ const uint64_t next_available_block_height = context.m_last_response_height - context.m_needed_objects.size() + 1;
+ if (tools::has_unpruned_block(next_available_block_height, context.m_remote_blockchain_height, context.m_pruning_seed))
+ {
+ MDEBUG(context << "This peer has unpruned next block at height " << next_available_block_height << ", not dropping");
+ return false;
+ }
}
- const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
- if ((now - request_time).total_microseconds() > REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD)
+
+ if (next_stripe > 0)
{
- MDEBUG(context << " we should download it as this span was requested long ago");
- return true;
+ unsigned int n_out_peers = 0, n_peers_on_next_stripe = 0;
+ m_p2p->for_each_connection([&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t support_flags)->bool{
+ if (!ctx.m_is_income)
+ ++n_out_peers;
+ if (ctx.m_state >= cryptonote_connection_context::state_synchronizing && tools::get_pruning_stripe(ctx.m_pruning_seed) == next_stripe)
+ ++n_peers_on_next_stripe;
+ return true;
+ });
+ const uint32_t distance = (peer_stripe + (1<<CRYPTONOTE_PRUNING_LOG_STRIPES) - next_stripe) % (1<<CRYPTONOTE_PRUNING_LOG_STRIPES);
+ if ((n_out_peers >= m_max_out_peers && n_peers_on_next_stripe == 0) || (distance > 1 && n_peers_on_next_stripe <= 2) || distance > 2)
+ {
+ MDEBUG(context << "we want seed " << next_stripe << ", and either " << n_out_peers << " is at max out peers ("
+ << m_max_out_peers << ") or distance " << distance << " from " << next_stripe << " to " << peer_stripe <<
+ " is too large and we have only " << n_peers_on_next_stripe << " peers on next seed, dropping connection to make space");
+ return true;
+ }
}
+ MDEBUG(context << "End of checks, not dropping");
return false;
}
//------------------------------------------------------------------------------------------------------------------------
template<class t_core>
+ void t_cryptonote_protocol_handler<t_core>::skip_unneeded_hashes(cryptonote_connection_context& context, bool check_block_queue) const
+ {
+ // take out blocks we already have
+ size_t skip = 0;
+ while (skip < context.m_needed_objects.size() && (m_core.have_block(context.m_needed_objects[skip]) || (check_block_queue && m_block_queue.have(context.m_needed_objects[skip]))))
+ {
+ // if we're popping the last hash, record it so we can ask again from that hash,
+ // this prevents never being able to progress on peers we get old hash lists from
+ if (skip + 1 == context.m_needed_objects.size())
+ context.m_last_known_hash = context.m_needed_objects[skip];
+ ++skip;
+ }
+ if (skip > 0)
+ {
+ MDEBUG(context << "skipping " << skip << "/" << context.m_needed_objects.size() << " blocks");
+ context.m_needed_objects = std::vector<crypto::hash>(context.m_needed_objects.begin() + skip, context.m_needed_objects.end());
+ }
+ }
+ //------------------------------------------------------------------------------------------------------------------------
+ template<class t_core>
bool t_cryptonote_protocol_handler<t_core>::request_missing_objects(cryptonote_connection_context& context, bool check_having_blocks, bool force_next_span)
{
// flush stale spans
@@ -1357,47 +1603,102 @@ skip:
{
do
{
- size_t nblocks = m_block_queue.get_num_filled_spans();
+ size_t nspans = m_block_queue.get_num_filled_spans();
size_t size = m_block_queue.get_data_size();
- if (nblocks < BLOCK_QUEUE_NBLOCKS_THRESHOLD || size < BLOCK_QUEUE_SIZE_THRESHOLD)
+ const uint64_t bc_height = m_core.get_current_blockchain_height();
+ const auto next_needed_pruning_stripe = get_next_needed_pruning_stripe();
+ const uint32_t add_stripe = tools::get_pruning_stripe(bc_height, context.m_remote_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES);
+ const uint32_t peer_stripe = tools::get_pruning_stripe(context.m_pruning_seed);
+ const size_t block_queue_size_threshold = m_block_download_max_size ? m_block_download_max_size : BLOCK_QUEUE_SIZE_THRESHOLD;
+ bool queue_proceed = nspans < BLOCK_QUEUE_NSPANS_THRESHOLD || size < block_queue_size_threshold;
+ // get rid of blocks we already requested, or already have
+ skip_unneeded_hashes(context, true);
+ uint64_t next_needed_height = m_block_queue.get_next_needed_height(bc_height);
+ uint64_t next_block_height;
+ if (context.m_needed_objects.empty())
+ next_block_height = next_needed_height;
+ else
+ next_block_height = context.m_last_response_height - context.m_needed_objects.size() + 1;
+ bool stripe_proceed_main = (add_stripe == 0 || peer_stripe == 0 || add_stripe == peer_stripe) && (next_block_height < bc_height + BLOCK_QUEUE_FORCE_DOWNLOAD_NEAR_BLOCKS || next_needed_height < bc_height + BLOCK_QUEUE_FORCE_DOWNLOAD_NEAR_BLOCKS);
+ bool stripe_proceed_secondary = tools::has_unpruned_block(next_block_height, context.m_remote_blockchain_height, context.m_pruning_seed);
+ bool proceed = stripe_proceed_main || (queue_proceed && stripe_proceed_secondary);
+ if (!stripe_proceed_main && !stripe_proceed_secondary && should_drop_connection(context, tools::get_pruning_stripe(next_block_height, context.m_remote_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES)))
+ {
+ if (!context.m_is_income)
+ m_p2p->add_used_stripe_peer(context);
+ return false; // drop outgoing connections
+ }
+
+ MDEBUG(context << "proceed " << proceed << " (queue " << queue_proceed << ", stripe " << stripe_proceed_main << "/" <<
+ stripe_proceed_secondary << "), " << next_needed_pruning_stripe.first << "-" << next_needed_pruning_stripe.second <<
+ " needed, bc add stripe " << add_stripe << ", we have " << peer_stripe << "), bc_height " << bc_height);
+ MDEBUG(context << " - next_block_height " << next_block_height << ", seed " << epee::string_tools::to_string_hex(context.m_pruning_seed) <<
+ ", next_needed_height "<< next_needed_height);
+ MDEBUG(context << " - last_response_height " << context.m_last_response_height << ", m_needed_objects size " << context.m_needed_objects.size());
+
+ // if we're waiting for next span, try to get it before unblocking threads below,
+ // or a runaway downloading of future spans might happen
+ if (stripe_proceed_main && should_download_next_span(context, true))
+ {
+ MDEBUG(context << " we should try for that next span too, we think we could get it faster, resuming");
+ force_next_span = true;
+ MLOG_PEER_STATE("resuming");
+ break;
+ }
+
+ if (proceed)
{
if (context.m_state != cryptonote_connection_context::state_standby)
{
- LOG_DEBUG_CC(context, "Block queue is " << nblocks << " and " << size << ", resuming");
+ LOG_DEBUG_CC(context, "Block queue is " << nspans << " and " << size << ", resuming");
+ MLOG_PEER_STATE("resuming");
}
break;
}
// this one triggers if all threads are in standby, which should not happen,
// but happened at least once, so we unblock at least one thread if so
- const boost::unique_lock<boost::mutex> sync{m_sync_lock, boost::try_to_lock};
+ boost::unique_lock<boost::mutex> sync{m_sync_lock, boost::try_to_lock};
if (sync.owns_lock())
{
- LOG_DEBUG_CC(context, "No other thread is adding blocks, resuming");
- break;
- }
+ bool filled = false;
+ boost::posix_time::ptime time;
+ boost::uuids::uuid connection_id;
+ if (m_block_queue.has_next_span(m_core.get_current_blockchain_height(), filled, time, connection_id) && filled)
+ {
+ LOG_DEBUG_CC(context, "No other thread is adding blocks, and next span needed is ready, resuming");
+ MLOG_PEER_STATE("resuming");
+ context.m_state = cryptonote_connection_context::state_standby;
+ ++context.m_callback_request_count;
+ m_p2p->request_callback(context);
+ return true;
+ }
+ else
+ {
+ sync.unlock();
- if (should_download_next_span(context))
- {
- MDEBUG(context << " we should try for that next span too, we think we could get it faster, resuming");
- force_next_span = true;
- break;
+ // if this has gone on for too long, drop incoming connection to guard against some wedge state
+ if (!context.m_is_income)
+ {
+ const uint64_t now = tools::get_tick_count();
+ const uint64_t dt = now - m_last_add_end_time;
+ if (tools::ticks_to_ns(dt) >= DROP_ON_SYNC_WEDGE_THRESHOLD)
+ {
+ MDEBUG(context << "Block addition seems to have wedged, dropping connection");
+ return false;
+ }
+ }
+ }
}
if (context.m_state != cryptonote_connection_context::state_standby)
{
- LOG_DEBUG_CC(context, "Block queue is " << nblocks << " and " << size << ", pausing");
+ if (!queue_proceed)
+ LOG_DEBUG_CC(context, "Block queue is " << nspans << " and " << size << ", pausing");
+ else if (!stripe_proceed_main && !stripe_proceed_secondary)
+ LOG_DEBUG_CC(context, "We do not have the stripe required to download another block, pausing");
context.m_state = cryptonote_connection_context::state_standby;
- }
-
- // this needs doing after we went to standby, so the callback knows what to do
- bool filled;
- if (m_block_queue.has_next_span(context.m_connection_id, filled) && !filled)
- {
- MDEBUG(context << " we have the next span, and it is scheduled, resuming");
- ++context.m_callback_request_count;
- m_p2p->request_callback(context);
- return true;
+ MLOG_PEER_STATE("pausing");
}
return true;
@@ -1405,7 +1706,9 @@ skip:
context.m_state = cryptonote_connection_context::state_synchronizing;
}
- MDEBUG(context << " request_missing_objects: check " << check_having_blocks << ", force_next_span " << force_next_span << ", m_needed_objects " << context.m_needed_objects.size() << " lrh " << context.m_last_response_height << ", chain " << m_core.get_current_blockchain_height());
+ MDEBUG(context << " request_missing_objects: check " << check_having_blocks << ", force_next_span " << force_next_span
+ << ", m_needed_objects " << context.m_needed_objects.size() << " lrh " << context.m_last_response_height << ", chain "
+ << m_core.get_current_blockchain_height() << ", pruning seed " << epee::string_tools::to_string_hex(context.m_pruning_seed));
if(context.m_needed_objects.size() || force_next_span)
{
//we know objects that we need, request this objects
@@ -1414,28 +1717,6 @@ skip:
size_t count = 0;
const size_t count_limit = m_core.get_block_sync_size(m_core.get_current_blockchain_height());
std::pair<uint64_t, uint64_t> span = std::make_pair(0, 0);
- {
- MDEBUG(context << " checking for gap");
- span = m_block_queue.get_start_gap_span();
- if (span.second > 0)
- {
- const uint64_t first_block_height_known = context.m_last_response_height - context.m_needed_objects.size() + 1;
- const uint64_t last_block_height_known = context.m_last_response_height;
- const uint64_t first_block_height_needed = span.first;
- const uint64_t last_block_height_needed = span.first + std::min(span.second, (uint64_t)count_limit) - 1;
- MDEBUG(context << " gap found, span: " << span.first << " - " << span.first + span.second - 1 << " (" << last_block_height_needed << ")");
- MDEBUG(context << " current known hashes from " << first_block_height_known << " to " << last_block_height_known);
- if (first_block_height_needed < first_block_height_known || last_block_height_needed > last_block_height_known)
- {
- MDEBUG(context << " we are missing some of the necessary hashes for this gap, requesting chain again");
- context.m_needed_objects.clear();
- context.m_last_response_height = 0;
- start_from_current_chain = true;
- goto skip;
- }
- MDEBUG(context << " we have the hashes for this gap");
- }
- }
if (force_next_span)
{
if (span.second == 0)
@@ -1452,6 +1733,7 @@ skip:
req.blocks.push_back(hash);
context.m_requested_objects.insert(hash);
}
+ m_block_queue.reset_next_span_time();
}
}
}
@@ -1465,22 +1747,20 @@ skip:
context.m_last_response_height = 0;
goto skip;
}
- // take out blocks we already have
- size_t skip = 0;
- while (skip < context.m_needed_objects.size() && m_core.have_block(context.m_needed_objects[skip]))
- {
- // if we're popping the last hash, record it so we can ask again from that hash,
- // this prevents never being able to progress on peers we get old hash lists from
- if (skip + 1 == context.m_needed_objects.size())
- context.m_last_known_hash = context.m_needed_objects[skip];
- ++skip;
- }
- if (skip > 0)
- context.m_needed_objects = std::vector<crypto::hash>(context.m_needed_objects.begin() + skip, context.m_needed_objects.end());
+ skip_unneeded_hashes(context, false);
const uint64_t first_block_height = context.m_last_response_height - context.m_needed_objects.size() + 1;
- span = m_block_queue.reserve_span(first_block_height, context.m_last_response_height, count_limit, context.m_connection_id, context.m_needed_objects);
+ span = m_block_queue.reserve_span(first_block_height, context.m_last_response_height, count_limit, context.m_connection_id, context.m_pruning_seed, context.m_remote_blockchain_height, context.m_needed_objects);
MDEBUG(context << " span from " << first_block_height << ": " << span.first << "/" << span.second);
+ if (span.second > 0)
+ {
+ const uint32_t stripe = tools::get_pruning_stripe(span.first, context.m_remote_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES);
+ if (context.m_pruning_seed && stripe != tools::get_pruning_stripe(context.m_pruning_seed))
+ {
+ MDEBUG(context << " starting early on next seed (" << span.first << " with stripe " << stripe <<
+ ", context seed " << epee::string_tools::to_string_hex(context.m_pruning_seed) << ")");
+ }
+ }
}
if (span.second == 0 && !force_next_span)
{
@@ -1489,6 +1769,8 @@ skip:
boost::uuids::uuid span_connection_id;
boost::posix_time::ptime time;
span = m_block_queue.get_next_span_if_scheduled(hashes, span_connection_id, time);
+ if (span.second > 0 && !tools::has_unpruned_block(span.first, context.m_remote_blockchain_height, context.m_pruning_seed))
+ span = std::make_pair(0, 0);
if (span.second > 0)
{
is_next = true;
@@ -1534,17 +1816,67 @@ skip:
}
context.m_last_request_time = boost::posix_time::microsec_clock::universal_time();
- LOG_PRINT_CCONTEXT_L1("-->>NOTIFY_REQUEST_GET_OBJECTS: blocks.size()=" << req.blocks.size() << ", txs.size()=" << req.txs.size()
+ MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_GET_OBJECTS: blocks.size()=" << req.blocks.size() << ", txs.size()=" << req.txs.size()
<< "requested blocks count=" << count << " / " << count_limit << " from " << span.first << ", first hash " << req.blocks.front());
//epee::net_utils::network_throttle_manager::get_global_throttle_inreq().logger_handle_net("log/dr-monero/net/req-all.data", sec, get_avg_block_size());
post_notify<NOTIFY_REQUEST_GET_OBJECTS>(req, context);
+ MLOG_PEER_STATE("requesting objects");
+ return true;
+ }
+
+ // if we're still around, we might be at a point where the peer is pruned, so we could either
+ // drop it to make space for other peers, or ask for a span further down the line
+ const uint32_t next_stripe = get_next_needed_pruning_stripe().first;
+ const uint32_t peer_stripe = tools::get_pruning_stripe(context.m_pruning_seed);
+ if (next_stripe && peer_stripe && next_stripe != peer_stripe)
+ {
+ // at this point, we have to either close the connection, or start getting blocks past the
+ // current point, or become dormant
+ MDEBUG(context << "this peer is pruned at seed " << epee::string_tools::to_string_hex(context.m_pruning_seed) <<
+ ", next stripe needed is " << next_stripe);
+ if (!context.m_is_income)
+ {
+ if (should_drop_connection(context, next_stripe))
+ {
+ m_p2p->add_used_stripe_peer(context);
+ return false; // drop outgoing connections
+ }
+ }
+ // we'll get back stuck waiting for the go ahead
+ context.m_state = cryptonote_connection_context::state_normal;
+ MLOG_PEER_STATE("Nothing to do for now, switching to normal state");
return true;
}
}
skip:
context.m_needed_objects.clear();
+
+ // we might have been called from the "received chain entry" handler, and end up
+ // here because we can't use any of those blocks (maybe because all of them are
+ // actually already requested). In this case, if we can add blocks instead, do so
+ if (m_core.get_current_blockchain_height() < m_core.get_target_blockchain_height())
+ {
+ const boost::unique_lock<boost::mutex> sync{m_sync_lock, boost::try_to_lock};
+ if (sync.owns_lock())
+ {
+ uint64_t start_height;
+ std::vector<cryptonote::block_complete_entry> blocks;
+ boost::uuids::uuid span_connection_id;
+ bool filled = false;
+ if (m_block_queue.get_next_span(start_height, blocks, span_connection_id, filled) && filled)
+ {
+ LOG_DEBUG_CC(context, "No other thread is adding blocks, resuming");
+ MLOG_PEER_STATE("will try to add blocks next");
+ context.m_state = cryptonote_connection_context::state_standby;
+ ++context.m_callback_request_count;
+ m_p2p->request_callback(context);
+ return true;
+ }
+ }
+ }
+
if(context.m_last_response_height < context.m_remote_blockchain_height-1)
{//we have to fetch more objects ids, request blockchain entry
@@ -1567,8 +1899,9 @@ skip:
//LOG_PRINT_CCONTEXT_L1("r = " << 200);
context.m_last_request_time = boost::posix_time::microsec_clock::universal_time();
- LOG_PRINT_CCONTEXT_L1("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() << ", start_from_current_chain " << start_from_current_chain);
+ MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() << ", start_from_current_chain " << start_from_current_chain);
post_notify<NOTIFY_REQUEST_CHAIN>(r, context);
+ MLOG_PEER_STATE("requesting chain");
}else
{
CHECK_AND_ASSERT_MES(context.m_last_response_height == context.m_remote_blockchain_height-1
@@ -1608,9 +1941,25 @@ skip:
<< ENDL
<< "Use the \"help\" command to see the list of available commands." << ENDL
<< "**********************************************************************");
+ m_sync_timer.pause();
+ if (ELPP->vRegistry()->allowed(el::Level::Info, "sync-info"))
+ {
+ const uint64_t sync_time = m_sync_timer.value();
+ const uint64_t add_time = m_add_timer.value();
+ if (sync_time && add_time)
+ {
+ MCLOG_YELLOW(el::Level::Info, "sync-info", "Sync time: " << sync_time/1e9/60 << " min, idle time " <<
+ (100.f * (1.0f - add_time / (float)sync_time)) << "%" << ", " <<
+ (10 * m_sync_download_objects_size / 1024 / 1024) / 10.f << " + " <<
+ (10 * m_sync_download_chain_size / 1024 / 1024) / 10.f << " MB downloaded, " <<
+ 100.0f * m_sync_old_spans_downloaded / m_sync_spans_downloaded << "% old spans, " <<
+ 100.0f * m_sync_bad_spans_downloaded / m_sync_spans_downloaded << "% bad spans");
+ }
+ }
m_core.on_synchronized();
}
m_core.safesyncmode(true);
+ m_p2p->clear_used_stripe_peers();
return true;
}
//------------------------------------------------------------------------------------------------------------------------
@@ -1631,6 +1980,11 @@ skip:
{
MLOG_P2P_MESSAGE("Received NOTIFY_RESPONSE_CHAIN_ENTRY: m_block_ids.size()=" << arg.m_block_ids.size()
<< ", m_start_height=" << arg.start_height << ", m_total_height=" << arg.total_height);
+ MLOG_PEER_STATE("received chain");
+
+ context.m_last_request_time = boost::date_time::not_a_date_time;
+
+ m_sync_download_chain_size += arg.m_block_ids.size() * sizeof(crypto::hash);
if(!arg.m_block_ids.size())
{
@@ -1644,7 +1998,14 @@ skip:
drop_connection(context, true, false);
return 1;
}
+ MDEBUG(context << "first block hash " << arg.m_block_ids.front() << ", last " << arg.m_block_ids.back());
+ if (arg.total_height >= CRYPTONOTE_MAX_BLOCK_NUMBER || arg.m_block_ids.size() >= CRYPTONOTE_MAX_BLOCK_NUMBER)
+ {
+ LOG_ERROR_CCONTEXT("sent wrong NOTIFY_RESPONSE_CHAIN_ENTRY, with total_height=" << arg.total_height << " and block_ids=" << arg.m_block_ids.size());
+ drop_connection(context, false, false);
+ return 1;
+ }
context.m_remote_blockchain_height = arg.total_height;
context.m_last_response_height = arg.start_height + arg.m_block_ids.size()-1;
if(context.m_last_response_height > context.m_remote_blockchain_height)
@@ -1664,6 +2025,7 @@ skip:
return 1;
}
+ context.m_needed_objects.clear();
uint64_t added = 0;
for(auto& bl_id: arg.m_block_ids)
{
@@ -1773,10 +2135,85 @@ skip:
}
//------------------------------------------------------------------------------------------------------------------------
template<class t_core>
+ std::string t_cryptonote_protocol_handler<t_core>::get_peers_overview() const
+ {
+ std::stringstream ss;
+ const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
+ m_p2p->for_each_connection([&](const connection_context &ctx, nodetool::peerid_type peer_id, uint32_t support_flags) {
+ const uint32_t stripe = tools::get_pruning_stripe(ctx.m_pruning_seed);
+ char state_char = cryptonote::get_protocol_state_char(ctx.m_state);
+ ss << stripe + state_char;
+ if (ctx.m_last_request_time != boost::date_time::not_a_date_time)
+ ss << (((now - ctx.m_last_request_time).total_microseconds() > IDLE_PEER_KICK_TIME) ? "!" : "?");
+ ss << + " ";
+ return true;
+ });
+ return ss.str();
+ }
+ //------------------------------------------------------------------------------------------------------------------------
+ template<class t_core>
+ std::pair<uint32_t, uint32_t> t_cryptonote_protocol_handler<t_core>::get_next_needed_pruning_stripe() const
+ {
+ const uint64_t want_height_from_blockchain = m_core.get_current_blockchain_height();
+ const uint64_t want_height_from_block_queue = m_block_queue.get_next_needed_height(want_height_from_blockchain);
+ const uint64_t want_height = std::max(want_height_from_blockchain, want_height_from_block_queue);
+ uint64_t blockchain_height = m_core.get_target_blockchain_height();
+ // if we don't know the remote chain size yet, assume infinitely large so we get the right stripe if we're not near the tip
+ if (blockchain_height == 0)
+ blockchain_height = CRYPTONOTE_MAX_BLOCK_NUMBER;
+ const uint32_t next_pruning_stripe = tools::get_pruning_stripe(want_height, blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES);
+ if (next_pruning_stripe == 0)
+ return std::make_pair(0, 0);
+ // if we already have a few peers on this stripe, but none on next one, try next one
+ unsigned int n_next = 0, n_subsequent = 0, n_others = 0;
+ const uint32_t subsequent_pruning_stripe = 1 + next_pruning_stripe % (1<<CRYPTONOTE_PRUNING_LOG_STRIPES);
+ m_p2p->for_each_connection([&](const connection_context &context, nodetool::peerid_type peer_id, uint32_t support_flags) {
+ if (context.m_state >= cryptonote_connection_context::state_synchronizing)
+ {
+ if (context.m_pruning_seed == 0 || tools::get_pruning_stripe(context.m_pruning_seed) == next_pruning_stripe)
+ ++n_next;
+ else if (tools::get_pruning_stripe(context.m_pruning_seed) == subsequent_pruning_stripe)
+ ++n_subsequent;
+ else
+ ++n_others;
+ }
+ return true;
+ });
+ const bool use_next = (n_next > m_max_out_peers / 2 && n_subsequent <= 1) || (n_next > 2 && n_subsequent == 0);
+ const uint32_t ret_stripe = use_next ? subsequent_pruning_stripe: next_pruning_stripe;
+ MIDEBUG(const std::string po = get_peers_overview(), "get_next_needed_pruning_stripe: want height " << want_height << " (" <<
+ want_height_from_blockchain << " from blockchain, " << want_height_from_block_queue << " from block queue), stripe " <<
+ next_pruning_stripe << " (" << n_next << "/" << m_max_out_peers << " on it and " << n_subsequent << " on " <<
+ subsequent_pruning_stripe << ", " << n_others << " others) -> " << ret_stripe << " (+" <<
+ (ret_stripe - next_pruning_stripe + (1 << CRYPTONOTE_PRUNING_LOG_STRIPES)) % (1 << CRYPTONOTE_PRUNING_LOG_STRIPES) <<
+ "), current peers " << po);
+ return std::make_pair(next_pruning_stripe, ret_stripe);
+ }
+ //------------------------------------------------------------------------------------------------------------------------
+ template<class t_core>
+ bool t_cryptonote_protocol_handler<t_core>::needs_new_sync_connections() const
+ {
+ const uint64_t target = m_core.get_target_blockchain_height();
+ const uint64_t height = m_core.get_current_blockchain_height();
+ if (target && target <= height)
+ return false;
+ size_t n_out_peers = 0;
+ m_p2p->for_each_connection([&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t support_flags)->bool{
+ if (!ctx.m_is_income)
+ ++n_out_peers;
+ return true;
+ });
+ if (n_out_peers >= m_max_out_peers)
+ return false;
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------
+ template<class t_core>
void t_cryptonote_protocol_handler<t_core>::drop_connection(cryptonote_connection_context &context, bool add_fail, bool flush_all_spans)
{
- LOG_DEBUG_CC(context, "dropping connection id " << context.m_connection_id <<
- ", add_fail " << add_fail << ", flush_all_spans " << flush_all_spans);
+ LOG_DEBUG_CC(context, "dropping connection id " << context.m_connection_id << " (pruning seed " <<
+ epee::string_tools::to_string_hex(context.m_pruning_seed) <<
+ "), add_fail " << add_fail << ", flush_all_spans " << flush_all_spans);
if (add_fail)
m_p2p->add_host_fail(context.m_remote_address);
@@ -1803,6 +2240,7 @@ skip:
}
m_block_queue.flush_spans(context.m_connection_id, false);
+ MLOG_PEER_STATE("closed");
}
//------------------------------------------------------------------------------------------------------------------------
diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp
index b5b747e97..37cb55ec8 100644
--- a/src/daemon/command_parser_executor.cpp
+++ b/src/daemon/command_parser_executor.cpp
@@ -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
diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h
index e2844e8b7..5b8927908 100644
--- a/src/daemon/command_parser_executor.h
+++ b/src/daemon/command_parser_executor.h
@@ -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
diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp
index 527ed2cf1..786d1a601 100644
--- a/src/daemon/command_server.cpp
+++ b/src/daemon/command_server.cpp
@@ -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)
diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp
index 4c7d68686..7cd61934f 100644
--- a/src/daemon/rpc_command_executor.cpp
+++ b/src/daemon/rpc_command_executor.cpp
@@ -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
diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h
index 1541a1a8e..de743065b 100644
--- a/src/daemon/rpc_command_executor.h
+++ b/src/daemon/rpc_command_executor.h
@@ -154,6 +154,10 @@ public:
bool sync_info();
bool pop_blocks(uint64_t num_blocks);
+
+ bool prune_blockchain();
+
+ bool check_blockchain_pruning();
};
} // namespace daemonize
diff --git a/src/debug_utilities/cn_deserialize.cpp b/src/debug_utilities/cn_deserialize.cpp
index 83422083b..f08f2b928 100644
--- a/src/debug_utilities/cn_deserialize.cpp
+++ b/src/debug_utilities/cn_deserialize.cpp
@@ -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;
diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h
index 0ef2dbb30..09c219a44 100644
--- a/src/p2p/net_node.h
+++ b/src/p2p/net_node.h
@@ -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;
};
diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl
index 25ac1ba18..c594984d4 100644
--- a/src/p2p/net_node.inl
+++ b/src/p2p/net_node.inl
@@ -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,7 +2094,7 @@ 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));
@@ -2038,6 +2102,42 @@ namespace nodetool
}
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)
{
MDEBUG("Attempting to add IGD port mapping.");
diff --git a/src/p2p/net_node_common.h b/src/p2p/net_node_common.h
index 656c6155b..075c18ef9 100644
--- a/src/p2p/net_node_common.h
+++ b/src/p2p/net_node_common.h
@@ -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()
+ {
+ }
};
}
diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h
index e7aad5abe..685fdc193 100644
--- a/src/p2p/net_peerlist.h
+++ b/src/p2p/net_peerlist.h
@@ -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();
diff --git a/src/p2p/net_peerlist_boost_serialization.h b/src/p2p/net_peerlist_boost_serialization.h
index e79207888..2f4a7e661 100644
--- a/src/p2p/net_peerlist_boost_serialization.h
+++ b/src/p2p/net_peerlist_boost_serialization.h
@@ -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>
diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h
index bb9d2635c..939cedf42 100644
--- a/src/p2p/p2p_protocol_defs.h
+++ b/src/p2p/p2p_protocol_defs.h
@@ -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)
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index d20000a53..08674a06e 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -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 = {
diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h
index 081ccc25d..83a7cfe27 100644
--- a/src/rpc/core_rpc_server.h
+++ b/src/rpc/core_rpc_server.h
@@ -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:
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index 0a07930ec..dfad5d6a7 100644
--- a/src/rpc/core_rpc_server_commands_defs.h
+++ b/src/rpc/core_rpc_server_commands_defs.h
@@ -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()
+ };
+ };
+
}
diff --git a/src/rpc/message_data_structs.h b/src/rpc/message_data_structs.h
index e09b6749e..73cf28cec 100644
--- a/src/rpc/message_data_structs.h
+++ b/src/rpc/message_data_structs.h
@@ -79,6 +79,7 @@ namespace rpc
uint32_t ip;
uint16_t port;
uint64_t last_seen;
+ uint32_t pruning_seed;
};
struct tx_in_pool
diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp
index 8b1af9c12..ee4fa4a19 100644
--- a/src/serialization/json_object.cpp
+++ b/src/serialization/json_object.cpp
@@ -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)
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index f02e5b6e1..febb28138 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -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;
diff --git a/tests/core_proxy/core_proxy.h b/tests/core_proxy/core_proxy.h
index 023c220ae..7888540cc 100644
--- a/tests/core_proxy/core_proxy.h
+++ b/tests/core_proxy/core_proxy.h
@@ -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; }
};
}
diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt
index f7012746d..5f7fa84a5 100644
--- a/tests/unit_tests/CMakeLists.txt
+++ b/tests/unit_tests/CMakeLists.txt
@@ -65,6 +65,7 @@ set(unit_tests_sources
notify.cpp
output_distribution.cpp
parse_amount.cpp
+ pruning.cpp
random.cpp
serialization.cpp
sha256.cpp
diff --git a/tests/unit_tests/ban.cpp b/tests/unit_tests/ban.cpp
index 12625a949..1e764c83e 100644
--- a/tests/unit_tests/ban.cpp
+++ b/tests/unit_tests/ban.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() {}
};
diff --git a/tests/unit_tests/pruning.cpp b/tests/unit_tests/pruning.cpp
new file mode 100644
index 000000000..83c35df68
--- /dev/null
+++ b/tests/unit_tests/pruning.cpp
@@ -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);
+}