aboutsummaryrefslogtreecommitdiff
path: root/src/blockchain_db
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 /src/blockchain_db
parentMerge pull request #5008 (diff)
parentPruning (diff)
downloadmonero-4a0e4c7d706f33fb72fe809434676b1de7b4b4f7.tar.xz
Merge pull request #4843
b750fb27 Pruning (moneromooo-monero)
Diffstat (limited to 'src/blockchain_db')
-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
4 files changed, 491 insertions, 25 deletions
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;