diff --git a/src/scanner.cpp b/src/scanner.cpp index 1533eed..b516228 100644 --- a/src/scanner.cpp +++ b/src/scanner.cpp @@ -416,15 +416,22 @@ namespace lws bool found_pub = false; db::address_index account_index{db::major_index::primary, db::minor_index::primary}; - crypto::key_derivation active_derived; + crypto::key_derivation active_derived{}; + crypto::public_key active_pub{}; // inspect the additional and traditional keys for (std::size_t attempt = 0; attempt < 2; ++attempt) { if (attempt == 0) + { active_derived = derived; + active_pub = key.pub_key; + } else if (!additional_derivations.empty()) + { active_derived = additional_derivations.at(index); + active_pub = additional_tx_pub_keys.data.at(index); + } else break; // inspection loop @@ -491,7 +498,6 @@ namespace lws lws::decrypt_payment_id(payment_id.second.short_, active_derived); } } - const bool added = output_action( user, db::output{ @@ -501,7 +507,7 @@ namespace lws amount, mixin, boost::numeric_cast<std::uint32_t>(index), - key.pub_key + active_pub }, timestamp, tx.unlock_time, diff --git a/tests/unit/scanner.test.cpp b/tests/unit/scanner.test.cpp index 10b2a66..2364b18 100644 --- a/tests/unit/scanner.test.cpp +++ b/tests/unit/scanner.test.cpp @@ -36,10 +36,11 @@ #include "cryptonote_core/cryptonote_tx_utils.h" // monero/src #include "db/chain.test.h" #include "db/storage.test.h" -#include "hardforks/hardforks.h" // monero/src -#include "net/zmq.h" // monero/src +#include "device/device_default.hpp" // monero/src +#include "hardforks/hardforks.h" // monero/src +#include "net/zmq.h" // monero/src #include "rpc/client.h" -#include "rpc/daemon_messages.h" // monero/src +#include "rpc/daemon_messages.h" // monero/src #include "scanner.h" #include "wire/error.h" #include "wire/json/write.h" @@ -143,23 +144,28 @@ namespace struct transaction { - cryptonote::transaction tx; + std::unique_ptr<cryptonote::transaction> tx; std::vector<crypto::secret_key> additional_keys; - crypto::secret_key key; - crypto::public_key pub_key; - crypto::public_key spend_public; + std::vector<crypto::public_key> pub_keys; + std::vector<crypto::public_key> spend_publics; }; transaction make_miner_tx(lest::env& lest_env, lws::db::block_id height, const lws::db::account_address& miner_address, bool use_view_tags) { static constexpr std::uint64_t fee = 0; + transaction tx{}; - crypto::generate_keys(tx.pub_key, tx.key); - EXPECT(add_tx_pub_key_to_extra(tx.tx, tx.pub_key)); + tx.tx.reset(new cryptonote::transaction); + tx.pub_keys.emplace_back(); + tx.spend_publics.emplace_back(); + + crypto::secret_key key; + crypto::generate_keys(tx.pub_keys.back(), key); + EXPECT(add_tx_pub_key_to_extra(*tx.tx, tx.pub_keys.back())); cryptonote::txin_gen in; in.height = std::uint64_t(height); - tx.tx.vin.push_back(in); + tx.tx->vin.push_back(in); // This will work, until size of constructed block is less then CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE uint64_t block_reward; @@ -167,24 +173,39 @@ namespace block_reward += fee; crypto::key_derivation derivation; - EXPECT(crypto::generate_key_derivation(miner_address.view_public, tx.key, derivation)); - EXPECT(crypto::derive_public_key(derivation, 0, miner_address.spend_public, tx.spend_public)); + EXPECT(crypto::generate_key_derivation(miner_address.view_public, key, derivation)); + EXPECT(crypto::derive_public_key(derivation, 0, miner_address.spend_public, tx.spend_publics.back())); crypto::view_tag view_tag; if (use_view_tags) crypto::derive_view_tag(derivation, 0, view_tag); cryptonote::tx_out out; - cryptonote::set_tx_out(block_reward, tx.spend_public, use_view_tags, view_tag, out); + cryptonote::set_tx_out(block_reward, tx.spend_publics.back(), use_view_tags, view_tag, out); - tx.tx.vout.push_back(out); - tx.tx.version = 2; - tx.tx.unlock_time = std::uint64_t(height) + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; + tx.tx->vout.push_back(out); + tx.tx->version = 2; + tx.tx->unlock_time = std::uint64_t(height) + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; return tx; } - transaction make_tx(lest::env& lest_env, const cryptonote::account_keys& keys, const std::uint32_t ring_base, const bool use_view_tag) + struct get_spend_public + { + std::vector<crypto::public_key>& pub_keys; + + template<typename T> + void operator()(const T&) const noexcept + {} + + void operator()(const cryptonote::txout_to_key& val) const + { pub_keys.push_back(val.key); } + + void operator()(const cryptonote::txout_to_tagged_key& val) const + { pub_keys.push_back(val.key); } + }; + + transaction make_tx(lest::env& lest_env, const cryptonote::account_keys& keys, std::vector<cryptonote::tx_destination_entry>& destinations, const std::uint32_t ring_base, const bool use_view_tag) { static constexpr std::uint64_t input_amount = 20000; static constexpr std::uint64_t output_amount = 8000; @@ -201,8 +222,13 @@ namespace EXPECT(crypto::generate_key_derivation(keys.m_account_address.m_view_public_key, og_tx_key, derivation)); EXPECT(crypto::derive_public_key(derivation, 0, keys.m_account_address.m_spend_public_key, spend_public)); + std::uint32_t index = -1; std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; - subaddresses[keys.m_account_address.m_spend_public_key] = {0, 0}; + for (const auto& destination : destinations) + { + ++index; + subaddresses[destination.addr.m_spend_public_key] = {0, index}; + } std::vector<cryptonote::tx_source_entry> sources; sources.emplace_back(); @@ -220,23 +246,38 @@ namespace sources.back().outputs.emplace_back(); sources.back().outputs.back().first = 15 + ring_base; sources.back().outputs.back().second.dest = rct::pk2rct(spend_public); - - std::vector<cryptonote::tx_destination_entry> destinations; - destinations.emplace_back(); - destinations.back().amount = output_amount; - destinations.back().addr = keys.m_account_address; - + transaction out{}; + out.tx.reset(new cryptonote::transaction); EXPECT( cryptonote::construct_tx_and_get_tx_key( - keys, subaddresses, sources, destinations, keys.m_account_address, {}, out.tx, 0, out.key, + keys, subaddresses, sources, destinations, keys.m_account_address, {}, *out.tx, 0, unused_key, out.additional_keys, true, {rct::RangeProofType::RangeProofBulletproof, 2}, use_view_tag ) ); - crypto::secret_key_to_public_key(out.key, out.pub_key); - crypto::generate_key_derivation(keys.m_account_address.m_view_public_key, out.key, derivation); - crypto::derive_public_key(derivation, 0, keys.m_account_address.m_spend_public_key, out.spend_public); + for (const auto& vout : out.tx->vout) + boost::apply_visitor(get_spend_public{out.spend_publics}, vout.target); + + if (out.additional_keys.empty()) + { + std::vector<cryptonote::tx_extra_field> extra; + EXPECT(cryptonote::parse_tx_extra(out.tx->extra, extra)); + + cryptonote::tx_extra_pub_key key; + EXPECT(cryptonote::find_tx_extra_field_by_type(extra, key)); + + out.pub_keys.emplace_back(); + out.pub_keys.back() = key.pub_key; + } + else + { + for (const auto& this_key : out.additional_keys) + { + out.pub_keys.emplace_back(); + EXPECT(crypto::secret_key_to_public_key(this_key, out.pub_keys.back())); + } + } return out; } } @@ -252,6 +293,23 @@ LWS_CASE("lws::scanner::sync and lws::scanner::run") keys.m_account_address.m_spend_public_key }; + cryptonote::account_keys keys_subaddr1{}; + cryptonote::account_keys keys_subaddr2{}; + { + hw::core::device_default hw{}; + keys_subaddr1.m_account_address = hw.get_subaddress(keys, cryptonote::subaddress_index{0, 1}); + keys_subaddr2.m_account_address = hw.get_subaddress(keys, cryptonote::subaddress_index{0, 2}); + + const auto sub1_secret = hw.get_subaddress_secret_key(keys.m_view_secret_key, cryptonote::subaddress_index{0, 1}); + const auto sub2_secret = hw.get_subaddress_secret_key(keys.m_view_secret_key, cryptonote::subaddress_index{0, 1}); + + sc_add(to_bytes(keys_subaddr1.m_spend_secret_key), to_bytes(sub1_secret), to_bytes(keys.m_spend_secret_key)); + sc_add(to_bytes(keys_subaddr1.m_view_secret_key), to_bytes(keys_subaddr1.m_spend_secret_key), to_bytes(keys.m_view_secret_key)); + + sc_add(to_bytes(keys_subaddr2.m_spend_secret_key), to_bytes(sub2_secret), to_bytes(keys.m_spend_secret_key)); + sc_add(to_bytes(keys_subaddr2.m_view_secret_key), to_bytes(keys_subaddr2.m_spend_secret_key), to_bytes(keys.m_view_secret_key)); + } + cryptonote::account_keys keys2{}; crypto::generate_keys(keys2.m_account_address.m_spend_public_key, keys2.m_spend_secret_key); crypto::generate_keys(keys2.m_account_address.m_view_public_key, keys2.m_view_secret_key); @@ -351,20 +409,73 @@ LWS_CASE("lws::scanner::sync and lws::scanner::run") SECTION("lws::scanner::run") { + { + const std::vector<lws::db::subaddress_dict> indexes{ + lws::db::subaddress_dict{ + lws::db::major_index::primary, + lws::db::index_ranges{ + lws::db::index_range{lws::db::minor_index(1), lws::db::minor_index(2)} + } + } + }; + const auto result = + db.upsert_subaddresses(lws::db::account_id(1), account, keys.m_view_secret_key, indexes, 2); + EXPECT(result); + EXPECT(result->size() == 1); + EXPECT(result->at(0).first == lws::db::major_index::primary); + EXPECT(result->at(0).second.size() == 1); + EXPECT(result->at(0).second.at(0).size() == 2); + EXPECT(result->at(0).second.at(0).at(0) == lws::db::minor_index(1)); + EXPECT(result->at(0).second.at(0).at(1) == lws::db::minor_index(2)); + } + + std::vector<cryptonote::tx_destination_entry> destinations; + destinations.emplace_back(); + destinations.back().amount = 8000; + destinations.back().addr = keys.m_account_address; + std::vector<epee::byte_slice> messages{}; transaction tx = make_miner_tx(lest_env, last_block.id, account, false); - transaction tx2 = make_tx(lest_env, keys, 20, true); - transaction tx3 = make_tx(lest_env, keys, 86, true); + EXPECT(tx.pub_keys.size() == 1); + EXPECT(tx.spend_publics.size() == 1); + + transaction tx2 = make_tx(lest_env, keys, destinations, 20, true); + EXPECT(tx2.pub_keys.size() == 1); + EXPECT(tx2.spend_publics.size() == 1); + + transaction tx3 = make_tx(lest_env, keys, destinations, 86, false); + EXPECT(tx3.pub_keys.size() == 1); + EXPECT(tx3.spend_publics.size() == 1); + + destinations.emplace_back(); + destinations.back().amount = 2000; + destinations.back().addr = keys_subaddr1.m_account_address; + destinations.back().is_subaddress = true; + + transaction tx4 = make_tx(lest_env, keys, destinations, 50, false); + EXPECT(tx4.pub_keys.size() == 1); + EXPECT(tx4.spend_publics.size() == 2); + + //destinations.emplace_back(); + //destinations.back().amount = 1000; + //destinations.back().addr = keys_subaddr2.m_account_address; + //destinations.back().is_subaddress = true; + + transaction tx5 = make_tx(lest_env, keys, destinations, 200, true); + //EXPECT(tx5.pub_keys.size() == 3); + //EXPECT(tx5.spend_publics.size() == 3); cryptonote::rpc::GetBlocksFast::Response bmessage{}; bmessage.start_height = std::uint64_t(last_block.id) + 1; bmessage.current_height = bmessage.start_height + 1; bmessage.blocks.emplace_back(); - bmessage.blocks.back().block.miner_tx = tx.tx; - bmessage.blocks.back().block.tx_hashes.push_back(cryptonote::get_transaction_hash(tx2.tx)); - bmessage.blocks.back().block.tx_hashes.push_back(cryptonote::get_transaction_hash(tx3.tx)); - bmessage.blocks.back().transactions.push_back(tx2.tx); - bmessage.blocks.back().transactions.push_back(tx3.tx); + bmessage.blocks.back().block.miner_tx = *tx.tx; + bmessage.blocks.back().block.tx_hashes.push_back(cryptonote::get_transaction_hash(*tx2.tx)); + bmessage.blocks.back().block.tx_hashes.push_back(cryptonote::get_transaction_hash(*tx3.tx)); + bmessage.blocks.back().block.tx_hashes.push_back(cryptonote::get_transaction_hash(*tx4.tx)); + bmessage.blocks.back().transactions.push_back(*tx2.tx); + bmessage.blocks.back().transactions.push_back(*tx3.tx); + bmessage.blocks.back().transactions.push_back(*tx4.tx); bmessage.output_indices.emplace_back(); bmessage.output_indices.back().emplace_back(); bmessage.output_indices.back().back().push_back(100); @@ -372,6 +483,9 @@ LWS_CASE("lws::scanner::sync and lws::scanner::run") bmessage.output_indices.back().back().push_back(101); bmessage.output_indices.back().emplace_back(); bmessage.output_indices.back().back().push_back(102); + bmessage.output_indices.back().emplace_back(); + bmessage.output_indices.back().back().push_back(200); + bmessage.output_indices.back().back().push_back(201); bmessage.blocks.push_back(bmessage.blocks.back()); bmessage.output_indices.push_back(bmessage.output_indices.back()); @@ -414,7 +528,7 @@ LWS_CASE("lws::scanner::sync and lws::scanner::run") { boost::thread server_thread(&rpc_thread, rpc.zmq_context(), std::cref(messages)); const join on_scope_exit{server_thread}; - lws::scanner::run(db.clone(), std::move(rpc), 1, epee::net_utils::ssl_verification_t::none); + lws::scanner::run(db.clone(), std::move(rpc), 1, epee::net_utils::ssl_verification_t::none, true); } hashes.push_back(cryptonote::get_block_hash(bmessage.blocks.back().block)); @@ -423,68 +537,144 @@ LWS_CASE("lws::scanner::sync and lws::scanner::run") const lws::db::block_id new_last_block_id = lws::db::block_id(std::uint64_t(last_block.id) + 2); EXPECT(get_account().scan_height == new_last_block_id); { - const std::unordered_map<crypto::hash, lws::db::output> expected{ + const std::map<std::pair<lws::db::output_id, std::uint32_t>, lws::db::output> expected{ { - cryptonote::get_transaction_hash(tx.tx), lws::db::output{ - lws::db::transaction_link{new_last_block_id, cryptonote::get_transaction_hash(tx.tx)}, + {lws::db::output_id{0, 100}, 35184372088830}, lws::db::output{ + lws::db::transaction_link{new_last_block_id, cryptonote::get_transaction_hash(*tx.tx)}, lws::db::output::spend_meta_{ - lws::db::output_id{0, 100}, 35184372088830, 0, 0, tx.pub_key + lws::db::output_id{0, 100}, 35184372088830, 0, 0, tx.pub_keys.at(0) }, 0, 0, - cryptonote::get_transaction_prefix_hash(tx.tx), - tx.spend_public, + cryptonote::get_transaction_prefix_hash(*tx.tx), + tx.spend_publics.at(0), rct::commit(35184372088830, rct::identity()), {}, lws::db::pack(lws::db::extra::coinbase_output, 0), {}, - 0 // fee + 0, // fee + lws::db::address_index{} }, }, { - cryptonote::get_transaction_hash(tx2.tx), lws::db::output{ - lws::db::transaction_link{new_last_block_id, cryptonote::get_transaction_hash(tx2.tx)}, + {lws::db::output_id{0, 101}, 8000}, lws::db::output{ + lws::db::transaction_link{new_last_block_id, cryptonote::get_transaction_hash(*tx2.tx)}, lws::db::output::spend_meta_{ - lws::db::output_id{0, 101}, 8000, 15, 0, tx2.pub_key + lws::db::output_id{0, 101}, 8000, 15, 0, tx2.pub_keys.at(0) }, 0, 0, - cryptonote::get_transaction_prefix_hash(tx2.tx), - tx2.spend_public, - tx2.tx.rct_signatures.outPk.at(0).mask, + cryptonote::get_transaction_prefix_hash(*tx2.tx), + tx2.spend_publics.at(0), + tx2.tx->rct_signatures.outPk.at(0).mask, {}, lws::db::pack(lws::db::extra::ringct_output, 8), {}, - 12000 // fee + 12000, // fee + lws::db::address_index{} }, }, { - cryptonote::get_transaction_hash(tx3.tx), lws::db::output{ - lws::db::transaction_link{new_last_block_id, cryptonote::get_transaction_hash(tx3.tx)}, + {lws::db::output_id{0, 102}, 8000}, lws::db::output{ + lws::db::transaction_link{new_last_block_id, cryptonote::get_transaction_hash(*tx3.tx)}, lws::db::output::spend_meta_{ - lws::db::output_id{0, 102}, 8000, 15, 0, tx3.pub_key + lws::db::output_id{0, 102}, 8000, 15, 0, tx3.pub_keys.at(0) }, 0, 0, - cryptonote::get_transaction_prefix_hash(tx3.tx), - tx3.spend_public, - tx3.tx.rct_signatures.outPk.at(0).mask, + cryptonote::get_transaction_prefix_hash(*tx3.tx), + tx3.spend_publics.at(0), + tx3.tx->rct_signatures.outPk.at(0).mask, {}, lws::db::pack(lws::db::extra::ringct_output, 8), {}, - 12000 // fee + 12000, // fee + lws::db::address_index{} }, + }, + { + {lws::db::output_id{0, 200}, 8000}, lws::db::output{ + lws::db::transaction_link{new_last_block_id, cryptonote::get_transaction_hash(*tx4.tx)}, + lws::db::output::spend_meta_{ + lws::db::output_id{0, 200}, 8000, 15, 0, tx4.pub_keys.at(0) + }, + 0, + 0, + cryptonote::get_transaction_prefix_hash(*tx4.tx), + tx4.spend_publics.at(0), + tx4.tx->rct_signatures.outPk.at(0).mask, + {}, + lws::db::pack(lws::db::extra::ringct_output, 8), + {}, + 10000, // fee + lws::db::address_index{} + } + }, + { + {lws::db::output_id{0, 201}, 8000}, lws::db::output{ + lws::db::transaction_link{new_last_block_id, cryptonote::get_transaction_hash(*tx4.tx)}, + lws::db::output::spend_meta_{ + lws::db::output_id{0, 201}, 8000, 15, 1, tx4.pub_keys.at(0) + }, + 0, + 0, + cryptonote::get_transaction_prefix_hash(*tx4.tx), + tx4.spend_publics.at(1), + tx4.tx->rct_signatures.outPk.at(1).mask, + {}, + lws::db::pack(lws::db::extra::ringct_output, 8), + {}, + 10000, // fee + lws::db::address_index{} + } + }, + { + {lws::db::output_id{0, 200}, 2000}, lws::db::output{ + lws::db::transaction_link{new_last_block_id, cryptonote::get_transaction_hash(*tx4.tx)}, + lws::db::output::spend_meta_{ + lws::db::output_id{0, 200}, 2000, 15, 0, tx4.pub_keys.at(0) + }, + 0, + 0, + cryptonote::get_transaction_prefix_hash(*tx4.tx), + tx4.spend_publics.at(0), + tx4.tx->rct_signatures.outPk.at(0).mask, + {}, + lws::db::pack(lws::db::extra::ringct_output, 8), + {}, + 10000, // fee + lws::db::address_index{lws::db::major_index::primary, lws::db::minor_index(1)} + } + }, + { + {lws::db::output_id{0, 201}, 2000}, lws::db::output{ + lws::db::transaction_link{new_last_block_id, cryptonote::get_transaction_hash(*tx4.tx)}, + lws::db::output::spend_meta_{ + lws::db::output_id{0, 201}, 2000, 15, 1, tx4.pub_keys.at(0) + }, + 0, + 0, + cryptonote::get_transaction_prefix_hash(*tx4.tx), + tx4.spend_publics.at(1), + tx4.tx->rct_signatures.outPk.at(1).mask, + {}, + lws::db::pack(lws::db::extra::ringct_output, 8), + {}, + 10000, // fee + lws::db::address_index{lws::db::major_index::primary, lws::db::minor_index(1)} + } } }; auto reader = MONERO_UNWRAP(db.start_read()); auto outputs = MONERO_UNWRAP(reader.get_outputs(lws::db::account_id(1))); - EXPECT(outputs.count() == 3); + EXPECT(outputs.count() == 5); auto output_it = outputs.make_iterator(); for (auto output_it = outputs.make_iterator(); !output_it.is_end(); ++output_it) { auto real_output = *output_it; - const auto expected_output = expected.find(real_output.link.tx_hash); + const auto expected_output = + expected.find(std::make_pair(real_output.spend_meta.id, real_output.spend_meta.amount)); EXPECT(expected_output != expected.end()); EXPECT(real_output.link.height == expected_output->second.link.height); @@ -501,6 +691,7 @@ LWS_CASE("lws::scanner::sync and lws::scanner::run") if (unpack(expected_output->second.extra).second == 8) EXPECT(real_output.payment_id.short_ == expected_output->second.payment_id.short_); EXPECT(real_output.fee == expected_output->second.fee); + EXPECT(real_output.recipient == expected_output->second.recipient); } auto spends = MONERO_UNWRAP(reader.get_spends(lws::db::account_id(1))); @@ -510,24 +701,26 @@ LWS_CASE("lws::scanner::sync and lws::scanner::run") auto real_spend = *spend_it; EXPECT(real_spend.link.height == new_last_block_id); - EXPECT(real_spend.link.tx_hash == cryptonote::get_transaction_hash(tx3.tx)); + EXPECT(real_spend.link.tx_hash == cryptonote::get_transaction_hash(*tx3.tx)); lws::db::output_id expected_out{0, 100}; EXPECT(real_spend.source == expected_out); EXPECT(real_spend.mixin_count == 15); EXPECT(real_spend.length == 0); EXPECT(real_spend.payment_id == crypto::hash{}); + EXPECT(real_spend.sender == lws::db::address_index{}); ++spend_it; EXPECT(!spend_it.is_end()); real_spend = *spend_it; EXPECT(real_spend.link.height == new_last_block_id); - EXPECT(real_spend.link.tx_hash == cryptonote::get_transaction_hash(tx3.tx)); + EXPECT(real_spend.link.tx_hash == cryptonote::get_transaction_hash(*tx3.tx)); expected_out = lws::db::output_id{0, 101}; EXPECT(real_spend.source == expected_out); EXPECT(real_spend.mixin_count == 15); EXPECT(real_spend.length == 0); EXPECT(real_spend.payment_id == crypto::hash{}); + EXPECT(real_spend.sender == lws::db::address_index{}); EXPECT(MONERO_UNWRAP(reader.get_outputs(lws::db::account_id(2))).count() == 0); EXPECT(MONERO_UNWRAP(reader.get_spends(lws::db::account_id(2))).count() == 0);