diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/unit_tests/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | tests/unit_tests/asset_wire.cpp | 169 | ||||
| -rw-r--r-- | tests/unit_tests/blockchain_db.cpp | 77 |
3 files changed, 247 insertions, 0 deletions
diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index b3328fe74..52df31b4a 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -31,6 +31,7 @@ set(unit_tests_sources apply_permutation.cpp address_from_url.cpp asset_confidential.cpp + asset_wire.cpp asset_types.cpp base58.cpp blockchain_db.cpp diff --git a/tests/unit_tests/asset_wire.cpp b/tests/unit_tests/asset_wire.cpp new file mode 100644 index 000000000..d7c26101e --- /dev/null +++ b/tests/unit_tests/asset_wire.cpp @@ -0,0 +1,169 @@ +#include "gtest/gtest.h" + +#include "cryptonote_basic/asset_wire.h" +#include "ringct/bulletproofs_plus.h" +#include "ringct/rctOps.h" +#include "string_tools.h" + +namespace +{ + cryptonote::assets::asset_transaction_payload make_payload() + { + cryptonote::assets::asset_transaction_payload payload; + payload.network = cryptonote::TESTNET; + payload.carrier_prefix_hash.data[0] = 0xc1; + + crypto::public_key issuer{}; + crypto::secret_key issuer_secret{}; + crypto::generate_keys(issuer, issuer_secret); + cryptonote::assets::issuance_payload issuance; + issuance.descriptor.network = cryptonote::TESTNET; + issuance.descriptor.issuer_key = issuer; + issuance.descriptor.issuance_nonce.data[0] = 0xc2; + issuance.descriptor.atomic_supply = 10; + issuance.descriptor.display_decimals = 0; + issuance.descriptor.metadata_reference = "ipfs://monzero-wire-vector"; + crypto::hash authorization{}; + if (!cryptonote::assets::derive_issuance_authorization_hash( + issuance.descriptor, authorization)) + throw std::runtime_error("failed to derive issuance authorization"); + crypto::generate_signature(authorization, issuer, issuer_secret, + issuance.issuer_signature); + payload.issuance = issuance; + + crypto::hash id{}; + if (!cryptonote::assets::derive_asset_id(issuance.descriptor, id)) + throw std::runtime_error("failed to derive asset id"); + cryptonote::assets::confidential_asset_balance balance; + balance.asset_id = id; + balance.pseudo_inputs.push_back({id, rct::commit(10, rct::zero())}); + balance.outputs.push_back(rct::commit(10, rct::zero())); + balance.range_proofs.push_back( + rct::bulletproof_plus_PROVE(10, rct::zero())); + payload.balances.push_back(balance); + rct::key secret{}, destination{}; + rct::skpkGen(secret, destination); + payload.output_destinations.push_back({destination}); + return payload; + } +} + +TEST(asset_wire, canonical_round_trip_preserves_verified_issuance) +{ + const auto original = make_payload(); + std::vector<uint8_t> encoded, reencoded; + std::string error; + ASSERT_TRUE(cryptonote::assets::encode_asset_transaction_payload( + original, encoded, &error)) << error; + cryptonote::assets::asset_transaction_payload decoded; + ASSERT_TRUE(cryptonote::assets::decode_asset_transaction_payload( + encoded, decoded, &error)) << error; + ASSERT_TRUE(cryptonote::assets::encode_asset_transaction_payload( + decoded, reencoded, &error)) << error; + ASSERT_EQ(encoded, reencoded); + ASSERT_TRUE(decoded.issuance); + ASSERT_TRUE(cryptonote::assets::verify_confidential_asset_transaction( + decoded.balances, {}, decoded.issuance->descriptor, &error)) << error; + ASSERT_TRUE(cryptonote::assets::verify_asset_transaction_payload( + decoded, {}, cryptonote::TESTNET, decoded.carrier_prefix_hash, &error)) << error; + EXPECT_FALSE(cryptonote::assets::verify_asset_transaction_payload( + decoded, {}, cryptonote::MAINNET, decoded.carrier_prefix_hash, &error)); + crypto::hash other_carrier = decoded.carrier_prefix_hash; + other_carrier.data[1] = 1; + EXPECT_FALSE(cryptonote::assets::verify_asset_transaction_payload( + decoded, {}, cryptonote::TESTNET, other_carrier, &error)); +} + +TEST(asset_wire, rejects_every_truncation_trailing_bytes_and_noncanonical_counts) +{ + const auto payload = make_payload(); + std::vector<uint8_t> encoded; + std::string error; + ASSERT_TRUE(cryptonote::assets::encode_asset_transaction_payload( + payload, encoded, &error)) << error; + for (size_t size = 0; size < encoded.size(); ++size) + { + cryptonote::assets::asset_transaction_payload decoded; + const std::vector<uint8_t> truncated(encoded.begin(), encoded.begin() + size); + EXPECT_FALSE(cryptonote::assets::decode_asset_transaction_payload( + truncated, decoded, &error)) << "accepted truncation at " << size; + } + auto trailing = encoded; + trailing.push_back(0); + cryptonote::assets::asset_transaction_payload decoded; + EXPECT_FALSE(cryptonote::assets::decode_asset_transaction_payload( + trailing, decoded, &error)); + auto unsupported = encoded; + unsupported[0] = 2; + EXPECT_FALSE(cryptonote::assets::decode_asset_transaction_payload( + unsupported, decoded, &error)); + auto excessive = payload; + excessive.balances.resize(cryptonote::assets::MAX_ASSET_BALANCE_GROUPS + 1, + payload.balances.front()); + excessive.output_destinations.resize(excessive.balances.size(), + payload.output_destinations.front()); + EXPECT_FALSE(cryptonote::assets::encode_asset_transaction_payload( + excessive, trailing, &error)); +} + +TEST(asset_wire, deterministic_output_identity_binds_every_field) +{ + const auto payload = make_payload(); + cryptonote::assets::confidential_asset_output output{ + payload.output_destinations.front().front(), + payload.balances.front().outputs.front()}; + crypto::hash first{}, repeated{}, changed{}; + std::string error; + ASSERT_TRUE(cryptonote::assets::derive_asset_output_id( + cryptonote::TESTNET, payload.carrier_prefix_hash, payload.balances.front().asset_id, 0, + output, first, &error)) << error; + ASSERT_TRUE(cryptonote::assets::derive_asset_output_id( + cryptonote::TESTNET, payload.carrier_prefix_hash, payload.balances.front().asset_id, 0, + output, repeated, &error)); + EXPECT_EQ(first, repeated); + ASSERT_TRUE(cryptonote::assets::derive_asset_output_id( + cryptonote::MAINNET, payload.carrier_prefix_hash, payload.balances.front().asset_id, 0, + output, changed, &error)); + EXPECT_NE(first, changed); + ASSERT_TRUE(cryptonote::assets::derive_asset_output_id( + cryptonote::TESTNET, payload.carrier_prefix_hash, payload.balances.front().asset_id, 1, + output, changed, &error)); + EXPECT_NE(first, changed); + output.commitment = rct::commit(9, rct::zero()); + ASSERT_TRUE(cryptonote::assets::derive_asset_output_id( + cryptonote::TESTNET, payload.carrier_prefix_hash, payload.balances.front().asset_id, 0, + output, changed, &error)); + EXPECT_NE(first, changed); +} + +TEST(asset_wire, fixed_wire_vector_has_stable_size_and_digest) +{ + cryptonote::assets::asset_transaction_payload payload; + payload.network = cryptonote::STAGENET; + payload.carrier_prefix_hash.data[0] = 0xe1; + cryptonote::assets::confidential_asset_balance balance; + balance.asset_id.data[0] = 0xe2; + balance.pseudo_inputs.push_back({balance.asset_id, rct::key{}}); + balance.outputs.push_back(rct::key{}); + rct::BulletproofPlus proof; + proof.A = rct::key{}; + proof.A1 = rct::key{}; + proof.B = rct::key{}; + proof.r1 = rct::key{}; + proof.s1 = rct::key{}; + proof.d1 = rct::key{}; + proof.V.resize(1); + proof.L.resize(1); + proof.R.resize(1); + balance.range_proofs.push_back(proof); + payload.balances.push_back(balance); + payload.output_destinations.push_back({rct::key{}}); + std::vector<uint8_t> encoded; + std::string error; + ASSERT_TRUE(cryptonote::assets::encode_asset_transaction_payload( + payload, encoded, &error)) << error; + ASSERT_EQ(492u, encoded.size()); + const crypto::hash digest = crypto::cn_fast_hash(encoded.data(), encoded.size()); + ASSERT_EQ("7ad38e0c9b90d1d458f69df1ca5c4c27689ba0c86e0fbcb08a2149166b3d999b", + epee::string_tools::pod_to_hex(digest)); +} diff --git a/tests/unit_tests/blockchain_db.cpp b/tests/unit_tests/blockchain_db.cpp index 124a88724..a2261bc60 100644 --- a/tests/unit_tests/blockchain_db.cpp +++ b/tests/unit_tests/blockchain_db.cpp @@ -42,6 +42,7 @@ #include "cryptonote_basic/asset_confidential.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "device/device.hpp" +#include "ringct/bulletproofs_plus.h" #include "ringct/rctSigs.h" using namespace cryptonote; @@ -111,6 +112,30 @@ assets::asset_ownership_proof make_db_ownership_proof( return proof; } +assets::asset_transaction_payload make_db_asset_issuance( + const crypto::hash& carrier) +{ + const assets::transaction_extension extension = make_asset_extension(carrier); + assets::asset_transaction_payload payload; + payload.network = TESTNET; + payload.carrier_prefix_hash = carrier; + payload.issuance = extension.issuance; + crypto::hash id{}; + if (!assets::derive_asset_id(extension.issuance.descriptor, id)) + throw std::runtime_error("failed to derive test asset id"); + const uint64_t supply = extension.issuance.descriptor.atomic_supply; + assets::confidential_asset_balance balance; + balance.asset_id = id; + balance.pseudo_inputs.push_back({id, rct::commit(supply, rct::zero())}); + balance.outputs.push_back(rct::commit(supply, rct::zero())); + balance.range_proofs.push_back(rct::bulletproof_plus_PROVE(supply, rct::zero())); + payload.balances.push_back(balance); + rct::key secret{}, destination{}; + rct::skpkGen(secret, destination); + payload.output_destinations.push_back({destination}); + return payload; +} + const std::vector<std::string> t_blocks = { "0100d5adc49a053b8818b2b6023cd2d532c6774e164a8fcacd603651cb3ea0cb7f9340b28ec016b4bc4ca301aa0101ff6e08acbb2702eab03067870349139bee7eab2ca2e030a6bb73d4f68ab6a3b6ca937214054cdac0843d028bbe23b57ea9bae53f12da93bb57bf8a2e40598d9fccd10c2921576e987d93cd80b4891302468738e391f07c4f2b356f7957160968e0bfef6e907c3cee2d8c23cbf04b089680c6868f01025a0f41f063e195a966051e3a29e17130a9ce97d48f55285b9bb04bdd55a09ae78088aca3cf0202d0f26169290450fe17e08974789c3458910b4db18361cdc564f8f2d0bdd2cf568090cad2c60e02d6f3483ec45505cc3be841046c7a12bf953ac973939bc7b727e54258e1881d4d80e08d84ddcb0102dae6dfb16d3e28aaaf43e00170b90606b36f35f38f8a3dceb5ee18199dd8f17c80c0caf384a30202385d7e57a4daba4cdd9e550a92dcc188838386e7581f13f09de796cbed4716a42101c052492a077abf41996b50c1b2e67fd7288bcd8c55cdc657b4e22d0804371f6901beb76a82ea17400cd6d7f595f70e1667d2018ed8f5a78d1ce07484222618c3cd" @@ -452,6 +477,58 @@ TYPED_TEST(BlockchainDBTest, AssetOwnershipResolvesAuthoritativeRingAndSpentStat *this->m_db, proof, TESTNET, carrier, &error)); } +TYPED_TEST(BlockchainDBTest, AssetTransactionStateAppliesAtomicallyAndRejectsReplay) +{ + 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 carrier{}; + carrier.data[0] = 0xd1; + const auto payload = make_db_asset_issuance(carrier); + std::vector<crypto::hash> output_ids; + std::string error; + this->m_db->block_wtxn_start(); + ASSERT_TRUE(assets::apply_asset_transaction_to_db(*this->m_db, payload, + TESTNET, carrier, 50, output_ids, &error)) << error; + this->m_db->block_wtxn_stop(); + ASSERT_EQ(1u, output_ids.size()); + + crypto::hash asset_id{}; + ASSERT_TRUE(assets::derive_asset_id(payload.issuance->descriptor, asset_id)); + uint64_t issuance_height = 0; + blobdata issuance_bytes; + ASSERT_TRUE(this->m_db->get_asset_record(asset_id, issuance_height, issuance_bytes)); + ASSERT_EQ(50u, issuance_height); + asset_output_data_t stored{}; + ASSERT_TRUE(this->m_db->get_asset_output(output_ids.front(), stored)); + ASSERT_EQ(asset_id, stored.asset_id); + ASSERT_EQ(50u, stored.height); + + std::vector<crypto::hash> replay_outputs; + this->m_db->block_wtxn_start(); + EXPECT_FALSE(assets::apply_asset_transaction_to_db(*this->m_db, payload, + TESTNET, carrier, 51, replay_outputs, &error)); + this->m_db->block_wtxn_abort(); + ASSERT_TRUE(replay_outputs.empty()); + + auto invalid = make_db_asset_issuance(crypto::hash{}); + crypto::hash invalid_carrier{}; + invalid_carrier.data[0] = 0xd2; + invalid.carrier_prefix_hash = invalid_carrier; + invalid.balances.front().outputs.front() = rct::commit( + invalid.issuance->descriptor.atomic_supply + 1, rct::zero()); + this->m_db->block_wtxn_start(); + EXPECT_FALSE(assets::apply_asset_transaction_to_db(*this->m_db, invalid, + TESTNET, invalid_carrier, 52, replay_outputs, &error)); + this->m_db->block_wtxn_abort(); + crypto::hash invalid_id{}; + ASSERT_TRUE(assets::derive_asset_id(invalid.issuance->descriptor, invalid_id)); + ASSERT_FALSE(this->m_db->get_asset_record(invalid_id, issuance_height, issuance_bytes)); +} + TYPED_TEST(BlockchainDBTest, AssetBlockExtensionsRebuildFromPersistentState) { const boost::filesystem::path temp_path = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); |
