From ce3f27b8ce8fbcef43cbecfb1debc795cd2535ef Mon Sep 17 00:00:00 2001 From: Lee *!* Clagett Date: Tue, 1 Apr 2025 05:35:25 -0400 Subject: [PATCH] Several big changes: - Stubbed out incomplete components to get fully compilation - Added a (mostly) working ledger output as example usage - Fixed several minor bugs discovered during first usage --- CMakeLists.txt | 13 ++ LICENSE | 28 --- src/CMakeLists.txt | 14 +- src/backend.cpp | 80 +++++-- src/backend.h | 5 +- src/error.cpp | 23 ++ src/error.h | 1 + src/ledger_main.cpp | 278 ++++++++++++++++++++++++ src/rpc.cpp | 49 ++++- src/rpc.h | 29 ++- src/subaddress_account.cpp | 148 +++++++++++++ src/subaddress_minor.cpp | 106 ++++++++++ src/subaddress_minor.h | 61 ++++++ src/transaction_history.h | 2 +- src/transaction_info.h | 2 - src/wallet.cpp | 364 ++++++++++++++++++++++++++++---- src/wallet.h | 265 ++++++++++++----------- src/wallet_manager.cpp | 55 +++-- src/wire/CMakeLists.txt | 10 +- src/wire/adapted/crypto.h | 20 +- src/wire/basic_value.cpp | 2 +- src/wire/json/CMakeLists.txt | 8 +- src/wire/json/base.h | 4 +- src/wire/json/read.cpp | 23 +- src/wire/json/read.h | 54 +++-- src/wire/msgpack/CMakeLists.txt | 8 +- src/wire/msgpack/read.cpp | 83 +++++++- src/wire/msgpack/read.h | 29 ++- src/wire/read.cpp | 22 +- src/wire/read.h | 154 +++++++++----- src/wire/traits.h | 55 +++-- src/wire/wrapper/CMakeLists.txt | 10 +- 32 files changed, 1594 insertions(+), 411 deletions(-) delete mode 100644 LICENSE create mode 100644 src/ledger_main.cpp create mode 100644 src/subaddress_account.cpp create mode 100644 src/subaddress_minor.cpp create mode 100644 src/subaddress_minor.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b1bdf7..02d81ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,22 +40,28 @@ set(MONERO_LIBRARIES serialization lmdb_lib net + checkpoints + hardforks cryptonote_core cryptonote_basic cryptonote_format_utils_basic ringct ringct_basic + mnemonics multisig blockchain_db common lmdb device + device_trezor cncrypto randomx epee easylogging version unbound + wallet + wallet_api ) set(MONERO_OPTIONAL unbound) @@ -64,12 +70,19 @@ set(MONERO_SEARCH_PATHS "/contrib/epee/src" "/external/db_drivers/liblmdb" "/external/easylogging++" + "/lib" "/src" + "src/checkpoints" "/src/crypto" "/src/cryptonote_basic" + "/src/cryptonote_core" + "/src/device" + "/src/device_trezor" + "/src/hardforks" "/src/lmdb" "/src/ringct" "/src/rpc" + "/src/wallet/api" ) diff --git a/LICENSE b/LICENSE deleted file mode 100644 index dbf279e..0000000 --- a/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2024, The Monero Project - -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/CMakeLists.txt b/src/CMakeLists.txt index 3cae6af..231b2bf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -27,21 +27,23 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. include_directories(.) +add_subdirectory(wire) # For both the server and admin utility. -set(monero-lwsf_sources +set(lwsf_sources address_book.cpp backend.cpp error.cpp pending_transaction.cpp rpc.cpp subaddress_account.cpp + subaddress_minor.cpp transaction_history.cpp transaction_info.cpp wallet.cpp wallet_manager.cpp ) -set(monero-lwsf_headers +set(lwsf_headers backend.h error.h lwsf_config.h @@ -52,9 +54,13 @@ set(monero-lwsf_headers wallet.h ) -add_executable(monero-lwsf dummy_main.cpp ${monero-lwsf_sources} ${monero-lwsf_headers}) -target_link_libraries(monero-lwsf +add_executable(lwsf-ledger ledger_main.cpp ${lwsf_sources} ${lwsf_headers}) +target_link_libraries(lwsf-ledger PRIVATE + lwsf-wire + lwsf-wire-json + lwsf-wire-msgpack + lwsf-wire-wrapper monero::libraries ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} diff --git a/src/backend.cpp b/src/backend.cpp index 0024320..507478a 100644 --- a/src/backend.cpp +++ b/src/backend.cpp @@ -29,6 +29,8 @@ #include "backend.h" #include +#include "crypto/crypto.h" // monero/src +#include "crypto/crypto-ops.h" // monero/src #include "cryptonote_basic/cryptonote_basic_impl.h" // monero/src #include "error.h" #include "lwsf_config.h" @@ -126,8 +128,7 @@ namespace lwsf { namespace internal { namespace backend void map_sub_account(F& format, T& self) { wire::object(format, - wire::field("minor", wire::trusted_array(std::ref(self.minor))), - WIRE_FIELD(label) + wire::field("minor", wire::trusted_array(std::ref(self.minor))) ); } @@ -207,6 +208,7 @@ namespace lwsf { namespace internal { namespace backend void map_account(F& format, T& self) { wire::object(format, + WIRE_FIELD(language), wire::field("addressbook", wire::trusted_array(std::ref(self.addressbook))), wire::field("subaccounts", wire::trusted_array(std::ref(self.subaccounts))), wire::field("txes", wire::trusted_array(std::ref(self.txes))), @@ -266,20 +268,34 @@ namespace lwsf { namespace internal { namespace backend out.sender = source.sender.value_or(rpc::address_meta{}); out.tx_pub = source.tx_pub_key; } - - void update_tx(const account& self, transaction& out, const rpc::transaction& source) + + crypto::secret_key get_spend_secret(const account& self, const std::optional& index) + { + if (!index || (index->maj_i == 0 && index->min_i == 0)) + return self.spend.sec; + + // m = Hs(a || index_major || index_minor) + const crypto::secret_key m = get_subaddress_secret_key(self.view.sec, index->maj_i, index->min_i); + + // D = B + M + crypto::secret_key out; + sc_add(to_bytes(out), to_bytes(m), to_bytes(self.spend.sec)); + return out; + } + + bool update_tx(const account& self, transaction& out, const rpc::transaction& source) { /* Let `receives` re-populate in `merge_output`. This works because the server supplies all info - there is no local info to keep. */ out.receives.clear(); - out.id = source.tx_hash; + out.id = source.hash; out.timestamp = source.timestamp; out.fee = std::uint64_t(source.fee.value_or(rpc::uint64_string(0))); out.height = source.height; out.unlock_time = source.unlock_time; out.payment_id = source.payment_id; - out.coinbase = source.is_coinbase; + out.coinbase = source.coinbase; std::uint64_t total_spent = 0; for (const auto& spend : source.spent_outputs) @@ -291,7 +307,7 @@ namespace lwsf { namespace internal { namespace backend if (!crypto::derive_public_key(derivation, spend.out_index, self.spend.pub, output_pub)) continue; crypto::secret_key output_secret{}; - crypto::derive_secret_key(derivation, spend.out_index, self.spend.sec, output_secret); + crypto::derive_secret_key(derivation, spend.out_index, get_spend_secret(self, spend.sender), output_secret); crypto::key_image image{}; crypto::generate_key_image(output_pub, output_secret, image); @@ -304,6 +320,12 @@ namespace lwsf { namespace internal { namespace backend } } + if (!std::uint64_t(source.total_received) && !total_spent) + return false; // used as decoy + + if (out.fee <= total_spent) + total_spent -= out.fee; + if (rpc::uint64_string(total_spent) < source.total_received) { out.direction = Monero::TransactionInfo::Direction_In; @@ -314,6 +336,8 @@ namespace lwsf { namespace internal { namespace backend out.direction = Monero::TransactionInfo::Direction_Out; out.amount = total_spent - std::uint64_t(source.total_received); } + + return true; } void merge_output(const std::shared_ptr& out, const rpc::output& source, const crypto::secret_key& view_key) @@ -326,7 +350,7 @@ namespace lwsf { namespace internal { namespace backend } std::vector> - merge_response(wallet& self, const rpc::get_address_txs& source, const rpc::get_unspent_outs& unspents) + merge_response(wallet& self, const rpc::get_address_txs& source, const rpc::get_unspent_outs_response& unspents) { std::vector> out; @@ -369,10 +393,10 @@ namespace lwsf { namespace internal { namespace backend for (const auto& tx : source.transactions) { - auto inserted = updated_txes.try_emplace(tx.tx_hash, nullptr); + auto inserted = updated_txes.try_emplace(tx.hash, nullptr); if (inserted.second) { - const auto existing = self.primary.txes.find(tx.tx_hash); + const auto existing = self.primary.txes.find(tx.hash); if (existing != self.primary.txes.end() && existing->second) inserted.first->second = std::make_shared(*existing->second); else @@ -381,7 +405,8 @@ namespace lwsf { namespace internal { namespace backend if (existing == self.primary.txes.end()) out.push_back(inserted.first->second); } - update_tx(self.primary, *inserted.first->second, tx); + if (!update_tx(self.primary, *inserted.first->second, tx)) + updated_txes.erase(tx.hash); } for (const auto& output : unspents.outputs) @@ -412,6 +437,9 @@ namespace lwsf { namespace internal { namespace backend std::map subaccounts; subaccounts.swap(self.subaccounts); + // Never remove special {0,0} account + self.subaccounts[0].minor[0] = std::move(subaccounts[0].minor[0]); + for (const auto& subaddr : subaddrs) { auto& account = self.subaccounts[subaddr.key]; @@ -421,7 +449,7 @@ namespace lwsf { namespace internal { namespace backend for (std::uint32_t minor = std::get<0>(minors); minor < std::uint64_t(std::get<1>(minors)) + 1; ++minor) { auto elem = account.minor.try_emplace(minor); - if (elem.second) + if (elem.second && (subaddr.key || minor)) elem.first->second = std::move(old.minor[minor]); } } @@ -430,6 +458,8 @@ namespace lwsf { namespace internal { namespace backend } // anonymous WIRE_DEFINE_OBJECT(address_book_entry, map_address_book_entry); + WIRE_DEFINE_OBJECT(subaddress, map_subaddress); + WIRE_DEFINE_OBJECT(sub_account, map_sub_account); WIRE_DEFINE_OBJECT(transfer_out, map_transfer_out); WIRE_DEFINE_OBJECT(transfer_in, map_transfer_in); WIRE_DEFINE_OBJECT(transaction, map_transaction); @@ -566,15 +596,28 @@ namespace lwsf { namespace internal { namespace backend return {}; } - expect outs_response{common_error::kInvalidArgument}; + expect outs_response{common_error::kInvalidArgument}; boost::unique_lock lock{sync}; + const auto now = std::chrono::steady_clock::now(); + if (!mandatory) // double-check + { + const std::chrono::steady_clock::time_point start{ + std::chrono::steady_clock::time_point::duration{last_sync.load()} + }; + if (now - start < config::refresh_interval) + return {}; + } + last_sync = now.time_since_epoch().count(); + const rpc::login login{primary.address, primary.view.sec}; lock.unlock(); - const auto txs_response = rpc::invoke(client, login); if (txs_response) - outs_response = rpc::invoke(client, login); + { + const rpc::get_unspent_outs_request request{login, rpc::uint64_string(0), 0, true}; + outs_response = rpc::invoke(client, request); + } if (!txs_response) return txs_response.error(); @@ -584,7 +627,6 @@ namespace lwsf { namespace internal { namespace backend lock.lock(); const std::uint64_t orig_scan_height = primary.scan_height; const auto new_txes = merge_response(*this, *txs_response, *outs_response); - last_sync = std::chrono::steady_clock::now().time_since_epoch().count(); const boost::lock_guard lock2{sync_listener}; if (!listener) @@ -670,6 +712,12 @@ namespace lwsf { namespace internal { namespace backend return {error::rpc_failure}; } + std::error_code wallet::restore_height(const std::uint64_t height) + { + /* TODO */ + throw std::logic_error{"restore_height not implemented"}; + } + std::error_code wallet::send_tx(epee::byte_slice tx_bytes) { const rpc::submit_raw_tx_request request{std::move(tx_bytes)}; diff --git a/src/backend.h b/src/backend.h index 5c523c6..435d7c8 100644 --- a/src/backend.h +++ b/src/backend.h @@ -177,6 +177,7 @@ namespace lwsf { namespace internal { namespace backend account() = delete; std::string address; //!> not serialized, recovered on read_bytes + std::string language; std::vector addressbook; std::map subaccounts; boost::container::flat_map, memory> txes; @@ -217,11 +218,11 @@ namespace lwsf { namespace internal { namespace backend //! Attempt std::error_code login(); - std::error_code add_subaccount(std::string label); - //! Refreshes txes information. Strong exception guarantee. Locks contents. std::error_code refresh(bool mandatory = false); + std::error_code add_subaccount(std::string label); + std::error_code restore_height(const std::uint64_t height); std::error_code send_tx(epee::byte_slice tx_bytes); diff --git a/src/error.cpp b/src/error.cpp index 4e7db1c..4bb87b3 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -33,6 +33,29 @@ namespace lwsf { switch (value) { + case error::none: + return "No error"; + case error::configuration: + return "Configuration issue"; + case error::connect_failure: + return "Connection to server failed"; + case error::crypto_failure: + return "Cryptography issue"; + case error::invalid_encoding: + return "File had invalid encoding"; + case error::invalid_scheme: + return "Invalid URL scheme"; + case error::read_failure: + return "Failed to read file"; + case error::rpc_failure: + return "RPC failed"; + case error::unexpected_userinfo: + return "Unexpected user+pass field in URL"; + case error::unsupported_format: + return "Could not unpack file"; + case error::write_failure: + return "Failed to write file"; + default: break; } diff --git a/src/error.h b/src/error.h index 84fe164..da54a3d 100644 --- a/src/error.h +++ b/src/error.h @@ -34,6 +34,7 @@ namespace lwsf enum class error : int { none = 0, //!< Must be zero for `expect<..>` + configuration, //!< Bad Configuration connect_failure, //!< Connection to server failed crypto_failure, //!< Failure in crypto invalid_encoding, //!< Invalid percent encoding diff --git a/src/ledger_main.cpp b/src/ledger_main.cpp new file mode 100644 index 0000000..75939ed --- /dev/null +++ b/src/ledger_main.cpp @@ -0,0 +1,278 @@ +// 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. + +#include "lws_frontend.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "common/command_line.h" // monero/src +#include "common/expect.h" // monero/src +#include "common/password.h" // monero/src +#include "common/util.h" // monero/src +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "error.h" + +namespace +{ + struct options + { + const command_line::arg_descriptor wallet_file; + const command_line::arg_descriptor lws_url; + const command_line::arg_descriptor network; + const command_line::arg_descriptor config_file; + + options() + : wallet_file{"wallet-file", "Path to read-and-store wallet. Omit to skip wallet cache storage."} + , lws_url{"lws-url", "://address:port of LWS server REST API. Omit to read cached data."} + , network{"network", "<\"main\"|\"stage\"|\"test\"> - Blockchain net type", "main"} + , config_file{"config-file", "Specify any option in a config file; = on separate lines"} + {} + + void prepare(boost::program_options::options_description& description) const + { + command_line::add_arg(description, wallet_file); + command_line::add_arg(description, lws_url); + command_line::add_arg(description, config_file); + command_line::add_arg(description, network); + command_line::add_arg(description, command_line::arg_help); + } + + Monero::NetworkType get_network(boost::program_options::variables_map const& args) const + { + const std::string net = command_line::get_arg(args, network); + if (net == "main") + return Monero::MAINNET; + else if (net == "stage") + return Monero::STAGENET; + else if (net == "test") + return Monero::TESTNET; + else + throw std::runtime_error{"Bad --network value"}; + } + }; + + struct program + { + std::string wallet_file; + std::string lws_url; + Monero::NetworkType network; + }; + + void print_help(std::ostream& out) + { + boost::program_options::options_description description{"Options"}; + options{}.prepare(description); + + out << "Usage: [options]" << std::endl; + out << description; + } + + std::optional get_program(int argc, char** argv) + { + namespace po = boost::program_options; + + const options opts{}; + po::variables_map args{}; + { + po::options_description description{"Options"}; + opts.prepare(description); + + po::store( + po::command_line_parser(argc, argv).options(description).run(), args + ); + po::notify(args); + + if (!command_line::is_arg_defaulted(args, opts.config_file)) + { + std::filesystem::path config_path{command_line::get_arg(args, opts.config_file)}; + if (!std::filesystem::exists(config_path)) + MONERO_THROW(lwsf::error::configuration, "Config file does not exist"); + + po::store( + po::parse_config_file(config_path.string().c_str(), description), args + ); + po::notify(args); + } + } + + if (command_line::get_arg(args, command_line::arg_help)) + { + print_help(std::cout); + return std::nullopt; + } + + program prog{ + command_line::get_arg(args, opts.wallet_file), + command_line::get_arg(args, opts.lws_url), + opts.get_network(args) + }; + + return prog; + } + + void run(program prog) + { + const std::unique_ptr wm{ + lwsf::WalletManagerFactory::getWalletManager() + }; + if (!wm) + throw std::runtime_error{"Unexpected WalletManager nullptr"}; + + std::unique_ptr wallet; + { + if (prog.wallet_file.empty() || !wm->walletExists(prog.wallet_file)) + { + std::cout << "Input seed words: "; + std::string seed; + std::getline(std::cin, seed, '\n'); + + std::string pass; + if (!prog.wallet_file.empty()) + { + const auto wiped = tools::password_container::prompt(true, "Wallet File Password"); + if (!wiped) + throw std::runtime_error{"Failed to get password"}; + pass.assign(wiped->password().data(), wiped->password().size()); + } + + wallet.reset(wm->recoveryWallet(prog.wallet_file, pass, seed, prog.network)); + } + else + { + const auto wiped = tools::password_container::prompt(false, "Wallet File Password"); + if (!wiped) + throw std::runtime_error{"Failed to get password"}; + + const std::string pass{wiped->password().data(), wiped->password().size()}; + wallet.reset(wm->openWallet(prog.wallet_file, pass, prog.network)); + + int status = 0; + std::string error; + wallet->statusWithErrorString(status, error); + if (status != Monero::Wallet::Status_Ok) + throw std::runtime_error{"Failed to open wallet: " + error}; + } + + if (!wallet) + throw std::runtime_error{"Unexpected Wallet nullptr"}; + } + + if (!prog.lws_url.empty()) + { + if (!wallet->init(prog.lws_url, 0, "", "", (prog.lws_url.substr(0, 8) == "https://"), true) || !wallet->refresh()) + throw std::runtime_error{"Failed to refresh wallet: " + wallet->errorString()}; + } + + if (!prog.wallet_file.empty()) + { + if (!wallet->store({})) + throw std::runtime_error{"Failed to store wallet file: " + wallet->errorString()}; + } + + std::cout << "; Primary Address: " << wallet->address() << std::endl; + std::cout << "; Blockchain Height: " << wallet->daemonBlockChainHeight() << std::endl; + std::cout << "; Start Height: " << wallet->getRefreshFromBlockHeight() << std::endl; + std::cout << "; Scan Height: " << wallet->blockChainHeight() << std::endl; + std::cout << "; Accounts: " << wallet->numSubaddressAccounts() << std::endl << std::endl; + + Monero::TransactionHistory* history = wallet->history(); + if (!history) + throw std::runtime_error{"Unexpected history nullptr"}; + + history->refresh(); + const std::vector& info_list = history->getAll(); + for (const Monero::TransactionInfo* info : info_list) + { + if (!info) + throw std::runtime_error{"Unexpected info nullptr"}; + + std::tm expanded{}; + const std::time_t timestamp = info->timestamp(); + if (!gmtime_r(std::addressof(timestamp), std::addressof(expanded))) + throw std::runtime_error{"gmtime failure"}; + + char buf[11] = {0}; + if (sizeof(buf) - 1 != std::strftime(buf, sizeof(buf), "%Y/%m/%d", std::addressof(expanded))) + throw std::runtime_error{"strftime failed"}; + + std::cout << buf << " (" << info->hash() << ") " << info->label() << std::endl; + + if (info->direction() == Monero::TransactionInfo::Direction_In) + { + std::cout << " Assets:Monero:" << info->subaddrAccount() << " " << cryptonote::print_money(info->amount()) << std::endl; + std::cout << " Income" << std::endl; + } + else + { + std::cout << " Expenses " << cryptonote::print_money(info->amount()) << std::endl; + std::cout << " Expenses:Monero:Fee " << cryptonote::print_money(info->fee()) << std::endl; + std::cout << " Assets:Monero:" << info->subaddrAccount() << std::endl; + } + } + } +} + +int main(int argc, char** argv) +{ + try + { + std::optional prog; + + try + { + prog = get_program(argc, argv); + } + catch (std::exception const& e) + { + std::cerr << e.what() << std::endl << std::endl; + print_help(std::cerr); + return EXIT_FAILURE; + } + + if (prog) + run(std::move(*prog)); + } + catch (std::exception const& e) + { + std::cerr << e.what() << std::endl;; + return EXIT_FAILURE; + } + catch (...) + { + std::cerr << "Unknown exception" << std::endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + diff --git a/src/rpc.cpp b/src/rpc.cpp index 83c9850..6365bd9 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -105,9 +105,9 @@ namespace lwsf { namespace internal { namespace rpc } void write_bytes(wire::writer& dest, const empty&) - { dest.boolean(true); } - void read_bytes(wire::reader& source, const empty&) - { source.boolean(); } + { wire::object(dest); } + void read_bytes(wire::reader& source, empty&) + { wire::object(source); } void write_bytes(wire::json_writer& dest, const login& self) { @@ -129,6 +129,23 @@ namespace lwsf { namespace internal { namespace rpc wire::object(source, WIRE_OPTIONAL_FIELD(start_height)); } + void read_bytes(wire::json_reader& source, daemon_status& self) + { + wire::object(source, + WIRE_FIELD(outgoing_connections_count), + WIRE_FIELD(incoming_connections_count), + WIRE_FIELD(height), + WIRE_FIELD(target_height) + ); + + } + + void write_bytes(wire::json_writer& dest, const uint64_string source) + { + const auto as_string = dest.to_string(std::uintmax_t(source)); + dest.string({as_string.data()}); + } + void read_bytes(wire::json_reader& source, uint64_string& dest) { dest = uint64_string(wire::integer::cast_unsigned(source.safe_unsigned_integer())); @@ -170,8 +187,8 @@ namespace lwsf { namespace internal { namespace rpc WIRE_FIELD(total_received), WIRE_FIELD(unlock_time), WIRE_OPTIONAL_FIELD(height), - WIRE_FIELD(tx_hash), - WIRE_FIELD(is_coinbase), + WIRE_FIELD(hash), + WIRE_FIELD(coinbase), WIRE_FIELD(mempool) ); @@ -200,10 +217,13 @@ namespace lwsf { namespace internal { namespace rpc // %Y-%m-%dT%H:%M:%SZ namespace qi = boost::spirit::qi; std::tm fields{}; - if (!qi::parse(timestamp->begin(), timestamp->end(), qi::ushort_ >> '-' >> qi::ushort_ >> '-' >> qi::ushort_ >> 'T' >> qi::ushort_ >> ':' >> qi::ushort_ >> qi::ushort_ >> 'Z', fields.tm_year, fields.tm_mon, fields.tm_mday, fields.tm_hour, fields.tm_min, fields.tm_sec)) + if (!qi::parse(timestamp->begin(), timestamp->end(), qi::ushort_ >> '-' >> qi::ushort_ >> '-' >> qi::ushort_ >> 'T' >> qi::ushort_ >> ':' >> qi::ushort_ >> ':' >> qi::ushort_ >> 'Z', fields.tm_year, fields.tm_mon, fields.tm_mday, fields.tm_hour, fields.tm_min, fields.tm_sec)) WIRE_DLOG_THROW(wire::error::schema::string, "Timestamp string invalid format"); - if ((self.timestamp = std::mktime(std::addressof(fields))) == -1) - WIRE_DLOG_THROW(wire::error::schema::string, "Invalid timestamp value"); + + fields.tm_year -= 1900; + --fields.tm_mon; + fields.tm_isdst = 0; + self.timestamp = timegm(std::addressof(fields)); } } @@ -288,7 +308,18 @@ namespace lwsf { namespace internal { namespace rpc self.rct = std::visit(convert_rct{}, raw_rct.value); } - void read_bytes(wire::json_reader& source, get_unspent_outs& self) + void write_bytes(wire::json_writer& dest, const get_unspent_outs_request& self) + { + wire::object(dest, + wire::field("address", std::cref(self.creds.address)), + wire::field("view_key", std::cref(self.creds.view_key)), + WIRE_FIELD_COPY(amount), + WIRE_FIELD_COPY(mixin), + WIRE_FIELD_COPY(use_dust) + ); + } + + void read_bytes(wire::json_reader& source, get_unspent_outs_response& self) { using min_output_size = wire::min_element_sizeof< ringct, crypto::hash, crypto::hash, crypto::public_key, crypto::public_key diff --git a/src/rpc.h b/src/rpc.h index 32e6b72..669191b 100644 --- a/src/rpc.h +++ b/src/rpc.h @@ -81,7 +81,7 @@ namespace lwsf { namespace internal { namespace rpc return result.error(); F out{}; - error = wire::json::from_bytes(std::move(*result), out); + error = wire::json::from_bytes(epee::to_span(*result), out); if (error) return error; return out; @@ -134,6 +134,7 @@ namespace lwsf { namespace internal { namespace rpc enum class uint64_string : std::uint64_t {}; + void write_bytes(wire::json_writer&, uint64_string); void read_bytes(wire::json_reader&, uint64_string&); struct address_meta @@ -178,8 +179,8 @@ namespace lwsf { namespace internal { namespace rpc uint64_string total_received; std::uint64_t unlock_time; std::optional height; - crypto::hash tx_hash; - bool is_coinbase; + crypto::hash hash; + bool coinbase; bool mempool; transaction() @@ -190,8 +191,8 @@ namespace lwsf { namespace internal { namespace rpc total_received(uint64_string(0)), unlock_time(0), height(), - tx_hash{}, - is_coinbase(false), + hash{}, + coinbase(false), mempool(false) {} }; @@ -273,16 +274,26 @@ namespace lwsf { namespace internal { namespace rpc }; void read_bytes(wire::json_reader&, output&); - struct get_unspent_outs + struct get_unspent_outs_request { - get_unspent_outs() = delete; + get_unspent_outs_request() = delete; + login creds; + uint64_string amount; + std::uint32_t mixin; + bool use_dust; + }; + void write_bytes(wire::json_writer&, const get_unspent_outs_request&); + + struct get_unspent_outs_response + { + get_unspent_outs_response() = delete; static constexpr const char* endpoint() noexcept { return "/get_unspent_outs"; } std::vector outputs; std::uint64_t per_byte_fee; std::uint64_t fee_mask; }; - void read_bytes(wire::json_reader&, get_unspent_outs&); + void read_bytes(wire::json_reader&, get_unspent_outs_response&); struct import_response @@ -335,7 +346,7 @@ namespace lwsf { namespace internal { namespace rpc }}} // lwsf // internal // rpc -WIRE_DECLARE_BLOB_NS(lwsf::internal::rpc::ringct); +WIRE_DECLARE_BLOB(lwsf::internal::rpc::ringct); namespace std { diff --git a/src/subaddress_account.cpp b/src/subaddress_account.cpp new file mode 100644 index 0000000..bb35743 --- /dev/null +++ b/src/subaddress_account.cpp @@ -0,0 +1,148 @@ +// 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. + +#include "subaddress_account.h" + +#include +#include +#include +#include "backend.h" +#include "cryptonote_basic/cryptonote_format_utils.h" // monero/src + +namespace lwsf { namespace internal +{ + namespace + { + struct balance + { + std::uint64_t unlocked; + std::uint64_t total; + + balance() + : unlocked(0), total(0) + {} + }; + + template + std::vector copy(const T& src) + { + std::vector out; + out.reserve(src.size()); + for (const auto& elem : src) + out.push_back(elem); + return out; + } + + void free_rows(std::vector& rows) noexcept + { + for (auto row : rows) + delete row; + rows.clear(); + } + } + + subaddress_account::subaddress_account(std::shared_ptr data) + : data_(std::move(data)), rows_() + { + if (!data_) + throw std::invalid_argument{"lwsf::internal::subaddress_account cannot be given nullptr"}; + } + + subaddress_account::~subaddress_account() + { + free_rows(rows_); + } + + void subaddress_account::addRow(const std::string &label) + { + data_->add_subaccount(label); + refresh(); + } + + void subaddress_account::setLabel(uint32_t accountIndex, const std::string &label) + { + { + const boost::lock_guard lock{data_->sync}; + data_->primary.subaccounts.at(accountIndex).minor.at(0).label = label; + } + refresh(); + } + + void subaddress_account::refresh() + { + try + { + free_rows(rows_); + std::unordered_map balances; + + const boost::lock_guard lock{data_->sync}; + const Monero::NetworkType net_type = data_->primary.type; + const std::uint32_t chain_height = data_->blockchain_height; + + for (const auto& tx : data_->primary.txes) + { + const bool unlocked = tx.second->is_unlocked(chain_height, net_type); + for (const auto& receive : tx.second->receives) + { + auto& balance = balances[receive.second.recipient.maj_i]; + balance.total += receive.second.amount; + if (unlocked) + balance.unlocked += receive.second.amount; + } + + for (const auto& spend : tx.second->spends) + { + auto& balance = balances[spend.second.sender.maj_i]; + balance.unlocked -= spend.second.amount; + balance.total -= spend.second.amount; + } + } + + rows_.reserve(data_->primary.subaccounts.size()); + for (const auto& sub : data_->primary.subaccounts) + { + const auto& balance = balances[sub.first]; + rows_.push_back( + new Monero::SubaddressAccountRow{ + sub.first, + data_->get_spend_address({sub.first, 0}), + sub.second.minor.empty() ? "" : sub.second.minor.begin()->second.label, + cryptonote::print_money(balance.total), + cryptonote::print_money(balance.unlocked) + } + ); + } + } + catch (...) + { + free_rows(rows_); + throw; + } + } +}} // lwsf // internal + diff --git a/src/subaddress_minor.cpp b/src/subaddress_minor.cpp new file mode 100644 index 0000000..d5bded3 --- /dev/null +++ b/src/subaddress_minor.cpp @@ -0,0 +1,106 @@ +// 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. + +#include "subaddress_minor.h" + +#include +#include +#include +#include "backend.h" +#include "cryptonote_basic/cryptonote_format_utils.h" // monero/src + +namespace lwsf { namespace internal +{ + namespace + { + void free_rows(std::vector& rows) noexcept + { + for (auto row : rows) + delete row; + rows.clear(); + } + } + + subaddress_minor::subaddress_minor(std::shared_ptr data) + : data_(std::move(data)), rows_() + { + if (!data_) + throw std::invalid_argument{"lwsf::internal::subaddress_minor cannot be given nullptr"}; + } + + subaddress_minor::~subaddress_minor() + { + free_rows(rows_); + } + + void subaddress_minor::refresh(const std::uint32_t accountIndex) + { + try + { + free_rows(rows_); + + const boost::lock_guard lock{data_->sync}; + const auto account = data_->primary.subaccounts.find(accountIndex); + if (account == data_->primary.subaccounts.end()) + return; + + rows_.reserve(account->second.minor.size()); + for (const auto& minor : account->second.minor) + { + rows_.push_back( + new Monero::SubaddressRow{ + minor.first, + data_->get_spend_address({accountIndex, minor.first}), + minor.second.label + } + ); + } + } + catch (...) + { + free_rows(rows_); + throw; + } + } + + void subaddress_minor::addRow(const std::uint32_t accountIndex, const std::string &label) + { + /* TODO */ + } + + void subaddress_minor::setLabel(const std::uint32_t accountIndex, const std::uint32_t addressIndex, const std::string &label) + { + { + const boost::lock_guard lock{data_->sync}; + data_->primary.subaccounts.at(accountIndex).minor.at(addressIndex).label = label; + } + refresh(accountIndex); + } + + }} // lwsf // internal + diff --git a/src/subaddress_minor.h b/src/subaddress_minor.h new file mode 100644 index 0000000..761235d --- /dev/null +++ b/src/subaddress_minor.h @@ -0,0 +1,61 @@ +// 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 + +#include +#include +#include +#include "wallet/api/wallet2_api.h" // monero/src + +namespace lwsf { namespace internal +{ + namespace backend { struct wallet; } + + class subaddress_minor final : public ::Monero::Subaddress + { + const std::shared_ptr data_; + std::vector rows_; + + public: + + explicit subaddress_minor(std::shared_ptr data); + + subaddress_minor(const subaddress_minor&) = delete; + subaddress_minor(subaddress_minor&&) = delete; + virtual ~subaddress_minor() override; + subaddress_minor& operator=(const subaddress_minor&) = delete; + subaddress_minor& operator=(subaddress_minor&&) = delete; + + void refresh(uint32_t accountIndex) override; + std::vector getAll() const override { return rows_; } + void addRow(uint32_t accountIndex, const std::string &label) override; + void setLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) override; + }; + +}} // lwsf // internal diff --git a/src/transaction_history.h b/src/transaction_history.h index 61ea6be..4963d73 100644 --- a/src/transaction_history.h +++ b/src/transaction_history.h @@ -37,7 +37,7 @@ namespace lwsf { namespace internal { namespace backend { class wallet; } - class transaction_history : public Monero::TransactionHistory + class transaction_history final : public Monero::TransactionHistory { const std::shared_ptr data_; std::vector txes_; diff --git a/src/transaction_info.h b/src/transaction_info.h index 1839ca2..7fa5681 100644 --- a/src/transaction_info.h +++ b/src/transaction_info.h @@ -28,8 +28,6 @@ #pragma once -#include "lws_frontend.h" - #include #include #include diff --git a/src/wallet.cpp b/src/wallet.cpp index 87e34e4..3a8a6a3 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -42,14 +42,17 @@ #include #include "address_book.h" #include "backend.h" +#include "cryptonote_basic/cryptonote_format_utils.h" // monero/src #include "error.h" -#include "hex.h" // monero/contrib/epee/include +#include "hex.h" // monero/contrib/epee/include #include "lwsf_config.h" +#include "mnemonics/electrum-words.h" // monero/src #include "net/net_parse_helpers.h" // monero/contrib/epee/include #include "net/parse.h" // monero/src #include "net/socks.h" // monero/src #include "net/socks_connect.h" // monero/src #include "subaddress_account.h" +#include "subaddress_minor.h" #include "transaction_history.h" #include "wire.h" #include "wire/msgpack.h" @@ -59,6 +62,9 @@ namespace lwsf { namespace internal namespace { + //! The stored wallet file always has this at beginning + static constexpr std::string_view file_magic{"lwsf-wallet-1.0"}; + struct null_connector { boost::unique_future @@ -150,7 +156,7 @@ namespace lwsf { namespace internal if (max_size() < upayload.size()) throw std::runtime_error{std::string{"Exceeded max size for "} + cipher_name()}; - if (tag_size() < std::numeric_limits::max() - upayload.size()) + if (std::numeric_limits::max() - upayload.size() < tag_size()) throw std::runtime_error{"Exceeded max size_t after authentication tag"}; encrypted_file out{ @@ -201,6 +207,7 @@ namespace lwsf { namespace internal throw std::runtime_error{"Failed to initialize libsodium"}; const auto key = get_key(password); + static_assert(key_size() == key.size()); epee::byte_stream buffer; buffer.put_n(0, epayload.size()); @@ -208,13 +215,14 @@ namespace lwsf { namespace internal static_assert( std::numeric_limits::max() <= std::numeric_limits::max() ); + unsigned long long out_bytes = buffer.size(); if (crypto_aead_chacha20poly1305_ietf_decrypt( buffer.data(), std::addressof(out_bytes), nullptr, epayload.data(), epayload.size(), nullptr, 0, - nonce.data(), key.data()) == 0) + nonce.data(), key.data()) != 0) { throw std::runtime_error{std::string{cipher_name()} + " decryption failed"}; } @@ -241,18 +249,27 @@ namespace lwsf { namespace internal epee::byte_slice try_load(const std::string& filename) { - std::ifstream file{filename, std::ios::binary | std::ios::ate}; + std::ifstream file{filename, std::ios::binary}; if (!file.is_open()) return nullptr; + { + std::string magic; + magic.resize(file_magic.size()); + file.read(magic.data(), magic.size()); + if (!file.good() || magic != file_magic) + return nullptr; + } + + file.seekg(0, std::ios::end); const auto size = file.tellg(); - if (size < 0) + if (size < 0 || std::size_t(size) < file_magic.size()) return nullptr; epee::byte_stream buffer; - buffer.put_n(0, std::size_t(size)); + buffer.put_n(0, std::size_t(size) - file_magic.size()); - file.seekg(0); + file.seekg(file_magic.size()); file.read(reinterpret_cast(buffer.data()), buffer.size()); if (file.good()) @@ -271,7 +288,10 @@ namespace lwsf { namespace internal bool rc = false; if (0 <= write(fd, contents.data(), contents.size()) && 0 <= fsync(fd)) { - const int dir = open(directory.c_str(), O_RDONLY); + const char* cwd = directory.c_str(); + if (strcmp(cwd, "") == 0) + cwd = "."; + const int dir = open(cwd, O_RDONLY); if (0 <= dir) { if (0 <= fsync(dir)) @@ -303,6 +323,23 @@ namespace lwsf { namespace internal ); return boost::chrono::nanoseconds{source.count()}; } + + void load_wallet(backend::account& self, const crypto::secret_key& from, const Monero::NetworkType nettype, const bool generated_locally) + { + cryptonote::account_base base{}; + base.generate(from, true); + + const auto& keys = base.get_keys(); + + self.view.sec = keys.m_view_secret_key; + self.view.pub = keys.m_account_address.m_view_public_key; + + self.spend.sec = keys.m_spend_secret_key; + self.spend.pub = keys.m_account_address.m_spend_public_key; + + self.generated_locally = generated_locally; + self.type = nettype; + } } // anonymous bool wallet::set_error(const std::error_code status) const @@ -368,8 +405,54 @@ namespace lwsf { namespace internal } } - wallet::wallet(create_tag, Monero::NetworkType nettype, std::string filename, std::string password, const std::uint64_t kdf_rounds) + bool wallet::is_wallet_file(const std::string& path) + { + return !try_load(path).empty(); + } + + bool wallet::verify_password(std::string path, const std::string& password) + { + // We don't use a separate keys file, remove the suffix is present + static constexpr const std::string_view keys_suffix{".keys"}; + if (keys_suffix.size() <= path.size() && std::string_view{path.data() + path.size() - keys_suffix.size()} == keys_suffix) + path.erase(path.size() - keys_suffix.size()); + + epee::byte_slice file = try_load(path); + if (file.empty()) + return false; + + encrypted_file contents{}; + if (!wire::msgpack::from_bytes(std::move(file), contents)) + return false; + return !contents.get_payload(password).empty(); + } + + std::vector wallet::find(const std::string& path) + { + std::vector out; + std::filesystem::path work_dir(path); + if(!std::filesystem::is_directory(path)) + return out; + + std::filesystem::recursive_directory_iterator end_itr; + for (std::filesystem::recursive_directory_iterator itr(path); itr != end_itr; ++itr) + { + if (std::filesystem::is_regular_file(itr->status())) + { + std::string filename = itr->path().filename().string(); + if (!try_load(filename).empty()) + out.push_back(std::move(filename)); + } + } + return out; + } + + wallet::wallet(create, Monero::NetworkType nettype, std::string filename, std::string password, const std::uint64_t kdf_rounds) : data_(std::make_shared()), + addressbook_(), + history_(), + subaddresses_(), + subaddress_minor_(), filename_(std::move(filename)), password_(std::move(password)), exception_error_(), @@ -391,26 +474,15 @@ namespace lwsf { namespace internal crypto::secret_key recovery; randombytes_buf(std::addressof(unwrap(unwrap(recovery))), sizeof(recovery)); - cryptonote::account_base base{}; - base.generate(recovery, true); - - const auto& keys = base.get_keys(); - - const boost::lock_guard lock{data_->sync}; - data_->primary.view.sec = keys.m_view_secret_key; - data_->primary.view.pub = keys.m_account_address.m_view_public_key; - - data_->primary.spend.sec = keys.m_spend_secret_key; - data_->primary.spend.pub = keys.m_account_address.m_spend_public_key; - - data_->primary.generated_locally = true; - data_->primary.type = nettype; - - store(std::string{}); + load_wallet(data_->primary, recovery, nettype, true); } - wallet::wallet(open_tag, Monero::NetworkType nettype, std::string filename, std::string password, const std::uint64_t kdf_rounds, std::shared_ptr data) - : data_(std::move(data)), + wallet::wallet(open, Monero::NetworkType nettype, std::string filename, std::string password, const std::uint64_t kdf_rounds) + : data_(std::make_shared()), + addressbook_(), + history_(), + subaddresses_(), + subaddress_minor_(), filename_(std::move(filename)), password_(std::move(password)), exception_error_(), @@ -463,8 +535,82 @@ namespace lwsf { namespace internal } } + wallet::wallet(from_mnemonic, const Monero::NetworkType nettype, std::string filename, std::string password, const std::uint64_t kdf_rounds, const std::string& mnemonic, const std::string& seed_offset) + : data_(std::make_shared()), + addressbook_(), + history_(), + subaddresses_(), + subaddress_minor_(), + filename_(std::move(filename)), + password_(std::move(password)), + exception_error_(), + status_(), + thread_(), + iterations_(kdf_rounds), + mixin_(config::mixin_default), + refresh_interval_(config::refresh_interval), + refresh_notify_(), + error_sync_(), + refresh_sync_(), + thread_state_(state::stop), + mandatory_refresh_(false) + { + if (sodium_init() < 0) + throw std::runtime_error{"Failed to initialize libsodium"}; + + crypto::secret_key recovery; + std::string language; + if (!crypto::ElectrumWords::words_to_bytes(mnemonic, recovery, language)) + { + exception_error_ = "Electrum-style word list failed verification"; + return; + } + + if (!seed_offset.empty()) + recovery = cryptonote::decrypt_key(recovery, seed_offset); + + load_wallet(data_->primary, recovery, nettype, false); + } + wallet::~wallet() { stop_refresh_loop(); } + std::string wallet::seed(const std::string& seed_offset) const + { + std::string language; + crypto::secret_key key; + { + const boost::lock_guard lock{data_->sync}; + language = data_->primary.language; + key = data_->primary.spend.sec; + } + + if (language.empty()) + language = "English"; + + if (!seed_offset.empty()) + key = cryptonote::encrypt_key(key, seed_offset); + + epee::wipeable_string electrum_words; + if (!crypto::ElectrumWords::bytes_to_words(key, electrum_words, language)) + { + set_critical(std::runtime_error{"Failed to crate seed from key for " + language}); + return {}; + } + return std::string{electrum_words.data(), electrum_words.size()}; + } + + std::string wallet::getSeedLanguage() const + { + const boost::lock_guard lock{data_->sync}; + return data_->primary.language; + } + + void wallet::setSeedLanguage(const std::string &arg) + { + const boost::lock_guard lock{data_->sync}; + data_->primary.language = arg; + } + int wallet::status() const { int out = 0; @@ -507,6 +653,11 @@ namespace lwsf { namespace internal return true; } + std::string wallet::address(const std::uint32_t accountIndex, const std::uint32_t addressIndex) const + { + return data_->get_spend_address({accountIndex, addressIndex}); + } + Monero::NetworkType wallet::nettype() const { return data_->primary.type; } std::string wallet::secretViewKey() const @@ -551,6 +702,7 @@ namespace lwsf { namespace internal epee::byte_stream buffer; buffer.reserve(payload_size + 2048); + buffer.write(file_magic.data(), file_magic.size()); if (!(status = wire::msgpack::to_bytes(buffer, contents))) { @@ -610,7 +762,7 @@ namespace lwsf { namespace internal stop_refresh_loop(); data_->client.disconnect(); - if (!setProxy(proxy_address)) + if (!proxy_address.empty() && !setProxy(proxy_address)) return false; epee::net_utils::http::url_content url{}; @@ -622,9 +774,12 @@ namespace lwsf { namespace internal if (url.schema == "https") use_ssl = true; - epee::net_utils::http::login login{daemon_username, daemon_password}; - const epee::net_utils::ssl_options_t options{ - use_ssl ? epee::net_utils::ssl_support_t::e_ssl_support_disabled : epee::net_utils::ssl_support_t::e_ssl_support_enabled + boost::optional login; + if (!daemon_username.empty() || !daemon_password.empty()) + login.emplace(daemon_username, daemon_password); + + epee::net_utils::ssl_options_t options{ + use_ssl ? epee::net_utils::ssl_support_t::e_ssl_support_enabled : epee::net_utils::ssl_support_t::e_ssl_support_disabled }; data_->client.set_server(std::move(url.host), std::to_string(url.port), std::move(login), std::move(options)); @@ -703,17 +858,75 @@ namespace lwsf { namespace internal return true; } + uint64_t wallet::balance(const uint32_t accountIndex) const + { + const boost::lock_guard lock{data_->sync}; + + std::uint64_t balance = 0; + for (const auto& tx : data_->primary.txes) + { + for (const auto& receive : tx.second->receives) + { + if (receive.second.recipient.maj_i == accountIndex) + balance += receive.second.amount; + } + + for (const auto& spend : tx.second->spends) + { + if (spend.second.sender.maj_i == accountIndex) + balance -= spend.second.amount; + } + } + + return balance; + } + + uint64_t wallet::unlockedBalance(const uint32_t accountIndex) const + { + const boost::lock_guard lock{data_->sync}; + const Monero::NetworkType net_type = data_->primary.type; + const std::uint32_t chain_height = data_->blockchain_height; + + std::uint64_t balance = 0; + for (const auto& tx : data_->primary.txes) + { + const bool unlocked = tx.second->is_unlocked(chain_height, net_type); + for (const auto& receive : tx.second->receives) + { + if (unlocked && receive.second.recipient.maj_i == accountIndex) + balance += receive.second.amount; + } + + for (const auto& spend : tx.second->spends) + { + if (spend.second.sender.maj_i == accountIndex) + balance -= spend.second.amount; + } + } + + return balance; + } + uint64_t wallet::blockChainHeight() const { const boost::unique_lock lock{data_->sync}; - return data_->blockchain_height; + return data_->primary.scan_height; } uint64_t wallet::daemonBlockChainHeight() const - { return blockChainHeight(); } + { + const boost::unique_lock lock{data_->sync}; + return data_->blockchain_height; + } uint64_t wallet::daemonBlockChainTargetHeight() const - { return blockChainHeight(); } + { return daemonBlockChainHeight(); } + + bool wallet::synchronized() const + { + const boost::lock_guard lock{data_->sync}; + return data_->blockchain_height == data_->primary.scan_height; + } void wallet::startRefresh() { @@ -732,7 +945,7 @@ namespace lwsf { namespace internal bool wallet::refresh() { - return set_error(data_->refresh(true)); + return set_error(data_->refresh(false)); } void wallet::refreshAsync() @@ -767,6 +980,63 @@ namespace lwsf { namespace internal return refresh_interval_.count(); } + void wallet::addSubaddressAccount(const std::string& label) + { + data_->add_subaccount(label); + } + + std::size_t wallet::numSubaddressAccounts() const + { + const boost::lock_guard lock{data_->sync}; + return data_->primary.subaccounts.size(); + } + + + std::size_t wallet::numSubaddresses(const std::uint32_t accountIndex) const + { + const boost::lock_guard lock{data_->sync}; + const auto account = data_->primary.subaccounts.find(accountIndex); + if (account != data_->primary.subaccounts.end()) + return account->second.minor.size(); + set_critical(std::runtime_error{"numSubaddresses failed, " + std::to_string(accountIndex) + " does not exist"}); + return 0; + } + + std::string wallet::getSubaddressLabel(const std::uint32_t accountIndex, const std::uint32_t addressIndex) const + { + const boost::lock_guard lock{data_->sync}; + const auto major = data_->primary.subaccounts.find(accountIndex); + if (major != data_->primary.subaccounts.end()) + { + const auto minor = major->second.minor.find(addressIndex); + if (minor != major->second.minor.end()) + return minor->second.label; + } + set_critical(std::runtime_error{"getSubaddressLabel failed, " + std::to_string(accountIndex) + "," + std::to_string(addressIndex) + " does not exist"}); + return {}; + } + + void wallet::setSubaddressLabel(const std::uint32_t accountIndex, const std::uint32_t addressIndex, const std::string &label) + { + const boost::lock_guard lock{data_->sync}; + auto major = data_->primary.subaccounts.find(accountIndex); + if (major != data_->primary.subaccounts.end()) + { + auto minor = major->second.minor.find(addressIndex); + if (minor != major->second.minor.end()) + { + minor->second.label = label; + return; + } + } + set_critical(std::runtime_error{"setSubaddressLabel failed, " + std::to_string(accountIndex) + "," + std::to_string(addressIndex) + " does not exist"}); + } + + void wallet::disposeTransaction(Monero::PendingTransaction * t) + { + delete t; + } + uint64_t wallet::estimateTransactionFee(const std::vector> &destinations, Monero::PendingTransaction::Priority priority) const { @@ -805,6 +1075,13 @@ namespace lwsf { namespace internal return addressbook_.get(); } + Monero::Subaddress* wallet::subaddress() + { + if (!subaddress_minor_) + subaddress_minor_ = std::make_unique(data_); + return subaddress_minor_.get(); + } + Monero::TransactionHistory* wallet::history() { if (!history_) @@ -825,7 +1102,8 @@ namespace lwsf { namespace internal const boost::lock_guard lock{data_->sync_listener}; data_->listener = listener; } - listener->onSetWallet(this); + if (listener) + listener->onSetWallet(this); } std::uint32_t wallet::defaultMixin() const @@ -842,15 +1120,17 @@ namespace lwsf { namespace internal { const boost::lock_guard lock{data_->sync}; data_->primary.attributes.try_emplace(key).first->second = val; + return true; } std::string wallet::getCacheAttribute(const std::string &key) const { const boost::lock_guard lock{data_->sync}; - const auto iter = data_->primary.attributes.find(key); - if (iter == data_->primary.attributes.end()) - return {}; - return iter->second; + const auto attribute = data_->primary.attributes.find(key); + if (attribute != data_->primary.attributes.end()) + return attribute->second; + set_critical(std::runtime_error{"attribute " + key + " does not exist"}); + return {}; } bool wallet::setUserNote(const std::string &txid, const std::string ¬e) @@ -921,8 +1201,4 @@ namespace lwsf { namespace internal return out; } - - bool wallet::checkTxKey(const std::string &txid, std::string tx_key, const std::string &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) - { - } }} // lwsf // internal diff --git a/src/wallet.h b/src/wallet.h index 8957aef..15d93ba 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -46,9 +46,7 @@ namespace internal { namespace backend { struct wallet; } - - //! \TODO Mark final when completely implemented - class wallet : public Monero::Wallet + class wallet final : public Monero::Wallet { enum class state : std::uint8_t { stop = 0, paused, skip_once, run }; @@ -56,8 +54,10 @@ namespace internal std::unique_ptr addressbook_; std::unique_ptr history_; std::unique_ptr subaddresses_; + std::unique_ptr subaddress_minor_; const std::string filename_; std::string password_; + std::string language_; mutable std::string exception_error_; mutable std::error_code status_; boost::thread thread_; @@ -77,11 +77,19 @@ namespace internal void refresh_loop(); public: - struct create_tag{}; - struct open_tag{}; + struct create{}; + struct from_keys{}; + struct from_mnemonic{}; + struct open{}; - wallet(create_tag, Monero::NetworkType nettype, std::string filename, std::string password, std::uint64_t kdf_rounds); - wallet(open_tag, Monero::NetworkType nettpe, std::string filename, std::string password, std::uint64_t kdf_rounds, std::shared_ptr data); + static bool is_wallet_file(const std::string& path); + static bool verify_password(std::string path, const std::string& password); + static std::vector find(const std::string& path); + + wallet(create, Monero::NetworkType nettype, std::string filename, std::string password, std::uint64_t kdf_rounds); + wallet(open, Monero::NetworkType nettpe, std::string filename, std::string password, std::uint64_t kdf_rounds); + wallet(from_mnemonic, Monero::NetworkType nettype, std::string path, std::string password, std::uint64_t kdf_rounds, const std::string& mnemonic, const std::string& seed_offset); + wallet(from_keys, Monero::NetworkType nettype, std::string path, std::string password, std::uint64_t kdf_rounds, const std::string& mnemonic, const std::string& seed_offset); wallet(const wallet&) = delete; wallet(wallet&&) = delete; @@ -89,10 +97,10 @@ namespace internal wallet& operator=(const wallet&) = delete; wallet& operator=(wallet&&) = delete; - virtual std::string seed(const std::string& seed_offset = "") const = 0; + virtual std::string seed(const std::string& seed_offset = "") const override; - virtual std::string getSeedLanguage() const = 0; - virtual void setSeedLanguage(const std::string &arg) = 0; + virtual std::string getSeedLanguage() const override; + virtual void setSeedLanguage(const std::string &arg) override; virtual int status() const override; virtual std::string errorString() const override; @@ -104,12 +112,12 @@ namespace internal virtual bool setDevicePin(const std::string &) override { return false; }; virtual bool setDevicePassphrase(const std::string &) override { return false; }; virtual std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const override; - virtual std::string path() const = 0; + virtual std::string path() const override { return filename_; } virtual Monero::NetworkType nettype() const override; //! returns current hard fork info - virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0; + virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const override { /* TODO */ }; //! check if hard fork rules should be used - virtual bool useForkRules(uint8_t version, int64_t early_blocks) const = 0; + virtual bool useForkRules(uint8_t version, int64_t early_blocks) const override { /* TODO */ return false; }; /*! * \brief integratedAddress - returns integrated address for current wallet address and given payment_id. * if passed "payment_id" param is an empty string or not-valid payment id string @@ -119,7 +127,7 @@ namespace internal * generated * \return - 106 characters string representing integrated address */ - virtual std::string integratedAddress(const std::string &payment_id) const = 0; + virtual std::string integratedAddress(const std::string &payment_id) const override { /* TODO */ return {}; } /*! * \brief secretViewKey - returns secret view key @@ -173,7 +181,7 @@ namespace internal * \brief keysFilename - returns keys filename. usually this formed as "wallet_filename".keys * \return */ - virtual std::string keysFilename() const = 0; + virtual std::string keysFilename() const override { return filename_; } /*! * \brief init - initializes wallet with daemon connection params. * if daemon_address is local address, "trusted daemon" will be set to true forcibly @@ -194,7 +202,7 @@ namespace internal * \param language * \return - true if created successfully */ - virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0; + virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const override { /* TODO */ return false; } /*! * \brief setRefreshFromBlockHeight - start refresh from block height on recover @@ -214,14 +222,14 @@ namespace internal * * \param recoveringFromSeed - true/false */ - virtual void setRecoveringFromSeed(bool recoveringFromSeed) = 0; + virtual void setRecoveringFromSeed(bool recoveringFromSeed) override { /* TODO */ }; /*! * \brief setRecoveringFromDevice - set state to recovering from device * * \param recoveringFromDevice - true/false */ - virtual void setRecoveringFromDevice(bool recoveringFromDevice) = 0; + virtual void setRecoveringFromDevice(bool recoveringFromDevice) override { /* TODO */ } /*! * \brief setSubaddressLookahead - set size of subaddress lookahead @@ -229,7 +237,7 @@ namespace internal * \param major - size fot the major index * \param minor - size fot the minor index */ - virtual void setSubaddressLookahead(uint32_t major, uint32_t minor) = 0; + virtual void setSubaddressLookahead(uint32_t major, uint32_t minor) override { /* TODO */ } /** * @brief connectToDaemon - connects to the daemon. TODO: check if it can be removed @@ -245,32 +253,20 @@ namespace internal virtual void setTrustedDaemon(bool) override {} virtual bool trustedDaemon() const override { return true; } virtual bool setProxy(const std::string &address) override; - virtual uint64_t balance(uint32_t accountIndex = 0) const = 0; - uint64_t balanceAll() const { - uint64_t result = 0; - for (uint32_t i = 0; i < numSubaddressAccounts(); ++i) - result += balance(i); - return result; - } - virtual uint64_t unlockedBalance(uint32_t accountIndex = 0) const = 0; - uint64_t unlockedBalanceAll() const { - uint64_t result = 0; - for (uint32_t i = 0; i < numSubaddressAccounts(); ++i) - result += unlockedBalance(i); - return result; - } + virtual uint64_t balance(uint32_t accountIndex = 0) const override; + virtual uint64_t unlockedBalance(uint32_t accountIndex = 0) const override; /** * @brief watchOnly - checks if wallet is watch only * @return - true if watch only */ - virtual bool watchOnly() const = 0; + virtual bool watchOnly() const override { /* TODO */ return false; } /** * @brief isDeterministic - checks if wallet keys are deterministic * @return - true if deterministic */ - virtual bool isDeterministic() const = 0; + virtual bool isDeterministic() const override { return true; } /** * @brief blockChainHeight - returns current blockchain height @@ -282,14 +278,14 @@ namespace internal * @brief approximateBlockChainHeight - returns approximate blockchain height calculated from date/time * @return */ - virtual uint64_t approximateBlockChainHeight() const = 0; + virtual uint64_t approximateBlockChainHeight() const override { /* TODO */ return 0; } /** * @brief estimateBlockChainHeight - returns estimate blockchain height. More accurate than approximateBlockChainHeight, * uses daemon height and falls back to calculation from date/time * @return **/ - virtual uint64_t estimateBlockChainHeight() const = 0; + virtual uint64_t estimateBlockChainHeight() const override { /* TODO */ return 0; } /** * @brief daemonBlockChainHeight - returns daemon blockchain height * @return 0 - in case error communicating with the daemon. @@ -308,36 +304,7 @@ namespace internal * @brief synchronized - checks if wallet was ever synchronized * @return */ - virtual bool synchronized() const = 0; - - static std::string displayAmount(uint64_t amount); - static uint64_t amountFromString(const std::string &amount); - static uint64_t amountFromDouble(double amount); - static std::string genPaymentId(); - static bool paymentIdValid(const std::string &paiment_id); - static bool addressValid(const std::string &str, Monero::NetworkType nettype); - static bool addressValid(const std::string &str, bool testnet) // deprecated - { - return addressValid(str, testnet ? Monero::TESTNET : Monero::MAINNET); - } - static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, Monero::NetworkType nettype, std::string &error); - static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error) // deprecated - { - return keyValid(secret_key_string, address_string, isViewKey, testnet ? Monero::TESTNET : Monero::MAINNET, error); - } - static std::string paymentIdFromAddress(const std::string &str, Monero::NetworkType nettype); - static std::string paymentIdFromAddress(const std::string &str, bool testnet) // deprecated - { - return paymentIdFromAddress(str, testnet ? Monero::TESTNET : Monero::MAINNET); - } - static uint64_t maximumAllowedAmount(); - // Easylogger wrapper - static void init(const char *argv0, const char *default_log_base_name) { init(argv0, default_log_base_name, "", true); } - static void init(const char *argv0, const char *default_log_base_name, const std::string &log_path, bool console); - static void debug(const std::string &category, const std::string &str); - static void info(const std::string &category, const std::string &str); - static void warning(const std::string &category, const std::string &str); - static void error(const std::string &category, const std::string &str); + virtual bool synchronized() const override; /** * @brief StartRefresh - Start/resume refresh thread (refresh every 10 seconds) @@ -363,12 +330,12 @@ namespace internal * @brief rescanBlockchain - rescans the wallet, updating transactions from daemon * @return - true if refreshed successfully; */ - virtual bool rescanBlockchain() = 0; + virtual bool rescanBlockchain() override { /* TODO */ return false; } /** * @brief rescanBlockchainAsync - rescans wallet asynchronously, starting from genesys */ - virtual void rescanBlockchainAsync() = 0; + virtual void rescanBlockchainAsync() override { /* TODO */ } /** * @brief setAutoRefreshInterval - setup interval for automatic refresh. @@ -386,84 +353,93 @@ namespace internal * @brief addSubaddressAccount - appends a new subaddress account at the end of the last major index of existing subaddress accounts * @param label - the label for the new account (which is the as the label of the primary address (accountIndex,0)) */ - virtual void addSubaddressAccount(const std::string& label) = 0; + virtual void addSubaddressAccount(const std::string& label) override; /** * @brief numSubaddressAccounts - returns the number of existing subaddress accounts */ - virtual size_t numSubaddressAccounts() const = 0; + virtual size_t numSubaddressAccounts() const override; /** * @brief numSubaddresses - returns the number of existing subaddresses associated with the specified subaddress account * @param accountIndex - the major index specifying the subaddress account */ - virtual size_t numSubaddresses(uint32_t accountIndex) const = 0; + virtual size_t numSubaddresses(uint32_t accountIndex) const override; /** * @brief addSubaddress - appends a new subaddress at the end of the last minor index of the specified subaddress account * @param accountIndex - the major index specifying the subaddress account * @param label - the label for the new subaddress */ - virtual void addSubaddress(uint32_t accountIndex, const std::string& label) = 0; + virtual void addSubaddress(uint32_t accountIndex, const std::string& label) override { /* TODO */ } /** * @brief getSubaddressLabel - gets the label of the specified subaddress * @param accountIndex - the major index specifying the subaddress account * @param addressIndex - the minor index specifying the subaddress */ - virtual std::string getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const = 0; + virtual std::string getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const override; /** * @brief setSubaddressLabel - sets the label of the specified subaddress * @param accountIndex - the major index specifying the subaddress account * @param addressIndex - the minor index specifying the subaddress * @param label - the new label for the specified subaddress */ - virtual void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) = 0; + virtual void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) override; /** * @brief multisig - returns current state of multisig wallet creation process * @return MultisigState struct */ - virtual Monero::MultisigState multisig() const = 0; + virtual Monero::MultisigState multisig() const override { /* TODO */ return {}; } /** * @brief getMultisigInfo * @return serialized and signed multisig info string */ - virtual std::string getMultisigInfo() const = 0; + virtual std::string getMultisigInfo() const override { /* TODO */ return {}; } /** * @brief makeMultisig - switches wallet in multisig state. The one and only creation phase for N / N wallets * @param info - vector of multisig infos from other participants obtained with getMulitisInfo call * @param threshold - number of required signers to make valid transaction. Must be <= number of participants * @return in case of N / N wallets returns empty string since no more key exchanges needed. For N - 1 / N wallets returns base58 encoded extra multisig info */ - virtual std::string makeMultisig(const std::vector& info, uint32_t threshold) = 0; + virtual std::string makeMultisig(const std::vector& info, uint32_t threshold) override { /* TODO */ return {}; } /** * @brief exchange_multisig_keys - provides additional key exchange round for arbitrary multisig schemes (like N-1/N, M/N) * @param info - base58 encoded key derivations returned by makeMultisig or exchangeMultisigKeys function call * @param force_update_use_with_caution - force multisig account to update even if not all signers contribute round messages * @return new info string if more rounds required or an empty string if wallet creation is done */ - virtual std::string exchangeMultisigKeys(const std::vector &info, const bool force_update_use_with_caution) = 0; + virtual std::string exchangeMultisigKeys(const std::vector &info, const bool force_update_use_with_caution) override { /* TODO */ return {}; } + /** + * @brief getMultisigKeyExchangeBooster - obtain partial information for the key exchange round after the in-progress round, + * to speed up another signer's key exchange process + * @param info - base58 encoded key derivations returned by makeMultisig or exchangeMultisigKeys function call + * @param threshold - number of required signers to make valid transaction. Must be <= number of participants. + * @param num_signers - total number of multisig participants. + * @return new info string if more rounds required or exception if no more rounds (i.e. no rounds to boost) + */ + virtual std::string getMultisigKeyExchangeBooster(const std::vector &info, const uint32_t threshold, const uint32_t num_signers) override { /* TODO */ return {}; } /** * @brief exportMultisigImages - exports transfers' key images * @param images - output paramter for hex encoded array of images * @return true if success */ - virtual bool exportMultisigImages(std::string& images) = 0; + virtual bool exportMultisigImages(std::string& images) override { /* TODO */ return false; } /** * @brief importMultisigImages - imports other participants' multisig images * @param images - array of hex encoded arrays of images obtained with exportMultisigImages * @return number of imported images */ - virtual size_t importMultisigImages(const std::vector& images) = 0; + virtual size_t importMultisigImages(const std::vector& images) override { /* TODO */ return false; } /** * @brief hasMultisigPartialKeyImages - checks if wallet needs to import multisig key images from other participants * @return true if there are partial key images */ - virtual bool hasMultisigPartialKeyImages() const = 0; + virtual bool hasMultisigPartialKeyImages() const override { /* TODO */ return false; } /** * @brief restoreMultisigTransaction creates PendingTransaction from signData * @param signData encrypted unsigned transaction. Obtained with PendingTransaction::multisigSignData * @return PendingTransaction */ - virtual Monero::PendingTransaction* restoreMultisigTransaction(const std::string& signData) = 0; + virtual Monero::PendingTransaction* restoreMultisigTransaction(const std::string& signData) override { /* TODO */ return nullptr; } /*! * \brief createTransactionMultDest creates transaction with multiple destinations. if dst_addr is an integrated address, payment_id is ignored @@ -482,7 +458,7 @@ namespace internal Monero::optional> amount, uint32_t mixin_count, Monero::PendingTransaction::Priority = Monero::PendingTransaction::Priority_Low, uint32_t subaddr_account = 0, - std::set subaddr_indices = {}) = 0; + std::set subaddr_indices = {}) override { /* TODO */ return nullptr; } /*! * \brief createTransaction creates transaction. if dst_addr is an integrated address, payment_id is ignored @@ -501,7 +477,7 @@ namespace internal std::optional amount, uint32_t mixin_count, Monero::PendingTransaction::Priority = Monero::PendingTransaction::Priority_Low, uint32_t subaddr_account = 0, - std::set subaddr_indices = {}) = 0; + std::set subaddr_indices = {}) override { /* TODO */ return nullptr; } /*! * \brief createSweepUnmixableTransaction creates transaction with unmixable outputs. @@ -509,28 +485,27 @@ namespace internal * after object returned */ - virtual Monero::PendingTransaction* createSweepUnmixableTransaction() = 0; + virtual Monero::PendingTransaction* createSweepUnmixableTransaction() override { /* TODO */ return nullptr; } /*! * \brief loadUnsignedTx - creates transaction from unsigned tx file * \return - UnsignedTransaction object. caller is responsible to check UnsignedTransaction::status() * after object returned */ - virtual Monero::UnsignedTransaction* loadUnsignedTx(const std::string &unsigned_filename) = 0; + virtual Monero::UnsignedTransaction* loadUnsignedTx(const std::string &unsigned_filename) override { /* TODO */ return nullptr; } /*! * \brief submitTransaction - submits transaction in signed tx file * \return - true on success */ - virtual bool submitTransaction(const std::string &fileName) = 0; + virtual bool submitTransaction(const std::string &fileName) override { /* TODO */ return false; } /*! * \brief disposeTransaction - destroys transaction object * \param t - pointer to the "PendingTransaction" object. Pointer is not valid after function returned; */ - virtual void disposeTransaction(Monero::PendingTransaction * t) override final - {} + virtual void disposeTransaction(Monero::PendingTransaction * t) override; /*! * \brief Estimates transaction fee. @@ -576,9 +551,45 @@ namespace internal */ virtual bool scanTransactions(const std::vector &txids) override; + /*! + * \brief setupBackgroundSync - setup background sync mode with just a view key + * \param background_sync_type - the mode the wallet background syncs in + * \param wallet_password + * \param background_cache_password - custom password to encrypt background cache, only needed for custom password background sync type + * \return - true on success + */ + virtual bool setupBackgroundSync(const BackgroundSyncType background_sync_type, const std::string &wallet_password, const Monero::optional &background_cache_password) override { return true; } + + /*! + * \brief getBackgroundSyncType - get mode the wallet background syncs in + * \return - the type, or off if type is unknown + */ + virtual BackgroundSyncType getBackgroundSyncType() const override { return Monero::Wallet::BackgroundSync_ReusePassword; } + + /** + * @brief startBackgroundSync - sync the chain in the background with just view key + */ + virtual bool startBackgroundSync() override { return true; } + + /** + * @brief stopBackgroundSync - bring back spend key and process background synced txs + * \param wallet_password + */ + virtual bool stopBackgroundSync(const std::string &wallet_password) override { return false; } + + /** + * @brief isBackgroundSyncing - returns true if the wallet is background syncing + */ + virtual bool isBackgroundSyncing() const override { return true; } + + /** + * @brief isBackgroundWallet - returns true if the wallet is a background wallet + */ + virtual bool isBackgroundWallet() const override { return true; } + virtual Monero::TransactionHistory* history() override; virtual Monero::AddressBook* addressBook() override; - virtual Monero::Subaddress* subaddress() = 0; + virtual Monero::Subaddress* subaddress() override; virtual Monero::SubaddressAccount* subaddressAccount() override; virtual void setListener(Monero::WalletListener*) override; /*! @@ -604,7 +615,7 @@ namespace internal * \param key - the key * \return the attached string, or empty string if there is none */ - virtual std::string getCacheAttribute(const std::string &key) const = 0; + virtual std::string getCacheAttribute(const std::string &key) const override; /*! * \brief setUserNote - attach an arbitrary string note to a txid * \param txid - the transaction id to attach the note to @@ -619,24 +630,24 @@ namespace internal */ virtual std::string getUserNote(const std::string &txid) const override; virtual std::string getTxKey(const std::string &txid) const override; - virtual bool checkTxKey(const std::string &txid, std::string tx_key, const std::string &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) = 0; - virtual std::string getTxProof(const std::string &txid, const std::string &address, const std::string &message) const = 0; - virtual bool checkTxProof(const std::string &txid, const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &received, bool &in_pool, uint64_t &confirmations) = 0; - virtual std::string getSpendProof(const std::string &txid, const std::string &message) const = 0; - virtual bool checkSpendProof(const std::string &txid, const std::string &message, const std::string &signature, bool &good) const = 0; + virtual bool checkTxKey(const std::string &txid, std::string tx_key, const std::string &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) override { /* TODO */ return false; } + virtual std::string getTxProof(const std::string &txid, const std::string &address, const std::string &message) const override { /* TODO */ return {}; } + virtual bool checkTxProof(const std::string &txid, const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &received, bool &in_pool, uint64_t &confirmations) override { /* TODO */ return false; } + virtual std::string getSpendProof(const std::string &txid, const std::string &message) const override { /* TODO */ return {}; } + virtual bool checkSpendProof(const std::string &txid, const std::string &message, const std::string &signature, bool &good) const override { /* TODO */ return false; } /*! * \brief getReserveProof - Generates a proof that proves the reserve of unspent funds * Parameters `account_index` and `amount` are ignored when `all` is true */ - virtual std::string getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const = 0; - virtual bool checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) const = 0; + virtual std::string getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const override { /* TODO */ return {}; } + virtual bool checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) const override { /* TODO */ return false; } /* * \brief signMessage - sign a message with the spend private key * \param message - the message to sign (arbitrary byte data) * \return the signature */ - virtual std::string signMessage(const std::string &message, const std::string &address = "") = 0; + virtual std::string signMessage(const std::string &message, const std::string &address = "") override { /* TODO */ return {}; } /*! * \brief verifySignedMessage - verify a signature matches a given message * \param message - the message (arbitrary byte data) @@ -644,14 +655,14 @@ namespace internal * \param signature - the signature * \return true if the signature verified, false otherwise */ - virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0; + virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const override { /* TODO */ return false; } /*! * \brief signMultisigParticipant signs given message with the multisig public signer key * \param message message to sign * \return signature in case of success. Sets status to Error and return empty string in case of error */ - virtual std::string signMultisigParticipant(const std::string &message) const = 0; + virtual std::string signMultisigParticipant(const std::string &message) const override { /* TODO */ return {}; } /*! * \brief verifyMessageWithPublicKey verifies that message was signed with the given public key * \param message message @@ -659,78 +670,78 @@ namespace internal * \param signature signature of the message * \return true if the signature is correct. false and sets error state in case of error */ - virtual bool verifyMessageWithPublicKey(const std::string &message, const std::string &publicKey, const std::string &signature) const = 0; + virtual bool verifyMessageWithPublicKey(const std::string &message, const std::string &publicKey, const std::string &signature) const override { /* TODO */ return false; } - virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector &unknown_parameters, std::string &error) = 0; - virtual std::string make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name, std::string &error) const = 0; + virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector &unknown_parameters, std::string &error) override { /* TODO */ return false; } + virtual std::string make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name, std::string &error) const override { /* TODO */ return {}; } - virtual std::string getDefaultDataDir() const = 0; + virtual std::string getDefaultDataDir() const override { /* TODO */ return {}; } /* * \brief rescanSpent - Rescan spent outputs - Can only be used with trusted daemon * \return true on success */ - virtual bool rescanSpent() = 0; + virtual bool rescanSpent() override { return false; } /* * \brief setOffline - toggle set offline on/off * \param offline - true/false */ - virtual void setOffline(bool offline) = 0; - virtual bool isOffline() const = 0; + virtual void setOffline(bool offline) override { /* TODO */ } + virtual bool isOffline() const override { /* TODO */ return false; } //! blackballs a set of outputs - virtual bool blackballOutputs(const std::vector &outputs, bool add) = 0; + virtual bool blackballOutputs(const std::vector &outputs, bool add) override { return false; } //! blackballs an output - virtual bool blackballOutput(const std::string &amount, const std::string &offset) = 0; + virtual bool blackballOutput(const std::string &amount, const std::string &offset) override { return false; } //! unblackballs an output - virtual bool unblackballOutput(const std::string &amount, const std::string &offset) = 0; + virtual bool unblackballOutput(const std::string &amount, const std::string &offset) override { return false; } //! gets the ring used for a key image, if any - virtual bool getRing(const std::string &key_image, std::vector &ring) const = 0; + virtual bool getRing(const std::string &key_image, std::vector &ring) const override { /* TODO */ return false; } //! gets the rings used for a txid, if any - virtual bool getRings(const std::string &txid, std::vector>> &rings) const = 0; + virtual bool getRings(const std::string &txid, std::vector>> &rings) const override { /* TODO */ return false; } //! sets the ring used for a key image - virtual bool setRing(const std::string &key_image, const std::vector &ring, bool relative) = 0; + virtual bool setRing(const std::string &key_image, const std::vector &ring, bool relative) override { /* TODO */ return false; } //! sets whether pre-fork outs are to be segregated - virtual void segregatePreForkOutputs(bool segregate) = 0; + virtual void segregatePreForkOutputs(bool segregate) override { /* TODO */ } //! sets the height where segregation should occur - virtual void segregationHeight(uint64_t height) = 0; + virtual void segregationHeight(uint64_t height) override { /* TODO */ } //! secondary key reuse mitigation - virtual void keyReuseMitigation2(bool mitigation) = 0; + virtual void keyReuseMitigation2(bool mitigation) override { /* TODO */ } //! locks/unlocks the keys file; returns true on success - virtual bool lockKeysFile() = 0; - virtual bool unlockKeysFile() = 0; + virtual bool lockKeysFile() override { return false; } + virtual bool unlockKeysFile() override { return true; } //! returns true if the keys file is locked - virtual bool isKeysFileLocked() = 0; + virtual bool isKeysFileLocked() override { return false; } /*! * \brief Queries backing device for wallet keys * \return Device they are on */ - virtual Device getDeviceType() const = 0; + virtual Device getDeviceType() const override { /* TODO */ return {}; } //! cold-device protocol key image sync - virtual uint64_t coldKeyImageSync(uint64_t &spent, uint64_t &unspent) = 0; + virtual uint64_t coldKeyImageSync(uint64_t &spent, uint64_t &unspent) override { /* TODO */ return 0; } //! shows address on device display - virtual void deviceShowAddress(uint32_t accountIndex, uint32_t addressIndex, const std::string &paymentId) = 0; + virtual void deviceShowAddress(uint32_t accountIndex, uint32_t addressIndex, const std::string &paymentId) override { /* TODO */ } //! attempt to reconnect to hardware device - virtual bool reconnectDevice() = 0; + virtual bool reconnectDevice() override { /* TODO */ return false; } //! get bytes received - virtual uint64_t getBytesReceived() = 0; + virtual uint64_t getBytesReceived() override { /* TODO */ return 0; } //! get bytes sent - virtual uint64_t getBytesSent() = 0; + virtual uint64_t getBytesSent() override { /* TODO */ return 0; } }; }} // lwsf // internal diff --git a/src/wallet_manager.cpp b/src/wallet_manager.cpp index ec05518..a5c6e63 100644 --- a/src/wallet_manager.cpp +++ b/src/wallet_manager.cpp @@ -96,9 +96,7 @@ namespace lwsf */ Monero::Wallet* createWallet(const std::string &path, const std::string &password, const std::string &language, Monero::NetworkType nettype, uint64_t kdf_rounds) override { - return new wallet{ - wallet::create_tag{}, nettype, path, password, kdf_rounds - }; + return new wallet{wallet::create{}, nettype, path, password, kdf_rounds}; } /*! @@ -113,15 +111,11 @@ namespace lwsf Monero::Wallet* openWallet(const std::string &path, const std::string &password, Monero::NetworkType nettype, uint64_t kdf_rounds, Monero::WalletListener* listener) override { - auto data = std::make_shared(); - data->listener = std::move(listener); auto out = std::make_unique( - wallet::open_tag{}, nettype, path, password, kdf_rounds, data + wallet::open{}, nettype, path, password, kdf_rounds ); - - // wallet file should be fully loaded in construction of `wallet`. - if (data->listener) - data->listener->onSetWallet(out.get()); + if (listener) + out->setListener(listener); return out.release(); } @@ -140,7 +134,11 @@ namespace lwsf Monero::NetworkType nettype = Monero::MAINNET, uint64_t restoreHeight = 0, uint64_t kdf_rounds = 1, const std::string &seed_offset = {}) override final { - //! \TODO Complete + auto out = std::make_unique( + wallet::from_mnemonic{}, nettype, path, password, kdf_rounds, mnemonic, seed_offset + ); + out->setRefreshFromBlockHeight(restoreHeight); + return out.release(); } /*! @@ -154,6 +152,7 @@ namespace lwsf */ Monero::Wallet* recoveryWallet(const std::string &path, const std::string &mnemonic, Monero::NetworkType nettype, uint64_t restoreHeight = 0) override { + return recoveryWallet(path, "", mnemonic, nettype, restoreHeight); } /*! @@ -236,9 +235,10 @@ namespace lwsf */ bool closeWallet(Monero::Wallet* const wallet, const bool store) override { - const std::unique_ptr destroy{wallet}; - if (wallet && store) - wallet->store(wallet->filename()); + if (wallet && store && !wallet->store({})) + return false; + delete wallet; + return true; } /* @@ -252,7 +252,7 @@ namespace lwsf */ bool walletExists(const std::string &path) override { - return std::filesystem::exists(std::filesystem::path{path}); + return lwsf::internal::wallet::is_wallet_file(path); } /*! @@ -267,9 +267,9 @@ namespace lwsf * This function will fail when the wallet keys file is opened because the wallet program locks the keys file. * In this case, Wallet::unlockKeysFile() and Wallet::lockKeysFile() need to be called before and after the call to this function, respectively. */ - bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key, uint64_t kdf_rounds) const override + bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool, uint64_t) const override { - //! \TODO Complete + lwsf::internal::wallet::verify_password(keys_file_name, password); } /*! @@ -294,7 +294,7 @@ namespace lwsf */ std::vector findWallets(const std::string &path) override { - //! \TODO Complete + return lwsf::internal::wallet::find(path); } std::string errorString() const override { return error_; } @@ -302,13 +302,28 @@ namespace lwsf //! set the daemon address (hostname and port) void setDaemonAddress(const std::string &address) override { - //! \TODO Complete + epee::net_utils::http::url_content url{}; + if (!epee::net_utils::parse_url(address, url)) + throw std::runtime_error{"Invalid LWS URL: " + address}; + if (!url.m_uri_content.m_path.empty()) + throw std::runtime_error{"LWS URL contains path (unsupported)"}; + + bool use_ssl = false; + if (url.schema == "https") + use_ssl = true; + + const epee::net_utils::ssl_options_t options{ + use_ssl ? epee::net_utils::ssl_support_t::e_ssl_support_disabled : epee::net_utils::ssl_support_t::e_ssl_support_enabled + }; + + client_.set_server(std::move(url.host), std::to_string(url.port), boost::none, std::move(options)); } //! returns whether the daemon can be reached, and its version number bool connected(uint32_t *version = NULL) override { - version = 0; + if (version) + *version = 0; return client_.is_connected(); } diff --git a/src/wire/CMakeLists.txt b/src/wire/CMakeLists.txt index 68c2a79..00758ed 100644 --- a/src/wire/CMakeLists.txt +++ b/src/wire/CMakeLists.txt @@ -26,12 +26,12 @@ # 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. -set(monero-lws-wire_sources basic_value.cpp error.cpp read.cpp write.cpp) -set(monero-lws-wire_headers basic_value.h error.h field.h filters.h fwd.h json.h read.h traits.h vector.h write.h) +set(lwsf-wire_sources basic_value.cpp error.cpp read.cpp write.cpp) +set(lwsf-wire_headers basic_value.h error.h field.h filters.h fwd.h json.h read.h traits.h vector.h write.h) -add_library(monero-lws-wire ${monero-lws-wire_sources} ${monero-lws-wire_headers}) -target_include_directories(monero-lws-wire PUBLIC "${LMDB_INCLUDE}") -target_link_libraries(monero-lws-wire PRIVATE monero::libraries) +add_library(lwsf-wire ${lwsf-wire_sources} ${lwsf-wire_headers}) +target_include_directories(lwsf-wire PUBLIC "${LMDB_INCLUDE}") +target_link_libraries(lwsf-wire PRIVATE monero::libraries) add_subdirectory(json) add_subdirectory(msgpack) diff --git a/src/wire/adapted/crypto.h b/src/wire/adapted/crypto.h index dab53ba..e781c00 100644 --- a/src/wire/adapted/crypto.h +++ b/src/wire/adapted/crypto.h @@ -39,7 +39,7 @@ namespace crypto template void read_bytes(R& source, crypto::secret_key& self) { - source.binary(epee::as_mut_byte_span(unwrap(unwrap(self)))); + source.binary(epee::as_mut_byte_span(unwrap(unwrap(self))), /* exact */ true); } template @@ -51,13 +51,13 @@ namespace crypto namespace wire { - WIRE_DECLARE_BLOB(crypto::ec_scalar); - WIRE_DECLARE_BLOB(crypto::hash); - WIRE_DECLARE_BLOB(crypto::hash8); - WIRE_DECLARE_BLOB(crypto::key_derivation); - WIRE_DECLARE_BLOB(crypto::key_image); - WIRE_DECLARE_BLOB(crypto::public_key); - WIRE_DECLARE_BLOB(crypto::signature); - WIRE_DECLARE_BLOB(crypto::view_tag); - WIRE_DECLARE_BLOB(rct::key); + WIRE_DECLARE_BLOB_NS(crypto::ec_scalar); + WIRE_DECLARE_BLOB_NS(crypto::hash); + WIRE_DECLARE_BLOB_NS(crypto::hash8); + WIRE_DECLARE_BLOB_NS(crypto::key_derivation); + WIRE_DECLARE_BLOB_NS(crypto::key_image); + WIRE_DECLARE_BLOB_NS(crypto::public_key); + WIRE_DECLARE_BLOB_NS(crypto::signature); + WIRE_DECLARE_BLOB_NS(crypto::view_tag); + WIRE_DECLARE_BLOB_NS(rct::key); } diff --git a/src/wire/basic_value.cpp b/src/wire/basic_value.cpp index 7c8c732..5cff8cf 100644 --- a/src/wire/basic_value.cpp +++ b/src/wire/basic_value.cpp @@ -49,6 +49,6 @@ namespace wire } void write_bytes(writer& dest, const basic_value& source) { - boost::apply_visitor([&dest] (const auto& val) { wire_write::bytes(dest, val); }, source.value); + std::visit([&dest] (const auto& val) { wire_write::bytes(dest, val); }, source.value); } } diff --git a/src/wire/json/CMakeLists.txt b/src/wire/json/CMakeLists.txt index aca1d81..b5a0535 100644 --- a/src/wire/json/CMakeLists.txt +++ b/src/wire/json/CMakeLists.txt @@ -26,8 +26,8 @@ # 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. -set(monero-lws_wire-json_sources error.cpp read.cpp write.cpp) -set(monero-lws_wire-json_headers base.h error.h fwd.h read.h write.h) +set(lwsf_wire-json_sources error.cpp read.cpp write.cpp) +set(lwsf_wire-json_headers base.h error.h fwd.h read.h write.h) -add_library(monero-lws-wire-json ${monero-lws_wire-json_sources} ${monero-lws-wire-json_headers}) -target_link_libraries(monero-lws-wire-json monero::libraries monero-lws-wire) +add_library(lwsf-wire-json ${lwsf_wire-json_sources} ${lwsf-wire-json_headers}) +target_link_libraries(lwsf-wire-json monero::libraries lwsf-wire) diff --git a/src/wire/json/base.h b/src/wire/json/base.h index 9020bb3..368d4bf 100644 --- a/src/wire/json/base.h +++ b/src/wire/json/base.h @@ -43,9 +43,9 @@ namespace wire using output_type = json_slice_writer; template - static std::error_code from_bytes(std::string&& source, T& dest) + static std::error_code from_bytes(const epee::span source, T& dest) { - return wire_read::from_bytes(std::move(source), dest); + return wire_read::from_bytes(source, dest); } template diff --git a/src/wire/json/read.cpp b/src/wire/json/read.cpp index 59b419c..39dcec6 100644 --- a/src/wire/json/read.cpp +++ b/src/wire/json/read.cpp @@ -25,7 +25,7 @@ // 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. -#include "serialization/wire/json/read.h" +#include "wire/json/read.h" #include #include @@ -34,9 +34,9 @@ #include "byte_stream.h" #include "hex.h" -#include "serialization/wire/basic_value.h" -#include "serialization/wire/error.h" -#include "serialization/wire/json/error.h" +#include "wire/basic_value.h" +#include "wire/error.h" +#include "wire/json/error.h" namespace { @@ -345,6 +345,21 @@ namespace wire } return std::string{std::move(str_buffer_)}; } + + std::size_t json_reader::string(epee::span dest, bool exact) + { + { + rapidjson_sax json_string{str_buffer_, error::schema::string}; + read_next_value(json_string); + } + + if (!exact && str_buffer_.size() < dest.size()) + dest = {dest.data(), str_buffer_.size()}; + if (str_buffer_.size() != dest.size()) + WIRE_DLOG_THROW(error::schema::string, "expected size " << dest.size() << " but got " << str_buffer_.size()); + return dest.size(); + + } epee::byte_slice json_reader::binary() { diff --git a/src/wire/json/read.h b/src/wire/json/read.h index 13151b2..d35ceee 100644 --- a/src/wire/json/read.h +++ b/src/wire/json/read.h @@ -28,18 +28,14 @@ #pragma once #include -#include +#include #include #include -#include -#include -#include -#include "wire/field.h" +#include "byte_slice.h" // monero/contrib/epee/include #include "wire/fwd.h" -#include "wire/json/base.h" #include "wire/read.h" -#include "wire/traits.h" +#include "span.h" // monero/contrib/epee/include namespace wire { @@ -48,7 +44,7 @@ namespace wire { struct rapidjson_sax; - std::string source_; + std::string str_buffer_; rapidjson::Reader reader_; void read_next_value(rapidjson_sax& handler); @@ -58,14 +54,21 @@ namespace wire //! Skips next value. \throw wire::exception if invalid JSON syntax. void skip_value(); + //! \throw wire::exception if next token not `[`. + std::size_t do_start_array(std::size_t) override final; + + //! \throw wire::exception if next token not `{`. + std::size_t do_start_object() override final; + public: - explicit json_reader(std::string&& source); + //! `source` must "outlive" `json_reader`. + explicit json_reader(epee::span source); //! \throw wire::exception if JSON parsing is incomplete. void check_complete() const override final; //! \throw wire::exception if array, object, or end of stream. - basic_value basic(); + basic_value basic() override final; //! \throw wire::exception if next token not a boolean. bool boolean() override final; @@ -76,7 +79,7 @@ namespace wire //! \throw wire::exception if next token not an unsigned integer. std::uintmax_t unsigned_integer() override final; - //! \throw wire::exception if next token is not an integer encoded as string OR unsigned integer + //! \throw wire::exception if next token is not an integer encoded as string std::uintmax_t safe_unsigned_integer(); //! \throw wire::exception if next token not a valid real number @@ -85,23 +88,30 @@ namespace wire //! \throw wire::exception if next token not a string std::string string() override final; + /*! Copy upcoming string directly into `dest`. + \throw wire::exception if next value not string + \throw wire::exception if next string exceeds `dest.size())` + \throw wire::exception if `exact == true` and next string size is not + `dest.size()` + \return Number of bytes read into `dest`. */ + std::size_t string(epee::span dest, bool exact) override final; + //! \throw wire::exception if next token cannot be read as hex epee::byte_slice binary() override final; - //! \throw wire::exception if next token cannot be read as hex into `dest`. - void binary(epee::span dest) override final; - - - //! \throw wire::exception if next token not `[`. - std::size_t start_array(std::size_t) override final; + /* Copy upcoming binary, encoded as a hex string, into `dest`. + \throw wire::exception if next value is not a string + \throw wire::exception if next string is not valid hex. + \throw wire::exception if `dest.size() * 2` is less than next string length + \throw wire::exception if `exact == true` and next string length is + not exactly `dest.size() * 2` + \return Number of bytes read into `dest` (not number of bytes read + from wire). */ + std::size_t binary(epee::span dest, bool exact) override final; //! Skips whitespace to next token. \return True if next token is eof or ']'. bool is_array_end(std::size_t count) override final; - - - //! \throw wire::exception if next token not `{`. - std::size_t start_object() override final; - + /*! \throw wire::exception if next token not key or `}`. \param[out] index of key match within `map`. \return True if another value to read. */ diff --git a/src/wire/msgpack/CMakeLists.txt b/src/wire/msgpack/CMakeLists.txt index 7ad3c63..dc8fbbc 100644 --- a/src/wire/msgpack/CMakeLists.txt +++ b/src/wire/msgpack/CMakeLists.txt @@ -26,8 +26,8 @@ # 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. -set(monero-lws_wire-msgpack_sources error.cpp read.cpp write.cpp) -set(monero-lws_wire-msgpack_headers base.h error.h fwd.h read.h write.h) +set(lwsf_wire-msgpack_sources error.cpp read.cpp write.cpp) +set(lwsf_wire-msgpack_headers base.h error.h fwd.h read.h write.h) -add_library(monero-lws-wire-msgpack ${monero-lws_wire-msgpack_sources} ${monero-lws-wire-msgpack_headers}) -target_link_libraries(monero-lws-wire-msgpack monero::libraries monero-lws-wire) +add_library(lwsf-wire-msgpack ${lwsf_wire-msgpack_sources} ${lwsf-wire-msgpack_headers}) +target_link_libraries(lwsf-wire-msgpack monero::libraries lwsf-wire) diff --git a/src/wire/msgpack/read.cpp b/src/wire/msgpack/read.cpp index b66a816..a3022b7 100644 --- a/src/wire/msgpack/read.cpp +++ b/src/wire/msgpack/read.cpp @@ -36,6 +36,7 @@ #include #include +#include "wire/basic_value.h" #include "wire/error.h" #include "wire/msgpack/error.h" @@ -331,7 +332,7 @@ namespace wire return read_integer(remaining_, next); } - std::uintmax_t msgpack_reader::do_unsigned_integer(const msgpack::tag next) + std::uintmax_t msgpack_reader:: do_unsigned_integer(const msgpack::tag next) { return read_integer(remaining_, next); } @@ -366,6 +367,62 @@ namespace wire WIRE_DLOG_THROW_(error::msgpack::incomplete); } + basic_value msgpack_reader::basic() + { + update_tags_remaining(); + const auto next = get_tag(); + + if (std::uint8_t(next) <= msgpack::ftag_unsigned::max()) + return {std::uintmax_t(next)}; + if (msgpack::ftag_signed::matches(next)) + return {do_integer(next)}; + + if (msgpack::ftag_string::matches(next)) + { + const auto bytes = read_string(remaining_, next); + return {std::string{reinterpret_cast(bytes.data()), bytes.size()}}; + } + + switch (next) + { + case msgpack::tag::nil: + return {nullptr}; + + case msgpack::tag::True: + return {true}; + case msgpack::tag::False: + return {false}; + + case msgpack::tag::int8: + case msgpack::tag::int16: + case msgpack::tag::int32: + case msgpack::tag::int64: + return {do_integer(next)}; + + case msgpack::tag::uint8: + case msgpack::tag::uint16: + case msgpack::tag::uint32: + case msgpack::tag::uint64: + return {do_unsigned_integer(next)}; +/* + case msgpack::tag::binary8: + case msgpack::tag::binary16: + case msgpack::tag::binary32: + return {epee::byte_slice{{read_binary(remaining_, next)}}}; */ + + case msgpack::tag::string8: + case msgpack::tag::string16: + case msgpack::tag::string32: + { + const auto bytes = read_string(remaining_, next); + return {std::string{reinterpret_cast(bytes.data()), bytes.size()}}; + } + default: + break; + } + WIRE_DLOG_THROW(error::schema::number, "expected a boolean, integer, float or string"); + } + bool msgpack_reader::boolean() { update_tags_remaining(); @@ -413,6 +470,19 @@ namespace wire return std::string{reinterpret_cast(bytes.data()), bytes.size()}; } + std::size_t msgpack_reader::string(epee::span dest, bool exact) + { + update_tags_remaining(); + const epee::span bytes = read_string(remaining_, get_tag()); + + if (!exact && bytes.size() < dest.size()) + dest = {dest.data(), bytes.size()}; + if (dest.size() != bytes.size()) + WIRE_DLOG_THROW(error::schema::string, "of size " << dest.size() << " but got " << bytes.size()); + std::memcpy(dest.data(), bytes.data(), dest.size()); + return dest.size(); + } + epee::byte_slice msgpack_reader::binary() { update_tags_remaining(); @@ -421,16 +491,19 @@ namespace wire return source_.get_slice(begin, begin + bytes.size()); } - void msgpack_reader::binary(epee::span dest) + std::size_t msgpack_reader::binary(epee::span dest, const bool exact) { update_tags_remaining(); const epee::span bytes = read_binary(remaining_, get_tag()); + if (!exact && bytes.size() < dest.size()) + dest = {dest.data(), bytes.size()}; if (dest.size() != bytes.size()) WIRE_DLOG_THROW(error::schema::fixed_binary, "of size " << dest.size() << " but got " << bytes.size()); std::memcpy(dest.data(), bytes.data(), dest.size()); + return dest.size(); } - std::size_t msgpack_reader::start_array(const std::size_t min_element_size) + std::size_t msgpack_reader::do_start_array(const std::size_t min_element_size) { const std::size_t upcoming = read_count(error::schema::array); @@ -440,7 +513,6 @@ namespace wire WIRE_DLOG_THROW(error::schema::array, upcoming << " array elements of at least " << min_element_size << " bytes each exceeds " << remaining_.size() << " remaining bytes"); tags_remaining_ += upcoming; - increment_depth(); return upcoming; } @@ -452,7 +524,7 @@ namespace wire return true; } - std::size_t msgpack_reader::start_object() + std::size_t msgpack_reader::do_start_object() { const std::size_t upcoming = read_count(error::schema::object); @@ -461,7 +533,6 @@ namespace wire if (limits::max() - tags_remaining_ < upcoming * 2) WIRE_DLOG_THROW_(error::msgpack::max_tree_size); tags_remaining_ += upcoming * 2; - increment_depth(); return upcoming; } diff --git a/src/wire/msgpack/read.h b/src/wire/msgpack/read.h index 4712fc2..1395050 100644 --- a/src/wire/msgpack/read.h +++ b/src/wire/msgpack/read.h @@ -75,6 +75,13 @@ namespace wire template std::size_t read_count(error::schema); + + //! \throw wire::exception if next token not `[`. + std::size_t do_start_array(std::size_t min_element_size) override final; + + //! \throw wire::exception if next token not `{`. + std::size_t do_start_object() override final; + public: explicit msgpack_reader(epee::byte_slice&& source) : reader(nullptr), source_(std::move(source)), tags_remaining_(1) @@ -85,6 +92,9 @@ namespace wire //! \throw wire::exception if JSON parsing is incomplete. void check_complete() const override final; + //! \throw wire::exception if array, object, or end of stream. + basic_value basic() override final; + //! \throw wire::exception if next token not a boolean. bool boolean() override final; @@ -114,23 +124,22 @@ namespace wire //! \throw wire::exception if next token not a string std::string string() override final; + /*! Copy upcoming string directly into `dest`. + \throw wire::exception if next value not string + \throw wire::exception if next string exceeds `dest.size())` + \throw wire::exception if `exact == true` and next string is not `dest.size()` + \return Number of bytes read into `dest`. */ + std::size_t string(epee::span dest, bool exact) override final; + //! \throw wire::exception if next token cannot be read as hex epee::byte_slice binary() override final; //! \throw wire::exception if next token cannot be read as hex into `dest`. - void binary(epee::span dest) override final; - - - //! \throw wire::exception if next token not `[`. - std::size_t start_array(std::size_t min_element_size) override final; + std::size_t binary(epee::span dest, bool exact) override final; //! \return true when `count == 0`. bool is_array_end(const std::size_t count) override final; - - - //! \throw wire::exception if next token not `{`. - std::size_t start_object() override final; - + /*! \throw wire::exception if next token not key or `}`. \param[out] index of key match within `map`. \return True if another value to read. */ diff --git a/src/wire/read.cpp b/src/wire/read.cpp index 1474c9c..a6c1ebc 100644 --- a/src/wire/read.cpp +++ b/src/wire/read.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2021, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -42,8 +42,21 @@ void wire::reader::decrement_depth() --depth_; } -[[noreturn]] void wire::integer::throw_exception(std::intmax_t source, std::intmax_t min, std::intmax_t max) +std::size_t wire::reader::start_array(std::size_t min_element_size) { + increment_depth(); + return do_start_array(min_element_size); +} + +std::size_t wire::reader::start_object() +{ + increment_depth(); + return do_start_object(); +} + +[[noreturn]] void wire::integer::throw_exception(const std::intmax_t source, const std::intmax_t min, const std::intmax_t max) +{ + static_assert(0 <= std::numeric_limits::max(), "expected 0 <= intmax_t::max"); static_assert( std::numeric_limits::max() <= std::numeric_limits::max(), "expected intmax_t::max <= uintmax_t::max" @@ -53,9 +66,9 @@ void wire::reader::decrement_depth() else throw_exception(std::uintmax_t(source), std::uintmax_t(max)); } -[[noreturn]] void wire::integer::throw_exception(std::uintmax_t source, std::uintmax_t max) +[[noreturn]] void wire::integer::throw_exception(const std::uintmax_t source, const std::uintmax_t max) { - WIRE_DLOG_THROW(error::schema::smaller_integer, source << " given when " << max << "is maximum permitted"); + WIRE_DLOG_THROW(error::schema::smaller_integer, source << " given when " << max << " is maximum permitted"); } [[noreturn]] void wire_read::throw_exception(const wire::error::schema code, const char* display, epee::span names) @@ -72,4 +85,3 @@ void wire::reader::decrement_depth() WIRE_DLOG_THROW(code, display << (name ? name : "")); } - diff --git a/src/wire/read.h b/src/wire/read.h index 7425191..aec3926 100644 --- a/src/wire/read.h +++ b/src/wire/read.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2023, The Monero Project +// Copyright (c) 2023, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -30,23 +30,52 @@ #include #include #include -#include #include -#include "byte_slice.h" // monero/contrib/epee/include -#include "common/expect.h" // monero/src -#include "span.h" // monero/contrib/epee/include +#include "byte_slice.h" // monero/contrib/epee/include #include "wire/error.h" #include "wire/field.h" +#include "wire/fwd.h" #include "wire/traits.h" +#include "span.h" // monero/contrib/epee/include + +/* + Custom types (e.g. `type` in namespace `ns`) can define an input function by: + * `namespace wire { template<> struct is_blob : std::true_type {}; }` + * `namespace wire { void read_bytes(writer&, ns::type&); }` + * `namespace ns { void read_bytes(wire::writer&, type&); }` + + See `traits.h` for `is_blob` requirements. `read_bytes` function can also + specify derived type for faster output (i.e. + `namespace ns { void read_bytes(wire::epee_reader&, type&); }`). Using the + derived type allows the compiler to de-virtualize and allows for custom + functions not defined by base interface. Using base interface allows for + multiple formats with minimal instruction count. */ namespace wire { //! Interface for converting "wire" (byte) formats to C/C++ objects without a DOM. class reader - { + { std::size_t depth_; //!< Tracks number of recursive objects and arrays + //! \throw wire::exception if max depth is reached + void increment_depth(); + //! \throw std::logic_error if already `depth() == 0`. + void decrement_depth(); + + /*! \param min_element_size of each array element in any format - if known. + Derived types with explicit element count should verify available + space, and throw a `wire::exception` on issues. + \throw wire::exception if next value not array + \throw wire::exception if not enough bytes for all array elements + (with epee/msgpack which has specified number of elements). + \return Number of values to read before calling `is_array_end()`. */ + virtual std::size_t do_start_array(std::size_t min_element_size) = 0; + + //! \throw wire::exception if not object begin. \return State to be given to `key(...)` function. + virtual std::size_t do_start_object() = 0; + protected: epee::span remaining_; //!< Derived class tracks unprocessed bytes here @@ -54,22 +83,16 @@ namespace wire : depth_(0), remaining_(remaining) {} - reader(const reader&) = default; - reader(reader&&) = default; - reader& operator=(const reader&) = default; - reader& operator=(reader&&) = default; - - //! \throw wire::exception if max depth is reached - void increment_depth(); - - //! \throw std::logic_error if already `depth() == 0`. - void decrement_depth(); - + reader(const reader&) = delete; + reader(reader&&) = delete; + reader& operator=(const reader&) = delete; + reader& operator=(reader&&) = delete; + public: struct key_map { - const char* name; - unsigned id; // dest, bool exact) = 0; + // ! \throw wire::exception if next value cannot be read as binary virtual epee::byte_slice binary() = 0; - //! \throw wire::exception if next value cannot be read as binary into `dest`. - virtual void binary(epee::span dest) = 0; + /*! Copy upcoming binary directly into `dest`. + \throw wire::exception if next value not binary + \throw wire::exception if next binary exceeds `dest.size()` + \throw wire::exception if `exact == true` and next binary is not `dest.size()`. + \return Number of bytes read into `dest`. */ + virtual std::size_t binary(epee::span dest, const bool exact) = 0; - /* \param min_element_size of each array element in any format - if known. + /*! \param min_element_size of each array element in any format - if known. Derived types with explicit element count should verify available space, and throw a `wire::exception` on issues. \throw wire::exception if next value not array \throw wire::exception if not enough bytes for all array elements (with epee/msgpack which has specified number of elements). - \return Number of values to read before calling `is_array_end()` */ - virtual std::size_t start_array(std::size_t min_element_size) = 0; + \return Number of values to read before calling `is_array_end()`. */ + std::size_t start_array(std::size_t min_element_size); //! \return True if there is another element to read. virtual bool is_array_end(std::size_t count) = 0; - //! \throw wire::exception if array end delimiter not present. void end_array() { decrement_depth(); } //! \throw wire::exception if not object begin. \return State to be given to `key(...)` function. - virtual std::size_t start_object() = 0; + std::size_t start_object(); /*! Read a key of an object field and match against a known list of keys. Skips or throws exceptions on unknown fields depending on implementation @@ -168,7 +204,7 @@ namespace wire template inline std::enable_if_t::value> read_bytes(R& source, T& dest) - { source.binary(epee::as_mut_byte_span(dest)); } + { source.binary(epee::as_mut_byte_span(dest), /*exact=*/ true); } //! Use `read_bytes(...)` method if available for `T`. template @@ -193,7 +229,7 @@ namespace wire "source must be signed integer type" ); if (source < limit::min() || limit::max() < source) - throw_exception(source, limit::min(), limit::max()); + throw_exception(source, limit::min(), limit::max()); return static_cast(source); } @@ -213,7 +249,7 @@ namespace wire throw_exception(source, limit::max()); return static_cast(source); } - } + } // integer //! read all current and future signed integer types template @@ -234,11 +270,12 @@ namespace wire namespace wire_read { - /*! Don't add a function called `read_bytes` to this namespace, it will prevent - ADL lookup. ADL lookup delays the function searching until the template - is used instead of when its defined. This allows the unqualified calls to - `read_bytes` in this namespace to "find" user functions that are declared - after these functions (the technique behind `boost::serialization`). */ + /*! Don't add a function called `read_bytes` to this namespace, it will + prevent 2-phase lookup. 2-phase lookup delays the function searching until + the template is used instead of when its defined. This allows the + unqualified calls to `read_bytes` in this namespace to "find" user + functions that are declared after these functions (the technique behind + `boost::serialization`). */ [[noreturn]] void throw_exception(wire::error::schema code, const char* display, epee::span name_list); @@ -248,10 +285,13 @@ namespace wire_read read_bytes(source, dest); // ADL (searches every associated namespace) } - //! \return `T` converted from `source` or error. + //! Use `source` to store information at `dest` template inline std::error_code from_bytes(T&& source, U& dest) { + if (wire::is_optional_root::value && source.empty()) + return {}; + try { R in{std::forward(source)}; @@ -262,6 +302,7 @@ namespace wire_read { return e.code(); } + return {}; } @@ -273,17 +314,18 @@ namespace wire_read } // Insert to sorted containers - template> + template inline auto array_insert(R& source, T& dest) -> decltype(dest.emplace_hint(dest.end(), std::declval()), bool(true)) { - V val{}; + using value_type = typename wire::remove_const_key::type; + value_type val{}; wire_read::bytes(source, val); dest.emplace_hint(dest.end(), std::move(val)); return true; } // Insert into unsorted containers - template + template inline auto array_insert(R& source, T& dest) -> decltype(dest.emplace_back(), dest.back(), bool(true)) { // more efficient to process the object in-place in many cases @@ -307,10 +349,6 @@ namespace wire_read if (max_element_count < count) throw_exception(wire::error::schema::array_max_element, "", nullptr); - // also checked by derived formats when count is known - if (min_element_size && (source.remaining().size() / min_element_size) < count) - throw_exception(wire::error::schema::array_min_size, "", nullptr); - dest.clear(); wire::reserve(dest, count); @@ -360,13 +398,13 @@ namespace wire_read } template - inline void unpack_field(std::size_t, R& source, wire::field_& dest) + inline void unpack_field(R& source, wire::field_& dest) { bytes(source, dest.get_value()); } template - inline void unpack_field(std::size_t, R& source, wire::field_& dest) + inline void unpack_field(R& source, wire::field_& dest) { if (!bool(dest.get_value())) dest.get_value().emplace(); @@ -395,10 +433,9 @@ namespace wire_read return (is_required() && !read_) ? field_.name : nullptr; } - //! Set all entries in `map` related to this field (expand variant types!). template - std::size_t set_mapping(std::size_t index, wire::reader::key_map (&map)[N]) + std::size_t set_mapping(std::size_t index, wire::reader::key_map (&map)[N]) noexcept { our_index_ = index; map[index].id = field_.id(); @@ -415,7 +452,7 @@ namespace wire_read if (read_) throw_exception(wire::error::schema::invalid_key, "duplicate", {std::addressof(field_.name), 1}); - unpack_field(index - our_index_, source, field_); + unpack_field(source, field_); read_ = true; return 1 + is_required(); } @@ -432,13 +469,11 @@ namespace wire_read // `expand_tracker_map` writes all `tracker` types to a table template - inline constexpr std::size_t expand_tracker_map(std::size_t index, const wire::reader::key_map (&)[N]) - { - return index; - } + inline void expand_tracker_map(std::size_t index, const wire::reader::key_map (&)[N]) noexcept + {} template - inline void expand_tracker_map(std::size_t index, wire::reader::key_map (&map)[N], tracker& head, tracker&... tail) + inline void expand_tracker_map(std::size_t index, wire::reader::key_map (&map)[N], tracker& head, tracker&... tail) noexcept { expand_tracker_map(head.set_mapping(index, map), map, tail...); } @@ -448,15 +483,16 @@ namespace wire_read { static constexpr const std::size_t total_subfields = wire::sum(fields.count()...); static_assert(total_subfields < 100, "algorithm uses too much stack space and linear searching"); + static_assert(sizeof...(T) <= total_subfields, "subfield parameter pack size mismatch"); std::size_t state = source.start_object(); std::size_t required = wire::sum(std::size_t(fields.is_required())...); - wire::reader::key_map map[total_subfields] = {}; + wire::reader::key_map map[total_subfields + 1] = {}; // +1 for empty object expand_tracker_map(0, map, fields...); std::size_t next = 0; - while (source.key(map, state, next)) + while (source.key({map, total_subfields}, state, next)) { switch (wire::sum(fields.try_read(source, next)...)) { @@ -473,7 +509,7 @@ namespace wire_read if (required) { - const char* missing[] = {fields.name_if_missing()...}; + const char* missing[] = {fields.name_if_missing()..., nullptr}; throw_exception(wire::error::schema::missing_key, "", missing); } @@ -502,4 +538,10 @@ namespace wire { wire_read::object(source, wire_read::tracker{std::move(fields)}...); } + + template + inline void object_fwd(const std::true_type /*is_read*/, R& source, T&&... fields) + { + wire::object(source, std::forward(fields)...); + } } diff --git a/src/wire/traits.h b/src/wire/traits.h index 0255949..be5bcda 100644 --- a/src/wire/traits.h +++ b/src/wire/traits.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2021-2024, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -27,17 +27,24 @@ #pragma once +#include #include #include -#define WIRE_DECLARE_BLOB(type) \ - template<> \ - struct is_blob \ - : std::true_type \ +#define WIRE_DECLARE_BLOB_NS(type) \ + template<> \ + struct is_blob \ + : std::true_type \ {} -#define WIRE_DECLARE_BLOB_NS(type) \ - namespace wire { WIRE_DECLARE_BLOB(type); } +#define WIRE_DECLARE_BLOB(type) \ + namespace wire { WIRE_DECLARE_BLOB_NS(type); } + +#define WIRE_DECLARE_OPTIONAL_ROOT(type) \ + template<> \ + struct is_optional_root \ + : std::true_type \ + {} namespace wire { @@ -55,20 +62,32 @@ namespace wire template using unwrap_reference_t = typename unwrap_reference::type; + template + struct remove_const_key + { + using type = T; + }; + + template + struct remove_const_key> + { + using type = std::pair; + }; + /*! Mark `T` as an array for writing, and reading when - `default_min_element_size::value != 0`. See `array_` in - `wrapper/array.h`. */ + `default_min_element_size::value != 0`. See `array_` in + `wrapper/array.h`. */ template struct is_array : std::false_type {}; /*! Mark `T` as fixed binary data for reading+writing. Concept requirements - for reading: - * `T` must be compatible with `epee::as_mut_byte_span` (`std::is_pod` - and no padding). - Concept requirements for writing: - * `T` must be compatible with `epee::as_byte_span` (std::is_pod` and - no padding). */ + for reading: + * `T` must be compatible with `epee::as_mut_byte_span` (`std::is_pod` + and no padding). + Concept requirements for writing: + * `T` must be compatible with `epee::as_byte_span` (std::is_pod` and + no padding). */ template struct is_blob : std::false_type {}; @@ -84,6 +103,12 @@ namespace wire : is_array // all array types in old output engine were optional when empty {}; + //! When `T` is being read as root object, allow an empty read buffer. + template + struct is_optional_root + : std::is_empty + {}; + //! A constraint for `wire_read::array` where a max of `N` elements can be read. template struct max_element_count diff --git a/src/wire/wrapper/CMakeLists.txt b/src/wire/wrapper/CMakeLists.txt index b1b6ea5..c688bff 100644 --- a/src/wire/wrapper/CMakeLists.txt +++ b/src/wire/wrapper/CMakeLists.txt @@ -26,10 +26,10 @@ # 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. -set(monero-lws-wire_sources variant.cpp) -set(monero-lws-wire_headers variant.h) +set(lwsf-wire_sources variant.cpp) +set(lwsf-wire_headers variant.h) -add_library(monero-lws-wire-wrapper ${monero-lws-wire_sources} ${monero-lws-wire_headers}) -target_include_directories(monero-lws-wire-wrapper PUBLIC "${LMDB_INCLUDE}") -target_link_libraries(monero-lws-wire-wrapper PRIVATE monero::libraries) +add_library(lwsf-wire-wrapper ${lwsf-wire_sources} ${lwsf-wire_headers}) +target_include_directories(lwsf-wire-wrapper PUBLIC "${LMDB_INCLUDE}") +target_link_libraries(lwsf-wire-wrapper PRIVATE monero::libraries)