From da1727b003563616730a143952baefbc4c34472f Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Mon, 15 Sep 2025 17:13:49 -0500 Subject: [PATCH 1/2] wallet: add `wallet2_basic` library types The `wallet2_basic` library is a minimalistic library for loading / storing historic `wallet2` files. It has several design goals: * Enable future migration away from monolithic `wallet2` codebase without losing funds * Simplify FCMP++/Carrot integration * Speed-up compile-time for current codebase, but especially the FCMP++/Carrot integration, which is split over several translation units --- src/wallet/CMakeLists.txt | 1 + src/wallet/wallet2.cpp | 2 + src/wallet/wallet2.h | 746 +----------------- src/wallet/wallet2_basic/CMakeLists.txt | 27 + src/wallet/wallet2_basic/README.md | 3 + .../wallet2_boost_serialization.h | 433 ++++++++++ .../wallet2_basic/wallet2_serialization.h | 169 ++++ src/wallet/wallet2_basic/wallet2_types.h | 319 ++++++++ tests/fuzz/cold-outputs.cpp | 1 + tests/fuzz/cold-transaction.cpp | 1 + tests/unit_tests/serialization.cpp | 2 + 11 files changed, 992 insertions(+), 712 deletions(-) create mode 100644 src/wallet/wallet2_basic/CMakeLists.txt create mode 100644 src/wallet/wallet2_basic/README.md create mode 100644 src/wallet/wallet2_basic/wallet2_boost_serialization.h create mode 100644 src/wallet/wallet2_basic/wallet2_serialization.h create mode 100644 src/wallet/wallet2_basic/wallet2_types.h diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index b976c02d77..a9f868e225 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -104,3 +104,4 @@ if(NOT IOS) endif() add_subdirectory(api) +add_subdirectory(wallet2_basic) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index aabb06521d..bc47152b15 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -93,6 +93,8 @@ using namespace epee; #include "device/device_cold.hpp" #include "device_trezor/device_trezor.hpp" #include "net/socks_connect.h" +#include "wallet2_basic/wallet2_boost_serialization.h" +#include "wallet2_basic/wallet2_serialization.h" extern "C" { diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index f6271bd36b..0a07950aee 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -34,12 +34,6 @@ #include #include -#if BOOST_VERSION >= 107400 -#include -#endif -#include -#include -#include #include #include #include @@ -53,7 +47,6 @@ #include "rpc/core_rpc_server_commands_defs.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_core/cryptonote_tx_utils.h" -#include "common/unordered_containers_boost_serialization.h" #include "common/util.h" #include "crypto/chacha.h" #include "crypto/hash.h" @@ -73,6 +66,7 @@ #include "message_store.h" #include "fee_priority.h" #include "fee_algorithm.h" +#include "wallet2_basic/wallet2_types.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2" @@ -174,44 +168,7 @@ private: } }; - class hashchain - { - public: - hashchain(): m_genesis(crypto::null_hash), m_offset(0) {} - - size_t size() const { return m_blockchain.size() + m_offset; } - size_t offset() const { return m_offset; } - const crypto::hash &genesis() const { return m_genesis; } - void push_back(const crypto::hash &hash) { if (m_offset == 0 && m_blockchain.empty()) m_genesis = hash; m_blockchain.push_back(hash); } - bool is_in_bounds(size_t idx) const { return idx >= m_offset && idx < size(); } - const crypto::hash &operator[](size_t idx) const { return m_blockchain[idx - m_offset]; } - crypto::hash &operator[](size_t idx) { return m_blockchain[idx - m_offset]; } - void crop(size_t height) { m_blockchain.resize(height - m_offset); } - void clear() { m_offset = 0; m_blockchain.clear(); } - bool empty() const { return m_blockchain.empty() && m_offset == 0; } - void trim(size_t height) { while (height > m_offset && m_blockchain.size() > 1) { m_blockchain.pop_front(); ++m_offset; } m_blockchain.shrink_to_fit(); } - void refill(const crypto::hash &hash) { m_blockchain.push_back(hash); --m_offset; } - - template - inline void serialize(t_archive &a, const unsigned int ver) - { - a & m_offset; - a & m_genesis; - a & m_blockchain; - } - - BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) - VARINT_FIELD(m_offset) - FIELD(m_genesis) - FIELD(m_blockchain) - END_SERIALIZE() - - private: - size_t m_offset; - crypto::hash m_genesis; - std::deque m_blockchain; - }; + using hashchain = wallet2_basic::hashchain; class wallet_keys_unlocker; class wallet2 @@ -223,30 +180,26 @@ private: public: static constexpr const std::chrono::seconds rpc_timeout = std::chrono::minutes(3) + std::chrono::seconds(30); - enum RefreshType { - RefreshFull, - RefreshOptimizeCoinbase, - RefreshNoCoinbase, - RefreshDefault = RefreshOptimizeCoinbase, - }; + using RefreshType = wallet2_basic::RefreshType; + static constexpr RefreshType RefreshFull = wallet2_basic::RefreshFull; + static constexpr RefreshType RefreshOptimizeCoinbase = wallet2_basic::RefreshOptimizeCoinbase; + static constexpr RefreshType RefreshNoCoinbase = wallet2_basic::RefreshNoCoinbase; + static constexpr RefreshType RefreshDefault = wallet2_basic::RefreshDefault; - enum AskPasswordType { - AskPasswordNever = 0, - AskPasswordOnAction = 1, - AskPasswordToDecrypt = 2, - }; + using AskPasswordType = wallet2_basic::AskPasswordType; + static constexpr AskPasswordType AskPasswordNever = wallet2_basic::AskPasswordNever; + static constexpr AskPasswordType AskPasswordOnAction = wallet2_basic::AskPasswordOnAction; + static constexpr AskPasswordType AskPasswordToDecrypt = wallet2_basic::AskPasswordToDecrypt; - enum BackgroundMiningSetupType { - BackgroundMiningMaybe = 0, - BackgroundMiningYes = 1, - BackgroundMiningNo = 2, - }; + using BackgroundMiningSetupType = wallet2_basic::BackgroundMiningSetupType; + static constexpr BackgroundMiningSetupType BackgroundMiningMaybe = wallet2_basic::BackgroundMiningMaybe; + static constexpr BackgroundMiningSetupType BackgroundMiningYes = wallet2_basic::BackgroundMiningYes; + static constexpr BackgroundMiningSetupType BackgroundMiningNo = wallet2_basic::BackgroundMiningNo; - enum BackgroundSyncType { - BackgroundSyncOff = 0, - BackgroundSyncReusePassword = 1, - BackgroundSyncCustomPassword = 2, - }; + using BackgroundSyncType = wallet2_basic::BackgroundSyncType; + static constexpr BackgroundSyncType BackgroundSyncOff = wallet2_basic::BackgroundSyncOff; + static constexpr BackgroundSyncType BackgroundSyncReusePassword = wallet2_basic::BackgroundSyncReusePassword; + static constexpr BackgroundSyncType BackgroundSyncCustomPassword = wallet2_basic::BackgroundSyncCustomPassword; static BackgroundSyncType background_sync_type_from_str(const std::string &background_sync_type_str) { @@ -256,10 +209,9 @@ private: throw std::logic_error("Unknown background sync type"); }; - enum ExportFormat { - Binary = 0, - Ascii, - }; + using ExportFormat = wallet2_basic::ExportFormat; + static constexpr ExportFormat Binary = wallet2_basic::Binary; + static constexpr ExportFormat Ascii = wallet2_basic::Ascii; static const char* tr(const char* str); @@ -293,29 +245,7 @@ private: wallet2(cryptonote::network_type nettype = cryptonote::MAINNET, uint64_t kdf_rounds = 1, bool unattended = false, std::unique_ptr http_client_factory = std::unique_ptr(new net::http::client_factory())); ~wallet2(); - struct multisig_info - { - struct LR - { - rct::key m_L; - rct::key m_R; - - BEGIN_SERIALIZE_OBJECT() - FIELD(m_L) - FIELD(m_R) - END_SERIALIZE() - }; - - crypto::public_key m_signer; - std::vector m_LR; - std::vector m_partial_key_images; // one per key the participant has - - BEGIN_SERIALIZE_OBJECT() - FIELD(m_signer) - FIELD(m_LR) - FIELD(m_partial_key_images) - END_SERIALIZE() - }; + using multisig_info = wallet2_basic::multisig_info; struct tx_scan_info_t { @@ -330,63 +260,7 @@ private: tx_scan_info_t(): amount(0), money_transfered(0), error(true) {} }; - struct transfer_details - { - uint64_t m_block_height; - cryptonote::transaction_prefix m_tx; - crypto::hash m_txid; - uint64_t m_internal_output_index; - uint64_t m_global_output_index; - bool m_spent; - bool m_frozen; - uint64_t m_spent_height; - crypto::key_image m_key_image; //TODO: key_image stored twice :( - rct::key m_mask; - uint64_t m_amount; - bool m_rct; - bool m_key_image_known; - bool m_key_image_request; // view wallets: we want to request it; cold wallets: it was requested - uint64_t m_pk_index; - cryptonote::subaddress_index m_subaddr_index; - bool m_key_image_partial; - std::vector m_multisig_k; - std::vector m_multisig_info; // one per other participant - std::vector> m_uses; - - bool is_rct() const { return m_rct; } - uint64_t amount() const { return m_amount; } - const crypto::public_key get_public_key() const { - crypto::public_key output_public_key; - THROW_WALLET_EXCEPTION_IF(m_tx.vout.size() <= m_internal_output_index, - error::wallet_internal_error, "Too few outputs, outputs may be corrupted"); - THROW_WALLET_EXCEPTION_IF(!get_output_public_key(m_tx.vout[m_internal_output_index], output_public_key), - error::wallet_internal_error, "Unable to get output public key from output"); - return output_public_key; - }; - - BEGIN_SERIALIZE_OBJECT() - FIELD(m_block_height) - FIELD(m_tx) - FIELD(m_txid) - FIELD(m_internal_output_index) - FIELD(m_global_output_index) - FIELD(m_spent) - FIELD(m_frozen) - FIELD(m_spent_height) - FIELD(m_key_image) - FIELD(m_mask) - FIELD(m_amount) - FIELD(m_rct) - FIELD(m_key_image_known) - FIELD(m_key_image_request) - FIELD(m_pk_index) - FIELD(m_subaddr_index) - FIELD(m_key_image_partial) - FIELD(m_multisig_k) - FIELD(m_multisig_info) - FIELD(m_uses) - END_SERIALIZE() - }; + using transfer_details = wallet2_basic::transfer_details; struct exported_transfer_details { @@ -428,32 +302,8 @@ private: END_SERIALIZE() }; - typedef std::vector amounts_container; - struct payment_details - { - crypto::hash m_tx_hash; - uint64_t m_amount; - amounts_container m_amounts; - uint64_t m_fee; - uint64_t m_block_height; - uint64_t m_unlock_time; - uint64_t m_timestamp; - bool m_coinbase; - cryptonote::subaddress_index m_subaddr_index; - - BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) - FIELD(m_tx_hash) - VARINT_FIELD(m_amount) - FIELD(m_amounts) - VARINT_FIELD(m_fee) - VARINT_FIELD(m_block_height) - VARINT_FIELD(m_unlock_time) - VARINT_FIELD(m_timestamp) - FIELD(m_coinbase) - FIELD(m_subaddr_index) - END_SERIALIZE() - }; + using amounts_container = wallet2_basic::amounts_container; + using payment_details = wallet2_basic::payment_details; struct address_tx : payment_details { @@ -461,87 +311,9 @@ private: bool m_incoming; }; - struct pool_payment_details - { - payment_details m_pd; - bool m_double_spend_seen; - - BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) - FIELD(m_pd) - FIELD(m_double_spend_seen) - END_SERIALIZE() - }; - - struct unconfirmed_transfer_details - { - cryptonote::transaction_prefix m_tx; - uint64_t m_amount_in; - uint64_t m_amount_out; - uint64_t m_change; - time_t m_sent_time; - std::vector m_dests; - crypto::hash m_payment_id; - enum { pending, pending_in_pool, failed } m_state; - uint64_t m_timestamp; - uint32_t m_subaddr_account; // subaddress account of your wallet to be used in this transfer - std::set m_subaddr_indices; // set of address indices used as inputs in this transfer - std::vector>> m_rings; // relative - - BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(1) - FIELD(m_tx) - VARINT_FIELD(m_amount_in) - VARINT_FIELD(m_amount_out) - VARINT_FIELD(m_change) - VARINT_FIELD(m_sent_time) - FIELD(m_dests) - FIELD(m_payment_id) - if (version >= 1) - VARINT_FIELD(m_state) - VARINT_FIELD(m_timestamp) - VARINT_FIELD(m_subaddr_account) - FIELD(m_subaddr_indices) - FIELD(m_rings) - END_SERIALIZE() - }; - - struct confirmed_transfer_details - { - cryptonote::transaction_prefix m_tx; - uint64_t m_amount_in; - uint64_t m_amount_out; - uint64_t m_change; - uint64_t m_block_height; - std::vector m_dests; - crypto::hash m_payment_id; - uint64_t m_timestamp; - uint64_t m_unlock_time; - uint32_t m_subaddr_account; // subaddress account of your wallet to be used in this transfer - std::set m_subaddr_indices; // set of address indices used as inputs in this transfer - std::vector>> m_rings; // relative - - confirmed_transfer_details(): m_amount_in(0), m_amount_out(0), m_change((uint64_t)-1), m_block_height(0), m_payment_id(crypto::null_hash), m_timestamp(0), m_unlock_time(0), m_subaddr_account((uint32_t)-1) {} - confirmed_transfer_details(const unconfirmed_transfer_details &utd, uint64_t height): - m_tx(utd.m_tx), m_amount_in(utd.m_amount_in), m_amount_out(utd.m_amount_out), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp), m_unlock_time(utd.m_tx.unlock_time), m_subaddr_account(utd.m_subaddr_account), m_subaddr_indices(utd.m_subaddr_indices), m_rings(utd.m_rings) {} - - BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(1) - if (version >= 1) - FIELD(m_tx) - VARINT_FIELD(m_amount_in) - VARINT_FIELD(m_amount_out) - VARINT_FIELD(m_change) - VARINT_FIELD(m_block_height) - FIELD(m_dests) - FIELD(m_payment_id) - VARINT_FIELD(m_timestamp) - VARINT_FIELD(m_unlock_time) - VARINT_FIELD(m_subaddr_account) - FIELD(m_subaddr_indices) - FIELD(m_rings) - END_SERIALIZE() - }; + using pool_payment_details = wallet2_basic::pool_payment_details; + using unconfirmed_transfer_details = wallet2_basic::unconfirmed_transfer_details; + using confirmed_transfer_details = wallet2_basic::confirmed_transfer_details; struct tx_construction_data { @@ -602,8 +374,8 @@ private: END_SERIALIZE() }; - typedef std::vector transfer_container; - typedef std::unordered_multimap payment_container; + using transfer_container = wallet2_basic::transfer_container; + using payment_container = wallet2_basic::payment_container; typedef std::set unique_index_container; struct multisig_sig @@ -758,24 +530,7 @@ private: END_SERIALIZE() }; - // GUI Address book - struct address_book_row - { - cryptonote::account_public_address m_address; - crypto::hash8 m_payment_id; - std::string m_description; - bool m_is_subaddress; - bool m_has_payment_id; - - BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) - FIELD(m_address) - FIELD(m_payment_id) - FIELD(m_description) - FIELD(m_is_subaddress) - FIELD(m_has_payment_id) - END_SERIALIZE() - }; + using address_book_row = wallet2_basic::address_book_row; struct reserve_proof_entry { @@ -797,53 +552,8 @@ private: END_SERIALIZE() }; - struct background_synced_tx_t - { - uint64_t index_in_background_sync_data; - cryptonote::transaction tx; - std::vector output_indices; - uint64_t height; - uint64_t block_timestamp; - bool double_spend_seen; - - BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) - VARINT_FIELD(index_in_background_sync_data) - - // prune tx; don't need to keep signature data - if (!tx.serialize_base(ar)) - return false; - - FIELD(output_indices) - VARINT_FIELD(height) - VARINT_FIELD(block_timestamp) - FIELD(double_spend_seen) - END_SERIALIZE() - }; - - struct background_sync_data_t - { - bool first_refresh_done = false; - uint64_t start_height = 0; - std::unordered_map txs; - - // Relevant wallet settings - uint64_t wallet_refresh_from_block_height; - size_t subaddress_lookahead_major; - size_t subaddress_lookahead_minor; - RefreshType wallet_refresh_type; - - BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) - FIELD(first_refresh_done) - FIELD(start_height) - FIELD(txs) - FIELD(wallet_refresh_from_block_height) - VARINT_FIELD(subaddress_lookahead_major) - VARINT_FIELD(subaddress_lookahead_minor) - VARINT_FIELD(wallet_refresh_type) - END_SERIALIZE() - }; + using background_synced_tx_t = wallet2_basic::background_synced_tx_t; + using background_sync_data_t = wallet2_basic::background_sync_data_t; typedef std::tuple get_outs_entry; @@ -2056,394 +1766,6 @@ private: }; } BOOST_CLASS_VERSION(tools::wallet2, 31) -BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 12) -BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1) -BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0) -BOOST_CLASS_VERSION(tools::wallet2::payment_details, 5) -BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1) -BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 8) -BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 6) -BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 18) -BOOST_CLASS_VERSION(tools::wallet2::background_synced_tx_t, 0) -BOOST_CLASS_VERSION(tools::wallet2::background_sync_data_t, 0) - -namespace boost -{ - namespace serialization - { - template - inline void serialize( - Archive & ar, - std::tuple & t, - const unsigned int /* file_version */ - ){ - ar & boost::serialization::make_nvp("f", std::get<0>(t)); - ar & boost::serialization::make_nvp("s", std::get<1>(t)); - ar & boost::serialization::make_nvp("t", std::get<2>(t)); - } - - template - inline typename std::enable_if::type initialize_transfer_details(Archive &a, tools::wallet2::transfer_details &x, const boost::serialization::version_type ver) - { - } - template - inline typename std::enable_if::type initialize_transfer_details(Archive &a, tools::wallet2::transfer_details &x, const boost::serialization::version_type ver) - { - if (ver < 1) - { - x.m_mask = rct::identity(); - x.m_amount = x.m_tx.vout[x.m_internal_output_index].amount; - } - if (ver < 2) - { - x.m_spent_height = 0; - } - if (ver < 4) - { - x.m_rct = x.m_tx.vout[x.m_internal_output_index].amount == 0; - } - if (ver < 6) - { - x.m_key_image_known = true; - } - if (ver < 7) - { - x.m_pk_index = 0; - } - if (ver < 8) - { - x.m_subaddr_index = {}; - } - if (ver < 9) - { - x.m_key_image_partial = false; - x.m_multisig_k.clear(); - x.m_multisig_info.clear(); - } - if (ver < 10) - { - x.m_key_image_request = false; - } - if (ver < 12) - { - x.m_frozen = false; - } - } - - template - inline void serialize(Archive &a, tools::wallet2::transfer_details &x, const boost::serialization::version_type ver) - { - a & x.m_block_height; - a & x.m_global_output_index; - a & x.m_internal_output_index; - if (ver < 3) - { - cryptonote::transaction tx; - a & tx; - x.m_tx = (const cryptonote::transaction_prefix&)tx; - x.m_txid = cryptonote::get_transaction_hash(tx); - } - else - { - a & x.m_tx; - } - a & x.m_spent; - a & x.m_key_image; - if (ver < 1) - { - // ensure mask and amount are set - initialize_transfer_details(a, x, ver); - return; - } - a & x.m_mask; - a & x.m_amount; - if (ver < 2) - { - initialize_transfer_details(a, x, ver); - return; - } - a & x.m_spent_height; - if (ver < 3) - { - initialize_transfer_details(a, x, ver); - return; - } - a & x.m_txid; - if (ver < 4) - { - initialize_transfer_details(a, x, ver); - return; - } - a & x.m_rct; - if (ver < 5) - { - initialize_transfer_details(a, x, ver); - return; - } - if (ver < 6) - { - // v5 did not properly initialize - uint8_t u; - a & u; - x.m_key_image_known = true; - return; - } - a & x.m_key_image_known; - if (ver < 7) - { - initialize_transfer_details(a, x, ver); - return; - } - a & x.m_pk_index; - if (ver < 8) - { - initialize_transfer_details(a, x, ver); - return; - } - a & x.m_subaddr_index; - if (ver < 9) - { - initialize_transfer_details(a, x, ver); - return; - } - a & x.m_multisig_info; - a & x.m_multisig_k; - a & x.m_key_image_partial; - if (ver < 10) - { - initialize_transfer_details(a, x, ver); - return; - } - a & x.m_key_image_request; - if (ver < 11) - { - initialize_transfer_details(a, x, ver); - return; - } - a & x.m_uses; - if (ver < 12) - { - initialize_transfer_details(a, x, ver); - return; - } - a & x.m_frozen; - } - - template - inline void serialize(Archive &a, tools::wallet2::multisig_info::LR &x, const boost::serialization::version_type ver) - { - a & x.m_L; - a & x.m_R; - } - - template - inline void serialize(Archive &a, tools::wallet2::multisig_info &x, const boost::serialization::version_type ver) - { - a & x.m_signer; - a & x.m_LR; - a & x.m_partial_key_images; - } - - template - inline void serialize(Archive &a, tools::wallet2::unconfirmed_transfer_details &x, const boost::serialization::version_type ver) - { - a & x.m_change; - a & x.m_sent_time; - if (ver < 5) - { - cryptonote::transaction tx; - a & tx; - x.m_tx = (const cryptonote::transaction_prefix&)tx; - } - else - { - a & x.m_tx; - } - if (ver < 1) - return; - a & x.m_dests; - a & x.m_payment_id; - if (ver < 2) - return; - a & x.m_state; - if (ver < 3) - return; - a & x.m_timestamp; - if (ver < 4) - return; - a & x.m_amount_in; - a & x.m_amount_out; - if (ver < 6) - { - // v<6 may not have change accumulated in m_amount_out, which is a pain, - // as it's readily understood to be sum of outputs. - // We convert it to include change from v6 - if (!typename Archive::is_saving() && x.m_change != (uint64_t)-1) - x.m_amount_out += x.m_change; - } - if (ver < 7) - { - x.m_subaddr_account = 0; - return; - } - a & x.m_subaddr_account; - a & x.m_subaddr_indices; - if (ver < 8) - return; - a & x.m_rings; - } - - template - inline void serialize(Archive &a, tools::wallet2::confirmed_transfer_details &x, const boost::serialization::version_type ver) - { - a & x.m_amount_in; - a & x.m_amount_out; - a & x.m_change; - a & x.m_block_height; - if (ver < 1) - return; - a & x.m_dests; - a & x.m_payment_id; - if (ver < 2) - return; - a & x.m_timestamp; - if (ver < 3) - { - // v<3 may not have change accumulated in m_amount_out, which is a pain, - // as it's readily understood to be sum of outputs. Whether it got added - // or not depends on whether it came from a unconfirmed_transfer_details - // (not included) or not (included). We can't reliably tell here, so we - // check whether either yields a "negative" fee, or use the other if so. - // We convert it to include change from v3 - if (!typename Archive::is_saving() && x.m_change != (uint64_t)-1) - { - if (x.m_amount_in > (x.m_amount_out + x.m_change)) - x.m_amount_out += x.m_change; - } - } - if (ver < 4) - { - if (!typename Archive::is_saving()) - x.m_unlock_time = 0; - return; - } - a & x.m_unlock_time; - if (ver < 5) - { - x.m_subaddr_account = 0; - return; - } - a & x.m_subaddr_account; - a & x.m_subaddr_indices; - if (ver < 6) - return; - a & x.m_rings; - } - - template - inline void serialize(Archive& a, tools::wallet2::payment_details& x, const boost::serialization::version_type ver) - { - a & x.m_tx_hash; - a & x.m_amount; - a & x.m_block_height; - a & x.m_unlock_time; - if (ver < 1) - return; - a & x.m_timestamp; - if (ver < 2) - { - x.m_coinbase = false; - x.m_subaddr_index = {}; - return; - } - a & x.m_subaddr_index; - if (ver < 3) - { - x.m_coinbase = false; - x.m_fee = 0; - return; - } - a & x.m_fee; - if (ver < 4) - { - x.m_coinbase = false; - return; - } - a & x.m_coinbase; - if (ver < 5) - return; - a & x.m_amounts; - } - - template - inline void serialize(Archive& a, tools::wallet2::pool_payment_details& x, const boost::serialization::version_type ver) - { - a & x.m_pd; - a & x.m_double_spend_seen; - } - - template - inline void serialize(Archive& a, tools::wallet2::address_book_row& x, const boost::serialization::version_type ver) - { - a & x.m_address; - if (ver < 18) - { - crypto::hash payment_id; - a & payment_id; - x.m_has_payment_id = !(payment_id == crypto::null_hash); - if (x.m_has_payment_id) - { - bool is_long = false; - for (int i = 8; i < 32; ++i) - is_long |= payment_id.data[i]; - if (is_long) - { - MWARNING("Long payment ID ignored on address book load"); - x.m_payment_id = crypto::null_hash8; - x.m_has_payment_id = false; - } - else - memcpy(x.m_payment_id.data, payment_id.data, 8); - } - } - a & x.m_description; - if (ver < 17) - { - x.m_is_subaddress = false; - return; - } - a & x.m_is_subaddress; - if (ver < 18) - return; - a & x.m_has_payment_id; - if (x.m_has_payment_id) - a & x.m_payment_id; - } - - template - inline void serialize(Archive& a, tools::wallet2::background_synced_tx_t &x, const boost::serialization::version_type ver) - { - a & x.index_in_background_sync_data; - a & x.tx; - a & x.output_indices; - a & x.height; - a & x.block_timestamp; - a & x.double_spend_seen; - } - - template - inline void serialize(Archive& a, tools::wallet2::background_sync_data_t &x, const boost::serialization::version_type ver) - { - a & x.first_refresh_done; - a & x.start_height; - a & x.txs; - a & x.wallet_refresh_from_block_height; - a & x.subaddress_lookahead_major; - a & x.subaddress_lookahead_minor; - a & x.wallet_refresh_type; - } - } -} namespace tools { diff --git a/src/wallet/wallet2_basic/CMakeLists.txt b/src/wallet/wallet2_basic/CMakeLists.txt new file mode 100644 index 0000000000..58ba75012d --- /dev/null +++ b/src/wallet/wallet2_basic/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (c) 2025, The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/wallet/wallet2_basic/README.md b/src/wallet/wallet2_basic/README.md new file mode 100644 index 0000000000..cf86e77ea9 --- /dev/null +++ b/src/wallet/wallet2_basic/README.md @@ -0,0 +1,3 @@ +# `wallet2_basic` + +The `wallet2_basic` library is a minimalistic library for loading / storing historic `wallet2` files. diff --git a/src/wallet/wallet2_basic/wallet2_boost_serialization.h b/src/wallet/wallet2_basic/wallet2_boost_serialization.h new file mode 100644 index 0000000000..01209d695f --- /dev/null +++ b/src/wallet/wallet2_basic/wallet2_boost_serialization.h @@ -0,0 +1,433 @@ +// Copyright (c) 2025, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +//local headers +#include "common/unordered_containers_boost_serialization.h" +#include "cryptonote_basic/cryptonote_boost_serialization.h" +#include "cryptonote_basic/account_boost_serialization.h" +#include "wallet2_types.h" + +//third party headers +#include +#include +#include + +//standard headers + +//forward declarations + +namespace boost +{ +namespace serialization +{ +template +void serialize(Archive &a, wallet2_basic::multisig_info::LR &x, const unsigned int ver) +{ + a & x.m_L; + a & x.m_R; +} + +template +void serialize(Archive &a, wallet2_basic::multisig_info &x, const unsigned int ver) +{ + a & x.m_signer; + a & x.m_LR; + a & x.m_partial_key_images; +} + +template +std::enable_if_t +initialize_transfer_details(Archive &a, wallet2_basic::transfer_details &x, const unsigned int ver) +{} + +template +std::enable_if_t +initialize_transfer_details(Archive &a, wallet2_basic::transfer_details &x, const unsigned int ver) +{ + if (ver < 1) + { + x.m_mask = rct::identity(); + x.m_amount = x.m_tx.vout[x.m_internal_output_index].amount; + } + if (ver < 2) + { + x.m_spent_height = 0; + } + if (ver < 4) + { + x.m_rct = x.m_tx.vout[x.m_internal_output_index].amount == 0; + } + if (ver < 6) + { + x.m_key_image_known = true; + } + if (ver < 7) + { + x.m_pk_index = 0; + } + if (ver < 8) + { + x.m_subaddr_index = {}; + } + if (ver < 9) + { + x.m_key_image_partial = false; + x.m_multisig_k.clear(); + x.m_multisig_info.clear(); + } + if (ver < 10) + { + x.m_key_image_request = false; + } + if (ver < 12) + { + x.m_frozen = false; + } +} + +template +void serialize(Archive &a, wallet2_basic::hashchain &x, const unsigned int ver) +{ + a & x.m_offset; + a & x.m_genesis; + a & x.m_blockchain; +} + +template +void serialize(Archive &a, wallet2_basic::transfer_details &x, const unsigned int ver) +{ + a & x.m_block_height; + a & x.m_global_output_index; + a & x.m_internal_output_index; + if (ver < 3) + { + cryptonote::transaction tx; + a & tx; + x.m_tx = (const cryptonote::transaction_prefix&)tx; + x.m_txid = cryptonote::get_transaction_hash(tx); + } + else + { + a & x.m_tx; + } + a & x.m_spent; + a & x.m_key_image; + if (ver < 1) + { + // ensure mask and amount are set + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_mask; + a & x.m_amount; + if (ver < 2) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_spent_height; + if (ver < 3) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_txid; + if (ver < 4) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_rct; + if (ver < 5) + { + initialize_transfer_details(a, x, ver); + return; + } + if (ver < 6) + { + // v5 did not properly initialize + uint8_t u = 0; + a & u; + x.m_key_image_known = true; + return; + } + a & x.m_key_image_known; + if (ver < 7) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_pk_index; + if (ver < 8) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_subaddr_index; + if (ver < 9) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_multisig_info; + a & x.m_multisig_k; + a & x.m_key_image_partial; + if (ver < 10) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_key_image_request; + if (ver < 11) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_uses; + if (ver < 12) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_frozen; +} + +template +void serialize(Archive &a, wallet2_basic::unconfirmed_transfer_details &x, const unsigned int ver) +{ + a & x.m_change; + a & x.m_sent_time; + if (ver < 5) + { + cryptonote::transaction tx; + a & tx; + x.m_tx = (const cryptonote::transaction_prefix&)tx; + } + else + { + a & x.m_tx; + } + if (ver < 1) + return; + a & x.m_dests; + a & x.m_payment_id; + if (ver < 2) + return; + a & x.m_state; + if (ver < 3) + return; + a & x.m_timestamp; + if (ver < 4) + return; + a & x.m_amount_in; + a & x.m_amount_out; + if (ver < 6) + { + // v<6 may not have change accumulated in m_amount_out, which is a pain, + // as it's readily understood to be sum of outputs. + // We convert it to include change from v6 + if (!typename Archive::is_saving() && x.m_change != (uint64_t)1) + x.m_amount_out += x.m_change; + } + if (ver < 7) + { + x.m_subaddr_account = 0; + return; + } + a & x.m_subaddr_account; + a & x.m_subaddr_indices; + if (ver < 8) + return; + a & x.m_rings; +} + +template +void serialize(Archive &a, wallet2_basic::confirmed_transfer_details &x, const unsigned int ver) +{ + a & x.m_amount_in; + a & x.m_amount_out; + a & x.m_change; + a & x.m_block_height; + if (ver < 1) + return; + a & x.m_dests; + a & x.m_payment_id; + if (ver < 2) + return; + a & x.m_timestamp; + if (ver < 3) + { + // v<3 may not have change accumulated in m_amount_out, which is a pain, + // as it's readily understood to be sum of outputs. Whether it got added + // or not depends on whether it came from a unconfirmed_transfer_details + // (not included) or not (included). We can't reliably tell here, so we + // check whether either yields a "negative" fee, or use the other if so. + // We convert it to include change from v3 + if (!typename Archive::is_saving() && x.m_change != (uint64_t)1) + { + if (x.m_amount_in > (x.m_amount_out + x.m_change)) + x.m_amount_out += x.m_change; + } + } + if (ver < 4) + { + if (!typename Archive::is_saving()) + x.m_unlock_time = 0; + return; + } + a & x.m_unlock_time; + if (ver < 5) + { + x.m_subaddr_account = 0; + return; + } + a & x.m_subaddr_account; + a & x.m_subaddr_indices; + if (ver < 6) + return; + a & x.m_rings; +} + +template +void serialize(Archive& a, wallet2_basic::payment_details& x, const unsigned int ver) +{ + a & x.m_tx_hash; + a & x.m_amount; + a & x.m_block_height; + a & x.m_unlock_time; + if (ver < 1) + return; + a & x.m_timestamp; + if (ver < 2) + { + x.m_coinbase = false; + x.m_subaddr_index = {}; + return; + } + a & x.m_subaddr_index; + if (ver < 3) + { + x.m_coinbase = false; + x.m_fee = 0; + return; + } + a & x.m_fee; + if (ver < 4) + { + x.m_coinbase = false; + return; + } + a & x.m_coinbase; + if (ver < 5) + return; + a & x.m_amounts; +} + +template +void serialize(Archive& a, wallet2_basic::pool_payment_details& x, const unsigned int ver) +{ + a & x.m_pd; + a & x.m_double_spend_seen; +} + +template +void serialize(Archive& a, wallet2_basic::address_book_row& x, const unsigned int ver) +{ + a & x.m_address; + if (ver < 18) + { + crypto::hash payment_id; + a & payment_id; + x.m_has_payment_id = !(payment_id == crypto::null_hash); + if (x.m_has_payment_id) + { + bool is_long = false; + for (int i = 8; i < 32; ++i) + is_long |= payment_id.data[i]; + if (is_long) + { + MWARNING("Long payment ID ignored on address book load"); + x.m_payment_id = crypto::null_hash8; + x.m_has_payment_id = false; + } + else + memcpy(x.m_payment_id.data, payment_id.data, 8); + } + } + a & x.m_description; + if (ver < 17) + { + x.m_is_subaddress = false; + return; + } + a & x.m_is_subaddress; + if (ver < 18) + return; + a & x.m_has_payment_id; + if (x.m_has_payment_id) + a & x.m_payment_id; +} + +template +inline void serialize(Archive& a, wallet2_basic::background_synced_tx_t &x, const unsigned int ver) +{ + a & x.index_in_background_sync_data; + a & x.tx; + a & x.output_indices; + a & x.height; + a & x.block_timestamp; + a & x.double_spend_seen; +} + +template +inline void serialize(Archive& a, wallet2_basic::background_sync_data_t &x, const unsigned int ver) +{ + a & x.first_refresh_done; + a & x.start_height; + a & x.txs; + a & x.wallet_refresh_from_block_height; + a & x.subaddress_lookahead_major; + a & x.subaddress_lookahead_minor; + a & x.wallet_refresh_type; +} +} // namespace serialization +} // namespace boost + +BOOST_CLASS_VERSION(wallet2_basic::hashchain, 0) +BOOST_CLASS_VERSION(wallet2_basic::transfer_details, 12) +BOOST_CLASS_VERSION(wallet2_basic::multisig_info::LR, 0) +BOOST_CLASS_VERSION(wallet2_basic::multisig_info, 1) +BOOST_CLASS_VERSION(wallet2_basic::unconfirmed_transfer_details, 8) +BOOST_CLASS_VERSION(wallet2_basic::confirmed_transfer_details, 6) +BOOST_CLASS_VERSION(wallet2_basic::payment_details, 5) +BOOST_CLASS_VERSION(wallet2_basic::pool_payment_details, 1) +BOOST_CLASS_VERSION(wallet2_basic::address_book_row, 18) +BOOST_CLASS_VERSION(wallet2_basic::background_synced_tx_t, 0) +BOOST_CLASS_VERSION(wallet2_basic::background_sync_data_t, 0) diff --git a/src/wallet/wallet2_basic/wallet2_serialization.h b/src/wallet/wallet2_basic/wallet2_serialization.h new file mode 100644 index 0000000000..fa29851e7e --- /dev/null +++ b/src/wallet/wallet2_basic/wallet2_serialization.h @@ -0,0 +1,169 @@ +// Copyright (c) 2025, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +//local headers +#include "wallet2_types.h" + +//third party headers + +//standard headers + +//forward declarations + +namespace wallet2_basic +{ +BEGIN_SERIALIZE_OBJECT_FN(hashchain) + VERSION_FIELD(0) + VARINT_FIELD_F(m_offset) + FIELD_F(m_genesis) + FIELD_F(m_blockchain) +END_SERIALIZE() + +BEGIN_SERIALIZE_OBJECT_FN(multisig_info::LR) + FIELD_F(m_L) + FIELD_F(m_R) +END_SERIALIZE() + +BEGIN_SERIALIZE_OBJECT_FN(multisig_info) + FIELD_F(m_signer) + FIELD_F(m_LR) + FIELD_F(m_partial_key_images) +END_SERIALIZE() + +BEGIN_SERIALIZE_OBJECT_FN(transfer_details) + FIELD_F(m_block_height) + FIELD_F(m_tx) + FIELD_F(m_txid) + FIELD_F(m_internal_output_index) + FIELD_F(m_global_output_index) + FIELD_F(m_spent) + FIELD_F(m_frozen) + FIELD_F(m_spent_height) + FIELD_F(m_key_image) + FIELD_F(m_mask) + FIELD_F(m_amount) + FIELD_F(m_rct) + FIELD_F(m_key_image_known) + FIELD_F(m_key_image_request) + FIELD_F(m_pk_index) + FIELD_F(m_subaddr_index) + FIELD_F(m_key_image_partial) + FIELD_F(m_multisig_k) + FIELD_F(m_multisig_info) + FIELD_F(m_uses) +END_SERIALIZE() + +BEGIN_SERIALIZE_OBJECT_FN(unconfirmed_transfer_details) + VERSION_FIELD(1) + FIELD_F(m_tx) + VARINT_FIELD_F(m_amount_in) + VARINT_FIELD_F(m_amount_out) + VARINT_FIELD_F(m_change) + VARINT_FIELD_F(m_sent_time) + FIELD_F(m_dests) + FIELD_F(m_payment_id) + if (version >= 1) + VARINT_FIELD_F(m_state) + VARINT_FIELD_F(m_timestamp) + VARINT_FIELD_F(m_subaddr_account) + FIELD_F(m_subaddr_indices) + FIELD_F(m_rings) +END_SERIALIZE() + +BEGIN_SERIALIZE_OBJECT_FN(confirmed_transfer_details) + VERSION_FIELD(1) + if (version >= 1) + FIELD_F(m_tx) + VARINT_FIELD_F(m_amount_in) + VARINT_FIELD_F(m_amount_out) + VARINT_FIELD_F(m_change) + VARINT_FIELD_F(m_block_height) + FIELD_F(m_dests) + FIELD_F(m_payment_id) + VARINT_FIELD_F(m_timestamp) + VARINT_FIELD_F(m_unlock_time) + VARINT_FIELD_F(m_subaddr_account) + FIELD_F(m_subaddr_indices) + FIELD_F(m_rings) +END_SERIALIZE() + +BEGIN_SERIALIZE_OBJECT_FN(payment_details) + VERSION_FIELD(0) + FIELD_F(m_tx_hash) + VARINT_FIELD_F(m_amount) + FIELD_F(m_amounts) + VARINT_FIELD_F(m_fee) + VARINT_FIELD_F(m_block_height) + VARINT_FIELD_F(m_unlock_time) + VARINT_FIELD_F(m_timestamp) + FIELD_F(m_coinbase) + FIELD_F(m_subaddr_index) +END_SERIALIZE() + +BEGIN_SERIALIZE_OBJECT_FN(pool_payment_details) + VERSION_FIELD(0) + FIELD_F(m_pd) + FIELD_F(m_double_spend_seen) +END_SERIALIZE() + +BEGIN_SERIALIZE_OBJECT_FN(address_book_row) + VERSION_FIELD(0) + FIELD_F(m_address) + FIELD_F(m_payment_id) + FIELD_F(m_description) + FIELD_F(m_is_subaddress) + FIELD_F(m_has_payment_id) +END_SERIALIZE() + +BEGIN_SERIALIZE_OBJECT_FN(background_synced_tx_t) + VERSION_FIELD(0) + VARINT_FIELD_F(index_in_background_sync_data) + + // prune tx; don't need to keep signature data + if (!v.tx.serialize_base(ar)) + return false; + + FIELD_F(output_indices) + VARINT_FIELD_F(height) + VARINT_FIELD_F(block_timestamp) + FIELD_F(double_spend_seen) +END_SERIALIZE() + +BEGIN_SERIALIZE_OBJECT_FN(background_sync_data_t) + VERSION_FIELD(0) + FIELD_F(first_refresh_done) + FIELD_F(start_height) + FIELD_F(txs) + FIELD_F(wallet_refresh_from_block_height) + VARINT_FIELD_F(subaddress_lookahead_major) + VARINT_FIELD_F(subaddress_lookahead_minor) + VARINT_FIELD_F(wallet_refresh_type) +END_SERIALIZE() +} //namespace wallet2_basic diff --git a/src/wallet/wallet2_basic/wallet2_types.h b/src/wallet/wallet2_basic/wallet2_types.h new file mode 100644 index 0000000000..55efadf1fd --- /dev/null +++ b/src/wallet/wallet2_basic/wallet2_types.h @@ -0,0 +1,319 @@ +// Copyright (c) 2025, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +//local headers +#include "cryptonote_basic/cryptonote_basic.h" +#include "cryptonote_basic/subaddress_index.h" +#include "wallet/wallet_errors.h" + +//third party headers + +//standard headers + +//forward declarations +namespace wallet2_basic +{ +class hashchain; +template class Archive> +bool do_serialize_object(Archive &ar, hashchain &v); +} +namespace boost { namespace serialization { +template void serialize(Archive&, wallet2_basic::hashchain&, unsigned int); +}} + + +namespace wallet2_basic +{ +/** + * @brief: caches a contiguous list of block hashes and a genesis block +*/ +class hashchain +{ +public: + hashchain(): m_genesis(crypto::null_hash), m_offset(0) {} + + /** + * @brief: get the "height" of the blockchain, not the number of hashes stored + */ + size_t size() const { return m_blockchain.size() + m_offset; } + /** + * @brief: get the height that the hash list begins at + */ + size_t offset() const { return m_offset; } + /** + * @brief: get the genesis bloch hash + */ + const crypto::hash &genesis() const { return m_genesis; } + /** + * @brief: add a block hash to the top of the chain + */ + void push_back(const crypto::hash &hash) { if (m_offset == 0 && m_blockchain.empty()) m_genesis = hash; m_blockchain.push_back(hash); } + /** + * @brief: query if there is a hash available for a given height + */ + bool is_in_bounds(size_t idx) const { return idx >= m_offset && idx < size(); } + /** + * @brief: get a const reference to the block hash at a given height + */ + const crypto::hash &operator[](size_t idx) const { return m_blockchain[idx - m_offset]; } + /** + * @brief: get a mutable reference to the block hash at a given height + */ + crypto::hash &operator[](size_t idx) { return m_blockchain[idx - m_offset]; } + /** + * @brief: crop stored hashes after a certain height, where the height of the top block == `height`-1 + */ + void crop(size_t height) { m_blockchain.resize(std::max(std::min(height, size()), m_offset) - m_offset); } + /** + * @brief: delete all stored hashes and set the offset to 0 + */ + void clear() { m_offset = 0; m_blockchain.clear(); } + /** + * @brief: query if the blockchain is "empty": there are no stored hashes and the offset is 0 + */ + bool empty() const { return m_blockchain.empty() && m_offset == 0; } + /** + * @brief: crop stored hashes before a certain height and shift the offset accordingly, but always leave at least 1 hash + */ + void trim(size_t height) { while (height > m_offset && m_blockchain.size() > 1) { m_blockchain.pop_front(); ++m_offset; } m_blockchain.shrink_to_fit(); } + /** + * @brief: push a block hash onto the chain and move all block hashes back by one block + */ + void refill(const crypto::hash &hash) { m_blockchain.push_back(hash); --m_offset; } + +private: + size_t m_offset; + crypto::hash m_genesis; + std::deque m_blockchain; + + template class Archive> + friend bool ::wallet2_basic::do_serialize_object(Archive &ar, hashchain &v); + template + friend void ::boost::serialization::serialize(Archive &a, hashchain &x, const unsigned int ver); +}; + +struct multisig_info +{ + struct LR + { + rct::key m_L; + rct::key m_R; + }; + + crypto::public_key m_signer; + std::vector m_LR; + std::vector m_partial_key_images; // one per key the participant has +}; + +struct transfer_details +{ + uint64_t m_block_height; + cryptonote::transaction_prefix m_tx; + crypto::hash m_txid; + uint64_t m_internal_output_index; + uint64_t m_global_output_index; + bool m_spent; + bool m_frozen; + uint64_t m_spent_height; + crypto::key_image m_key_image; //TODO: key_image stored twice :( + rct::key m_mask; + uint64_t m_amount; + bool m_rct; + bool m_key_image_known; + bool m_key_image_request; // view wallets: we want to request it; cold wallets: it was requested + uint64_t m_pk_index; + cryptonote::subaddress_index m_subaddr_index; + bool m_key_image_partial; + std::vector m_multisig_k; + std::vector m_multisig_info; // one per other participant + std::vector> m_uses; + + bool is_rct() const { return m_rct; } + uint64_t amount() const { return m_amount; } + const crypto::public_key get_public_key() const { + crypto::public_key output_public_key; + THROW_WALLET_EXCEPTION_IF(m_tx.vout.size() <= m_internal_output_index, + tools::error::wallet_internal_error, "Too few outputs, outputs may be corrupted"); + THROW_WALLET_EXCEPTION_IF(!get_output_public_key(m_tx.vout[m_internal_output_index], output_public_key), + tools::error::wallet_internal_error, "Unable to get output public key from output"); + return output_public_key; + }; +}; + +typedef std::vector transfer_container; + +struct unconfirmed_transfer_details +{ + cryptonote::transaction_prefix m_tx; + uint64_t m_amount_in; + uint64_t m_amount_out; + uint64_t m_change; + time_t m_sent_time; + std::vector m_dests; + crypto::hash m_payment_id; + enum { pending, pending_in_pool, failed } m_state; + uint64_t m_timestamp; + uint32_t m_subaddr_account; // subaddress account of your wallet to be used in this transfer + std::set m_subaddr_indices; // set of address indices used as inputs in this transfer + std::vector>> m_rings; // relative +}; + +struct confirmed_transfer_details +{ + cryptonote::transaction_prefix m_tx; + uint64_t m_amount_in; + uint64_t m_amount_out; + uint64_t m_change; + uint64_t m_block_height; + std::vector m_dests; + crypto::hash m_payment_id; + uint64_t m_timestamp; + uint64_t m_unlock_time; + uint32_t m_subaddr_account; // subaddress account of your wallet to be used in this transfer + std::set m_subaddr_indices; // set of address indices used as inputs in this transfer + std::vector>> m_rings; // relative + + confirmed_transfer_details() + : m_amount_in(0) + , m_amount_out(0) + , m_change((uint64_t)-1) + , m_block_height(0) + , m_payment_id(crypto::null_hash) + , m_timestamp(0) + , m_unlock_time(0) + , m_subaddr_account((uint32_t)-1) + {} + + confirmed_transfer_details(const unconfirmed_transfer_details &utd, uint64_t height) + : m_tx(utd.m_tx) + , m_amount_in(utd.m_amount_in) + , m_amount_out(utd.m_amount_out) + , m_change(utd.m_change) + , m_block_height(height) + , m_dests(utd.m_dests) + , m_payment_id(utd.m_payment_id) + , m_timestamp(utd.m_timestamp) + , m_unlock_time(utd.m_tx.unlock_time) + , m_subaddr_account(utd.m_subaddr_account) + , m_subaddr_indices(utd.m_subaddr_indices) + , m_rings(utd.m_rings) + {} +}; + +typedef std::vector amounts_container; +struct payment_details +{ + crypto::hash m_tx_hash; + uint64_t m_amount; + amounts_container m_amounts; + uint64_t m_fee; + uint64_t m_block_height; + uint64_t m_unlock_time; + uint64_t m_timestamp; + bool m_coinbase; + cryptonote::subaddress_index m_subaddr_index; +}; + +typedef std::unordered_multimap payment_container; + +struct pool_payment_details +{ + payment_details m_pd; + bool m_double_spend_seen; +}; + +// GUI Address book +struct address_book_row +{ + cryptonote::account_public_address m_address; + crypto::hash8 m_payment_id; + std::string m_description; + bool m_is_subaddress; + bool m_has_payment_id; +}; + +enum RefreshType +{ + RefreshFull, + RefreshOptimizeCoinbase, + RefreshNoCoinbase, + RefreshDefault = RefreshOptimizeCoinbase, +}; + +enum AskPasswordType +{ + AskPasswordNever = 0, + AskPasswordOnAction = 1, + AskPasswordToDecrypt = 2, +}; + +enum BackgroundMiningSetupType +{ + BackgroundMiningMaybe = 0, + BackgroundMiningYes = 1, + BackgroundMiningNo = 2, +}; + +enum BackgroundSyncType +{ + BackgroundSyncOff = 0, + BackgroundSyncReusePassword = 1, + BackgroundSyncCustomPassword = 2, +}; + +enum ExportFormat +{ + Binary = 0, + Ascii, +}; + +struct background_synced_tx_t +{ + uint64_t index_in_background_sync_data; + cryptonote::transaction tx; + std::vector output_indices; + uint64_t height; + uint64_t block_timestamp; + bool double_spend_seen; +}; + +struct background_sync_data_t +{ + bool first_refresh_done = false; + uint64_t start_height = 0; + std::unordered_map txs; + + // Relevant wallet settings + uint64_t wallet_refresh_from_block_height; + size_t subaddress_lookahead_major; + size_t subaddress_lookahead_minor; + RefreshType wallet_refresh_type; +}; +} // namespace wallet2_basic diff --git a/tests/fuzz/cold-outputs.cpp b/tests/fuzz/cold-outputs.cpp index 72847a260c..929500d91a 100644 --- a/tests/fuzz/cold-outputs.cpp +++ b/tests/fuzz/cold-outputs.cpp @@ -32,6 +32,7 @@ #include "cryptonote_basic/cryptonote_basic.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "wallet/wallet2.h" +#include "wallet/wallet2_basic/wallet2_serialization.h" #include "fuzzer.h" static tools::wallet2 *wallet = NULL; diff --git a/tests/fuzz/cold-transaction.cpp b/tests/fuzz/cold-transaction.cpp index 395de3fd5b..e5f45446ef 100644 --- a/tests/fuzz/cold-transaction.cpp +++ b/tests/fuzz/cold-transaction.cpp @@ -32,6 +32,7 @@ #include "cryptonote_basic/cryptonote_basic.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "wallet/wallet2.h" +#include "wallet/wallet2_basic/wallet2_serialization.h" #include "fuzzer.h" static tools::wallet2 *wallet = NULL; diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index d15e3f378d..1bf725a586 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -45,6 +45,8 @@ #include "serialization/containers.h" #include "serialization/binary_utils.h" #include "wallet/wallet2.h" +#include "wallet/wallet2_basic/wallet2_boost_serialization.h" +#include "wallet/wallet2_basic/wallet2_serialization.h" #include "gtest/gtest.h" #include "unit_tests_utils.h" #include "device/device.hpp" From 99e4b658a2030f754897e06fdc66a0925b578aa2 Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Thu, 25 Sep 2025 17:45:31 -0500 Subject: [PATCH 2/2] @j-berman #10084 review: indent Co-authored-by: j-berman --- .../wallet2_boost_serialization.h | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/wallet/wallet2_basic/wallet2_boost_serialization.h b/src/wallet/wallet2_basic/wallet2_boost_serialization.h index 01209d695f..4460d4fba3 100644 --- a/src/wallet/wallet2_basic/wallet2_boost_serialization.h +++ b/src/wallet/wallet2_basic/wallet2_boost_serialization.h @@ -254,7 +254,7 @@ void serialize(Archive &a, wallet2_basic::unconfirmed_transfer_details &x, const // as it's readily understood to be sum of outputs. // We convert it to include change from v6 if (!typename Archive::is_saving() && x.m_change != (uint64_t)1) - x.m_amount_out += x.m_change; + x.m_amount_out += x.m_change; } if (ver < 7) { @@ -292,14 +292,14 @@ void serialize(Archive &a, wallet2_basic::confirmed_transfer_details &x, const u // We convert it to include change from v3 if (!typename Archive::is_saving() && x.m_change != (uint64_t)1) { - if (x.m_amount_in > (x.m_amount_out + x.m_change)) - x.m_amount_out += x.m_change; + if (x.m_amount_in > (x.m_amount_out + x.m_change)) + x.m_amount_out += x.m_change; } } if (ver < 4) { if (!typename Archive::is_saving()) - x.m_unlock_time = 0; + x.m_unlock_time = 0; return; } a & x.m_unlock_time; @@ -368,17 +368,17 @@ void serialize(Archive& a, wallet2_basic::address_book_row& x, const unsigned in x.m_has_payment_id = !(payment_id == crypto::null_hash); if (x.m_has_payment_id) { - bool is_long = false; - for (int i = 8; i < 32; ++i) - is_long |= payment_id.data[i]; - if (is_long) - { - MWARNING("Long payment ID ignored on address book load"); - x.m_payment_id = crypto::null_hash8; - x.m_has_payment_id = false; - } - else - memcpy(x.m_payment_id.data, payment_id.data, 8); + bool is_long = false; + for (int i = 8; i < 32; ++i) + is_long |= payment_id.data[i]; + if (is_long) + { + MWARNING("Long payment ID ignored on address book load"); + x.m_payment_id = crypto::null_hash8; + x.m_has_payment_id = false; + } + else + memcpy(x.m_payment_id.data, payment_id.data, 8); } } a & x.m_description;