diff options
| author | cenobite pinhead <pinhead@pinhead> | 2026-08-15 22:29:55 +0100 |
|---|---|---|
| committer | cenobite pinhead <pinhead@pinhead> | 2026-08-15 22:29:55 +0100 |
| commit | 7337f301cb48d4107058363a79f1e4aebcf2b8dc (patch) | |
| tree | 9f1df004934beebe190d847983f40fee9ae35ca3 | |
| parent | b47eefee8a8b75b7840e29e8758eb361e48b2a04 (diff) | |
| download | monzero-core-7337f301cb48d4107058363a79f1e4aebcf2b8dc.tar.gz monzero-core-7337f301cb48d4107058363a79f1e4aebcf2b8dc.tar.xz monzero-core-7337f301cb48d4107058363a79f1e4aebcf2b8dc.zip | |
persist experimental asset registry in LMDB
| -rw-r--r-- | src/blockchain_db/blockchain_db.h | 8 | ||||
| -rw-r--r-- | src/blockchain_db/lmdb/db_lmdb.cpp | 131 | ||||
| -rw-r--r-- | src/blockchain_db/lmdb/db_lmdb.h | 10 | ||||
| -rw-r--r-- | src/blockchain_db/testdb.h | 4 | ||||
| -rw-r--r-- | tests/unit_tests/blockchain_db.cpp | 44 |
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) { |
