aboutsummaryrefslogtreecommitdiff
path: root/tests/unit_tests
diff options
context:
space:
mode:
authorcenobite pinhead <pinhead@pinhead>2026-08-15 23:14:28 +0100
committercenobite pinhead <pinhead@pinhead>2026-08-15 23:14:28 +0100
commit01cd9b0c3b7719ecd6ce45a32192dd1152ebd9ad (patch)
treee43a188ab6b1690b74b785364cb6ea11897c65db /tests/unit_tests
parent2c882e39b3f23e2511f5ba90a31d0a168ff1d8ae (diff)
downloadmonzero-core-01cd9b0c3b7719ecd6ce45a32192dd1152ebd9ad.tar.gz
monzero-core-01cd9b0c3b7719ecd6ce45a32192dd1152ebd9ad.tar.xz
monzero-core-01cd9b0c3b7719ecd6ce45a32192dd1152ebd9ad.zip
persist asset outputs and spent key images
Diffstat (limited to 'tests/unit_tests')
-rw-r--r--tests/unit_tests/blockchain_db.cpp147
1 files changed, 147 insertions, 0 deletions
diff --git a/tests/unit_tests/blockchain_db.cpp b/tests/unit_tests/blockchain_db.cpp
index f5149a41e..124a88724 100644
--- a/tests/unit_tests/blockchain_db.cpp
+++ b/tests/unit_tests/blockchain_db.cpp
@@ -39,7 +39,10 @@
#include "blockchain_db/blockchain_db.h"
#include "blockchain_db/asset_db.h"
#include "blockchain_db/lmdb/db_lmdb.h"
+#include "cryptonote_basic/asset_confidential.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
+#include "device/device.hpp"
+#include "ringct/rctSigs.h"
using namespace cryptonote;
using epee::string_tools::pod_to_hex;
@@ -69,6 +72,45 @@ assets::transaction_extension make_asset_extension(const crypto::hash& carrier)
return extension;
}
+assets::asset_ownership_proof make_db_ownership_proof(
+ const crypto::hash& id, const crypto::hash& carrier)
+{
+ constexpr size_t real = 5;
+ assets::asset_ownership_proof proof;
+ proof.asset_id = id;
+ rct::ctkeyV public_ring;
+ rct::key spend_secret{}, input_mask{};
+ const rct::key amount = rct::d2h(10);
+ for (size_t index = 0; index < assets::CONFIDENTIAL_ASSET_RING_SIZE; ++index)
+ {
+ assets::asset_ring_member member;
+ member.asset_id = id;
+ member.output_id.data[0] = static_cast<unsigned char>(index + 1);
+ rct::key ignored;
+ rct::skpkGen(ignored, member.public_output.dest);
+ rct::skpkGen(ignored, member.public_output.mask);
+ proof.ring.push_back(member);
+ }
+ rct::skpkGen(spend_secret, proof.ring[real].public_output.dest);
+ input_mask = rct::skGen();
+ rct::addKeys2(proof.ring[real].public_output.mask, input_mask, amount, rct::H);
+ for (const auto& member : proof.ring)
+ public_ring.push_back(member.public_output);
+ const rct::key pseudo_mask = rct::skGen();
+ rct::addKeys2(proof.pseudo_input, pseudo_mask, amount, rct::H);
+ rct::key message;
+ std::string error;
+ if (!assets::derive_asset_ownership_message(proof, TESTNET, carrier, message, &error))
+ throw std::runtime_error(error);
+ rct::ctkey input_secret;
+ input_secret.dest = spend_secret;
+ input_secret.mask = input_mask;
+ proof.signature = rct::proveRctCLSAGSimple(message, public_ring, input_secret,
+ pseudo_mask, proof.pseudo_input, real, hw::get_device("default"));
+ std::memcpy(&proof.key_image, &proof.signature.I, sizeof(proof.key_image));
+ return proof;
+}
+
const std::vector<std::string> t_blocks =
{
"0100d5adc49a053b8818b2b6023cd2d532c6774e164a8fcacd603651cb3ea0cb7f9340b28ec016b4bc4ca301aa0101ff6e08acbb2702eab03067870349139bee7eab2ca2e030a6bb73d4f68ab6a3b6ca937214054cdac0843d028bbe23b57ea9bae53f12da93bb57bf8a2e40598d9fccd10c2921576e987d93cd80b4891302468738e391f07c4f2b356f7957160968e0bfef6e907c3cee2d8c23cbf04b089680c6868f01025a0f41f063e195a966051e3a29e17130a9ce97d48f55285b9bb04bdd55a09ae78088aca3cf0202d0f26169290450fe17e08974789c3458910b4db18361cdc564f8f2d0bdd2cf568090cad2c60e02d6f3483ec45505cc3be841046c7a12bf953ac973939bc7b727e54258e1881d4d80e08d84ddcb0102dae6dfb16d3e28aaaf43e00170b90606b36f35f38f8a3dceb5ee18199dd8f17c80c0caf384a30202385d7e57a4daba4cdd9e550a92dcc188838386e7581f13f09de796cbed4716a42101c052492a077abf41996b50c1b2e67fd7288bcd8c55cdc657b4e22d0804371f6901beb76a82ea17400cd6d7f595f70e1667d2018ed8f5a78d1ce07484222618c3cd"
@@ -317,6 +359,99 @@ TYPED_TEST(BlockchainDBTest, AssetRecordsPersistAndRollbackAtomically)
ASSERT_FALSE(this->m_db->get_asset_record(later, height, payload));
}
+TYPED_TEST(BlockchainDBTest, AssetOutputsAndKeyImagesPersistAndRollbackAtomically)
+{
+ 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_id{}, later_id{}, aborted_id{}, asset_id{};
+ first_id.data[0] = 1;
+ later_id.data[0] = 2;
+ aborted_id.data[0] = 3;
+ asset_id.data[0] = 0xa7;
+ rct::key first_secret{}, first_key{}, later_key{}, aborted_key{};
+ rct::skpkGen(first_secret, first_key);
+ rct::skpkGen(first_secret, later_key);
+ rct::skpkGen(first_secret, aborted_key);
+
+ asset_output_data_t first{asset_id, first_key, rct::commit(11, rct::zero()), 30};
+ asset_output_data_t later{asset_id, later_key, rct::commit(12, rct::zero()), 31};
+ asset_output_data_t aborted{asset_id, aborted_key, rct::commit(13, rct::zero()), 32};
+ crypto::key_image spent{}, aborted_spent{};
+ reinterpret_cast<unsigned char*>(&spent)[0] = 0x41;
+ reinterpret_cast<unsigned char*>(&aborted_spent)[0] = 0x42;
+
+ ASSERT_NO_THROW(this->m_db->add_asset_output(first_id, first));
+ ASSERT_THROW(this->m_db->add_asset_output(first_id, first), DB_ERROR);
+ ASSERT_NO_THROW(this->m_db->add_asset_output(later_id, later));
+ ASSERT_NO_THROW(this->m_db->add_asset_key_image(spent, 31));
+ ASSERT_THROW(this->m_db->add_asset_key_image(spent, 31), KEY_IMAGE_EXISTS);
+
+ this->m_db->block_wtxn_start();
+ ASSERT_NO_THROW(this->m_db->add_asset_output(aborted_id, aborted));
+ ASSERT_NO_THROW(this->m_db->add_asset_key_image(aborted_spent, 32));
+ this->m_db->block_wtxn_abort();
+
+ asset_output_data_t restored{};
+ ASSERT_FALSE(this->m_db->get_asset_output(aborted_id, restored));
+ ASSERT_FALSE(this->m_db->has_asset_key_image(aborted_spent));
+ ASSERT_NO_THROW(this->m_db->close());
+ ASSERT_NO_THROW(this->m_db->open(dir_path));
+
+ ASSERT_TRUE(this->m_db->get_asset_output(first_id, restored));
+ ASSERT_EQ(first.asset_id, restored.asset_id);
+ ASSERT_EQ(first.destination, restored.destination);
+ ASSERT_EQ(first.commitment, restored.commitment);
+ ASSERT_EQ(first.height, restored.height);
+ ASSERT_TRUE(this->m_db->has_asset_key_image(spent));
+
+ ASSERT_NO_THROW(this->m_db->remove_asset_outputs_from_height(31));
+ ASSERT_NO_THROW(this->m_db->remove_asset_key_images_from_height(31));
+ ASSERT_TRUE(this->m_db->get_asset_output(first_id, restored));
+ ASSERT_FALSE(this->m_db->get_asset_output(later_id, restored));
+ ASSERT_FALSE(this->m_db->has_asset_key_image(spent));
+}
+
+TYPED_TEST(BlockchainDBTest, AssetOwnershipResolvesAuthoritativeRingAndSpentState)
+{
+ 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 id{}, carrier{};
+ id.data[0] = 0x91;
+ carrier.data[0] = 0x92;
+ const auto proof = make_db_ownership_proof(id, carrier);
+ std::string error;
+ EXPECT_FALSE(assets::verify_asset_ownership_against_db(
+ *this->m_db, proof, TESTNET, carrier, &error));
+
+ for (const auto& member : proof.ring)
+ {
+ asset_output_data_t output{member.asset_id, member.public_output.dest,
+ member.public_output.mask, 40};
+ ASSERT_NO_THROW(this->m_db->add_asset_output(member.output_id, output));
+ }
+ ASSERT_TRUE(assets::verify_asset_ownership_against_db(
+ *this->m_db, proof, TESTNET, carrier, &error)) << error;
+
+ ASSERT_NO_THROW(this->m_db->add_asset_key_image(proof.key_image, 41));
+ EXPECT_FALSE(assets::verify_asset_ownership_against_db(
+ *this->m_db, proof, TESTNET, carrier, &error));
+ ASSERT_NO_THROW(this->m_db->remove_asset_key_images_from_height(41));
+ ASSERT_TRUE(assets::verify_asset_ownership_against_db(
+ *this->m_db, proof, TESTNET, carrier, &error)) << error;
+
+ ASSERT_NO_THROW(this->m_db->remove_asset_outputs_from_height(40));
+ EXPECT_FALSE(assets::verify_asset_ownership_against_db(
+ *this->m_db, proof, TESTNET, carrier, &error));
+}
+
TYPED_TEST(BlockchainDBTest, AssetBlockExtensionsRebuildFromPersistentState)
{
const boost::filesystem::path temp_path = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
@@ -370,8 +505,15 @@ TYPED_TEST(BlockchainDBTest, PopBlockRemovesAssetStateAtDetachedHeight)
this->init_hard_fork();
crypto::hash id{};
+ crypto::hash output_id{};
+ crypto::key_image spent{};
id.data[0] = 0xa5;
+ output_id.data[0] = 0xa6;
+ reinterpret_cast<unsigned char*>(&spent)[0] = 0xa7;
const blobdata payload("detached asset");
+ rct::key output_key{}, output_secret{};
+ rct::skpkGen(output_secret, output_key);
+ const asset_output_data_t asset_output{id, output_key, rct::commit(50, rct::zero()), 1};
block popped;
std::vector<transaction> transactions;
{
@@ -379,11 +521,16 @@ TYPED_TEST(BlockchainDBTest, PopBlockRemovesAssetStateAtDetachedHeight)
ASSERT_NO_THROW(this->m_db->add_block(this->m_blocks[0], t_sizes[0], t_sizes[0], t_diffs[0], t_coins[0], this->m_txs[0]));
ASSERT_NO_THROW(this->m_db->add_block(this->m_blocks[1], t_sizes[1], t_sizes[1], t_diffs[1], t_coins[1], this->m_txs[1]));
ASSERT_NO_THROW(this->m_db->add_asset_record(id, 1, blobdata_ref(payload)));
+ ASSERT_NO_THROW(this->m_db->add_asset_output(output_id, asset_output));
+ ASSERT_NO_THROW(this->m_db->add_asset_key_image(spent, 1));
}
ASSERT_NO_THROW(this->m_db->pop_block(popped, transactions));
uint64_t height = 0;
blobdata restored;
+ asset_output_data_t restored_output{};
ASSERT_FALSE(this->m_db->get_asset_record(id, height, restored));
+ ASSERT_FALSE(this->m_db->get_asset_output(output_id, restored_output));
+ ASSERT_FALSE(this->m_db->has_asset_key_image(spent));
ASSERT_EQ(1u, this->m_db->height());
}