aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/blockchain_db/blockchain_db.cpp7
-rw-r--r--src/blockchain_db/blockchain_db.h118
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.cpp1763
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.h83
-rw-r--r--src/cryptonote_core/blockchain.cpp5
5 files changed, 1213 insertions, 763 deletions
diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp
index a66f4a403..68f635d18 100644
--- a/src/blockchain_db/blockchain_db.cpp
+++ b/src/blockchain_db/blockchain_db.cpp
@@ -82,14 +82,17 @@ void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const transacti
}
}
- add_transaction_data(blk_hash, tx, tx_hash);
+ uint64_t tx_id = add_transaction_data(blk_hash, tx, tx_hash);
+
+ std::vector<uint64_t> amount_output_indices;
// iterate tx.vout using indices instead of C++11 foreach syntax because
// we need the index
for (uint64_t i = 0; i < tx.vout.size(); ++i)
{
- add_output(tx_hash, tx.vout[i], i, tx.unlock_time);
+ amount_output_indices.push_back(add_output(tx_hash, tx.vout[i], i, tx.unlock_time));
}
+ add_tx_amount_output_indices(tx_id, amount_output_indices);
}
uint64_t BlockchainDB::add_block( const block& blk
diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h
index 3585bd061..1445dd13c 100644
--- a/src/blockchain_db/blockchain_db.h
+++ b/src/blockchain_db/blockchain_db.h
@@ -59,6 +59,39 @@
* Unspent transaction outputs are duplicated to quickly gather random
* outputs to use for mixins
*
+ * Indices and Identifiers:
+ * The word "index" is used ambiguously throughout this code. It is
+ * particularly confusing when talking about the output or transaction
+ * tables since their indexing can refer to themselves or each other.
+ * I have attempted to clarify these usages here:
+ *
+ * Blocks, transactions, and outputs are all identified by a hash.
+ * For storage efficiency, a 64-bit integer ID is used instead of the hash
+ * inside the DB. Tables exist to map between hash and ID. A block ID is
+ * also referred to as its "height". Transactions and outputs generally are
+ * not referred to by ID outside of this module, but the tx ID is returned
+ * by tx_exists() and used by get_tx_amount_output_indices(). Like their
+ * corresponding hashes, IDs are globally unique.
+ *
+ * The remaining uses of the word "index" refer to local offsets, and are
+ * not globally unique. An "amount output index" N refers to the Nth output
+ * of a specific amount. An "output local index" N refers to the Nth output
+ * of a specific tx.
+ *
+ * Exceptions:
+ * DB_ERROR -- generic
+ * DB_OPEN_FAILURE
+ * DB_CREATE_FAILURE
+ * DB_SYNC_FAILURE
+ * BLOCK_DNE
+ * BLOCK_PARENT_DNE
+ * BLOCK_EXISTS
+ * BLOCK_INVALID -- considering making this multiple errors
+ * TX_DNE
+ * TX_EXISTS
+ * OUTPUT_DNE
+ * OUTPUT_EXISTS
+ * KEY_IMAGE_EXISTS
*/
namespace cryptonote
@@ -80,6 +113,15 @@ struct output_data_t
};
#pragma pack(pop)
+#pragma pack(push, 1)
+struct tx_data_t
+{
+ uint64_t tx_id;
+ uint64_t unlock_time;
+ uint64_t block_id;
+};
+#pragma pack(pop)
+
/***********************************
* Exception Definitions
***********************************/
@@ -311,14 +353,18 @@ private:
* and the other data passed here, not the separate outputs of the
* transaction.
*
+ * It returns a tx ID, which is a mapping from the tx_hash. The tx ID
+ * is used in #add_tx_amount_output_indices().
+ *
* If any of this cannot be done, the subclass should throw the corresponding
* subclass of DB_EXCEPTION
*
* @param blk_hash the hash of the block containing the transaction
* @param tx the transaction to be added
* @param tx_hash the hash of the transaction
+ * @return the transaction ID
*/
- virtual void add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash) = 0;
+ virtual uint64_t add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash) = 0;
/**
* @brief remove data about a transaction
@@ -348,6 +394,9 @@ private:
* future, this tracking (of the number, at least) should be moved to
* this class, as it is necessary and the same among all BlockchainDB.
*
+ * It returns an amount output index, which is the index of the output
+ * for its specified amount.
+ *
* This data should be stored in such a manner that the only thing needed to
* reverse the process is the tx_out.
*
@@ -358,25 +407,24 @@ private:
* @param tx_output the output
* @param local_index index of the output in its transaction
* @param unlock_time unlock time/height of the output
+ * @return amount output index
*/
- virtual void add_output(const crypto::hash& tx_hash, const tx_out& tx_output, const uint64_t& local_index, const uint64_t unlock_time) = 0;
+ virtual uint64_t add_output(const crypto::hash& tx_hash, const tx_out& tx_output, const uint64_t& local_index, const uint64_t unlock_time) = 0;
/**
- * @brief remove an output
- *
- * The subclass implementing this will remove all output data it stored
- * in add_output().
+ * @brief store amount output indices for a tx's outputs
*
- * In addition, the subclass is responsible for correctly decrementing
- * its global output counter (this may be automatic for some, such as using
- * a database backend "count" feature).
+ * The subclass implementing this will add the amount output indices to its
+ * backing store in a suitable manner. The tx_id will be the same one that
+ * was returned from #add_output().
*
* If any of this cannot be done, the subclass should throw the corresponding
* subclass of DB_EXCEPTION
*
- * @param tx_output the output to be removed
+ * @param tx_id ID of the transaction containing these outputs
+ * @param amount_output_indices the amount output indices of the transaction
*/
- virtual void remove_output(const tx_out& tx_output) = 0;
+ virtual void add_tx_amount_output_indices(const uint64_t tx_id, const std::vector<uint64_t>& amount_output_indices) = 0;
/**
* @brief store a spent key
@@ -414,18 +462,6 @@ private:
*/
void pop_block();
- /**
- * @brief helper function for add_transactions, to add each individual transaction
- *
- * This function is called by add_transactions() for each transaction to be
- * added.
- *
- * @param blk_hash hash of the block which has the transaction
- * @param tx the transaction to add
- * @param tx_hash_ptr the hash of the transaction, if already calculated
- */
- void add_transaction(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash* tx_hash_ptr = NULL);
-
// helper function to remove transaction from blockchain
/**
* @brief helper function to remove transaction from the blockchain
@@ -444,6 +480,18 @@ private:
protected:
+ /**
+ * @brief helper function for add_transactions, to add each individual transaction
+ *
+ * This function is called by add_transactions() for each transaction to be
+ * added.
+ *
+ * @param blk_hash hash of the block which has the transaction
+ * @param tx the transaction to add
+ * @param tx_hash_ptr the hash of the transaction, if already calculated
+ */
+ void add_transaction(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash* tx_hash_ptr = NULL);
+
mutable uint64_t time_tx_exists = 0; //!< a performance metric
uint64_t time_commit1 = 0; //!< a performance metric
bool m_auto_remove_logs = true; //!< whether or not to automatically remove old logs
@@ -930,10 +978,12 @@ public:
* given hash and return true if so, false otherwise.
*
* @param h the hash to check against
+ * @param tx_id (optional) returns the tx_id for the tx hash
*
* @return true if the transaction exists, otherwise false
*/
virtual bool tx_exists(const crypto::hash& h) const = 0;
+ virtual bool tx_exists(const crypto::hash& h, uint64_t& tx_id) const = 0;
// return unlock time of tx with hash <h>
/**
@@ -1125,36 +1175,20 @@ public:
virtual bool can_thread_bulk_indices() const = 0;
/**
- * @brief gets output indices (global) for a transaction's outputs
- *
- * The subclass should fetch the global output indices for each output
- * in the transaction with the given hash.
- *
- * If the transaction does not exist, the subclass should throw TX_DNE.
- *
- * If an output cannot be found, the subclass should throw OUTPUT_DNE.
- *
- * @param h a transaction hash
- *
- * @return a list of global output indices
- */
- virtual std::vector<uint64_t> get_tx_output_indices(const crypto::hash& h) const = 0;
-
- /**
* @brief gets output indices (amount-specific) for a transaction's outputs
*
* The subclass should fetch the amount-specific output indices for each
- * output in the transaction with the given hash.
+ * output in the transaction with the given ID.
*
* If the transaction does not exist, the subclass should throw TX_DNE.
*
* If an output cannot be found, the subclass should throw OUTPUT_DNE.
*
- * @param h a transaction hash
+ * @param tx_id a transaction ID
*
* @return a list of amount-specific output indices
*/
- virtual std::vector<uint64_t> get_tx_amount_output_indices(const crypto::hash& h) const = 0;
+ virtual std::vector<uint64_t> get_tx_amount_output_indices(const uint64_t tx_id) const = 0;
/**
* @brief check if a key image is stored as spent
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index 51fba058d..8c51c09b1 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -46,7 +46,7 @@ using epee::string_tools::pod_to_hex;
// Increase when the DB changes in a non backward compatible way, and there
// is no automatic conversion, so that a full resync is needed.
-#define VERSION 0
+#define VERSION 1
namespace
{
@@ -65,6 +65,8 @@ inline void throw1(const T &e)
throw e;
}
+#define MDB_val_set(var, val) MDB_val var = {sizeof(val), (void *)&val}
+
template<typename T>
struct MDB_val_copy: public MDB_val
{
@@ -110,18 +112,10 @@ private:
int compare_uint64(const MDB_val *a, const MDB_val *b)
{
-#ifdef MISALIGNED_OK
- const uint64_t va = *(const uint64_t*)a->mv_data;
- const uint64_t vb = *(const uint64_t*)b->mv_data;
-#else
- uint64_t va, vb;
- memcpy(&va, a->mv_data, sizeof(uint64_t));
- memcpy(&vb, b->mv_data, sizeof(uint64_t));
-#endif
- if (va < vb) return -1;
- else if (va == vb) return 0;
- else return 1;
-};
+ const uint64_t va = *(const uint64_t *)a->mv_data;
+ const uint64_t vb = *(const uint64_t *)b->mv_data;
+ return (va < vb) ? -1 : va > vb;
+}
int compare_uint8(const MDB_val *a, const MDB_val *b)
{
@@ -151,23 +145,41 @@ int compare_string(const MDB_val *a, const MDB_val *b)
return strcmp(va, vb);
}
+/* DB schema:
+ *
+ * Table Key Data
+ * ----- --- ----
+ * blocks block ID block blob
+ * block_heights block hash block height
+ * block_info block ID {block metadata}
+ *
+ * txs txn ID txn blob
+ * tx_indices txn hash {txn ID, metadata}
+ * tx_outputs txn ID [txn amount output indices]
+ *
+ * output_txs output ID {txn hash, local index}
+ * output_amounts amount [{amount output index, metadata}...]
+ *
+ * spent_keys input hash -
+ *
+ * Note: where the data items are of uniform size, DUPFIXED tables have
+ * been used to save space. In most of these cases, a dummy "zerokval"
+ * key is used when accessing the table; the Key listed above will be
+ * attached as a prefix on the Data to serve as the DUPSORT key.
+ * (DUPFIXED saves 8 bytes per record.)
+ *
+ * The output_amounts table doesn't use a dummy key, but uses DUPSORT.
+ */
const char* const LMDB_BLOCKS = "blocks";
-const char* const LMDB_BLOCK_TIMESTAMPS = "block_timestamps";
const char* const LMDB_BLOCK_HEIGHTS = "block_heights";
-const char* const LMDB_BLOCK_HASHES = "block_hashes";
-const char* const LMDB_BLOCK_SIZES = "block_sizes";
-const char* const LMDB_BLOCK_DIFFS = "block_diffs";
-const char* const LMDB_BLOCK_COINS = "block_coins";
+const char* const LMDB_BLOCK_INFO = "block_info";
const char* const LMDB_TXS = "txs";
-const char* const LMDB_TX_UNLOCKS = "tx_unlocks";
-const char* const LMDB_TX_HEIGHTS = "tx_heights";
+const char* const LMDB_TX_INDICES = "tx_indices";
const char* const LMDB_TX_OUTPUTS = "tx_outputs";
const char* const LMDB_OUTPUT_TXS = "output_txs";
-const char* const LMDB_OUTPUT_INDICES = "output_indices";
const char* const LMDB_OUTPUT_AMOUNTS = "output_amounts";
-const char* const LMDB_OUTPUT_KEYS = "output_keys";
const char* const LMDB_SPENT_KEYS = "spent_keys";
const char* const LMDB_HF_STARTING_HEIGHTS = "hf_starting_heights";
@@ -175,6 +187,8 @@ const char* const LMDB_HF_VERSIONS = "hf_versions";
const char* const LMDB_PROPERTIES = "properties";
+const char zerokey[8] = {0};
+const MDB_val zerokval = { sizeof(zerokey), (void *)zerokey };
const std::string lmdb_error(const std::string& error_string, int mdb_res)
{
@@ -214,6 +228,39 @@ inline void lmdb_db_open(MDB_txn* txn, const char* name, int flags, MDB_dbi& dbi
namespace cryptonote
{
+
+typedef struct mdb_block_info
+{
+ uint64_t bi_height;
+ uint64_t bi_timestamp;
+ uint64_t bi_coins;
+ uint64_t bi_size; // a size_t really but we need 32-bit compat
+ difficulty_type bi_diff;
+ crypto::hash bi_hash;
+} mdb_block_info;
+
+typedef struct blk_height {
+ crypto::hash bh_hash;
+ uint64_t bh_height;
+} blk_height;
+
+typedef struct txindex {
+ crypto::hash key;
+ tx_data_t data;
+} txindex;
+
+typedef struct outkey {
+ uint64_t amount_index;
+ uint64_t output_id;
+ output_data_t data;
+} outkey;
+
+typedef struct outtx {
+ uint64_t output_id;
+ crypto::hash tx_hash;
+ uint64_t local_index;
+} outtx;
+
std::atomic<uint64_t> mdb_txn_safe::num_active_txns{0};
std::atomic_flag mdb_txn_safe::creation_gate = ATOMIC_FLAG_INIT;
@@ -539,70 +586,55 @@ void BlockchainLMDB::add_block(const block& blk, const size_t& block_size, const
mdb_txn_cursors *m_cursors = &m_wcursors;
CURSOR(block_heights)
- MDB_val_copy<crypto::hash> val_h(blk_hash);
- if (mdb_cursor_get(m_cur_block_heights, &val_h, NULL, MDB_SET) == 0)
+ blk_height bh = {blk_hash, m_height};
+ MDB_val_set(val_h, bh);
+ if (mdb_cursor_get(m_cur_block_heights, (MDB_val *)&zerokval, &val_h, MDB_GET_BOTH) == 0)
throw1(BLOCK_EXISTS("Attempting to add block that's already in the db"));
if (m_height > 0)
{
- MDB_val_copy<crypto::hash> parent_key(blk.prev_id);
- MDB_val parent_h;
- int result = mdb_cursor_get(m_cur_block_heights, &parent_key, &parent_h, MDB_SET);
+ MDB_val_set(parent_key, blk.prev_id);
+ int result = mdb_cursor_get(m_cur_block_heights, (MDB_val *)&zerokval, &parent_key, MDB_GET_BOTH);
if (result)
{
LOG_PRINT_L3("m_height: " << m_height);
LOG_PRINT_L3("parent_key: " << blk.prev_id);
throw0(DB_ERROR(lmdb_error("Failed to get top block hash to check for new block's parent: ", result).c_str()));
}
- uint64_t parent_height = *(const uint64_t *)parent_h.mv_data;
- if (parent_height != m_height - 1)
+ blk_height *prev = (blk_height *)parent_key.mv_data;
+ if (prev->bh_height != m_height - 1)
throw0(BLOCK_PARENT_DNE("Top block is not new block's parent"));
}
int result = 0;
- MDB_val_copy<uint64_t> key(m_height);
+ MDB_val_set(key, m_height);
CURSOR(blocks)
- CURSOR(block_sizes)
- CURSOR(block_timestamps)
- CURSOR(block_diffs)
- CURSOR(block_coins)
- CURSOR(block_hashes)
+ CURSOR(block_info)
MDB_val_copy<blobdata> blob(block_to_blob(blk));
result = mdb_cursor_put(m_cur_blocks, &key, &blob, MDB_APPEND);
if (result)
throw0(DB_ERROR(lmdb_error("Failed to add block blob to db transaction: ", result).c_str()));
- MDB_val_copy<size_t> sz(block_size);
- result = mdb_cursor_put(m_cur_block_sizes, &key, &sz, MDB_APPEND);
- if (result)
- throw0(DB_ERROR(lmdb_error("Failed to add block size to db transaction: ", result).c_str()));
-
- MDB_val_copy<uint64_t> ts(blk.timestamp);
- result = mdb_cursor_put(m_cur_block_timestamps, &key, &ts, MDB_APPEND);
- if (result)
- throw0(DB_ERROR(lmdb_error("Failed to add block timestamp to db transaction: ", result).c_str()));
+ mdb_block_info bi;
+ bi.bi_height = m_height;
+ bi.bi_timestamp = blk.timestamp;
+ bi.bi_coins = coins_generated;
+ bi.bi_size = block_size;
+ bi.bi_diff = cumulative_difficulty;
+ bi.bi_hash = blk_hash;
- MDB_val_copy<difficulty_type> diff(cumulative_difficulty);
- result = mdb_cursor_put(m_cur_block_diffs, &key, &diff, MDB_APPEND);
+ MDB_val_set(val, bi);
+ result = mdb_cursor_put(m_cur_block_info, (MDB_val *)&zerokval, &val, MDB_APPENDDUP);
if (result)
- throw0(DB_ERROR(lmdb_error("Failed to add block cumulative difficulty to db transaction: ", result).c_str()));
+ throw0(DB_ERROR(lmdb_error("Failed to add block info to db transaction: ", result).c_str()));
- MDB_val_copy<uint64_t> coinsgen(coins_generated);
- result = mdb_cursor_put(m_cur_block_coins, &key, &coinsgen, MDB_APPEND);
- if (result)
- throw0(DB_ERROR(lmdb_error("Failed to add block total generated coins to db transaction: ", result).c_str()));
-
- result = mdb_cursor_put(m_cur_block_heights, &val_h, &key, 0);
+ result = mdb_cursor_put(m_cur_block_heights, (MDB_val *)&zerokval, &val_h, 0);
if (result)
throw0(DB_ERROR(lmdb_error("Failed to add block height by hash to db transaction: ", result).c_str()));
- result = mdb_cursor_put(m_cur_block_hashes, &key, &val_h, MDB_APPEND);
- if (result)
- throw0(DB_ERROR(lmdb_error("Failed to add block hash to db transaction: ", result).c_str()));
-
m_cum_size += block_size;
m_cum_count++;
}
@@ -617,66 +649,80 @@ void BlockchainLMDB::remove_block()
if (m_height == 0)
throw0(BLOCK_DNE ("Attempting to remove block from an empty blockchain"));
+ mdb_txn_cursors *m_cursors = &m_wcursors;
+ CURSOR(block_info)
+ CURSOR(block_heights)
+ CURSOR(blocks)
MDB_val_copy<uint64_t> k(m_height - 1);
- MDB_val h;
- if ((result = mdb_get(*m_write_txn, m_block_hashes, &k, &h)))
+ MDB_val h = k;
+ if ((result = mdb_cursor_get(m_cur_block_info, (MDB_val *)&zerokval, &h, MDB_GET_BOTH)))
throw1(BLOCK_DNE(lmdb_error("Attempting to remove block that's not in the db: ", result).c_str()));
- if ((result = mdb_del(*m_write_txn, m_blocks, &k, NULL)))
- throw1(DB_ERROR(lmdb_error("Failed to add removal of block to db transaction: ", result).c_str()));
-
- if ((result = mdb_del(*m_write_txn, m_block_sizes, &k, NULL)))
- throw1(DB_ERROR(lmdb_error("Failed to add removal of block size to db transaction: ", result).c_str()));
-
- if ((result = mdb_del(*m_write_txn, m_block_diffs, &k, NULL)))
- throw1(DB_ERROR(lmdb_error("Failed to add removal of block cumulative difficulty to db transaction: ", result).c_str()));
-
- if ((result = mdb_del(*m_write_txn, m_block_coins, &k, NULL)))
- throw1(DB_ERROR(lmdb_error("Failed to add removal of block total generated coins to db transaction: ", result).c_str()));
-
- if ((result = mdb_del(*m_write_txn, m_block_timestamps, &k, NULL)))
- throw1(DB_ERROR(lmdb_error("Failed to add removal of block timestamp to db transaction: ", result).c_str()));
-
- if ((result = mdb_del(*m_write_txn, m_block_heights, &h, NULL)))
+ // must use h now; deleting from m_block_info will invalidate it
+ mdb_block_info *bi = (mdb_block_info *)h.mv_data;
+ blk_height bh = {bi->bi_hash, 0};
+ h.mv_data = (void *)&bh;
+ h.mv_size = sizeof(bh);
+ if ((result = mdb_cursor_get(m_cur_block_heights, (MDB_val *)&zerokval, &h, MDB_GET_BOTH)))
+ throw1(DB_ERROR(lmdb_error("Failed to locate block height by hash for removal: ", result).c_str()));
+ if ((result = mdb_cursor_del(m_cur_block_heights, 0)))
throw1(DB_ERROR(lmdb_error("Failed to add removal of block height by hash to db transaction: ", result).c_str()));
- if ((result = mdb_del(*m_write_txn, m_block_hashes, &k, NULL)))
- throw1(DB_ERROR(lmdb_error("Failed to add removal of block hash to db transaction: ", result).c_str()));
+ if ((result = mdb_cursor_get(m_cur_blocks, &k, NULL, MDB_SET)))
+ throw1(DB_ERROR(lmdb_error("Failed to locate block for removal: ", result).c_str()));
+ if ((result = mdb_cursor_del(m_cur_blocks, 0)))
+ throw1(DB_ERROR(lmdb_error("Failed to add removal of block to db transaction: ", result).c_str()));
+
+ if ((result = mdb_cursor_del(m_cur_block_info, 0)))
+ throw1(DB_ERROR(lmdb_error("Failed to add removal of block info to db transaction: ", result).c_str()));
}
-void BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash)
+uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash)
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
mdb_txn_cursors *m_cursors = &m_wcursors;
- int result = 0;
+ int result;
+ uint64_t tx_id = m_num_txs;
CURSOR(txs)
- CURSOR(tx_heights)
- CURSOR(tx_unlocks)
+ CURSOR(tx_indices)
- MDB_val_copy<crypto::hash> val_h(tx_hash);
- MDB_val unused;
- if (mdb_cursor_get(m_cur_txs, &val_h, &unused, MDB_SET) == 0)
- throw1(TX_EXISTS("Attempting to add transaction that's already in the db"));
+ MDB_val_set(val_tx_id, tx_id);
+ MDB_val_set(val_h, tx_hash);
+ result = mdb_cursor_get(m_cur_tx_indices, (MDB_val *)&zerokval, &val_h, MDB_GET_BOTH);
+ if (result == 0) {
+ txindex *tip = (txindex *)val_h.mv_data;
+ throw1(TX_EXISTS(std::string("Attempting to add transaction that's already in the db (tx id ").append(boost::lexical_cast<std::string>(tip->data.tx_id)).append(")").c_str()));
+ } else if (result != MDB_NOTFOUND) {
+ throw1(DB_ERROR(lmdb_error(std::string("Error checking if tx index exists for tx hash ") + epee::string_tools::pod_to_hex(tx_hash) + ": ", result).c_str()));
+ }
- MDB_val_copy<blobdata> blob(tx_to_blob(tx));
- result = mdb_cursor_put(m_cur_txs, &val_h, &blob, 0);
- if (result)
- throw0(DB_ERROR(lmdb_error("Failed to add tx blob to db transaction: ", result).c_str()));
+ txindex ti;
+ ti.key = tx_hash;
+ ti.data.tx_id = tx_id;
+ ti.data.unlock_time = tx.unlock_time;
+ ti.data.block_id = m_height; // we don't need blk_hash since we know m_height
- MDB_val_copy<uint64_t> height(m_height);
- result = mdb_cursor_put(m_cur_tx_heights, &val_h, &height, 0);
+ val_h.mv_size = sizeof(ti);
+ val_h.mv_data = (void *)&ti;
+
+ result = mdb_cursor_put(m_cur_tx_indices, (MDB_val *)&zerokval, &val_h, 0);
if (result)
- throw0(DB_ERROR(lmdb_error("Failed to add tx block height to db transaction: ", result).c_str()));
+ throw0(DB_ERROR(lmdb_error("Failed to add tx data to db transaction: ", result).c_str()));
- MDB_val_copy<uint64_t> unlock_time(tx.unlock_time);
- result = mdb_cursor_put(m_cur_tx_unlocks, &val_h, &unlock_time, 0);
+ MDB_val_copy<blobdata> blob(tx_to_blob(tx));
+ result = mdb_cursor_put(m_cur_txs, &val_tx_id, &blob, MDB_APPEND);
if (result)
- throw0(DB_ERROR(lmdb_error("Failed to add tx unlock time to db transaction: ", result).c_str()));
+ throw0(DB_ERROR(lmdb_error("Failed to add tx blob to db transaction: ", result).c_str()));
+
+ m_num_txs++;
+ return tx_id;
}
+// TODO: compare pros and cons of looking up the tx hash's tx index once and
+// passing it in to functions like this
void BlockchainLMDB::remove_transaction_data(const crypto::hash& tx_hash, const transaction& tx)
{
int result;
@@ -684,28 +730,49 @@ void BlockchainLMDB::remove_transaction_data(const crypto::hash& tx_hash, const
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
- MDB_val val_h = {sizeof(tx_hash), (void *)&tx_hash};
- MDB_val unused;
- if (mdb_get(*m_write_txn, m_txs, &val_h, &unused))
+ mdb_txn_cursors *m_cursors = &m_wcursors;
+ CURSOR(tx_indices)
+ CURSOR(txs)
+ CURSOR(tx_outputs)
+
+ MDB_val_set(val_h, tx_hash);
+
+ if (mdb_cursor_get(m_cur_tx_indices, (MDB_val *)&zerokval, &val_h, MDB_GET_BOTH))
throw1(TX_DNE("Attempting to remove transaction that isn't in the db"));
+ txindex *tip = (txindex *)val_h.mv_data;
+ MDB_val_set(val_tx_id, tip->data.tx_id);
- if ((result = mdb_del(*m_write_txn, m_txs, &val_h, NULL)))
+ if ((result = mdb_cursor_get(m_cur_txs, &val_tx_id, NULL, MDB_SET)))
+ throw1(DB_ERROR(lmdb_error("Failed to locate tx for removal: ", result).c_str()));
+ result = mdb_cursor_del(m_cur_txs, 0);
+ if (result)
throw1(DB_ERROR(lmdb_error("Failed to add removal of tx to db transaction: ", result).c_str()));
- if ((result = mdb_del(*m_write_txn, m_tx_unlocks, &val_h, NULL)))
- throw1(DB_ERROR(lmdb_error("Failed to add removal of tx unlock time to db transaction: ", result).c_str()));
- if ((result = mdb_del(*m_write_txn, m_tx_heights, &val_h, NULL)))
- throw1(DB_ERROR(lmdb_error("Failed to add removal of tx block height to db transaction: ", result).c_str()));
- remove_tx_outputs(&val_h, tx);
+ remove_tx_outputs(tip->data.tx_id, tx);
- result = mdb_del(*m_write_txn, m_tx_outputs, &val_h, NULL);
+ result = mdb_cursor_get(m_cur_tx_outputs, &val_tx_id, NULL, MDB_SET);
if (result == MDB_NOTFOUND)
LOG_PRINT_L1("tx has no outputs to remove: " << tx_hash);
else if (result)
- throw1(DB_ERROR(lmdb_error("Failed to add removal of tx outputs to db transaction: ", result).c_str()));
+ throw1(DB_ERROR(lmdb_error("Failed to locate tx outputs for removal: ", result).c_str()));
+ if (!result)
+ {
+ result = mdb_cursor_del(m_cur_tx_outputs, 0);
+ if (result)
+ throw1(DB_ERROR(lmdb_error("Failed to add removal of tx outputs to db transaction: ", result).c_str()));
+ }
+
+ // Don't delete the tx_indices entry until the end, after we're done with val_tx_id
+ if (mdb_cursor_del(m_cur_tx_indices, 0))
+ throw1(DB_ERROR("Failed to add removal of tx index to db transaction"));
+
+ m_num_txs--;
}
-void BlockchainLMDB::add_output(const crypto::hash& tx_hash, const tx_out& tx_output, const uint64_t& local_index, const uint64_t unlock_time)
+uint64_t BlockchainLMDB::add_output(const crypto::hash& tx_hash,
+ const tx_out& tx_output,
+ const uint64_t& local_index,
+ const uint64_t unlock_time)
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
@@ -714,187 +781,130 @@ void BlockchainLMDB::add_output(const crypto::hash& tx_hash, const tx_out& tx_ou
int result = 0;
CURSOR(output_txs)
- CURSOR(tx_outputs)
- CURSOR(output_indices)
CURSOR(output_amounts)
- CURSOR(output_keys)
- MDB_val_copy<uint64_t> k(m_num_outputs);
- MDB_val_copy<crypto::hash> v(tx_hash);
+ if (tx_output.target.type() != typeid(txout_to_key))
+ throw0(DB_ERROR("Wrong output type: expected txout_to_key"));
- result = mdb_cursor_put(m_cur_output_txs, &k, &v, MDB_APPEND);
- if (result)
- throw0(DB_ERROR(lmdb_error("Failed to add output tx hash to db transaction: ", result).c_str()));
- result = mdb_cursor_put(m_cur_tx_outputs, &v, &k, 0);
- if (result)
- throw0(DB_ERROR(lmdb_error("Failed to add <tx hash, global output index> to db transaction: ", result).c_str()));
+ outtx ot = {m_num_outputs, tx_hash, local_index};
+ MDB_val_set(vot, ot);
- MDB_val_copy<uint64_t> val_local_index(local_index);
- result = mdb_cursor_put(m_cur_output_indices, &k, &val_local_index, MDB_APPEND);
+ result = mdb_cursor_put(m_cur_output_txs, (MDB_val *)&zerokval, &vot, MDB_APPENDDUP);
if (result)
- throw0(DB_ERROR(lmdb_error("Failed to add tx output index to db transaction: ", result).c_str()));
+ throw0(DB_ERROR(lmdb_error("Failed to add output tx hash to db transaction: ", result).c_str()));
+ outkey ok;
+ MDB_val data;
MDB_val_copy<uint64_t> val_amount(tx_output.amount);
- result = mdb_cursor_put(m_cur_output_amounts, &val_amount, &k, 0);
- if (result)
- throw0(DB_ERROR(lmdb_error("Failed to add output amount to db transaction: ", result).c_str()));
-
- if (tx_output.target.type() == typeid(txout_to_key))
- {
- output_data_t od;
- od.pubkey = boost::get < txout_to_key > (tx_output.target).key;
- od.unlock_time = unlock_time;
- od.height = m_height;
-
- MDB_val_copy<output_data_t> data(od);
- //MDB_val_copy<crypto::public_key> val_pubkey(boost::get<txout_to_key>(tx_output.target).key);
- if ((result = mdb_cursor_put(m_cur_output_keys, &k, &data, MDB_APPEND)))
- throw0(DB_ERROR(lmdb_error("Failed to add output pubkey to db transaction: ", result).c_str()));
- }
+ result = mdb_cursor_get(m_cur_output_amounts, &val_amount, &data, MDB_SET);
+ if (!result)
+ {
+ mdb_size_t num_elems = 0;
+ result = mdb_cursor_count(m_cur_output_amounts, &num_elems);
+ if (result)
+ throw0(DB_ERROR(std::string("Failed to get number of outputs for amount: ").append(mdb_strerror(result)).c_str()));
+ ok.amount_index = num_elems;
+ }
+ else if (result != MDB_NOTFOUND)
+ throw0(DB_ERROR(lmdb_error("Failed to get output amount in db transaction: ", result).c_str()));
else
- {
- throw0(DB_ERROR("Wrong output type: expected txout_to_key"));
- }
+ ok.amount_index = 0;
+ ok.output_id = m_num_outputs;
+ ok.data.pubkey = boost::get < txout_to_key > (tx_output.target).key;
+ ok.data.unlock_time = unlock_time;
+ ok.data.height = m_height;
+ data.mv_data = &ok;
+ data.mv_size = sizeof(ok);
+
+ if ((result = mdb_cursor_put(m_cur_output_amounts, &val_amount, &data, MDB_APPENDDUP)))
+ throw0(DB_ERROR(lmdb_error("Failed to add output pubkey to db transaction: ", result).c_str()));
m_num_outputs++;
+ return ok.amount_index;
}
-void BlockchainLMDB::remove_tx_outputs(const MDB_val *tx_hash, const transaction& tx)
+void BlockchainLMDB::add_tx_amount_output_indices(const uint64_t tx_id,
+ const std::vector<uint64_t>& amount_output_indices)
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
-
+ check_open();
mdb_txn_cursors *m_cursors = &m_wcursors;
- MDB_val v;
CURSOR(tx_outputs)
- auto result = mdb_cursor_get(m_cur_tx_outputs, (MDB_val *)tx_hash, &v, MDB_SET);
- if (result == MDB_NOTFOUND)
- {
- LOG_PRINT_L2("tx has no outputs, so no global output indices");
- }
- else if (result)
- {
- throw0(DB_ERROR(lmdb_error("DB error attempting to get an output", result).c_str()));
- }
- else
- {
- mdb_size_t num_elems = 0;
- mdb_cursor_count(m_cur_tx_outputs, &num_elems);
+ int result = 0;
- mdb_cursor_get(m_cur_tx_outputs, (MDB_val *)tx_hash, &v, MDB_LAST_DUP);
+ int num_outputs = amount_output_indices.size();
- for (uint64_t i = num_elems; i > 0; --i)
- {
- const tx_out tx_output = tx.vout[i-1];
-#ifndef MISALIGNED_OK
- uint64_t tv;
- memcpy(&tv, v.mv_data, sizeof(uint64_t));
- v.mv_data = &tv;
-#endif
- remove_output((const MDB_val *)&v, tx_output.amount);
- if (i > 1)
- {
- mdb_cursor_get(m_cur_tx_outputs, (MDB_val *)tx_hash, &v, MDB_PREV_DUP);
- }
- }
- }
-}
+ MDB_val_set(k_tx_id, tx_id);
+ MDB_val v;
+ v.mv_data = (void *)amount_output_indices.data();
+ v.mv_size = sizeof(uint64_t) * num_outputs;
+ // LOG_PRINT_L1("tx_outputs[tx_hash] size: " << v.mv_size);
-// TODO: probably remove this function
-void BlockchainLMDB::remove_output(const tx_out& tx_output)
-{
- LOG_PRINT_L3("BlockchainLMDB::" << __func__ << " (unused version - does nothing)");
- return;
+ result = mdb_cursor_put(m_cur_tx_outputs, &k_tx_id, &v, MDB_APPEND);
+ if (result)
+ throw0(DB_ERROR(std::string("Failed to add <tx hash, amount output index array> to db transaction: ").append(mdb_strerror(result)).c_str()));
}
-void BlockchainLMDB::remove_output(const MDB_val *out_index, const uint64_t amount)
+void BlockchainLMDB::remove_tx_outputs(const uint64_t tx_id, const transaction& tx)
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
- check_open();
- auto result = mdb_del(*m_write_txn, m_output_indices, (MDB_val *)out_index, NULL);
- if (result == MDB_NOTFOUND)
- {
- LOG_PRINT_L0("Unexpected: global output index not found in m_output_indices");
- }
- else if (result)
- {
- throw1(DB_ERROR(lmdb_error("Error adding removal of output tx index to db transaction", result).c_str()));
- }
+ std::vector<uint64_t> amount_output_indices = get_tx_amount_output_indices(tx_id);
- result = mdb_del(*m_write_txn, m_output_txs, (MDB_val *)out_index, NULL);
- // if (result != 0 && result != MDB_NOTFOUND)
- // throw1(DB_ERROR("Error adding removal of output tx hash to db transaction"));
- if (result == MDB_NOTFOUND)
- {
- LOG_PRINT_L0("Unexpected: global output index not found in m_output_txs");
- }
- else if (result)
+ if (amount_output_indices.empty())
{
- throw1(DB_ERROR(lmdb_error("Error adding removal of output tx hash to db transaction", result).c_str()));
+ if (tx.vout.empty())
+ LOG_PRINT_L2("tx has no outputs, so no output indices");
+ else
+ throw0(DB_ERROR("tx has outputs, but no output indices found"));
}
- result = mdb_del(*m_write_txn, m_output_keys, (MDB_val *)out_index, NULL);
- if (result == MDB_NOTFOUND)
+ for (uint64_t i = tx.vout.size(); i > 0; --i)
{
- LOG_PRINT_L0("Unexpected: global output index not found in m_output_keys");
+ const tx_out tx_output = tx.vout[i-1];
+ remove_output(tx_output.amount, amount_output_indices[i-1]);
}
- else if (result)
- throw1(DB_ERROR(lmdb_error("Error adding removal of output pubkey to db transaction", result).c_str()));
-
- remove_amount_output_index(amount, out_index);
-
- m_num_outputs--;
}
-void BlockchainLMDB::remove_amount_output_index(const uint64_t amount, const MDB_val *global_output_index)
+void BlockchainLMDB::remove_output(const uint64_t amount, const uint64_t& out_index)
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
mdb_txn_cursors *m_cursors = &m_wcursors;
CURSOR(output_amounts);
+ CURSOR(output_txs);
- MDB_val_copy<uint64_t> k(amount);
- MDB_val v;
+ MDB_val_set(k, amount);
+ MDB_val_set(v, out_index);
- auto result = mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_SET);
+ auto result = mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_GET_BOTH);
if (result == MDB_NOTFOUND)
throw1(OUTPUT_DNE("Attempting to get an output index by amount and amount index, but amount not found"));
else if (result)
throw0(DB_ERROR(lmdb_error("DB error attempting to get an output", result).c_str()));
- mdb_size_t num_elems = 0;
- mdb_cursor_count(m_cur_output_amounts, &num_elems);
-
- mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_LAST_DUP);
-
- uint64_t amount_output_index = 0;
- bool found_index = false;
- for (uint64_t i = num_elems; i > 0; --i)
- {
- mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_GET_CURRENT);
- if (!memcmp(v.mv_data, global_output_index->mv_data, sizeof(uint64_t)))
- {
- amount_output_index = i-1;
- found_index = true;
- break;
- }
- if (i > 1)
- mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_PREV_DUP);
- }
- if (found_index)
+ outkey *ok = (outkey *)v.mv_data;
+ MDB_val_set(otxk, ok->output_id);
+ result = mdb_cursor_get(m_cur_output_txs, (MDB_val *)&zerokval, &otxk, MDB_GET_BOTH);
+ if (result == MDB_NOTFOUND)
{
- // found the amount output index
- // now delete it
- int result = mdb_cursor_del(m_cur_output_amounts, 0);
- if (result)
- throw0(DB_ERROR(lmdb_error(std::string("Error deleting amount output index ").append(boost::lexical_cast<std::string>(amount_output_index).append(": ")).c_str(), result).c_str()));
+ LOG_PRINT_L0("Unexpected: global output index not found in m_output_txs");
}
- else
+ else if (result)
{
- // not found
- throw1(OUTPUT_DNE("Failed to find amount output index"));
+ throw1(DB_ERROR(lmdb_error("Error adding removal of output tx to db transaction", result).c_str()));
}
+ result = mdb_cursor_del(m_cur_output_txs, 0);
+ if (result)
+ throw0(DB_ERROR(lmdb_error(std::string("Error deleting output index ").append(boost::lexical_cast<std::string>(out_index).append(": ")).c_str(), result).c_str()));
+
+ // now delete the amount
+ result = mdb_cursor_del(m_cur_output_amounts, 0);
+ if (result)
+ throw0(DB_ERROR(lmdb_error(std::string("Error deleting amount for output index ").append(boost::lexical_cast<std::string>(out_index).append(": ")).c_str(), result).c_str()));
+
+ m_num_outputs--;
}
void BlockchainLMDB::add_spent_key(const crypto::key_image& k_image)
@@ -905,27 +915,33 @@ void BlockchainLMDB::add_spent_key(const crypto::key_image& k_image)
CURSOR(spent_keys)
- MDB_val_copy<crypto::key_image> val_key(k_image);
- MDB_val unused;
- if (mdb_cursor_get(m_cur_spent_keys, &val_key, &unused, MDB_SET) == 0)
+ MDB_val k = {sizeof(k_image), (void *)&k_image};
+ if (auto result = mdb_cursor_put(m_cur_spent_keys, (MDB_val *)&zerokval, &k, MDB_NODUPDATA)) {
+ if (result == MDB_KEYEXIST)
throw1(KEY_IMAGE_EXISTS("Attempting to add spent key image that's already in the db"));
-
- char anything = '\0';
- unused.mv_size = sizeof(char);
- unused.mv_data = &anything;
- if (auto result = mdb_cursor_put(m_cur_spent_keys, &val_key, &unused, 0))
- throw1(DB_ERROR(lmdb_error("Error adding spent key image to db transaction: ", result).c_str()));
+ else
+ throw1(DB_ERROR(lmdb_error("Error adding spent key image to db transaction: ", result).c_str()));
+ }
}
void BlockchainLMDB::remove_spent_key(const crypto::key_image& k_image)
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
+ mdb_txn_cursors *m_cursors = &m_wcursors;
+
+ CURSOR(spent_keys)
- MDB_val_copy<crypto::key_image> k(k_image);
- auto result = mdb_del(*m_write_txn, m_spent_keys, &k, NULL);
+ MDB_val k = {sizeof(k_image), (void *)&k_image};
+ auto result = mdb_cursor_get(m_cur_spent_keys, (MDB_val *)&zerokval, &k, MDB_GET_BOTH);
if (result != 0 && result != MDB_NOTFOUND)
- throw1(DB_ERROR(lmdb_error("Error adding removal of key image to db transaction", result).c_str()));
+ throw1(DB_ERROR(lmdb_error("Error finding spent key to remove", result).c_str()));
+ if (!result)
+ {
+ result = mdb_cursor_del(m_cur_spent_keys, 0);
+ if (result)
+ throw1(DB_ERROR(lmdb_error("Error adding removal of key image to db transaction", result).c_str()));
+ }
}
blobdata BlockchainLMDB::output_to_blob(const tx_out& output) const
@@ -951,19 +967,6 @@ tx_out BlockchainLMDB::output_from_blob(const blobdata& blob) const
return o;
}
-uint64_t BlockchainLMDB::get_output_global_index(const uint64_t& amount, const uint64_t& index)
-{
- LOG_PRINT_L3("BlockchainLMDB::" << __func__);
- std::vector <uint64_t> offsets;
- std::vector <uint64_t> global_indices;
- offsets.push_back(index);
- get_output_global_indices(amount, offsets, global_indices);
- if (!global_indices.size())
- throw1(OUTPUT_DNE("Attempting to get an output index by amount and amount index, but amount not found"));
-
- return global_indices[0];
-}
-
void BlockchainLMDB::check_open() const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
@@ -1076,39 +1079,31 @@ void BlockchainLMDB::open(const std::string& filename, const int mdb_flags)
// uses macros to avoid having to change things too many places
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_TIMESTAMPS, MDB_INTEGERKEY | MDB_CREATE, m_block_timestamps, "Failed to open db handle for m_block_timestamps");
- lmdb_db_open(txn, LMDB_BLOCK_HEIGHTS, MDB_CREATE, m_block_heights, "Failed to open db handle for m_block_heights");
- lmdb_db_open(txn, LMDB_BLOCK_HASHES, MDB_INTEGERKEY | MDB_CREATE, m_block_hashes, "Failed to open db handle for m_block_hashes");
- lmdb_db_open(txn, LMDB_BLOCK_SIZES, MDB_INTEGERKEY | MDB_CREATE, m_block_sizes, "Failed to open db handle for m_block_sizes");
- lmdb_db_open(txn, LMDB_BLOCK_DIFFS, MDB_INTEGERKEY | MDB_CREATE, m_block_diffs, "Failed to open db handle for m_block_diffs");
- lmdb_db_open(txn, LMDB_BLOCK_COINS, MDB_INTEGERKEY | MDB_CREATE, m_block_coins, "Failed to open db handle for m_block_coins");
+ lmdb_db_open(txn, LMDB_BLOCK_INFO, MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_block_info, "Failed to open db handle for m_block_info");
+ lmdb_db_open(txn, LMDB_BLOCK_HEIGHTS, MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_block_heights, "Failed to open db handle for m_block_heights");
- lmdb_db_open(txn, LMDB_TXS, MDB_CREATE, m_txs, "Failed to open db handle for m_txs");
- lmdb_db_open(txn, LMDB_TX_UNLOCKS, MDB_CREATE, m_tx_unlocks, "Failed to open db handle for m_tx_unlocks");
- lmdb_db_open(txn, LMDB_TX_HEIGHTS, MDB_CREATE, m_tx_heights, "Failed to open db handle for m_tx_heights");
- lmdb_db_open(txn, LMDB_TX_OUTPUTS, MDB_DUPSORT | MDB_CREATE, m_tx_outputs, "Failed to open db handle for m_tx_outputs");
+ 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_TX_INDICES, MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_tx_indices, "Failed to open db handle for m_tx_indices");
+ lmdb_db_open(txn, LMDB_TX_OUTPUTS, MDB_INTEGERKEY | MDB_CREATE, m_tx_outputs, "Failed to open db handle for m_tx_outputs");
- lmdb_db_open(txn, LMDB_OUTPUT_TXS, MDB_INTEGERKEY | MDB_CREATE, m_output_txs, "Failed to open db handle for m_output_txs");
- lmdb_db_open(txn, LMDB_OUTPUT_INDICES, MDB_INTEGERKEY | MDB_CREATE, m_output_indices, "Failed to open db handle for m_output_indices");
+ lmdb_db_open(txn, LMDB_OUTPUT_TXS, MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_output_txs, "Failed to open db handle for m_output_txs");
lmdb_db_open(txn, LMDB_OUTPUT_AMOUNTS, MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED | MDB_CREATE, m_output_amounts, "Failed to open db handle for m_output_amounts");
- lmdb_db_open(txn, LMDB_OUTPUT_KEYS, MDB_INTEGERKEY | MDB_CREATE, m_output_keys, "Failed to open db handle for m_output_keys");
- lmdb_db_open(txn, LMDB_SPENT_KEYS, MDB_CREATE, m_spent_keys, "Failed to open db handle for m_spent_keys");
+ lmdb_db_open(txn, LMDB_SPENT_KEYS, MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_spent_keys, "Failed to open db handle for m_spent_keys");
lmdb_db_open(txn, LMDB_HF_STARTING_HEIGHTS, MDB_CREATE, m_hf_starting_heights, "Failed to open db handle for m_hf_starting_heights");
- lmdb_db_open(txn, LMDB_HF_VERSIONS, MDB_CREATE, m_hf_versions, "Failed to open db handle for m_hf_versions");
+ lmdb_db_open(txn, LMDB_HF_VERSIONS, MDB_INTEGERKEY | MDB_CREATE, m_hf_versions, "Failed to open db handle for m_hf_versions");
lmdb_db_open(txn, LMDB_PROPERTIES, MDB_CREATE, m_properties, "Failed to open db handle for m_properties");
+ mdb_set_dupsort(txn, m_spent_keys, compare_hash32);
+ mdb_set_dupsort(txn, m_block_heights, compare_hash32);
+ mdb_set_dupsort(txn, m_tx_indices, compare_hash32);
mdb_set_dupsort(txn, m_output_amounts, compare_uint64);
- mdb_set_dupsort(txn, m_tx_outputs, compare_uint64);
- mdb_set_compare(txn, m_spent_keys, compare_hash32);
- mdb_set_compare(txn, m_block_heights, compare_hash32);
- mdb_set_compare(txn, m_txs, compare_hash32);
- mdb_set_compare(txn, m_tx_unlocks, compare_hash32);
- mdb_set_compare(txn, m_tx_heights, compare_hash32);
+ mdb_set_dupsort(txn, m_output_txs, compare_uint64);
+ mdb_set_dupsort(txn, m_block_info, compare_uint64);
+
mdb_set_compare(txn, m_hf_starting_heights, compare_uint8);
- mdb_set_compare(txn, m_hf_versions, compare_uint64);
mdb_set_compare(txn, m_properties, compare_string);
// get and keep current height
@@ -1118,34 +1113,18 @@ void BlockchainLMDB::open(const std::string& filename, const int mdb_flags)
LOG_PRINT_L2("Setting m_height to: " << db_stats.ms_entries);
m_height = db_stats.ms_entries;
+ // get and keep current number of txs
+ if ((result = mdb_stat(txn, m_txs, &db_stats)))
+ throw0(DB_ERROR(lmdb_error("Failed to query m_txs: ", result).c_str()));
+ m_num_txs = db_stats.ms_entries;
+
// get and keep current number of outputs
- if ((result = mdb_stat(txn, m_output_indices, &db_stats)))
- throw0(DB_ERROR(lmdb_error("Failed to query m_output_indices: ", result).c_str()));
+ if ((result = mdb_stat(txn, m_output_txs, &db_stats)))
+ throw0(DB_ERROR(lmdb_error("Failed to query m_output_txs: ", result).c_str()));
m_num_outputs = db_stats.ms_entries;
bool compatible = true;
- // ND: This "new" version of the lmdb database is incompatible with
- // the previous version. Ensure that the output_keys database is
- // sizeof(output_data_t) in length. Otherwise, inform user and
- // terminate.
- if(m_height > 0)
- {
- MDB_val_copy<uint64_t> k(0);
- MDB_val v;
- auto get_result = mdb_get(txn, m_output_keys, &k, &v);
- if(get_result != MDB_SUCCESS)
- {
- txn.abort();
- m_open = false;
- return;
- }
-
- // LOG_PRINT_L0("Output keys size: " << v.mv_size);
- if(v.mv_size != sizeof(output_data_t))
- compatible = false;
- }
-
MDB_val_copy<const char*> k("version");
MDB_val v;
auto get_result = mdb_get(txn, m_properties, &k, &v);
@@ -1159,13 +1138,21 @@ void BlockchainLMDB::open(const std::string& filename, const int mdb_flags)
#if VERSION > 0
else if (*(const uint32_t*)v.mv_data < VERSION)
{
- compatible = false;
+ // Note that there was a schema change within version 0 as well.
+ // See commit e5d2680094ee15889934fe28901e4e133cda56f2 2015/07/10
+ // We don't handle the old format previous to that commit.
+ txn.commit();
+ m_open = true;
+ migrate(*(const uint32_t *)v.mv_data);
+ return;
}
#endif
}
else
{
- // if not found, but we're on version 0, it's fine. If the DB's empty, it's fine too.
+ // if not found, and the DB is non-empty, this is probably
+ // an "old" version 0, which we don't handle. If the DB is
+ // empty it's fine.
if (VERSION > 0 && m_height > 0)
compatible = false;
}
@@ -1244,20 +1231,12 @@ void BlockchainLMDB::reset()
if (auto result = mdb_txn_begin(m_env, NULL, 0, txn))
throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str()));
mdb_drop(txn, m_blocks, 0);
- mdb_drop(txn, m_block_timestamps, 0);
+ mdb_drop(txn, m_block_info, 0);
mdb_drop(txn, m_block_heights, 0);
- mdb_drop(txn, m_block_hashes, 0);
- mdb_drop(txn, m_block_sizes, 0);
- mdb_drop(txn, m_block_diffs, 0);
- mdb_drop(txn, m_block_coins, 0);
mdb_drop(txn, m_txs, 0);
- mdb_drop(txn, m_tx_unlocks, 0);
- mdb_drop(txn, m_tx_heights, 0);
mdb_drop(txn, m_tx_outputs, 0);
mdb_drop(txn, m_output_txs, 0);
- mdb_drop(txn, m_output_indices, 0);
mdb_drop(txn, m_output_amounts, 0);
- mdb_drop(txn, m_output_keys, 0);
mdb_drop(txn, m_spent_keys, 0);
mdb_drop(txn, m_hf_starting_heights, 0);
mdb_drop(txn, m_hf_versions, 0);
@@ -1367,8 +1346,8 @@ bool BlockchainLMDB::block_exists(const crypto::hash& h) const
RCURSOR(block_heights);
bool ret = false;
- MDB_val_copy<crypto::hash> key(h);
- auto get_result = mdb_cursor_get(m_cur_block_heights, &key, NULL, MDB_SET);
+ MDB_val_set(key, h);
+ auto get_result = mdb_cursor_get(m_cur_block_heights, (MDB_val *)&zerokval, &key, MDB_GET_BOTH);
if (get_result == MDB_NOTFOUND)
{
LOG_PRINT_L3("Block with hash " << epee::string_tools::pod_to_hex(h) << " not found in db");
@@ -1398,15 +1377,15 @@ uint64_t BlockchainLMDB::get_block_height(const crypto::hash& h) const
TXN_PREFIX_RDONLY();
RCURSOR(block_heights);
- MDB_val_copy<crypto::hash> key(h);
- MDB_val result;
- auto get_result = mdb_cursor_get(m_cur_block_heights, &key, &result, MDB_SET);
+ MDB_val_set(key, h);
+ auto get_result = mdb_cursor_get(m_cur_block_heights, (MDB_val *)&zerokval, &key, MDB_GET_BOTH);
if (get_result == MDB_NOTFOUND)
throw1(BLOCK_DNE("Attempted to retrieve non-existent block height"));
else if (get_result)
throw0(DB_ERROR("Error attempting to retrieve a block height from the db"));
- uint64_t ret = *(const uint64_t *)result.mv_data;
+ blk_height *bhp = (blk_height *)key.mv_data;
+ uint64_t ret = bhp->bh_height;
TXN_POSTFIX_RDONLY();
return ret;
}
@@ -1456,11 +1435,10 @@ uint64_t BlockchainLMDB::get_block_timestamp(const uint64_t& height) const
check_open();
TXN_PREFIX_RDONLY();
- RCURSOR(block_timestamps);
+ RCURSOR(block_info);
- MDB_val_copy<uint64_t> key(height);
- MDB_val result;
- auto get_result = mdb_cursor_get(m_cur_block_timestamps, &key, &result, MDB_SET);
+ MDB_val_set(result, height);
+ auto get_result = mdb_cursor_get(m_cur_block_info, (MDB_val *)&zerokval, &result, MDB_GET_BOTH);
if (get_result == MDB_NOTFOUND)
{
throw0(BLOCK_DNE(std::string("Attempt to get timestamp from height ").append(boost::lexical_cast<std::string>(height)).append(" failed -- timestamp not in db").c_str()));
@@ -1468,7 +1446,8 @@ uint64_t BlockchainLMDB::get_block_timestamp(const uint64_t& height) const
else if (get_result)
throw0(DB_ERROR("Error attempting to retrieve a timestamp from the db"));
- uint64_t ret = *(const uint64_t *)result.mv_data;
+ mdb_block_info *bi = (mdb_block_info *)result.mv_data;
+ uint64_t ret = bi->bi_timestamp;
TXN_POSTFIX_RDONLY();
return ret;
}
@@ -1493,11 +1472,10 @@ size_t BlockchainLMDB::get_block_size(const uint64_t& height) const
check_open();
TXN_PREFIX_RDONLY();
- RCURSOR(block_sizes);
+ RCURSOR(block_info);
- MDB_val_copy<uint64_t> key(height);
- MDB_val result;
- auto get_result = mdb_cursor_get(m_cur_block_sizes, &key, &result, MDB_SET);
+ MDB_val_set(result, height);
+ auto get_result = mdb_cursor_get(m_cur_block_info, (MDB_val *)&zerokval, &result, MDB_GET_BOTH);
if (get_result == MDB_NOTFOUND)
{
throw0(BLOCK_DNE(std::string("Attempt to get block size from height ").append(boost::lexical_cast<std::string>(height)).append(" failed -- block size not in db").c_str()));
@@ -1505,7 +1483,8 @@ size_t BlockchainLMDB::get_block_size(const uint64_t& height) const
else if (get_result)
throw0(DB_ERROR("Error attempting to retrieve a block size from the db"));
- size_t ret = *(const size_t *)result.mv_data;
+ mdb_block_info *bi = (mdb_block_info *)result.mv_data;
+ size_t ret = bi->bi_size;
TXN_POSTFIX_RDONLY();
return ret;
}
@@ -1516,11 +1495,10 @@ difficulty_type BlockchainLMDB::get_block_cumulative_difficulty(const uint64_t&
check_open();
TXN_PREFIX_RDONLY();
- RCURSOR(block_diffs);
+ RCURSOR(block_info);
- MDB_val_copy<uint64_t> key(height);
- MDB_val result;
- auto get_result = mdb_cursor_get(m_cur_block_diffs, &key, &result, MDB_SET);
+ MDB_val_set(result, height);
+ auto get_result = mdb_cursor_get(m_cur_block_info, (MDB_val *)&zerokval, &result, MDB_GET_BOTH);
if (get_result == MDB_NOTFOUND)
{
throw0(BLOCK_DNE(std::string("Attempt to get cumulative difficulty from height ").append(boost::lexical_cast<std::string>(height)).append(" failed -- difficulty not in db").c_str()));
@@ -1528,7 +1506,8 @@ difficulty_type BlockchainLMDB::get_block_cumulative_difficulty(const uint64_t&
else if (get_result)
throw0(DB_ERROR("Error attempting to retrieve a cumulative difficulty from the db"));
- difficulty_type ret = *(const difficulty_type*)result.mv_data;
+ mdb_block_info *bi = (mdb_block_info *)result.mv_data;
+ difficulty_type ret = bi->bi_diff;
TXN_POSTFIX_RDONLY();
return ret;
}
@@ -1556,11 +1535,10 @@ uint64_t BlockchainLMDB::get_block_already_generated_coins(const uint64_t& heigh
check_open();
TXN_PREFIX_RDONLY();
- RCURSOR(block_coins);
+ RCURSOR(block_info);
- MDB_val_copy<uint64_t> key(height);
- MDB_val result;
- auto get_result = mdb_cursor_get(m_cur_block_coins, &key, &result, MDB_SET);
+ MDB_val_set(result, height);
+ auto get_result = mdb_cursor_get(m_cur_block_info, (MDB_val *)&zerokval, &result, MDB_GET_BOTH);
if (get_result == MDB_NOTFOUND)
{
throw0(BLOCK_DNE(std::string("Attempt to get generated coins from height ").append(boost::lexical_cast<std::string>(height)).append(" failed -- block size not in db").c_str()));
@@ -1568,7 +1546,8 @@ uint64_t BlockchainLMDB::get_block_already_generated_coins(const uint64_t& heigh
else if (get_result)
throw0(DB_ERROR("Error attempting to retrieve a total generated coins from the db"));
- uint64_t ret = *(const uint64_t*)result.mv_data;
+ mdb_block_info *bi = (mdb_block_info *)result.mv_data;
+ uint64_t ret = bi->bi_coins;
TXN_POSTFIX_RDONLY();
return ret;
}
@@ -1579,11 +1558,10 @@ crypto::hash BlockchainLMDB::get_block_hash_from_height(const uint64_t& height)
check_open();
TXN_PREFIX_RDONLY();
- RCURSOR(block_hashes);
+ RCURSOR(block_info);
- MDB_val_copy<uint64_t> key(height);
- MDB_val result;
- auto get_result = mdb_cursor_get(m_cur_block_hashes, &key, &result, MDB_SET);
+ MDB_val_set(result, height);
+ auto get_result = mdb_cursor_get(m_cur_block_info, (MDB_val *)&zerokval, &result, MDB_GET_BOTH);
if (get_result == MDB_NOTFOUND)
{
throw0(BLOCK_DNE(std::string("Attempt to get hash from height ").append(boost::lexical_cast<std::string>(height)).append(" failed -- hash not in db").c_str()));
@@ -1591,7 +1569,8 @@ crypto::hash BlockchainLMDB::get_block_hash_from_height(const uint64_t& height)
else if (get_result)
throw0(DB_ERROR(lmdb_error("Error attempting to retrieve a block hash from the db: ", get_result).c_str()));
- crypto::hash ret = *(const crypto::hash*)result.mv_data;
+ mdb_block_info *bi = (mdb_block_info *)result.mv_data;
+ crypto::hash ret = bi->bi_hash;
TXN_POSTFIX_RDONLY();
return ret;
}
@@ -1664,15 +1643,58 @@ bool BlockchainLMDB::tx_exists(const crypto::hash& h) const
check_open();
TXN_PREFIX_RDONLY();
+ RCURSOR(tx_indices);
RCURSOR(txs);
- MDB_val_copy<crypto::hash> key(h);
- MDB_val result;
+ MDB_val_set(key, h);
+ bool tx_found = false;
+
+ TIME_MEASURE_START(time1);
+ auto get_result = mdb_cursor_get(m_cur_tx_indices, (MDB_val *)&zerokval, &key, MDB_GET_BOTH);
+ if (get_result == 0)
+ tx_found = true;
+ else if (get_result != MDB_NOTFOUND)
+ throw0(DB_ERROR(lmdb_error(std::string("DB error attempting to fetch transaction index from hash ") + epee::string_tools::pod_to_hex(h) + ": ", get_result).c_str()));
+
+ // This isn't needed as part of the check. we're not checking consistency of db.
+ // get_result = mdb_cursor_get(m_cur_txs, &val_tx_index, &result, MDB_SET);
+ TIME_MEASURE_FINISH(time1);
+ time_tx_exists += time1;
+
+ TXN_POSTFIX_RDONLY();
+
+ if (! tx_found)
+ {
+ LOG_PRINT_L1("transaction with hash " << epee::string_tools::pod_to_hex(h) << " not found in db");
+ return false;
+ }
+
+ // Below not needed due to above comment.
+ // if (get_result == MDB_NOTFOUND)
+ // throw0(DB_ERROR(std::string("transaction with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found at index").c_str()));
+ // else if (get_result)
+ // throw0(DB_ERROR(lmdb_error(std::string("DB error attempting to fetch transaction ") + epee::string_tools::pod_to_hex(h) + " at index: ", get_result).c_str()));
+ return true;
+}
+
+bool BlockchainLMDB::tx_exists(const crypto::hash& h, uint64_t& tx_id) const
+{
+ LOG_PRINT_L3("BlockchainLMDB::" << __func__);
+ check_open();
+
+ TXN_PREFIX_RDONLY();
+ RCURSOR(tx_indices);
+
+ MDB_val_set(v, h);
TIME_MEASURE_START(time1);
- auto get_result = mdb_cursor_get(m_cur_txs, &key, &result, MDB_SET);
+ auto get_result = mdb_cursor_get(m_cur_tx_indices, (MDB_val *)&zerokval, &v, MDB_GET_BOTH);
TIME_MEASURE_FINISH(time1);
time_tx_exists += time1;
+ if (!get_result) {
+ txindex *tip = (txindex *)v.mv_data;
+ tx_id = tip->data.tx_id;
+ }
TXN_POSTFIX_RDONLY();
@@ -1695,17 +1717,17 @@ uint64_t BlockchainLMDB::get_tx_unlock_time(const crypto::hash& h) const
check_open();
TXN_PREFIX_RDONLY();
- RCURSOR(tx_unlocks);
+ RCURSOR(tx_indices);
- MDB_val_copy<crypto::hash> key(h);
- MDB_val result;
- auto get_result = mdb_cursor_get(m_cur_tx_unlocks, &key, &result, MDB_SET);
+ MDB_val_set(v, h);
+ auto get_result = mdb_cursor_get(m_cur_tx_indices, (MDB_val *)&zerokval, &v, MDB_GET_BOTH);
if (get_result == MDB_NOTFOUND)
- throw1(TX_DNE(std::string("tx unlock time with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found in db").c_str()));
+ throw1(TX_DNE(lmdb_error(std::string("tx data with hash ") + epee::string_tools::pod_to_hex(h) + " not found in db: ", get_result).c_str()));
else if (get_result)
- throw0(DB_ERROR(lmdb_error("DB error attempting to fetch tx unlock time from hash", get_result).c_str()));
+ throw0(DB_ERROR(lmdb_error("DB error attempting to fetch tx data from hash: ", get_result).c_str()));
- uint64_t ret = *(const uint64_t*)result.mv_data;
+ txindex *tip = (txindex *)v.mv_data;
+ uint64_t ret = tip->data.unlock_time;
TXN_POSTFIX_RDONLY();
return ret;
}
@@ -1716,11 +1738,18 @@ transaction BlockchainLMDB::get_tx(const crypto::hash& h) const
check_open();
TXN_PREFIX_RDONLY();
+ RCURSOR(tx_indices);
RCURSOR(txs);
- MDB_val_copy<crypto::hash> key(h);
+ MDB_val_set(v, h);
MDB_val result;
- auto get_result = mdb_cursor_get(m_cur_txs, &key, &result, MDB_SET);
+ auto get_result = mdb_cursor_get(m_cur_tx_indices, (MDB_val *)&zerokval, &v, MDB_GET_BOTH);
+ if (get_result == 0)
+ {
+ txindex *tip = (txindex *)v.mv_data;
+ MDB_val_set(val_tx_id, tip->data.tx_id);
+ get_result = mdb_cursor_get(m_cur_txs, &val_tx_id, &result, MDB_SET);
+ }
if (get_result == MDB_NOTFOUND)
throw1(TX_DNE(std::string("tx with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found in db").c_str()));
else if (get_result)
@@ -1746,8 +1775,8 @@ uint64_t BlockchainLMDB::get_tx_count() const
TXN_PREFIX_RDONLY();
MDB_stat db_stats;
- if (mdb_stat(m_txn, m_txs, &db_stats))
- throw0(DB_ERROR("Failed to query m_txs"));
+ if (mdb_stat(m_txn, m_tx_indices, &db_stats))
+ throw0(DB_ERROR("Failed to query m_tx_indices"));
TXN_POSTFIX_RDONLY();
@@ -1774,19 +1803,19 @@ uint64_t BlockchainLMDB::get_tx_block_height(const crypto::hash& h) const
check_open();
TXN_PREFIX_RDONLY();
- RCURSOR(tx_heights);
+ RCURSOR(tx_indices);
- MDB_val_copy<crypto::hash> key(h);
- MDB_val result;
- auto get_result = mdb_cursor_get(m_cur_tx_heights, &key, &result, MDB_SET);
+ MDB_val_set(v, h);
+ auto get_result = mdb_cursor_get(m_cur_tx_indices, (MDB_val *)&zerokval, &v, MDB_GET_BOTH);
if (get_result == MDB_NOTFOUND)
{
- throw1(TX_DNE(std::string("tx height with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found in db").c_str()));
+ throw1(TX_DNE(std::string("tx_data_t with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found in db").c_str()));
}
else if (get_result)
throw0(DB_ERROR(lmdb_error("DB error attempting to fetch tx height from hash", get_result).c_str()));
- uint64_t ret = *(const uint64_t*)result.mv_data;
+ txindex *tip = (txindex *)v.mv_data;
+ uint64_t ret = tip->data.block_id;
TXN_POSTFIX_RDONLY();
return ret;
}
@@ -1815,24 +1844,54 @@ uint64_t BlockchainLMDB::get_num_outputs(const uint64_t& amount) const
return num_elems;
}
+// This is a lot harder now that we've removed the output_keys index
output_data_t BlockchainLMDB::get_output_key(const uint64_t &global_index) const
{
- LOG_PRINT_L3("BlockchainLMDB::" << __func__);
+ LOG_PRINT_L3("BlockchainLMDB::" << __func__ << " (unused version - does nothing)");
check_open();
-
TXN_PREFIX_RDONLY();
- RCURSOR(output_keys);
+ RCURSOR(output_txs);
+ RCURSOR(tx_indices);
+ RCURSOR(txs);
- MDB_val_copy<uint64_t> k(global_index);
- MDB_val v;
- auto get_result = mdb_cursor_get(m_cur_output_keys, &k, &v, MDB_SET);
+ output_data_t od;
+ MDB_val_set(v, global_index);
+ auto get_result = mdb_cursor_get(m_cur_output_txs, (MDB_val *)&zerokval, &v, MDB_GET_BOTH);
if (get_result == MDB_NOTFOUND)
- throw1(OUTPUT_DNE("Attempting to get output pubkey by global index, but key does not exist"));
+ throw1(OUTPUT_DNE("output with given index not in db"));
else if (get_result)
- throw0(DB_ERROR("Error attempting to retrieve an output pubkey from the db"));
- output_data_t ret = *(const output_data_t *) v.mv_data;
+ throw0(DB_ERROR("DB error attempting to fetch output tx hash"));
+
+ outtx *ot = (outtx *)v.mv_data;
+
+ MDB_val_set(val_h, ot->tx_hash);
+ get_result = mdb_cursor_get(m_cur_tx_indices, (MDB_val *)&zerokval, &val_h, MDB_GET_BOTH);
+ if (get_result)
+ throw0(DB_ERROR(lmdb_error(std::string("DB error attempting to fetch transaction index from hash ") + epee::string_tools::pod_to_hex(ot->tx_hash) + ": ", get_result).c_str()));
+
+ txindex *tip = (txindex *)val_h.mv_data;
+ MDB_val_set(val_tx_id, tip->data.tx_id);
+ MDB_val result;
+ get_result = mdb_cursor_get(m_cur_txs, &val_tx_id, &result, MDB_SET);
+ if (get_result == MDB_NOTFOUND)
+ throw1(TX_DNE(std::string("tx with hash ").append(epee::string_tools::pod_to_hex(ot->tx_hash)).append(" not found in db").c_str()));
+ else if (get_result)
+ throw0(DB_ERROR(lmdb_error("DB error attempting to fetch tx from hash", get_result).c_str()));
+
+ blobdata bd;
+ bd.assign(reinterpret_cast<char*>(result.mv_data), result.mv_size);
+
+ transaction tx;
+ if (!parse_and_validate_tx_from_blob(bd, tx))
+ throw0(DB_ERROR("Failed to parse tx from blob retrieved from the db"));
+
+ const tx_out tx_output = tx.vout[ot->local_index];
+ od.unlock_time = tip->data.unlock_time;
+ od.height = tip->data.block_id;
+ od.pubkey = boost::get<txout_to_key>(tx_output.target).key;
+
TXN_POSTFIX_RDONLY();
- return ret;
+ return od;
}
output_data_t BlockchainLMDB::get_output_key(const uint64_t& amount, const uint64_t& index)
@@ -1840,37 +1899,41 @@ output_data_t BlockchainLMDB::get_output_key(const uint64_t& amount, const uint6
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
- uint64_t glob_index = get_output_global_index(amount, index);
- return get_output_key(glob_index);
+ TXN_PREFIX_RDONLY();
+ RCURSOR(output_amounts);
+
+ MDB_val_set(k, amount);
+ MDB_val_set(v, index);
+ auto get_result = mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_GET_BOTH);
+ if (get_result == MDB_NOTFOUND)
+ throw1(OUTPUT_DNE("Attempting to get output pubkey by index, but key does not exist"));
+ else if (get_result)
+ throw0(DB_ERROR("Error attempting to retrieve an output pubkey from the db"));
+ outkey *okp = (outkey *)v.mv_data;
+ output_data_t ret = okp->data;
+ TXN_POSTFIX_RDONLY();
+ return ret;
}
-tx_out_index BlockchainLMDB::get_output_tx_and_index_from_global(const uint64_t& index) const
+tx_out_index BlockchainLMDB::get_output_tx_and_index_from_global(const uint64_t& output_id) const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
TXN_PREFIX_RDONLY();
RCURSOR(output_txs);
- RCURSOR(output_indices);
- MDB_val_copy<uint64_t> k(index);
- MDB_val v;
+ MDB_val_set(v, output_id);
- auto get_result = mdb_cursor_get(m_cur_output_txs, &k, &v, MDB_SET);
+ auto get_result = mdb_cursor_get(m_cur_output_txs, (MDB_val *)&zerokval, &v, MDB_GET_BOTH);
if (get_result == MDB_NOTFOUND)
throw1(OUTPUT_DNE("output with given index not in db"));
else if (get_result)
throw0(DB_ERROR("DB error attempting to fetch output tx hash"));
- crypto::hash tx_hash = *(const crypto::hash*)v.mv_data;
+ outtx *ot = (outtx *)v.mv_data;
+ tx_out_index ret = tx_out_index(ot->tx_hash, ot->local_index);
- get_result = mdb_cursor_get(m_cur_output_indices, &k, &v, MDB_SET);
- if (get_result == MDB_NOTFOUND)
- throw1(OUTPUT_DNE("output with given index not in db"));
- else if (get_result)
- throw0(DB_ERROR("DB error attempting to fetch output tx index"));
-
- tx_out_index ret = tx_out_index(tx_hash, *(const uint64_t *)v.mv_data);
TXN_POSTFIX_RDONLY();
return ret;
}
@@ -1888,128 +1951,55 @@ tx_out_index BlockchainLMDB::get_output_tx_and_index(const uint64_t& amount, con
return indices[0];
}
-std::vector<uint64_t> BlockchainLMDB::get_tx_output_indices(const crypto::hash& h) const
+std::vector<uint64_t> BlockchainLMDB::get_tx_amount_output_indices(const uint64_t tx_id) const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
+
check_open();
- std::vector<uint64_t> index_vec;
TXN_PREFIX_RDONLY();
RCURSOR(tx_outputs);
- MDB_val_copy<crypto::hash> k(h);
+ int result = 0;
+ MDB_val_set(k_tx_id, tx_id);
MDB_val v;
- auto result = mdb_cursor_get(m_cur_tx_outputs, &k, &v, MDB_SET);
+ std::vector<uint64_t> amount_output_indices;
+
+ result = mdb_cursor_get(m_cur_tx_outputs, &k_tx_id, &v, MDB_SET);
if (result == MDB_NOTFOUND)
- throw1(OUTPUT_DNE("Attempting to get an output by tx hash and tx index, but output not found"));
+ LOG_PRINT_L0("WARNING: Unexpected: tx has no amount indices stored in "
+ "tx_outputs, but it should have an empty entry even if it's a tx without "
+ "outputs");
else if (result)
- throw0(DB_ERROR(lmdb_error("DB error attempting to get an output", result).c_str()));
-
- mdb_size_t num_elems = 0;
- mdb_cursor_count(m_cur_tx_outputs, &num_elems);
+ throw0(DB_ERROR(lmdb_error("DB error attempting to get data for tx_outputs[tx_index]", result).c_str()));
- mdb_cursor_get(m_cur_tx_outputs, &k, &v, MDB_FIRST_DUP);
+ uint64_t* indices = (uint64_t*)v.mv_data;
+ int num_outputs = v.mv_size / sizeof(uint64_t);
- for (uint64_t i = 0; i < num_elems; ++i)
+ for (int i = 0; i < num_outputs; ++i)
{
- mdb_cursor_get(m_cur_tx_outputs, &k, &v, MDB_GET_CURRENT);
- index_vec.push_back(*(const uint64_t *)v.mv_data);
- mdb_cursor_get(m_cur_tx_outputs, &k, &v, MDB_NEXT_DUP);
+ // LOG_PRINT_L0("amount output index[" << 2*i << "]" << ": " << paired_indices[2*i] << " global output index: " << paired_indices[2*i+1]);
+ amount_output_indices.push_back(indices[i]);
}
+ indices = nullptr;
TXN_POSTFIX_RDONLY();
-
- return index_vec;
+ return amount_output_indices;
}
-std::vector<uint64_t> BlockchainLMDB::get_tx_amount_output_indices(const crypto::hash& h) const
-{
- LOG_PRINT_L3("BlockchainLMDB::" << __func__);
- check_open();
- std::vector<uint64_t> index_vec;
- std::vector<uint64_t> index_vec2;
-
- // get the transaction's global output indices first
- index_vec = get_tx_output_indices(h);
- // these are next used to obtain the amount output indices
-
- transaction tx = get_tx(h);
-
- TXN_PREFIX_RDONLY();
- RCURSOR(output_amounts);
-
- uint64_t i = 0;
- uint64_t global_index;
- BOOST_FOREACH(const auto& vout, tx.vout)
- {
- uint64_t amount = vout.amount;
-
- global_index = index_vec[i];
-
- MDB_val_copy<uint64_t> k(amount);
- MDB_val v;
-
- auto result = mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_SET);
- if (result == MDB_NOTFOUND)
- throw1(OUTPUT_DNE("Attempting to get an output index by amount and amount index, but amount not found"));
- else if (result)
- throw0(DB_ERROR(lmdb_error("DB error attempting to get an output", result).c_str()));
-
- mdb_size_t num_elems = 0;
- mdb_cursor_count(m_cur_output_amounts, &num_elems);
-
- mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_FIRST_DUP);
-
- uint64_t amount_output_index = 0;
- uint64_t output_index = 0;
- bool found_index = false;
- for (uint64_t j = 0; j < num_elems; ++j)
- {
- mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_GET_CURRENT);
- output_index = *(const uint64_t *)v.mv_data;
- if (output_index == global_index)
- {
- amount_output_index = j;
- found_index = true;
- break;
- }
- mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_NEXT_DUP);
- }
- if (found_index)
- {
- index_vec2.push_back(amount_output_index);
- }
- else
- {
- // not found
- TXN_POSTFIX_RDONLY();
- throw1(OUTPUT_DNE("specified output not found in db"));
- }
-
- ++i;
- }
-
- TXN_POSTFIX_RDONLY();
-
- return index_vec2;
-}
-
-
bool BlockchainLMDB::has_key_image(const crypto::key_image& img) const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
+ bool ret;
+
TXN_PREFIX_RDONLY();
RCURSOR(spent_keys);
- bool ret = false;
- MDB_val_copy<crypto::key_image> val_key(img);
- if (mdb_cursor_get(m_cur_spent_keys, &val_key, NULL, MDB_SET) == 0)
- {
- ret = true;
- }
+ MDB_val k = {sizeof(img), (void *)&img};
+ ret = (mdb_cursor_get(m_cur_spent_keys, (MDB_val *)&zerokval, &k, MDB_GET_BOTH) == 0);
TXN_POSTFIX_RDONLY();
return ret;
@@ -2023,10 +2013,10 @@ bool BlockchainLMDB::for_all_key_images(std::function<bool(const crypto::key_ima
TXN_PREFIX_RDONLY();
RCURSOR(spent_keys);
- MDB_val k;
- MDB_val v;
+ MDB_val k, v;
bool ret = true;
+ k = zerokval;
MDB_cursor_op op = MDB_FIRST;
while (1)
{
@@ -2036,7 +2026,7 @@ bool BlockchainLMDB::for_all_key_images(std::function<bool(const crypto::key_ima
break;
if (ret < 0)
throw0(DB_ERROR("Failed to enumerate key images"));
- const crypto::key_image k_image = *(const crypto::key_image*)k.mv_data;
+ const crypto::key_image k_image = *(const crypto::key_image*)v.mv_data;
if (!f(k_image)) {
ret = false;
break;
@@ -2096,6 +2086,7 @@ bool BlockchainLMDB::for_all_transactions(std::function<bool(const crypto::hash&
TXN_PREFIX_RDONLY();
RCURSOR(txs);
+ RCURSOR(tx_indices);
MDB_val k;
MDB_val v;
@@ -2104,13 +2095,22 @@ bool BlockchainLMDB::for_all_transactions(std::function<bool(const crypto::hash&
MDB_cursor_op op = MDB_FIRST;
while (1)
{
- int ret = mdb_cursor_get(m_cur_txs, &k, &v, op);
+ int ret = mdb_cursor_get(m_cur_tx_indices, &k, &v, op);
op = MDB_NEXT;
if (ret == MDB_NOTFOUND)
break;
if (ret)
- throw0(DB_ERROR("Failed to enumerate transactions"));
- const crypto::hash hash = *(const crypto::hash*)k.mv_data;
+ throw0(DB_ERROR(lmdb_error("Failed to enumerate transactions: ", ret).c_str()));
+
+ txindex *ti = (txindex *)v.mv_data;
+ const crypto::hash hash = ti->key;
+ k.mv_data = (void *)&ti->data.tx_id;
+ k.mv_size = sizeof(ti->data.tx_id);
+ ret = mdb_cursor_get(m_cur_txs, &k, &v, MDB_SET);
+ if (ret == MDB_NOTFOUND)
+ break;
+ if (ret)
+ throw0(DB_ERROR(lmdb_error("Failed to enumerate transactions: ", ret).c_str()));
blobdata bd;
bd.assign(reinterpret_cast<char*>(v.mv_data), v.mv_size);
transaction tx;
@@ -2149,8 +2149,8 @@ bool BlockchainLMDB::for_all_outputs(std::function<bool(uint64_t amount, const c
if (ret)
throw0(DB_ERROR("Failed to enumerate outputs"));
uint64_t amount = *(const uint64_t*)k.mv_data;
- uint64_t global_index = *(const uint64_t*)v.mv_data;
- tx_out_index toi = get_output_tx_and_index_from_global(global_index);
+ outkey *ok = (outkey *)v.mv_data;
+ tx_out_index toi = get_output_tx_and_index_from_global(ok->output_id);
if (!f(amount, toi.first, toi.second)) {
ret = false;
break;
@@ -2269,6 +2269,10 @@ void BlockchainLMDB::batch_abort()
void BlockchainLMDB::set_batch_transactions(bool batch_transactions)
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
+ if ((batch_transactions) && (m_batch_transactions))
+ {
+ LOG_PRINT_L0("WARNING: batch transaction mode already enabled, but asked to enable batch mode");
+ }
m_batch_transactions = batch_transactions;
LOG_PRINT_L3("batch transactions " << (m_batch_transactions ? "enabled" : "disabled"));
}
@@ -2433,6 +2437,7 @@ uint64_t BlockchainLMDB::add_block(const block& blk, const size_t& block_size, c
}
}
+ uint64_t num_txs = m_num_txs;
uint64_t num_outputs = m_num_outputs;
try
{
@@ -2444,6 +2449,7 @@ uint64_t BlockchainLMDB::add_block(const block& blk, const size_t& block_size, c
}
catch (...)
{
+ m_num_txs = num_txs;
m_num_outputs = num_outputs;
block_txn_abort();
throw;
@@ -2459,6 +2465,7 @@ void BlockchainLMDB::pop_block(block& blk, std::vector<transaction>& txs)
block_txn_start(false);
+ uint64_t num_txs = m_num_txs;
uint64_t num_outputs = m_num_outputs;
try
{
@@ -2467,6 +2474,7 @@ void BlockchainLMDB::pop_block(block& blk, std::vector<transaction>& txs)
}
catch (...)
{
+ m_num_txs = num_txs;
m_num_outputs = num_outputs;
block_txn_abort();
throw;
@@ -2484,211 +2492,88 @@ void BlockchainLMDB::get_output_tx_and_index_from_global(const std::vector<uint6
TXN_PREFIX_RDONLY();
RCURSOR(output_txs);
- RCURSOR(output_indices);
- for (const uint64_t &index : global_indices)
+ for (const uint64_t &output_id : global_indices)
{
- MDB_val_copy<uint64_t> k(index);
- MDB_val v;
+ MDB_val_set(v, output_id);
- auto get_result = mdb_cursor_get(m_cur_output_txs, &k, &v, MDB_SET);
+ auto get_result = mdb_cursor_get(m_cur_output_txs, (MDB_val *)&zerokval, &v, MDB_GET_BOTH);
if (get_result == MDB_NOTFOUND)
throw1(OUTPUT_DNE("output with given index not in db"));
else if (get_result)
throw0(DB_ERROR("DB error attempting to fetch output tx hash"));
- crypto::hash tx_hash = *(const crypto::hash*) v.mv_data;
-
- get_result = mdb_cursor_get(m_cur_output_indices, &k, &v, MDB_SET);
- if (get_result == MDB_NOTFOUND)
- throw1(OUTPUT_DNE("output with given index not in db"));
- else if (get_result)
- throw0(DB_ERROR("DB error attempting to fetch output tx index"));
-
- auto result = tx_out_index(tx_hash, *(const uint64_t *) v.mv_data);
+ outtx *ot = (outtx *)v.mv_data;
+ auto result = tx_out_index(ot->tx_hash, ot->local_index);
tx_out_indices.push_back(result);
}
TXN_POSTFIX_RDONLY();
}
-void BlockchainLMDB::get_output_global_indices(const uint64_t& amount, const std::vector<uint64_t> &offsets,
- std::vector<uint64_t> &global_indices)
+void BlockchainLMDB::get_output_key(const uint64_t &amount, const std::vector<uint64_t> &offsets, std::vector<output_data_t> &outputs)
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
- TIME_MEASURE_START(txx);
+ TIME_MEASURE_START(db3);
check_open();
- global_indices.clear();
-
- uint64_t max = 0;
- for (const uint64_t &index : offsets)
- {
- if (index > max)
- max = index;
- }
+ outputs.clear();
TXN_PREFIX_RDONLY();
- RCURSOR(output_amounts);
-
- MDB_val_copy<uint64_t> k(amount);
- MDB_val v;
- auto result = mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_SET);
- if (result == MDB_NOTFOUND)
- throw1(OUTPUT_DNE("Attempting to get an output index by amount and amount index, but amount not found"));
- else if (result)
- throw0(DB_ERROR(lmdb_error("DB error attempting to get an output", result).c_str()));
-
- mdb_size_t num_elems = 0;
- mdb_cursor_count(m_cur_output_amounts, &num_elems);
- if (max <= 1 && num_elems <= max)
- throw1(OUTPUT_DNE("Attempting to get an output index by amount and amount index, but output not found"));
- uint64_t t_dbmul = 0;
- uint64_t t_dbscan = 0;
- if (max <= 1)
- {
- for (const uint64_t& index : offsets)
- {
- mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_FIRST_DUP);
- for (uint64_t i = 0; i < index; ++i)
- {
- mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_NEXT_DUP);
- }
+ RCURSOR(output_amounts);
- mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_GET_CURRENT);
- uint64_t glob_index = *(const uint64_t*) v.mv_data;
- LOG_PRINT_L3("Amount: " << amount << " M0->v: " << glob_index);
- global_indices.push_back(glob_index);
- }
- }
- else
+ MDB_val_set(k, amount);
+ for (const uint64_t &index : offsets)
{
- uint32_t curcount = 0;
- uint32_t blockstart = 0;
- for (const uint64_t& index : offsets)
- {
- if (index >= num_elems)
- {
- LOG_PRINT_L1("Index: " << index << " Elems: " << num_elems << " partial results found for get_output_tx_and_index");
- break;
- }
- if (!curcount && index > num_elems/2)
- {
- mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_LAST_DUP);
- mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_PREV); /* kludge to unset C_EOF */
- mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_NEXT);
- mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_GET_MULTIPLE);
-
- curcount = num_elems;
- while(1)
- {
- TIME_MEASURE_START(db1);
- int count = v.mv_size / sizeof(uint64_t);
- curcount -= count;
- if (curcount > index)
- {
- mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_PREV_MULTIPLE);
- } else
- {
- blockstart = curcount;
- curcount += count;
- break;
- }
- TIME_MEASURE_FINISH(db1);
- t_dbmul += db1;
- }
-
- } else
- {
- while (index >= curcount)
- {
- TIME_MEASURE_START(db1);
- if (mdb_cursor_get(m_cur_output_amounts, &k, &v, curcount == 0 ? MDB_GET_MULTIPLE : MDB_NEXT_MULTIPLE) != 0)
- {
- // allow partial results
- result = false;
- break;
- }
+ MDB_val_set(v, index);
- int count = v.mv_size / sizeof(uint64_t);
-
- blockstart = curcount;
- curcount += count;
- TIME_MEASURE_FINISH(db1);
- t_dbmul += db1;
- }
- }
-
- LOG_PRINT_L3("Records returned: " << curcount << " Index: " << index);
- TIME_MEASURE_START(db2);
- uint64_t actual_index = index - blockstart;
- uint64_t glob_index = ((const uint64_t*) v.mv_data)[actual_index];
-
- LOG_PRINT_L3("Amount: " << amount << " M1->v: " << glob_index);
- global_indices.push_back(glob_index);
-
- TIME_MEASURE_FINISH(db2);
- t_dbscan += db2;
+ auto get_result = mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_GET_BOTH);
+ if (get_result == MDB_NOTFOUND)
+ throw1(OUTPUT_DNE("Attempting to get output pubkey by global index, but key does not exist"));
+ else if (get_result)
+ throw0(DB_ERROR(lmdb_error("Error attempting to retrieve an output pubkey from the db", get_result).c_str()));
- }
+ outkey *okp = (outkey *)v.mv_data;
+ output_data_t data = okp->data;
+ outputs.push_back(data);
}
TXN_POSTFIX_RDONLY();
- TIME_MEASURE_FINISH(txx);
- LOG_PRINT_L3("txx: " << txx << " db1: " << t_dbmul << " db2: " << t_dbscan);
+ TIME_MEASURE_FINISH(db3);
+ LOG_PRINT_L3("db3: " << db3);
}
-void BlockchainLMDB::get_output_key(const uint64_t &amount, const std::vector<uint64_t> &offsets, std::vector<output_data_t> &outputs)
+void BlockchainLMDB::get_output_tx_and_index(const uint64_t& amount, const std::vector<uint64_t> &offsets, std::vector<tx_out_index> &indices)
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
- TIME_MEASURE_START(db3);
check_open();
- outputs.clear();
+ indices.clear();
+ std::vector <uint64_t> tx_indices;
TXN_PREFIX_RDONLY();
- std::vector <uint64_t> global_indices;
- get_output_global_indices(amount, offsets, global_indices);
-
- if (global_indices.size() > 0)
- {
- RCURSOR(output_keys);
- for (const uint64_t &index : global_indices)
- {
- MDB_val_copy<uint64_t> k(index);
- MDB_val v;
+ RCURSOR(output_amounts);
- auto get_result = mdb_cursor_get(m_cur_output_keys, &k, &v, MDB_SET);
- if (get_result == MDB_NOTFOUND)
- throw1(OUTPUT_DNE("Attempting to get output pubkey by global index, but key does not exist"));
- else if (get_result)
- throw0(DB_ERROR(lmdb_error("Error attempting to retrieve an output pubkey from the db", get_result).c_str()));
+ MDB_val_set(k, amount);
+ for (const uint64_t &index : offsets)
+ {
+ MDB_val_set(v, index);
- output_data_t data = *(const output_data_t *) v.mv_data;
- outputs.push_back(data);
- }
+ auto get_result = mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_GET_BOTH);
+ if (get_result == MDB_NOTFOUND)
+ throw1(OUTPUT_DNE("Attempting to get output by index, but key does not exist"));
+ else if (get_result)
+ throw0(DB_ERROR(lmdb_error("Error attempting to retrieve an output from the db", get_result).c_str()));
+ outkey *okp = (outkey *)v.mv_data;
+ tx_indices.push_back(okp->output_id);
}
- TXN_POSTFIX_RDONLY();
-
- TIME_MEASURE_FINISH(db3);
- LOG_PRINT_L3("db3: " << db3);
-}
-
-void BlockchainLMDB::get_output_tx_and_index(const uint64_t& amount, const std::vector<uint64_t> &offsets, std::vector<tx_out_index> &indices)
-{
- LOG_PRINT_L3("BlockchainLMDB::" << __func__);
- check_open();
- indices.clear();
-
- std::vector <uint64_t> global_indices;
- get_output_global_indices(amount, offsets, global_indices);
TIME_MEASURE_START(db3);
- if(global_indices.size() > 0)
+ if(tx_indices.size() > 0)
{
- get_output_tx_and_index_from_global(global_indices, indices);
+ get_output_tx_and_index_from_global(tx_indices, indices);
}
TIME_MEASURE_FINISH(db3);
LOG_PRINT_L3("db3: " << db3);
@@ -2815,7 +2700,7 @@ uint64_t BlockchainLMDB::get_hard_fork_starting_height(uint8_t version) const
MDB_val_copy<uint8_t> val_key(version);
MDB_val val_ret;
- uint64_t ret;
+ uint64_t ret = 0;
auto result = mdb_get(m_txn, m_hf_starting_heights, &val_key, &val_ret);
if (result == MDB_SUCCESS)
{
@@ -2892,4 +2777,558 @@ void BlockchainLMDB::fixup()
BlockchainDB::fixup();
}
+#define RENAME_DB(name) \
+ k.mv_data = (void *)name; \
+ k.mv_size = sizeof(name)-1; \
+ result = mdb_cursor_open(txn, 1, &c_cur); \
+ if (result) \
+ throw0(DB_ERROR(lmdb_error("Failed to open a cursor for " name ": ", result).c_str())); \
+ result = mdb_cursor_get(c_cur, &k, NULL, MDB_SET_KEY); \
+ if (result) \
+ throw0(DB_ERROR(lmdb_error("Failed to get DB record for " name ": ", result).c_str())); \
+ ptr = (char *)k.mv_data; \
+ ptr[sizeof(name)-2] = 's'
+
+#define LOGIF(y) if (y <= epee::log_space::log_singletone::get_log_detalisation_level())
+
+void BlockchainLMDB::migrate_0_1()
+{
+ LOG_PRINT_L3("BlockchainLMDB::" << __func__);
+ uint64_t i, z;
+ int result;
+ mdb_txn_safe txn(false);
+ MDB_val k, v;
+ char *ptr;
+
+ LOG_PRINT_YELLOW("Migrating blockchain from DB version 0 to 1 - this may take a while:", LOG_LEVEL_0);
+ LOG_PRINT_L0("updating blocks, hf_versions, outputs, txs, and spent_keys tables...");
+
+ LOG_PRINT_L0("Total number of blocks: " << m_height);
+ LOG_PRINT_L1("block migration will update block_heights, block_info, and hf_versions...");
+
+ do {
+ LOG_PRINT_L1("migrating block_heights:");
+ MDB_dbi o_heights;
+
+ unsigned int flags;
+ 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()));
+ result = mdb_dbi_flags(txn, m_block_heights, &flags);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to retrieve block_heights flags: ", result).c_str()));
+ /* if the flags are what we expect, this table has already been migrated */
+ if ((flags & (MDB_INTEGERKEY|MDB_DUPSORT|MDB_DUPFIXED)) == (MDB_INTEGERKEY|MDB_DUPSORT|MDB_DUPFIXED)) {
+ txn.abort();
+ LOG_PRINT_L1(" block_heights already migrated");
+ break;
+ }
+
+ /* the block_heights table name is the same but the old version and new version
+ * have incompatible DB flags. Create a new table with the right flags. We want
+ * the name to be similar to the old name so that it will occupy the same location
+ * in the DB.
+ */
+ o_heights = m_block_heights;
+ lmdb_db_open(txn, "block_heightr", MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_block_heights, "Failed to open db handle for block_heightr");
+ mdb_set_dupsort(txn, m_block_heights, compare_hash32);
+
+ MDB_cursor *c_old, *c_cur;
+ blk_height bh;
+ MDB_val_set(nv, bh);
+
+ /* old table was k(hash), v(height).
+ * new table is DUPFIXED, k(zeroval), v{hash, height}.
+ */
+ i = 0;
+ z = m_height;
+ while(1) {
+ if (!(i % 2000)) {
+ if (i) {
+ LOGIF(1) {
+ std::cout << i << " / " << z << " \r" << std::flush;
+ }
+ txn.commit();
+ 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()));
+ }
+ result = mdb_cursor_open(txn, m_block_heights, &c_cur);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to open a cursor for block_heightr: ", result).c_str()));
+ result = mdb_cursor_open(txn, o_heights, &c_old);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to open a cursor for block_heights: ", result).c_str()));
+ if (!i) {
+ MDB_stat ms;
+ mdb_stat(txn, m_block_heights, &ms);
+ i = ms.ms_entries;
+ }
+ }
+ result = mdb_cursor_get(c_old, &k, &v, MDB_NEXT);
+ if (result == MDB_NOTFOUND) {
+ txn.commit();
+ break;
+ }
+ else if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to get a record from block_heights: ", result).c_str()));
+ bh.bh_hash = *(crypto::hash *)k.mv_data;
+ bh.bh_height = *(uint64_t *)v.mv_data;
+ result = mdb_cursor_put(c_cur, (MDB_val *)&zerokval, &nv, MDB_APPENDDUP);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to put a record into block_heightr: ", result).c_str()));
+ /* we delete the old records immediately, so the overall DB and mapsize should not grow.
+ * This is a little slower than just letting mdb_drop() delete it all at the end, but
+ * it saves a significant amount of disk space.
+ */
+ result = mdb_cursor_del(c_old, 0);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to delete a record from block_heights: ", result).c_str()));
+ i++;
+ }
+
+ 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()));
+ /* Delete the old table */
+ result = mdb_drop(txn, o_heights, 1);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to delete old block_heights table: ", result).c_str()));
+
+ RENAME_DB("block_heightr");
+
+ /* close and reopen to get old dbi slot back */
+ mdb_dbi_close(m_env, m_block_heights);
+ lmdb_db_open(txn, "block_heights", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, m_block_heights, "Failed to open db handle for block_heights");
+ mdb_set_dupsort(txn, m_block_heights, compare_hash32);
+ txn.commit();
+
+ } while(0);
+
+ /* old tables are k(height), v(value).
+ * new table is DUPFIXED, k(zeroval), v{height, values...}.
+ */
+ do {
+ LOG_PRINT_L1("migrating block info:");
+
+ MDB_dbi coins;
+ 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()));
+ result = mdb_dbi_open(txn, "block_coins", 0, &coins);
+ if (result == MDB_NOTFOUND) {
+ txn.abort();
+ LOG_PRINT_L1(" block_info already migrated");
+ break;
+ }
+ MDB_dbi diffs, hashes, sizes, timestamps;
+ mdb_block_info bi;
+ MDB_val_set(nv, bi);
+
+ lmdb_db_open(txn, "block_diffs", 0, diffs, "Failed to open db handle for block_diffs");
+ lmdb_db_open(txn, "block_hashes", 0, hashes, "Failed to open db handle for block_hashes");
+ lmdb_db_open(txn, "block_sizes", 0, sizes, "Failed to open db handle for block_sizes");
+ lmdb_db_open(txn, "block_timestamps", 0, timestamps, "Failed to open db handle for block_timestamps");
+ MDB_cursor *c_cur, *c_coins, *c_diffs, *c_hashes, *c_sizes, *c_timestamps;
+ i = 0;
+ z = m_height;
+ while(1) {
+ MDB_val k, v;
+ if (!(i % 2000)) {
+ if (i) {
+ LOGIF(1) {
+ std::cout << i << " / " << z << " \r" << std::flush;
+ }
+ txn.commit();
+ 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()));
+ }
+ result = mdb_cursor_open(txn, m_block_info, &c_cur);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to open a cursor for block_info: ", result).c_str()));
+ result = mdb_cursor_open(txn, coins, &c_coins);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to open a cursor for block_coins: ", result).c_str()));
+ result = mdb_cursor_open(txn, diffs, &c_diffs);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to open a cursor for block_diffs: ", result).c_str()));
+ result = mdb_cursor_open(txn, hashes, &c_hashes);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to open a cursor for block_hashes: ", result).c_str()));
+ result = mdb_cursor_open(txn, sizes, &c_sizes);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to open a cursor for block_coins: ", result).c_str()));
+ result = mdb_cursor_open(txn, timestamps, &c_timestamps);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to open a cursor for block_timestamps: ", result).c_str()));
+ if (!i) {
+ MDB_stat ms;
+ mdb_stat(txn, m_block_info, &ms);
+ i = ms.ms_entries;
+ }
+ }
+ result = mdb_cursor_get(c_coins, &k, &v, MDB_NEXT);
+ if (result == MDB_NOTFOUND) {
+ break;
+ } else if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to get a record from block_coins: ", result).c_str()));
+ bi.bi_height = *(uint64_t *)k.mv_data;
+ bi.bi_coins = *(uint64_t *)v.mv_data;
+ result = mdb_cursor_get(c_diffs, &k, &v, MDB_NEXT);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to get a record from block_diffs: ", result).c_str()));
+ bi.bi_diff = *(uint64_t *)v.mv_data;
+ result = mdb_cursor_get(c_hashes, &k, &v, MDB_NEXT);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to get a record from block_hashes: ", result).c_str()));
+ bi.bi_hash = *(crypto::hash *)v.mv_data;
+ result = mdb_cursor_get(c_sizes, &k, &v, MDB_NEXT);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to get a record from block_sizes: ", result).c_str()));
+ if (v.mv_size == sizeof(uint32_t))
+ bi.bi_size = *(uint32_t *)v.mv_data;
+ else
+ bi.bi_size = *(uint64_t *)v.mv_data; // this is a 32/64 compat bug in version 0
+ result = mdb_cursor_get(c_timestamps, &k, &v, MDB_NEXT);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to get a record from block_timestamps: ", result).c_str()));
+ bi.bi_timestamp = *(uint64_t *)v.mv_data;
+ result = mdb_cursor_put(c_cur, (MDB_val *)&zerokval, &nv, MDB_APPENDDUP);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to put a record into block_info: ", result).c_str()));
+ result = mdb_cursor_del(c_coins, 0);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to delete a record from block_coins: ", result).c_str()));
+ result = mdb_cursor_del(c_diffs, 0);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to delete a record from block_diffs: ", result).c_str()));
+ result = mdb_cursor_del(c_hashes, 0);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to delete a record from block_hashes: ", result).c_str()));
+ result = mdb_cursor_del(c_sizes, 0);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to delete a record from block_sizes: ", result).c_str()));
+ result = mdb_cursor_del(c_timestamps, 0);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to delete a record from block_timestamps: ", result).c_str()));
+ i++;
+ }
+ mdb_cursor_close(c_timestamps);
+ mdb_cursor_close(c_sizes);
+ mdb_cursor_close(c_hashes);
+ mdb_cursor_close(c_diffs);
+ mdb_cursor_close(c_coins);
+ result = mdb_drop(txn, timestamps, 1);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to delete block_timestamps from the db: ", result).c_str()));
+ result = mdb_drop(txn, sizes, 1);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to delete block_sizes from the db: ", result).c_str()));
+ result = mdb_drop(txn, hashes, 1);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to delete block_hashes from the db: ", result).c_str()));
+ result = mdb_drop(txn, diffs, 1);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to delete block_diffs from the db: ", result).c_str()));
+ result = mdb_drop(txn, coins, 1);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to delete block_coins from the db: ", result).c_str()));
+ txn.commit();
+ } while(0);
+
+ do {
+ LOG_PRINT_L1("migrating hf_versions:");
+ MDB_dbi o_hfv;
+
+ unsigned int flags;
+ 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()));
+ result = mdb_dbi_flags(txn, m_hf_versions, &flags);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to retrieve hf_versions flags: ", result).c_str()));
+ /* if the flags are what we expect, this table has already been migrated */
+ if (flags & MDB_INTEGERKEY) {
+ txn.abort();
+ LOG_PRINT_L1(" hf_versions already migrated");
+ break;
+ }
+
+ /* the hf_versions table name is the same but the old version and new version
+ * have incompatible DB flags. Create a new table with the right flags.
+ */
+ o_hfv = m_hf_versions;
+ lmdb_db_open(txn, "hf_versionr", MDB_INTEGERKEY | MDB_CREATE, m_hf_versions, "Failed to open db handle for hf_versionr");
+
+ MDB_cursor *c_old, *c_cur;
+ i = 0;
+ z = m_height;
+
+ while(1) {
+ if (!(i % 2000)) {
+ if (i) {
+ LOGIF(1) {
+ std::cout << i << " / " << z << " \r" << std::flush;
+ }
+ txn.commit();
+ 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()));
+ }
+ result = mdb_cursor_open(txn, m_hf_versions, &c_cur);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to open a cursor for spent_keyr: ", result).c_str()));
+ result = mdb_cursor_open(txn, o_hfv, &c_old);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to open a cursor for spent_keys: ", result).c_str()));
+ if (!i) {
+ MDB_stat ms;
+ mdb_stat(txn, m_hf_versions, &ms);
+ i = ms.ms_entries;
+ }
+ }
+ result = mdb_cursor_get(c_old, &k, &v, MDB_NEXT);
+ if (result == MDB_NOTFOUND) {
+ txn.commit();
+ break;
+ }
+ else if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to get a record from hf_versions: ", result).c_str()));
+ result = mdb_cursor_put(c_cur, &k, &v, MDB_APPEND);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to put a record into hf_versionr: ", result).c_str()));
+ result = mdb_cursor_del(c_old, 0);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to delete a record from hf_versions: ", result).c_str()));
+ i++;
+ }
+
+ 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()));
+ /* Delete the old table */
+ result = mdb_drop(txn, o_hfv, 1);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to delete old hf_versions table: ", result).c_str()));
+ RENAME_DB("hf_versionr");
+ mdb_dbi_close(m_env, m_hf_versions);
+ lmdb_db_open(txn, "hf_versions", MDB_INTEGERKEY, m_hf_versions, "Failed to open db handle for hf_versions");
+
+ txn.commit();
+ } while(0);
+
+ do {
+ LOG_PRINT_L1("deleting old indices:");
+
+ /* Delete all other tables, we're just going to recreate them */
+ MDB_dbi dbi;
+ 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()));
+
+ result = mdb_dbi_open(txn, "tx_unlocks", 0, &dbi);
+ if (result == MDB_NOTFOUND) {
+ txn.abort();
+ LOG_PRINT_L1(" old indices already deleted");
+ break;
+ }
+ txn.abort();
+
+#define DELETE_DB(x) do { \
+ LOG_PRINT_L1(" " x ":"); \
+ 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())); \
+ result = mdb_dbi_open(txn, x, 0, &dbi); \
+ if (!result) { \
+ result = mdb_drop(txn, dbi, 1); \
+ if (result) \
+ throw0(DB_ERROR(lmdb_error("Failed to delete " x ": ", result).c_str())); \
+ txn.commit(); \
+ } } while(0)
+
+ DELETE_DB("tx_heights");
+ DELETE_DB("output_txs");
+ DELETE_DB("output_indices");
+ DELETE_DB("output_keys");
+ DELETE_DB("spent_keys");
+ DELETE_DB("output_amounts");
+ DELETE_DB("tx_outputs");
+ DELETE_DB("tx_unlocks");
+
+ /* reopen new DBs with correct flags */
+ 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()));
+ lmdb_db_open(txn, LMDB_OUTPUT_TXS, MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_output_txs, "Failed to open db handle for m_output_txs");
+ mdb_set_dupsort(txn, m_output_txs, compare_uint64);
+ lmdb_db_open(txn, LMDB_TX_OUTPUTS, MDB_INTEGERKEY | MDB_CREATE, m_tx_outputs, "Failed to open db handle for m_tx_outputs");
+ lmdb_db_open(txn, LMDB_SPENT_KEYS, MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_spent_keys, "Failed to open db handle for m_spent_keys");
+ mdb_set_dupsort(txn, m_spent_keys, compare_hash32);
+ lmdb_db_open(txn, LMDB_OUTPUT_AMOUNTS, MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED | MDB_CREATE, m_output_amounts, "Failed to open db handle for m_output_amounts");
+ mdb_set_dupsort(txn, m_output_amounts, compare_uint64);
+ txn.commit();
+ m_num_txs = 0;
+ m_num_outputs = 0;
+ } while(0);
+
+ do {
+ LOG_PRINT_L1("migrating txs and outputs:");
+
+ unsigned int flags;
+ 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()));
+ result = mdb_dbi_flags(txn, m_txs, &flags);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to retrieve txs flags: ", result).c_str()));
+ /* if the flags are what we expect, this table has already been migrated */
+ if (flags & MDB_INTEGERKEY) {
+ txn.abort();
+ LOG_PRINT_L1(" txs already migrated");
+ break;
+ }
+
+ MDB_dbi o_txs;
+ blobdata bd;
+ block b;
+ MDB_val hk;
+
+ o_txs = m_txs;
+ mdb_set_compare(txn, o_txs, compare_hash32);
+ lmdb_db_open(txn, "txr", MDB_INTEGERKEY | MDB_CREATE, m_txs, "Failed to open db handle for txr");
+
+ txn.commit();
+
+ MDB_cursor *c_blocks, *c_txs, *c_props, *c_cur;
+ i = 0;
+ z = m_height;
+
+ hk.mv_size = sizeof(crypto::hash);
+ set_batch_transactions(true);
+ batch_start(1000);
+ txn.m_txn = m_write_txn->m_txn;
+ m_height = 0;
+
+ while(1) {
+ if (!(i % 1000)) {
+ if (i) {
+ LOGIF(1) {
+ std::cout << i << " / " << z << " \r" << std::flush;
+ }
+ MDB_val_set(pk, "txblk");
+ MDB_val_set(pv, m_height);
+ result = mdb_cursor_put(c_props, &pk, &pv, 0);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to update txblk property: ", result).c_str()));
+ txn.commit();
+ 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()));
+ m_write_txn->m_txn = txn.m_txn;
+ m_write_batch_txn->m_txn = txn.m_txn;
+ memset(&m_wcursors, 0, sizeof(m_wcursors));
+ }
+ result = mdb_cursor_open(txn, m_blocks, &c_blocks);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to open a cursor for blocks: ", result).c_str()));
+ result = mdb_cursor_open(txn, m_properties, &c_props);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to open a cursor for properties: ", result).c_str()));
+ result = mdb_cursor_open(txn, o_txs, &c_txs);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to open a cursor for txs: ", result).c_str()));
+ if (!i) {
+ MDB_stat ms;
+ mdb_stat(txn, m_output_txs, &ms);
+ m_num_outputs = ms.ms_entries;
+ mdb_stat(txn, m_txs, &ms);
+ m_num_txs = i = ms.ms_entries;
+ if (i) {
+ m_num_txs = i;
+ MDB_val_set(pk, "txblk");
+ result = mdb_cursor_get(c_props, &pk, &k, MDB_SET);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to get a record from properties: ", result).c_str()));
+ m_height = *(uint64_t *)k.mv_data;
+ }
+ }
+ if (i) {
+ result = mdb_cursor_get(c_blocks, &k, &v, MDB_SET);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to get a record from blocks: ", result).c_str()));
+ }
+ }
+ result = mdb_cursor_get(c_blocks, &k, &v, MDB_NEXT);
+ if (result == MDB_NOTFOUND) {
+ MDB_val_set(pk, "txblk");
+ mdb_cursor_get(c_props, &pk, &v, MDB_SET);
+ mdb_cursor_del(c_props, 0);
+ batch_stop();
+ break;
+ } else if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to get a record from blocks: ", result).c_str()));
+
+ bd.assign(reinterpret_cast<char*>(v.mv_data), v.mv_size);
+ if (!parse_and_validate_block_from_blob(bd, b))
+ throw0(DB_ERROR("Failed to parse block from blob retrieved from the db"));
+
+ add_transaction(null_hash, b.miner_tx);
+ for (unsigned int j = 0; j<b.tx_hashes.size(); j++) {
+ transaction tx;
+ hk.mv_data = &b.tx_hashes[j];
+ result = mdb_cursor_get(c_txs, &hk, &v, MDB_SET);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to get record from txs: ", result).c_str()));
+ bd.assign(reinterpret_cast<char*>(v.mv_data), v.mv_size);
+ if (!parse_and_validate_tx_from_blob(bd, tx))
+ throw0(DB_ERROR("Failed to parse tx from blob retrieved from the db"));
+ add_transaction(null_hash, tx, &b.tx_hashes[j]);
+ result = mdb_cursor_del(c_txs, 0);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to get record from txs: ", result).c_str()));
+ }
+ i++;
+ m_height = i;
+ }
+ 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()));
+ result = mdb_drop(txn, o_txs, 1);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to delete txs from the db: ", result).c_str()));
+
+ RENAME_DB("txr");
+
+ mdb_dbi_close(m_env, m_txs);
+
+ lmdb_db_open(txn, "txs", MDB_INTEGERKEY, m_txs, "Failed to open db handle for txs");
+
+ txn.commit();
+ } while(0);
+
+ uint32_t version = 1;
+ v.mv_data = (void *)&version;
+ v.mv_size = sizeof(version);
+ MDB_val_copy<const char *> vk("version");
+ 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()));
+ result = mdb_put(txn, m_properties, &vk, &v, 0);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to update version for the db: ", result).c_str()));
+ txn.commit();
+}
+
+void BlockchainLMDB::migrate(const uint32_t oldversion)
+{
+ switch(oldversion) {
+ case 0:
+ migrate_0_1(); /* FALLTHRU */
+ default:
+ ;
+ }
+}
+
} // namespace cryptonote
diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h
index 6cd3e0e8f..c7121bf63 100644
--- a/src/blockchain_db/lmdb/db_lmdb.h
+++ b/src/blockchain_db/lmdb/db_lmdb.h
@@ -43,20 +43,13 @@ typedef struct mdb_txn_cursors
{
MDB_cursor *m_txc_blocks;
MDB_cursor *m_txc_block_heights;
- MDB_cursor *m_txc_block_hashes;
- MDB_cursor *m_txc_block_timestamps;
- MDB_cursor *m_txc_block_sizes;
- MDB_cursor *m_txc_block_diffs;
- MDB_cursor *m_txc_block_coins;
+ MDB_cursor *m_txc_block_info;
MDB_cursor *m_txc_output_txs;
- MDB_cursor *m_txc_output_indices;
MDB_cursor *m_txc_output_amounts;
- MDB_cursor *m_txc_output_keys;
MDB_cursor *m_txc_txs;
- MDB_cursor *m_txc_tx_heights;
- MDB_cursor *m_txc_tx_unlocks;
+ MDB_cursor *m_txc_tx_indices;
MDB_cursor *m_txc_tx_outputs;
MDB_cursor *m_txc_spent_keys;
@@ -66,18 +59,11 @@ typedef struct mdb_txn_cursors
#define m_cur_blocks m_cursors->m_txc_blocks
#define m_cur_block_heights m_cursors->m_txc_block_heights
-#define m_cur_block_hashes m_cursors->m_txc_block_hashes
-#define m_cur_block_timestamps m_cursors->m_txc_block_timestamps
-#define m_cur_block_sizes m_cursors->m_txc_block_sizes
-#define m_cur_block_diffs m_cursors->m_txc_block_diffs
-#define m_cur_block_coins m_cursors->m_txc_block_coins
+#define m_cur_block_info m_cursors->m_txc_block_info
#define m_cur_output_txs m_cursors->m_txc_output_txs
-#define m_cur_output_indices m_cursors->m_txc_output_indices
#define m_cur_output_amounts m_cursors->m_txc_output_amounts
-#define m_cur_output_keys m_cursors->m_txc_output_keys
#define m_cur_txs m_cursors->m_txc_txs
-#define m_cur_tx_heights m_cursors->m_txc_tx_heights
-#define m_cur_tx_unlocks m_cursors->m_txc_tx_unlocks
+#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_hf_versions m_cursors->m_txc_hf_versions
@@ -87,18 +73,11 @@ typedef struct mdb_rflags
bool m_rf_txn;
bool m_rf_blocks;
bool m_rf_block_heights;
- bool m_rf_block_hashes;
- bool m_rf_block_timestamps;
- bool m_rf_block_sizes;
- bool m_rf_block_diffs;
- bool m_rf_block_coins;
+ bool m_rf_block_info;
bool m_rf_output_txs;
- bool m_rf_output_indices;
bool m_rf_output_amounts;
- bool m_rf_output_keys;
bool m_rf_txs;
- bool m_rf_tx_heights;
- bool m_rf_tx_unlocks;
+ bool m_rf_tx_indices;
bool m_rf_tx_outputs;
bool m_rf_spent_keys;
bool m_rf_hf_versions;
@@ -223,6 +202,7 @@ public:
virtual uint64_t height() const;
virtual bool tx_exists(const crypto::hash& h) const;
+ virtual bool tx_exists(const crypto::hash& h, uint64_t& tx_index) const;
virtual uint64_t get_tx_unlock_time(const crypto::hash& h) const;
@@ -246,10 +226,8 @@ public:
virtual tx_out_index get_output_tx_and_index(const uint64_t& amount, const uint64_t& index);
virtual void get_output_tx_and_index(const uint64_t& amount, const std::vector<uint64_t> &offsets, std::vector<tx_out_index> &indices);
- virtual void get_output_global_indices(const uint64_t& amount, const std::vector<uint64_t> &offsets, std::vector<uint64_t> &indices);
- virtual std::vector<uint64_t> get_tx_output_indices(const crypto::hash& h) const;
- virtual std::vector<uint64_t> get_tx_amount_output_indices(const crypto::hash& h) const;
+ virtual std::vector<uint64_t> get_tx_amount_output_indices(const uint64_t tx_id) const;
virtual bool has_key_image(const crypto::key_image& img) const;
@@ -306,18 +284,23 @@ private:
virtual void remove_block();
- virtual void add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash);
+ virtual uint64_t add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash);
virtual void remove_transaction_data(const crypto::hash& tx_hash, const transaction& tx);
- virtual void add_output(const crypto::hash& tx_hash, const tx_out& tx_output, const uint64_t& local_index, const uint64_t unlock_time);
+ virtual uint64_t add_output(const crypto::hash& tx_hash,
+ const tx_out& tx_output,
+ const uint64_t& local_index,
+ const uint64_t unlock_time
+ );
- virtual void remove_output(const tx_out& tx_output);
+ virtual void add_tx_amount_output_indices(const uint64_t tx_id,
+ const std::vector<uint64_t>& amount_output_indices
+ );
- void remove_tx_outputs(const MDB_val *tx_hash, const transaction& tx);
+ void remove_tx_outputs(const uint64_t tx_id, const transaction& tx);
- void remove_output(const MDB_val *out_index, const uint64_t amount);
- void remove_amount_output_index(const uint64_t amount, const MDB_val *global_output_index);
+ void remove_output(const uint64_t amount, const uint64_t& out_index);
virtual void add_spent_key(const crypto::key_image& k_image);
@@ -349,16 +332,6 @@ private:
*/
tx_out output_from_blob(const blobdata& blob) const;
- /**
- * @brief get the global index of the index-th output of the given amount
- *
- * @param amount the output amount
- * @param index the index into the set of outputs of that amount
- *
- * @return the global index of the desired output
- */
- uint64_t get_output_global_index(const uint64_t& amount, const uint64_t& index);
-
void check_open() const;
virtual bool is_read_only() const;
@@ -366,25 +339,24 @@ private:
// fix up anything that may be wrong due to past bugs
virtual void fixup();
+ // migrate from older DB version to current
+ void migrate(const uint32_t oldversion);
+
+ // migrate from DB version 0 to 1
+ void migrate_0_1();
+
MDB_env* m_env;
MDB_dbi m_blocks;
MDB_dbi m_block_heights;
- MDB_dbi m_block_hashes;
- MDB_dbi m_block_timestamps;
- MDB_dbi m_block_sizes;
- MDB_dbi m_block_diffs;
- MDB_dbi m_block_coins;
+ MDB_dbi m_block_info;
MDB_dbi m_txs;
- MDB_dbi m_tx_unlocks;
- MDB_dbi m_tx_heights;
+ MDB_dbi m_tx_indices;
MDB_dbi m_tx_outputs;
MDB_dbi m_output_txs;
- MDB_dbi m_output_indices;
MDB_dbi m_output_amounts;
- MDB_dbi m_output_keys;
MDB_dbi m_spent_keys;
@@ -394,6 +366,7 @@ private:
MDB_dbi m_properties;
uint64_t m_height;
+ uint64_t m_num_txs;
uint64_t m_num_outputs;
mutable uint64_t m_cum_size; // used in batch size estimation
mutable int m_cum_count;
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index b4445756d..d332f8997 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -1971,14 +1971,15 @@ bool Blockchain::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<u
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
- if (!m_db->tx_exists(tx_id))
+ uint64_t tx_index;
+ if (!m_db->tx_exists(tx_id, tx_index))
{
LOG_PRINT_RED_L1("warning: get_tx_outputs_gindexs failed to find transaction with id = " << tx_id);
return false;
}
// get amount output indexes, currently referred to in parts as "output global indices", but they are actually specific to amounts
- indexs = m_db->get_tx_amount_output_indices(tx_id);
+ indexs = m_db->get_tx_amount_output_indices(tx_index);
CHECK_AND_ASSERT_MES(indexs.size(), false, "internal error: global indexes for transaction " << tx_id << " is empty");
return true;