aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/blockchain_db/blockchain_db.h8
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.cpp131
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.h10
-rw-r--r--src/blockchain_db/testdb.h4
-rw-r--r--tests/unit_tests/blockchain_db.cpp44
5 files changed, 196 insertions, 1 deletions
diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h
index a163ef98c..c176a8ef5 100644
--- a/src/blockchain_db/blockchain_db.h
+++ b/src/blockchain_db/blockchain_db.h
@@ -1784,6 +1784,14 @@ public:
*/
virtual bool for_all_alt_blocks(std::function<bool(const crypto::hash &blkid, const alt_block_data_t &data, const cryptonote::blobdata_ref *blob)> f, bool include_blob = false) const = 0;
+ // Experimental Monzero asset registry storage. These records are kept in
+ // the blockchain database so block application and rollback can share the
+ // same database transaction as native chain state.
+ virtual void add_asset_record(const crypto::hash &asset_id, uint64_t height, const cryptonote::blobdata_ref &payload) = 0;
+ virtual bool get_asset_record(const crypto::hash &asset_id, uint64_t &height, cryptonote::blobdata &payload) const = 0;
+ virtual void remove_asset_records_from_height(uint64_t height) = 0;
+ virtual bool for_all_asset_records(std::function<bool(const crypto::hash&, uint64_t, const cryptonote::blobdata_ref&)>) const = 0;
+
//
// Hard fork related storage
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index 938fc306c..0c3a4f470 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -58,7 +58,7 @@ using epee::string_tools::pod_to_hex;
using namespace crypto;
// Increase when the DB structure changes
-#define VERSION 5
+#define VERSION 6
namespace
{
@@ -237,6 +237,9 @@ const char* const LMDB_TXPOOL_BLOB = "txpool_blob";
const char* const LMDB_ALT_BLOCKS = "alt_blocks";
+const char* const LMDB_ASSET_RECORDS = "asset_records";
+const char* const LMDB_ASSET_HEIGHTS = "asset_heights";
+
const char* const LMDB_HF_STARTING_HEIGHTS = "hf_starting_heights";
const char* const LMDB_HF_VERSIONS = "hf_versions";
@@ -1506,6 +1509,9 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags)
lmdb_db_open(txn, LMDB_ALT_BLOCKS, MDB_CREATE, m_alt_blocks, "Failed to open db handle for m_alt_blocks");
+ lmdb_db_open(txn, LMDB_ASSET_RECORDS, MDB_CREATE, m_asset_records, "Failed to open db handle for m_asset_records");
+ lmdb_db_open(txn, LMDB_ASSET_HEIGHTS, MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_asset_heights, "Failed to open db handle for m_asset_heights");
+
// this subdb is dropped on sight, so it may not be present when we open the DB.
// Since we use MDB_CREATE, we'll get an exception if we open read-only and it does not exist.
// So we don't open for read-only, and also not drop below. It is not used elsewhere.
@@ -1530,6 +1536,8 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags)
mdb_set_compare(txn, m_txpool_meta, compare_hash32);
mdb_set_compare(txn, m_txpool_blob, compare_hash32);
mdb_set_compare(txn, m_alt_blocks, compare_hash32);
+ mdb_set_compare(txn, m_asset_records, compare_hash32);
+ mdb_set_dupsort(txn, m_asset_heights, compare_hash32);
mdb_set_compare(txn, m_properties, compare_string);
if (!(mdb_flags & MDB_RDONLY))
@@ -1702,6 +1710,10 @@ void BlockchainLMDB::reset()
throw0(DB_ERROR(lmdb_error("Failed to drop m_hf_versions: ", result).c_str()));
if (auto result = mdb_drop(txn, m_properties, 0))
throw0(DB_ERROR(lmdb_error("Failed to drop m_properties: ", result).c_str()));
+ if (auto result = mdb_drop(txn, m_asset_records, 0))
+ throw0(DB_ERROR(lmdb_error("Failed to drop m_asset_records: ", result).c_str()));
+ if (auto result = mdb_drop(txn, m_asset_heights, 0))
+ throw0(DB_ERROR(lmdb_error("Failed to drop m_asset_heights: ", result).c_str()));
// init with current version
MDB_val_str(k, "version");
@@ -2436,6 +2448,108 @@ bool BlockchainLMDB::for_all_alt_blocks(std::function<bool(const crypto::hash&,
return ret;
}
+void BlockchainLMDB::add_asset_record(const crypto::hash &asset_id, uint64_t height, const cryptonote::blobdata_ref &payload)
+{
+ check_open();
+ TXN_BLOCK_PREFIX(0);
+ MDB_val key = {sizeof(asset_id), const_cast<crypto::hash*>(&asset_id)};
+ std::vector<char> record(sizeof(height) + payload.size());
+ std::memcpy(record.data(), &height, sizeof(height));
+ if (!payload.empty())
+ std::memcpy(record.data() + sizeof(height), payload.data(), payload.size());
+ MDB_val value = {record.size(), record.data()};
+ int result = mdb_put(*txn_ptr, m_asset_records, &key, &value, MDB_NOOVERWRITE);
+ if (result == MDB_KEYEXIST)
+ throw1(DB_ERROR("Attempting to add an asset record that already exists"));
+ if (result)
+ throw1(DB_ERROR(lmdb_error("Error adding asset record: ", result).c_str()));
+ MDB_val_copy<uint64_t> height_key(height);
+ MDB_val id_value = {sizeof(asset_id), const_cast<crypto::hash*>(&asset_id)};
+ if ((result = mdb_put(*txn_ptr, m_asset_heights, &height_key, &id_value, MDB_NODUPDATA)))
+ throw1(DB_ERROR(lmdb_error("Error indexing asset record height: ", result).c_str()));
+ TXN_BLOCK_POSTFIX_SUCCESS();
+}
+
+bool BlockchainLMDB::get_asset_record(const crypto::hash &asset_id, uint64_t &height, cryptonote::blobdata &payload) const
+{
+ check_open();
+ TXN_PREFIX_RDONLY();
+ MDB_val key = {sizeof(asset_id), const_cast<crypto::hash*>(&asset_id)}, value;
+ const int result = mdb_get(m_txn, m_asset_records, &key, &value);
+ if (result == MDB_NOTFOUND)
+ return false;
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Error retrieving asset record: ", result).c_str()));
+ if (value.mv_size < sizeof(height))
+ throw0(DB_ERROR("Asset record is too small"));
+ std::memcpy(&height, value.mv_data, sizeof(height));
+ payload.assign(static_cast<const char*>(value.mv_data) + sizeof(height), value.mv_size - sizeof(height));
+ return true;
+}
+
+void BlockchainLMDB::remove_asset_records_from_height(uint64_t height)
+{
+ check_open();
+ TXN_BLOCK_PREFIX(0);
+ MDB_cursor *cursor = nullptr;
+ int result = mdb_cursor_open(*txn_ptr, m_asset_heights, &cursor);
+ if (result)
+ throw1(DB_ERROR(lmdb_error("Error opening asset height cursor: ", result).c_str()));
+ MDB_val_copy<uint64_t> key(height);
+ MDB_val value;
+ result = mdb_cursor_get(cursor, &key, &value, MDB_SET_RANGE);
+ while (result == MDB_SUCCESS)
+ {
+ MDB_val asset_key = {value.mv_size, value.mv_data};
+ const int deleted = mdb_del(*txn_ptr, m_asset_records, &asset_key, nullptr);
+ if (deleted != MDB_SUCCESS && deleted != MDB_NOTFOUND)
+ {
+ mdb_cursor_close(cursor);
+ throw1(DB_ERROR(lmdb_error("Error removing asset record: ", deleted).c_str()));
+ }
+ if ((result = mdb_cursor_del(cursor, 0)) != MDB_SUCCESS)
+ {
+ mdb_cursor_close(cursor);
+ throw1(DB_ERROR(lmdb_error("Error removing asset height index: ", result).c_str()));
+ }
+ result = mdb_cursor_get(cursor, &key, &value, MDB_NEXT);
+ }
+ mdb_cursor_close(cursor);
+ if (result != MDB_NOTFOUND)
+ throw1(DB_ERROR(lmdb_error("Error iterating asset height index: ", result).c_str()));
+ TXN_BLOCK_POSTFIX_SUCCESS();
+}
+
+bool BlockchainLMDB::for_all_asset_records(std::function<bool(const crypto::hash&, uint64_t, const cryptonote::blobdata_ref&)> f) const
+{
+ check_open();
+ TXN_PREFIX_RDONLY();
+ MDB_cursor *cursor = nullptr;
+ int result = mdb_cursor_open(m_txn, m_asset_records, &cursor);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Error opening asset record cursor: ", result).c_str()));
+ MDB_val key, value;
+ bool ret = true;
+ for (MDB_cursor_op op = MDB_FIRST; ; op = MDB_NEXT)
+ {
+ result = mdb_cursor_get(cursor, &key, &value, op);
+ if (result == MDB_NOTFOUND)
+ break;
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Error enumerating asset records: ", result).c_str()));
+ if (key.mv_size != sizeof(crypto::hash) || value.mv_size < sizeof(uint64_t))
+ throw0(DB_ERROR("Asset record has an invalid size"));
+ crypto::hash id;
+ uint64_t record_height;
+ std::memcpy(&id, key.mv_data, sizeof(id));
+ std::memcpy(&record_height, value.mv_data, sizeof(record_height));
+ const cryptonote::blobdata_ref payload{static_cast<const char*>(value.mv_data) + sizeof(record_height), value.mv_size - sizeof(record_height)};
+ if (!f(id, record_height, payload)) { ret = false; break; }
+ }
+ mdb_cursor_close(cursor);
+ return ret;
+}
+
bool BlockchainLMDB::block_exists(const crypto::hash& h, uint64_t *height) const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
@@ -5704,6 +5818,19 @@ void BlockchainLMDB::migrate_4_5()
txn.commit();
}
+void BlockchainLMDB::migrate_5_6()
+{
+ LOG_PRINT_L3("BlockchainLMDB::" << __func__);
+ mdb_txn_safe txn(false);
+ if (const int result = mdb_txn_begin(m_env, nullptr, 0, txn))
+ throw0(DB_ERROR(lmdb_error("Failed to create transaction for DB v6 migration: ", result).c_str()));
+ MDB_val_str(key, "version");
+ MDB_val_copy<uint32_t> value(6);
+ if (const int result = mdb_put(txn, m_properties, &key, &value, 0))
+ throw0(DB_ERROR(lmdb_error("Failed to update DB version to 6: ", result).c_str()));
+ txn.commit();
+}
+
void BlockchainLMDB::migrate(const uint32_t oldversion)
{
if (oldversion < 1)
@@ -5716,6 +5843,8 @@ void BlockchainLMDB::migrate(const uint32_t oldversion)
migrate_3_4();
if (oldversion < 5)
migrate_4_5();
+ if (oldversion < 6)
+ migrate_5_6();
}
} // namespace cryptonote
diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h
index bdae44948..d116bcec5 100644
--- a/src/blockchain_db/lmdb/db_lmdb.h
+++ b/src/blockchain_db/lmdb/db_lmdb.h
@@ -311,6 +311,10 @@ public:
virtual bool for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, uint64_t height, size_t tx_idx)> f) const;
virtual bool for_all_outputs(uint64_t amount, const std::function<bool(uint64_t height)> &f) const;
virtual bool for_all_alt_blocks(std::function<bool(const crypto::hash &blkid, const alt_block_data_t &data, const cryptonote::blobdata_ref *blob)> f, bool include_blob = false) const;
+ virtual void add_asset_record(const crypto::hash &asset_id, uint64_t height, const cryptonote::blobdata_ref &payload);
+ virtual bool get_asset_record(const crypto::hash &asset_id, uint64_t &height, cryptonote::blobdata &payload) const;
+ virtual void remove_asset_records_from_height(uint64_t height);
+ virtual bool for_all_asset_records(std::function<bool(const crypto::hash&, uint64_t, const cryptonote::blobdata_ref&)>) const;
virtual uint64_t add_block( const std::pair<block, blobdata>& blk
, size_t block_weight
@@ -443,6 +447,9 @@ private:
// migrate from DB version 4 to 5
void migrate_4_5();
+ // migrate from DB version 5 to 6
+ void migrate_5_6();
+
void cleanup_batch();
private:
@@ -470,6 +477,9 @@ private:
MDB_dbi m_alt_blocks;
+ MDB_dbi m_asset_records;
+ MDB_dbi m_asset_heights;
+
MDB_dbi m_hf_starting_heights;
MDB_dbi m_hf_versions;
diff --git a/src/blockchain_db/testdb.h b/src/blockchain_db/testdb.h
index fe8078d5e..fb97ccee4 100644
--- a/src/blockchain_db/testdb.h
+++ b/src/blockchain_db/testdb.h
@@ -166,6 +166,10 @@ public:
virtual uint64_t get_alt_block_count() override { return 0; }
virtual void drop_alt_blocks() override {}
virtual bool for_all_alt_blocks(std::function<bool(const crypto::hash &blkid, const alt_block_data_t &data, const cryptonote::blobdata_ref *blob)> f, bool include_blob = false) const override { return true; }
+ virtual void add_asset_record(const crypto::hash&, uint64_t, const cryptonote::blobdata_ref&) override {}
+ virtual bool get_asset_record(const crypto::hash&, uint64_t&, cryptonote::blobdata&) const override { return false; }
+ virtual void remove_asset_records_from_height(uint64_t) override {}
+ virtual bool for_all_asset_records(std::function<bool(const crypto::hash&, uint64_t, const cryptonote::blobdata_ref&)>) const override { return true; }
};
}
diff --git a/tests/unit_tests/blockchain_db.cpp b/tests/unit_tests/blockchain_db.cpp
index 17828645a..423106522 100644
--- a/tests/unit_tests/blockchain_db.cpp
+++ b/tests/unit_tests/blockchain_db.cpp
@@ -251,6 +251,50 @@ TYPED_TEST(BlockchainDBTest, OpenAndClose)
ASSERT_NO_THROW(this->m_db->close());
}
+TYPED_TEST(BlockchainDBTest, AssetRecordsPersistAndRollbackAtomically)
+{
+ const boost::filesystem::path temp_path = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
+ const std::string dir_path = temp_path.string();
+ this->set_prefix(dir_path);
+ ASSERT_NO_THROW(this->m_db->open(dir_path));
+ this->get_filenames();
+
+ crypto::hash first{}, aborted{}, later{};
+ reinterpret_cast<unsigned char*>(&first)[0] = 1;
+ reinterpret_cast<unsigned char*>(&aborted)[0] = 2;
+ reinterpret_cast<unsigned char*>(&later)[0] = 3;
+ const blobdata first_payload("signed issuance one");
+ const blobdata aborted_payload("must not survive abort");
+ const blobdata later_payload("signed issuance later");
+
+ ASSERT_NO_THROW(this->m_db->add_asset_record(first, 7, blobdata_ref(first_payload)));
+ ASSERT_THROW(this->m_db->add_asset_record(first, 8, blobdata_ref(first_payload)), DB_ERROR);
+
+ this->m_db->block_wtxn_start();
+ ASSERT_NO_THROW(this->m_db->add_asset_record(aborted, 8, blobdata_ref(aborted_payload)));
+ this->m_db->block_wtxn_abort();
+ uint64_t height = 0;
+ blobdata payload;
+ ASSERT_FALSE(this->m_db->get_asset_record(aborted, height, payload));
+
+ ASSERT_NO_THROW(this->m_db->add_asset_record(aborted, 10, blobdata_ref(aborted_payload)));
+ ASSERT_NO_THROW(this->m_db->add_asset_record(later, 11, blobdata_ref(later_payload)));
+ ASSERT_NO_THROW(this->m_db->close());
+ ASSERT_NO_THROW(this->m_db->open(dir_path));
+
+ ASSERT_TRUE(this->m_db->get_asset_record(first, height, payload));
+ ASSERT_EQ(7u, height);
+ ASSERT_EQ(first_payload, payload);
+ size_t count = 0;
+ ASSERT_TRUE(this->m_db->for_all_asset_records([&count](const crypto::hash&, uint64_t, const blobdata_ref&) { ++count; return true; }));
+ ASSERT_EQ(3u, count);
+
+ ASSERT_NO_THROW(this->m_db->remove_asset_records_from_height(10));
+ ASSERT_TRUE(this->m_db->get_asset_record(first, height, payload));
+ ASSERT_FALSE(this->m_db->get_asset_record(aborted, height, payload));
+ ASSERT_FALSE(this->m_db->get_asset_record(later, height, payload));
+}
+
TYPED_TEST(BlockchainDBTest, AddBlock)
{