diff options
Diffstat (limited to 'src')
23 files changed, 618 insertions, 89 deletions
diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat Binary files differindex f0a0dbb35..eee34e60f 100644 --- a/src/blocks/checkpoints.dat +++ b/src/blocks/checkpoints.dat diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index 6be80dbbd..db2296df9 100644 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -241,6 +241,7 @@ namespace cryptonote ADD_CHECKPOINT2(2182500, "0d22b5f81982eff21d094af9e821dc2007e6342069e3b1a37b15d97646353124", "0xead4a874083492"); ADD_CHECKPOINT2(2661600, "41c9060e8426012238e8a26da26fcb90797436896cc70886a894c2c560bcccf2", "0x2e0d87526ff161f"); ADD_CHECKPOINT2(2677000, "1b9fee6246eeb176bd17d637bf252e9af54a4218675f01b4449cc0901867f9eb", "0x2f165bc1a5163ba"); + ADD_CHECKPOINT2(2706000, "d8eb144c5e1fe6b329ecc900ec95e7792fccff84175fb23a25ed59d7299a511c", "0x310f7d89372f705"); return true; } diff --git a/src/cryptonote_basic/hardfork.h b/src/cryptonote_basic/hardfork.h index 32f669c71..f43710f4f 100644 --- a/src/cryptonote_basic/hardfork.h +++ b/src/cryptonote_basic/hardfork.h @@ -231,6 +231,11 @@ namespace cryptonote */ uint64_t get_window_size() const { return window_size; } + /** + * @brief returns info for all known hard forks + */ + const std::vector<hardfork_t>& get_hardforks() const { return heights; } + private: uint8_t get_block_version(uint64_t height) const; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index e0cd8e899..b4abde1ad 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -5604,7 +5604,7 @@ void Blockchain::cancel() } #if defined(PER_BLOCK_CHECKPOINT) -static const char expected_block_hashes_hash[] = "a8b24ef4eeea7241b374d4526a3f7c351b53abe7006a3d7eee02ce0af2cc6d66"; +static const char expected_block_hashes_hash[] = "a913c311ed4cbe42c5b36c13215021dd50705b17d03ddc2e637ab7e85b22ac89"; void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints) { if (get_checkpoints == nullptr || !m_fast_sync) diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 7a94f6358..4795fc55c 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -159,6 +159,13 @@ namespace cryptonote bool deinit(); /** + * @brief get a set of blockchain checkpoint hashes + * + * @return set of blockchain checkpoint hashes + */ + const checkpoints& get_checkpoints() const { return m_checkpoints; } + + /** * @brief assign a set of blockchain checkpoint hashes * * @param chk_pts the set of checkpoints to assign @@ -893,6 +900,13 @@ namespace cryptonote uint64_t get_earliest_ideal_height_for_version(uint8_t version) const { return m_hardfork->get_earliest_ideal_height_for_version(version); } /** + * @brief returns info for all known hard forks + * + * @return the hardforks + */ + const std::vector<hardfork_t>& get_hardforks() const { return m_hardfork->get_hardforks(); } + + /** * @brief get information about hardfork voting for a version * * @param version the version in question diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 95cd1c83b..31e4e0414 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -252,6 +252,10 @@ namespace cryptonote m_pprotocol = &m_protocol_stub; } //----------------------------------------------------------------------------------- + const checkpoints& core::get_checkpoints() const + { + return m_blockchain_storage.get_checkpoints(); + } void core::set_checkpoints(checkpoints&& chk_pts) { m_blockchain_storage.set_checkpoints(std::move(chk_pts)); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 5f134a999..6dc513570 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -437,6 +437,13 @@ namespace cryptonote void set_cryptonote_protocol(i_cryptonote_protocol* pprotocol); /** + * @copydoc Blockchain::get_checkpoints + * + * @note see Blockchain::get_checkpoints() + */ + const checkpoints& get_checkpoints() const; + + /** * @copydoc Blockchain::set_checkpoints * * @note see Blockchain::set_checkpoints() diff --git a/src/device_trezor/device_trezor.cpp b/src/device_trezor/device_trezor.cpp index 58c36f2c9..3f7b10be4 100644 --- a/src/device_trezor/device_trezor.cpp +++ b/src/device_trezor/device_trezor.cpp @@ -511,7 +511,7 @@ namespace trezor { tools::wallet2::signed_tx_set & signed_tx, hw::tx_aux_data & aux_data) { - CHECK_AND_ASSERT_THROW_MES(unsigned_tx.transfers.first == 0, "Unsuported non zero offset"); + CHECK_AND_ASSERT_THROW_MES(std::get<0>(unsigned_tx.transfers) == 0, "Unsuported non zero offset"); TREZOR_AUTO_LOCK_CMD(); require_connected(); @@ -522,7 +522,7 @@ namespace trezor { const size_t num_tx = unsigned_tx.txes.size(); m_num_transations_to_sign = num_tx; signed_tx.key_images.clear(); - signed_tx.key_images.resize(unsigned_tx.transfers.second.size()); + signed_tx.key_images.resize(std::get<2>(unsigned_tx.transfers).size()); for(size_t tx_idx = 0; tx_idx < num_tx; ++tx_idx) { std::shared_ptr<protocol::tx::Signer> signer; @@ -566,8 +566,8 @@ namespace trezor { cpend.key_images = key_images; // KI sync - for(size_t cidx=0, trans_max=unsigned_tx.transfers.second.size(); cidx < trans_max; ++cidx){ - signed_tx.key_images[cidx] = unsigned_tx.transfers.second[cidx].m_key_image; + for(size_t cidx=0, trans_max=std::get<2>(unsigned_tx.transfers).size(); cidx < trans_max; ++cidx){ + signed_tx.key_images[cidx] = std::get<2>(unsigned_tx.transfers)[cidx].m_key_image; } size_t num_sources = cdata.tx_data.sources.size(); @@ -579,9 +579,9 @@ namespace trezor { CHECK_AND_ASSERT_THROW_MES(src_idx < cdata.tx.vin.size(), "Invalid idx_mapped"); size_t idx_map_src = cdata.tx_data.selected_transfers[idx_mapped]; - CHECK_AND_ASSERT_THROW_MES(idx_map_src >= unsigned_tx.transfers.first, "Invalid offset"); + CHECK_AND_ASSERT_THROW_MES(idx_map_src >= std::get<0>(unsigned_tx.transfers), "Invalid offset"); - idx_map_src -= unsigned_tx.transfers.first; + idx_map_src -= std::get<0>(unsigned_tx.transfers); CHECK_AND_ASSERT_THROW_MES(idx_map_src < signed_tx.key_images.size(), "Invalid key image index"); const auto vini = boost::get<cryptonote::txin_to_key>(cdata.tx.vin[src_idx]); diff --git a/src/device_trezor/trezor/protocol.hpp b/src/device_trezor/trezor/protocol.hpp index fa8355200..7ffadd9aa 100644 --- a/src/device_trezor/trezor/protocol.hpp +++ b/src/device_trezor/trezor/protocol.hpp @@ -230,8 +230,8 @@ namespace tx { } const tools::wallet2::transfer_details & get_transfer(size_t idx) const { - CHECK_AND_ASSERT_THROW_MES(idx < m_unsigned_tx->transfers.second.size() + m_unsigned_tx->transfers.first && idx >= m_unsigned_tx->transfers.first, "Invalid transfer index"); - return m_unsigned_tx->transfers.second[idx - m_unsigned_tx->transfers.first]; + CHECK_AND_ASSERT_THROW_MES(idx < std::get<2>(m_unsigned_tx->transfers).size() + std::get<0>(m_unsigned_tx->transfers) && idx >= std::get<0>(m_unsigned_tx->transfers), "Invalid transfer index"); + return std::get<2>(m_unsigned_tx->transfers)[idx - std::get<0>(m_unsigned_tx->transfers)]; } const tools::wallet2::transfer_details & get_source_transfer(size_t idx) const { diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 5304333ff..16bcf2c04 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2290,6 +2290,12 @@ namespace cryptonote return m_bootstrap_daemon->handle_result(false, {}); } + if (bootstrap_daemon_height < m_core.get_checkpoints().get_max_height()) + { + MINFO("Bootstrap daemon height is lower than the latest checkpoint"); + return m_bootstrap_daemon->handle_result(false, {}); + } + if (!m_p2p.get_payload_object().no_sync()) { uint64_t top_height = m_core.get_current_blockchain_height(); @@ -2855,6 +2861,10 @@ namespace cryptonote res.version = CORE_RPC_VERSION; res.release = MONERO_VERSION_IS_RELEASE; + res.current_height = m_core.get_current_blockchain_height(); + res.target_height = m_p2p.get_payload_object().is_synchronized() ? 0 : m_core.get_target_blockchain_height(); + for (const auto &hf : m_core.get_blockchain_storage().get_hardforks()) + res.hard_forks.push_back({hf.version, hf.height}); res.status = CORE_RPC_STATUS_OK; return true; } diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 1be2610ff..e1222f6eb 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -88,7 +88,7 @@ namespace cryptonote // advance which version they will stop working with // Don't go over 32767 for any of these #define CORE_RPC_VERSION_MAJOR 3 -#define CORE_RPC_VERSION_MINOR 10 +#define CORE_RPC_VERSION_MINOR 11 #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) @@ -2123,15 +2123,34 @@ namespace cryptonote }; typedef epee::misc_utils::struct_init<request_t> request; + struct hf_entry + { + uint8_t hf_version; + uint64_t height; + + bool operator==(const hf_entry& hfe) const { return hf_version == hfe.hf_version && height == hfe.height; } + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(hf_version) + KV_SERIALIZE(height) + END_KV_SERIALIZE_MAP() + }; + struct response_t: public rpc_response_base { uint32_t version; bool release; + uint64_t current_height; + uint64_t target_height; + std::vector<hf_entry> hard_forks; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(version) KV_SERIALIZE(release) + KV_SERIALIZE_OPT(current_height, (uint64_t)0) + KV_SERIALIZE_OPT(target_height, (uint64_t)0) + KV_SERIALIZE_OPT(hard_forks, std::vector<hf_entry>()) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; diff --git a/src/serialization/tuple.h b/src/serialization/tuple.h new file mode 100644 index 000000000..6d98e05b0 --- /dev/null +++ b/src/serialization/tuple.h @@ -0,0 +1,169 @@ +// Copyright (c) 2014-2020, 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. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once +#include <memory> +#include "serialization.h" + +namespace serialization +{ + namespace detail + { + template <typename Archive, class T> + bool serialize_tuple_element(Archive& ar, T& e) + { + return ::do_serialize(ar, e); + } + + template <typename Archive> + bool serialize_tuple_element(Archive& ar, uint64_t& e) + { + ar.serialize_varint(e); + return true; + } + } +} + +template <template <bool> class Archive, class E0, class E1, class E2> +inline bool do_serialize(Archive<false>& ar, std::tuple<E0,E1,E2>& p) +{ + size_t cnt; + ar.begin_array(cnt); + if (!ar.good()) + return false; + if (cnt != 3) + return false; + + if (!::serialization::detail::serialize_tuple_element(ar, std::get<0>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if (!::serialization::detail::serialize_tuple_element(ar, std::get<1>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if (!::serialization::detail::serialize_tuple_element(ar, std::get<2>(p))) + return false; + if (!ar.good()) + return false; + + ar.end_array(); + return true; +} + +template <template <bool> class Archive, class E0, class E1, class E2> +inline bool do_serialize(Archive<true>& ar, std::tuple<E0,E1,E2>& p) +{ + ar.begin_array(3); + if (!ar.good()) + return false; + if(!::serialization::detail::serialize_tuple_element(ar, std::get<0>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if(!::serialization::detail::serialize_tuple_element(ar, std::get<1>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if(!::serialization::detail::serialize_tuple_element(ar, std::get<2>(p))) + return false; + if (!ar.good()) + return false; + ar.end_array(); + return true; +} + +template <template <bool> class Archive, class E0, class E1, class E2, class E3> +inline bool do_serialize(Archive<false>& ar, std::tuple<E0,E1,E2,E3>& p) +{ + size_t cnt; + ar.begin_array(cnt); + if (!ar.good()) + return false; + if (cnt != 4) + return false; + + if (!::serialization::detail::serialize_tuple_element(ar, std::get<0>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if (!::serialization::detail::serialize_tuple_element(ar, std::get<1>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if (!::serialization::detail::serialize_tuple_element(ar, std::get<2>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if (!::serialization::detail::serialize_tuple_element(ar, std::get<3>(p))) + return false; + if (!ar.good()) + return false; + + ar.end_array(); + return true; +} + +template <template <bool> class Archive, class E0, class E1, class E2, class E3> +inline bool do_serialize(Archive<true>& ar, std::tuple<E0,E1,E2,E3>& p) +{ + ar.begin_array(4); + if (!ar.good()) + return false; + if(!::serialization::detail::serialize_tuple_element(ar, std::get<0>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if(!::serialization::detail::serialize_tuple_element(ar, std::get<1>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if(!::serialization::detail::serialize_tuple_element(ar, std::get<2>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if(!::serialization::detail::serialize_tuple_element(ar, std::get<3>(p))) + return false; + if (!ar.good()) + return false; + ar.end_array(); + return true; +} + diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 2fb538c73..860c3f0b0 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -181,7 +181,6 @@ namespace const command_line::arg_descriptor<bool> arg_restore_from_seed = {"restore-from-seed", sw::tr("alias for --restore-deterministic-wallet"), false}; const command_line::arg_descriptor<bool> arg_restore_multisig_wallet = {"restore-multisig-wallet", sw::tr("Recover multisig wallet using Electrum-style mnemonic seed"), false}; const command_line::arg_descriptor<bool> arg_non_deterministic = {"non-deterministic", sw::tr("Generate non-deterministic view and spend keys"), false}; - const command_line::arg_descriptor<bool> arg_allow_mismatched_daemon_version = {"allow-mismatched-daemon-version", sw::tr("Allow communicating with a daemon that uses a different RPC version"), false}; const command_line::arg_descriptor<uint64_t> arg_restore_height = {"restore-height", sw::tr("Restore from specific blockchain height"), 0}; const command_line::arg_descriptor<std::string> arg_restore_date = {"restore-date", sw::tr("Restore from estimated blockchain height on specified date"), ""}; const command_line::arg_descriptor<bool> arg_do_not_relay = {"do-not-relay", sw::tr("The newly created transaction will not be relayed to the monero network"), false}; @@ -3233,8 +3232,7 @@ bool simple_wallet::scan_tx(const std::vector<std::string> &args) } simple_wallet::simple_wallet() - : m_allow_mismatched_daemon_version(false) - , m_refresh_progress_reporter(*this) + : m_refresh_progress_reporter(*this) , m_idle_run(true) , m_auto_refresh_enabled(false) , m_auto_refresh_refreshing(false) @@ -4759,7 +4757,6 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_ m_restore_deterministic_wallet = command_line::get_arg(vm, arg_restore_deterministic_wallet) || command_line::get_arg(vm, arg_restore_from_seed); m_restore_multisig_wallet = command_line::get_arg(vm, arg_restore_multisig_wallet); m_non_deterministic = command_line::get_arg(vm, arg_non_deterministic); - m_allow_mismatched_daemon_version = command_line::get_arg(vm, arg_allow_mismatched_daemon_version); m_restore_height = command_line::get_arg(vm, arg_restore_height); m_restore_date = command_line::get_arg(vm, arg_restore_date); m_do_not_relay = command_line::get_arg(vm, arg_do_not_relay); @@ -4790,12 +4787,20 @@ bool simple_wallet::try_connect_to_daemon(bool silent, uint32_t* version) uint32_t version_ = 0; if (!version) version = &version_; - if (!m_wallet->check_connection(version)) + bool wallet_is_outdated, daemon_is_outdated = false; + if (!m_wallet->check_connection(version, NULL, 200000, &wallet_is_outdated, &daemon_is_outdated)) { if (!silent) { if (m_wallet->is_offline()) fail_msg_writer() << tr("wallet failed to connect to daemon, because it is set to offline mode"); + else if (wallet_is_outdated) + fail_msg_writer() << tr("wallet failed to connect to daemon, because it is not up to date. ") << + tr("Please make sure you are running the latest wallet."); + else if (daemon_is_outdated) + fail_msg_writer() << tr("wallet failed to connect to daemon: ") << m_wallet->get_daemon_address() << ". " << + tr("Daemon is not up to date. " + "Please make sure the daemon is running the latest version or change the daemon address using the 'set_daemon' command."); else fail_msg_writer() << tr("wallet failed to connect to daemon: ") << m_wallet->get_daemon_address() << ". " << tr("Daemon either is not started or wrong port was passed. " @@ -4803,7 +4808,7 @@ bool simple_wallet::try_connect_to_daemon(bool silent, uint32_t* version) } return false; } - if (!m_allow_mismatched_daemon_version && ((*version >> 16) != CORE_RPC_VERSION_MAJOR)) + if (!m_wallet->is_mismatched_daemon_version_allowed() && ((*version >> 16) != CORE_RPC_VERSION_MAJOR)) { if (!silent) fail_msg_writer() << boost::format(tr("Daemon uses a different RPC major version (%u) than the wallet (%u): %s. Either update one of them, or use --allow-mismatched-daemon-version.")) % (*version>>16) % CORE_RPC_VERSION_MAJOR % m_wallet->get_daemon_address(); @@ -7925,8 +7930,10 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, bool simple_wallet::accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs) { std::string extra_message; - if (!txs.transfers.second.empty()) - extra_message = (boost::format("%u outputs to import. ") % (unsigned)txs.transfers.second.size()).str(); + if (!std::get<2>(txs.new_transfers).empty()) + extra_message = (boost::format("%u outputs to import. ") % (unsigned)std::get<2>(txs.new_transfers).size()).str(); + else if (!std::get<2>(txs.transfers).empty()) + extra_message = (boost::format("%u outputs to import. ") % (unsigned)std::get<2>(txs.transfers).size()).str(); return accept_loaded_tx([&txs](){return txs.txes.size();}, [&txs](size_t n)->const tools::wallet2::tx_construction_data&{return txs.txes[n];}, extra_message); } //---------------------------------------------------------------------------------------------------- @@ -10640,7 +10647,6 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_restore_multisig_wallet ); command_line::add_arg(desc_params, arg_non_deterministic ); command_line::add_arg(desc_params, arg_electrum_seed ); - command_line::add_arg(desc_params, arg_allow_mismatched_daemon_version); command_line::add_arg(desc_params, arg_restore_height); command_line::add_arg(desc_params, arg_restore_date); command_line::add_arg(desc_params, arg_do_not_relay); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index b18fa2dd6..0f2fe7bc6 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -429,7 +429,6 @@ namespace cryptonote bool m_restore_deterministic_wallet; // recover flag bool m_restore_multisig_wallet; // recover flag bool m_non_deterministic; // old 2-random generation - bool m_allow_mismatched_daemon_version; bool m_restoring; // are we restoring, by whatever method? uint64_t m_restore_height; // optional bool m_do_not_relay; diff --git a/src/version.cpp.in b/src/version.cpp.in index 91fdc9902..a28be1a1d 100644 --- a/src/version.cpp.in +++ b/src/version.cpp.in @@ -1,5 +1,5 @@ #define DEF_MONERO_VERSION_TAG "@VERSIONTAG@" -#define DEF_MONERO_VERSION "0.18.1.0" +#define DEF_MONERO_VERSION "0.18.1.1" #define DEF_MONERO_RELEASE_NAME "Fluorine Fermi" #define DEF_MONERO_VERSION_FULL DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG #define DEF_MONERO_VERSION_IS_RELEASE @VERSION_IS_RELEASE@ diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 1ee2e20b6..470206bc5 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1146,8 +1146,8 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file // Check tx data and construct confirmation message std::string extra_message; - if (!transaction->m_unsigned_tx_set.transfers.second.empty()) - extra_message = (boost::format("%u outputs to import. ") % (unsigned)transaction->m_unsigned_tx_set.transfers.second.size()).str(); + if (!std::get<2>(transaction->m_unsigned_tx_set.transfers).empty()) + extra_message = (boost::format("%u outputs to import. ") % (unsigned)std::get<2>(transaction->m_unsigned_tx_set.transfers).size()).str(); transaction->checkLoadedTx([&transaction](){return transaction->m_unsigned_tx_set.txes.size();}, [&transaction](size_t n)->const tools::wallet2::tx_construction_data&{return transaction->m_unsigned_tx_set.txes[n];}, extra_message); setStatus(transaction->status(), transaction->errorString()); @@ -2173,9 +2173,15 @@ bool WalletImpl::connectToDaemon() Wallet::ConnectionStatus WalletImpl::connected() const { uint32_t version = 0; - m_is_connected = m_wallet->check_connection(&version, NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS); + bool wallet_is_outdated, daemon_is_outdated = false; + m_is_connected = m_wallet->check_connection(&version, NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS, &wallet_is_outdated, &daemon_is_outdated); if (!m_is_connected) - return Wallet::ConnectionStatus_Disconnected; + { + if (!m_wallet->light_wallet() && (wallet_is_outdated || daemon_is_outdated)) + return Wallet::ConnectionStatus_WrongVersion; + else + return Wallet::ConnectionStatus_Disconnected; + } // Version check is not implemented in light wallets nodes/wallets if (!m_wallet->light_wallet() && (version >> 16) != CORE_RPC_VERSION_MAJOR) return Wallet::ConnectionStatus_WrongVersion; diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 7810abdd2..0a9ea8f7b 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -82,18 +82,21 @@ void NodeRPCProxy::invalidate() m_rpc_payment_seed_hash = crypto::null_hash; m_rpc_payment_next_seed_hash = crypto::null_hash; m_height_time = 0; + m_target_height_time = 0; m_rpc_payment_diff = 0; m_rpc_payment_credits_per_hash_found = 0; m_rpc_payment_height = 0; m_rpc_payment_cookie = 0; + m_daemon_hard_forks.clear(); } -boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version) +boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version, std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, uint64_t &height, uint64_t &target_height) { if (m_offline) return boost::optional<std::string>("offline"); if (m_rpc_version == 0) { + const time_t now = time(NULL); cryptonote::COMMAND_RPC_GET_VERSION::request req_t = AUTO_VAL_INIT(req_t); cryptonote::COMMAND_RPC_GET_VERSION::response resp_t = AUTO_VAL_INIT(resp_t); { @@ -101,9 +104,28 @@ boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t, m_http_client, rpc_timeout); RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_version"); } + m_rpc_version = resp_t.version; + m_daemon_hard_forks.clear(); + for (const auto &hf : resp_t.hard_forks) + m_daemon_hard_forks.push_back(std::make_pair(hf.hf_version, hf.height)); + if (resp_t.current_height > 0 || resp_t.target_height > 0) + { + m_height = resp_t.current_height; + m_target_height = resp_t.target_height; + m_height_time = now; + m_target_height_time = now; + } } + rpc_version = m_rpc_version; + daemon_hard_forks = m_daemon_hard_forks; + boost::optional<std::string> result = get_height(height); + if (result) + return result; + result = get_target_height(target_height); + if (result) + return result; return boost::optional<std::string>(); } @@ -138,6 +160,7 @@ boost::optional<std::string> NodeRPCProxy::get_info() m_adjusted_time = resp_t.adjusted_time; m_get_info_time = now; m_height_time = now; + m_target_height_time = now; } return boost::optional<std::string>(); } @@ -160,6 +183,13 @@ boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height) boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height) { + const time_t now = time(NULL); + if (now < m_target_height_time + 30) // re-cache every 30 seconds + { + height = m_target_height; + return boost::optional<std::string>(); + } + auto res = get_info(); if (res) return res; diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h index 07675cdb0..e320565ac 100644 --- a/src/wallet/node_rpc_proxy.h +++ b/src/wallet/node_rpc_proxy.h @@ -48,7 +48,7 @@ public: void invalidate(); void set_offline(bool offline) { m_offline = offline; } - boost::optional<std::string> get_rpc_version(uint32_t &version); + boost::optional<std::string> get_rpc_version(uint32_t &rpc_version, std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, uint64_t &height, uint64_t &target_height); boost::optional<std::string> get_height(uint64_t &height); void set_height(uint64_t h); boost::optional<std::string> get_target_height(uint64_t &height); @@ -103,6 +103,8 @@ private: crypto::hash m_rpc_payment_next_seed_hash; uint32_t m_rpc_payment_cookie; time_t m_height_time; + time_t m_target_height_time; + std::vector<std::pair<uint8_t, uint64_t>> m_daemon_hard_forks; }; } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 6234427a6..588ddd572 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -47,6 +47,7 @@ using namespace epee; #include "cryptonote_config.h" +#include "hardforks/hardforks.h" #include "cryptonote_core/tx_sanity_check.h" #include "wallet_rpc_helpers.h" #include "wallet2.h" @@ -275,6 +276,7 @@ struct options { const command_line::arg_descriptor<bool> no_dns = {"no-dns", tools::wallet2::tr("Do not use DNS"), false}; const command_line::arg_descriptor<bool> offline = {"offline", tools::wallet2::tr("Do not connect to a daemon, nor use DNS"), false}; const command_line::arg_descriptor<std::string> extra_entropy = {"extra-entropy", tools::wallet2::tr("File containing extra entropy to initialize the PRNG (any data, aim for 256 bits of entropy to be useful, which typically means more than 256 bits of data)")}; + const command_line::arg_descriptor<bool> allow_mismatched_daemon_version = {"allow-mismatched-daemon-version", tools::wallet2::tr("Allow communicating with a daemon that uses a different version"), false}; }; void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file, std::string &mms_file) @@ -484,6 +486,9 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl add_extra_entropy_thread_safe(data.data(), data.size()); } + if (command_line::has_arg(vm, opts.allow_mismatched_daemon_version)) + wallet->allow_mismatched_daemon_version(true); + try { if (!command_line::is_arg_defaulted(vm, opts.tx_notify)) @@ -1218,7 +1223,9 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std m_export_format(ExportFormat::Binary), m_load_deprecated_formats(false), m_credits_target(0), - m_enable_multisig(false) + m_enable_multisig(false), + m_has_ever_refreshed_from_node(false), + m_allow_mismatched_daemon_version(false) { set_rpc_client_secret_key(rct::rct2sk(rct::skGen())); } @@ -1278,6 +1285,7 @@ void wallet2::init_options(boost::program_options::options_description& desc_par command_line::add_arg(desc_params, opts.no_dns); command_line::add_arg(desc_params, opts.offline); command_line::add_arg(desc_params, opts.extra_entropy); + command_line::add_arg(desc_params, opts.allow_mismatched_daemon_version); } std::pair<std::unique_ptr<wallet2>, tools::password_container> wallet2::make_from_json(const boost::program_options::variables_map& vm, bool unattended, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) @@ -2914,6 +2922,26 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo refresh(trusted_daemon, start_height, blocks_fetched, received_money); } //---------------------------------------------------------------------------------------------------- +void check_block_hard_fork_version(cryptonote::network_type nettype, uint8_t hf_version, uint64_t height, bool &wallet_is_outdated, bool &daemon_is_outdated) +{ + const size_t wallet_num_hard_forks = nettype == TESTNET ? num_testnet_hard_forks + : nettype == STAGENET ? num_stagenet_hard_forks : num_mainnet_hard_forks; + const hardfork_t *wallet_hard_forks = nettype == TESTNET ? testnet_hard_forks + : nettype == STAGENET ? stagenet_hard_forks : mainnet_hard_forks; + + wallet_is_outdated = static_cast<size_t>(hf_version) > wallet_num_hard_forks; + if (wallet_is_outdated) + return; + + // check block's height falls within wallet's expected range for block's given version + uint64_t start_height = hf_version == 1 ? 0 : wallet_hard_forks[hf_version - 1].height; + uint64_t end_height = static_cast<size_t>(hf_version) + 1 > wallet_num_hard_forks + ? std::numeric_limits<uint64_t>::max() + : wallet_hard_forks[hf_version].height; + + daemon_is_outdated = height < start_height || height >= end_height; +} +//---------------------------------------------------------------------------------------------------- void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &last, bool &error, std::exception_ptr &exception) { error = false; @@ -2955,6 +2983,23 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks error = true; break; } + + if (!m_allow_mismatched_daemon_version) + { + // make sure block's hard fork version is expected at the block's height + uint8_t hf_version = parsed_blocks[i].block.major_version; + uint64_t height = blocks_start_height + i; + bool wallet_is_outdated = false; + bool daemon_is_outdated = false; + check_block_hard_fork_version(m_nettype, hf_version, height, wallet_is_outdated, daemon_is_outdated); + THROW_WALLET_EXCEPTION_IF(wallet_is_outdated || daemon_is_outdated, error::incorrect_fork_version, + "Unexpected hard fork version v" + std::to_string(hf_version) + " at height " + std::to_string(height) + ". " + + (wallet_is_outdated + ? "Make sure your wallet is up to date" + : "Make sure the node you are connected to is running the latest version") + ); + } + parsed_blocks[i].o_indices = std::move(o_indices[i]); } @@ -3535,6 +3580,8 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo throw std::runtime_error("proxy exception in refresh thread"); } + m_has_ever_refreshed_from_node = true; + if(!first && blocks_start_height == next_blocks_start_height) { m_node_rpc_proxy.set_height(m_blockchain.size()); @@ -3571,6 +3618,11 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo THROW_WALLET_EXCEPTION_IF(!waiter.wait(), error::wallet_internal_error, "Exception in thread pool"); throw; } + catch (const error::incorrect_fork_version&) + { + THROW_WALLET_EXCEPTION_IF(!waiter.wait(), error::wallet_internal_error, "Exception in thread pool"); + throw; + } catch (const std::exception&) { blocks_fetched += added_blocks; @@ -4181,6 +4233,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_auto_mine_for_rpc_payment_threshold = -1.0f; m_credits_target = 0; m_enable_multisig = false; + m_allow_mismatched_daemon_version = false; } else if(json.IsObject()) { @@ -5322,7 +5375,7 @@ bool wallet2::prepare_file_names(const std::string& file_path) return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout) +bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout, bool *wallet_is_outdated, bool *daemon_is_outdated) { THROW_WALLET_EXCEPTION_IF(!m_is_initialized, error::wallet_not_initialized); @@ -5359,20 +5412,99 @@ bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout) } } - if (!m_rpc_version) + if (!m_rpc_version && !check_version(version, wallet_is_outdated, daemon_is_outdated)) + return false; + if (version) + *version = m_rpc_version; + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::check_version(uint32_t *version, bool *wallet_is_outdated, bool *daemon_is_outdated) +{ + uint32_t rpc_version; + std::vector<std::pair<uint8_t, uint64_t>> daemon_hard_forks; + uint64_t height; + uint64_t target_height; + if (m_node_rpc_proxy.get_rpc_version(rpc_version, daemon_hard_forks, height, target_height)) { - cryptonote::COMMAND_RPC_GET_VERSION::request req_t = AUTO_VAL_INIT(req_t); - cryptonote::COMMAND_RPC_GET_VERSION::response resp_t = AUTO_VAL_INIT(resp_t); - bool r = invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t); - if(!r || resp_t.status != CORE_RPC_STATUS_OK) { - if(version) - *version = 0; + if(version) + *version = 0; + return false; + } + + // check wallet compatibility with daemon's hard fork version + if (!m_allow_mismatched_daemon_version) + if (!check_hard_fork_version(m_nettype, daemon_hard_forks, height, target_height, wallet_is_outdated, daemon_is_outdated)) return false; + + m_rpc_version = rpc_version; + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::check_hard_fork_version(cryptonote::network_type nettype, const std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, const uint64_t height, const uint64_t target_height, bool *wallet_is_outdated, bool *daemon_is_outdated) +{ + const size_t wallet_num_hard_forks = nettype == TESTNET ? num_testnet_hard_forks + : nettype == STAGENET ? num_stagenet_hard_forks : num_mainnet_hard_forks; + const hardfork_t *wallet_hard_forks = nettype == TESTNET ? testnet_hard_forks + : nettype == STAGENET ? stagenet_hard_forks : mainnet_hard_forks; + + // First check if wallet or daemon is outdated (whether either are unaware of + // a hard fork). Then check if fork has passed rendering versions incompatible + if (daemon_hard_forks.size() > 0) + { + bool daemon_outdated = daemon_hard_forks.size() < wallet_num_hard_forks; + bool wallet_outdated = daemon_hard_forks.size() > wallet_num_hard_forks; + + if (daemon_is_outdated) + *daemon_is_outdated = daemon_outdated; + if (wallet_is_outdated) + *wallet_is_outdated = wallet_outdated; + + if (daemon_outdated) + { + uint64_t daemon_missed_fork_height = wallet_hard_forks[daemon_hard_forks.size()].height; + + // If the daemon missed the fork, then technically it is no longer part of + // the Monero network. Don't connect. + bool daemon_missed_fork = height >= daemon_missed_fork_height || target_height >= daemon_missed_fork_height; + if (daemon_missed_fork) + return false; + } + else if (wallet_outdated) + { + uint64_t wallet_missed_fork_height = daemon_hard_forks[wallet_num_hard_forks].second; + + // If the wallet missed the fork, then technically it is no longer able + // to communicate with the Monero network. Don't connect. + bool wallet_missed_fork = height >= wallet_missed_fork_height || target_height >= wallet_missed_fork_height; + if (wallet_missed_fork) + return false; } - m_rpc_version = resp_t.version; } - if (version) - *version = m_rpc_version; + else + { + // Non-updated daemons won't return daemon_hard_forks in response to + // get_version. Fall back to extra call to get_hard_fork_info by version. + uint64_t daemon_fork_height; + get_hard_fork_info(wallet_num_hard_forks-1/* wallet expects "double fork" pattern */, daemon_fork_height); + bool daemon_outdated = daemon_fork_height == std::numeric_limits<uint64_t>::max(); + + if (daemon_is_outdated) + *daemon_is_outdated = daemon_outdated; + + if (daemon_outdated) + { + uint64_t daemon_missed_fork_height = wallet_hard_forks[wallet_num_hard_forks-2].height; + bool daemon_missed_fork = height >= daemon_missed_fork_height || target_height >= daemon_missed_fork_height; + if (daemon_missed_fork) + return false; + } + + // Don't need to check if wallet is outdated here because the daemons updated + // for a future hard fork will serve daemon_hard_forks above. The check for + // an outdated wallet is done above using daemon_hard_forks. + } return true; } @@ -6602,9 +6734,9 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s //---------------------------------------------------------------------------------------------------- bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &txs, signed_tx_set &signed_txes) { - if (!exported_txs.new_transfers.second.empty()) + if (!std::get<2>(exported_txs.new_transfers).empty()) import_outputs(exported_txs.new_transfers); - else + else if (!std::get<2>(exported_txs.transfers).empty()) import_outputs(exported_txs.transfers); // sign the transactions @@ -10798,7 +10930,7 @@ void wallet2::cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_tx_ { txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx, m_account.get_device())); } - txs.transfers = std::make_pair(0, m_transfers); + txs.transfers = std::make_tuple(0, m_transfers.size(), m_transfers); auto dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev); CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement cold signing interface"); @@ -13101,18 +13233,29 @@ void wallet2::import_blockchain(const std::tuple<size_t, crypto::hash, std::vect m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx); } //---------------------------------------------------------------------------------------------------- -std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> wallet2::export_outputs(bool all) const +std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> wallet2::export_outputs(bool all, uint32_t start, uint32_t count) const { PERF_TIMER(export_outputs); std::vector<tools::wallet2::exported_transfer_details> outs; + // invalid cases + THROW_WALLET_EXCEPTION_IF(count == 0, error::wallet_internal_error, "Nothing requested"); + THROW_WALLET_EXCEPTION_IF(!all && start > 0, error::wallet_internal_error, "Incremental mode is incompatible with non-zero start"); + + // valid cases: + // all: all outputs, subject to start/count + // !all: incremental, subject to count + // for convenience, start/count are allowed to go past the valid range, then nothing is returned + size_t offset = 0; if (!all) while (offset < m_transfers.size() && (m_transfers[offset].m_key_image_known && !m_transfers[offset].m_key_image_request)) ++offset; + else + offset = start; outs.reserve(m_transfers.size() - offset); - for (size_t n = offset; n < m_transfers.size(); ++n) + for (size_t n = offset; n < m_transfers.size() && n - offset < count; ++n) { const transfer_details &td = m_transfers[n]; @@ -13130,20 +13273,22 @@ std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> wall etd.m_flags.m_key_image_partial = td.m_key_image_partial; etd.m_amount = td.m_amount; etd.m_additional_tx_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); + etd.m_subaddr_index_major = td.m_subaddr_index.major; + etd.m_subaddr_index_minor = td.m_subaddr_index.minor; outs.push_back(etd); } - return std::make_pair(offset, outs); + return std::make_tuple(offset, m_transfers.size(), outs); } //---------------------------------------------------------------------------------------------------- -std::string wallet2::export_outputs_to_str(bool all) const +std::string wallet2::export_outputs_to_str(bool all, uint32_t start, uint32_t count) const { PERF_TIMER(export_outputs_to_str); std::stringstream oss; binary_archive<true> ar(oss); - auto outputs = export_outputs(all); + auto outputs = export_outputs(all, start, count); THROW_WALLET_EXCEPTION_IF(!::serialization::serialize(ar, outputs), error::wallet_internal_error, "Failed to serialize output data"); std::string magic(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC)); @@ -13156,21 +13301,35 @@ std::string wallet2::export_outputs_to_str(bool all) const return magic + ciphertext; } //---------------------------------------------------------------------------------------------------- -size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs) +size_t wallet2::import_outputs(const std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs) { PERF_TIMER(import_outputs); - THROW_WALLET_EXCEPTION_IF(outputs.first > m_transfers.size(), error::wallet_internal_error, + THROW_WALLET_EXCEPTION_IF(m_has_ever_refreshed_from_node, error::wallet_internal_error, + "Hot wallets cannot import outputs"); + + // we can now import piecemeal + const size_t offset = std::get<0>(outputs); + const size_t num_outputs = std::get<1>(outputs); + const std::vector<tools::wallet2::transfer_details> &output_array = std::get<2>(outputs); + + THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Imported outputs omit more outputs that we know of"); - const size_t offset = outputs.first; + THROW_WALLET_EXCEPTION_IF(offset >= num_outputs, error::wallet_internal_error, + "Offset is larger than total outputs"); + THROW_WALLET_EXCEPTION_IF(output_array.size() > num_outputs - offset, error::wallet_internal_error, + "Offset is larger than total outputs"); + const size_t original_size = m_transfers.size(); - m_transfers.resize(offset + outputs.second.size()); - for (size_t i = 0; i < offset; ++i) - m_transfers[i].m_key_image_request = false; - for (size_t i = 0; i < outputs.second.size(); ++i) + if (offset + output_array.size() > m_transfers.size()) + m_transfers.resize(offset + output_array.size()); + else if (num_outputs < m_transfers.size()) + m_transfers.resize(num_outputs); + + for (size_t i = 0; i < output_array.size(); ++i) { - transfer_details td = outputs.second[i]; + transfer_details td = output_array[i]; // skip those we've already imported, or which have different data if (i + offset < original_size) @@ -13204,6 +13363,8 @@ process: THROW_WALLET_EXCEPTION_IF(td.m_internal_output_index >= td.m_tx.vout.size(), error::wallet_internal_error, "Internal index is out of range"); crypto::public_key out_key = td.get_public_key(); + if (should_expand(td.m_subaddr_index)) + create_one_off_subaddress(td.m_subaddr_index); bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, out_key, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, td.m_key_image, m_account.get_device()); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); if (should_expand(td.m_subaddr_index)) @@ -13222,24 +13383,38 @@ process: return m_transfers.size(); } //---------------------------------------------------------------------------------------------------- -size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs) +size_t wallet2::import_outputs(const std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs) { PERF_TIMER(import_outputs); - THROW_WALLET_EXCEPTION_IF(outputs.first > m_transfers.size(), error::wallet_internal_error, + THROW_WALLET_EXCEPTION_IF(m_has_ever_refreshed_from_node, error::wallet_internal_error, + "Hot wallets cannot import outputs"); + + // we can now import piecemeal + const size_t offset = std::get<0>(outputs); + const size_t num_outputs = std::get<1>(outputs); + const std::vector<tools::wallet2::exported_transfer_details> &output_array = std::get<2>(outputs); + + THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Imported outputs omit more outputs that we know of. Try using export_outputs all."); - const size_t offset = outputs.first; + THROW_WALLET_EXCEPTION_IF(offset >= num_outputs, error::wallet_internal_error, + "Offset is larger than total outputs"); + THROW_WALLET_EXCEPTION_IF(output_array.size() > num_outputs - offset, error::wallet_internal_error, + "Offset is larger than total outputs"); + const size_t original_size = m_transfers.size(); - m_transfers.resize(offset + outputs.second.size()); - for (size_t i = 0; i < offset; ++i) - m_transfers[i].m_key_image_request = false; - for (size_t i = 0; i < outputs.second.size(); ++i) + if (offset + output_array.size() > m_transfers.size()) + m_transfers.resize(offset + output_array.size()); + else if (num_outputs < m_transfers.size()) + m_transfers.resize(num_outputs); + + for (size_t i = 0; i < output_array.size(); ++i) { - exported_transfer_details etd = outputs.second[i]; + exported_transfer_details etd = output_array[i]; transfer_details &td = m_transfers[i + offset]; - // setup td with "cheao" loaded data + // setup td with "cheap" loaded data td.m_block_height = 0; td.m_txid = crypto::null_hash; td.m_global_output_index = etd.m_global_output_index; @@ -13252,6 +13427,8 @@ size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wall td.m_key_image_known = etd.m_flags.m_key_image_known; td.m_key_image_request = etd.m_flags.m_key_image_request; td.m_key_image_partial = false; + td.m_subaddr_index.major = etd.m_subaddr_index_major; + td.m_subaddr_index.minor = etd.m_subaddr_index_minor; // skip those we've already imported, or which have different data if (i + offset < original_size) @@ -13292,6 +13469,8 @@ size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wall const crypto::public_key &tx_pub_key = etd.m_tx_pubkey; const std::vector<crypto::public_key> &additional_tx_pub_keys = etd.m_additional_tx_keys; const crypto::public_key& out_key = etd.m_pubkey; + if (should_expand(td.m_subaddr_index)) + create_one_off_subaddress(td.m_subaddr_index); bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, out_key, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, td.m_key_image, m_account.get_device()); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); if (should_expand(td.m_subaddr_index)) @@ -13348,7 +13527,7 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) { std::string body(data, headerlen); - std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> new_outputs; + std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> new_outputs; try { binary_archive<false> ar{epee::strspan<std::uint8_t>(body)}; @@ -13358,9 +13537,9 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) } catch (...) {} if (!loaded) - new_outputs.second.clear(); + std::get<2>(new_outputs).clear(); - std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> outputs; + std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::transfer_details>> outputs; if (!loaded) try { binary_archive<false> ar{epee::strspan<std::uint8_t>(body)}; @@ -13385,15 +13564,16 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) if (!loaded) { - outputs.first = 0; - outputs.second = {}; + std::get<0>(outputs) = 0; + std::get<1>(outputs) = 0; + std::get<2>(outputs) = {}; } - imported_outputs = new_outputs.second.empty() ? import_outputs(outputs) : import_outputs(new_outputs); + imported_outputs = !std::get<2>(new_outputs).empty() ? import_outputs(new_outputs) : !std::get<2>(outputs).empty() ? import_outputs(outputs) : 0; } catch (const std::exception &e) { - THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to import outputs") + e.what()); + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to import outputs: ") + e.what()); } return imported_outputs; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 3fce616e3..115651e3b 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -63,6 +63,7 @@ #include "serialization/crypto.h" #include "serialization/string.h" #include "serialization/pair.h" +#include "serialization/tuple.h" #include "serialization/containers.h" #include "wallet_errors.h" @@ -401,9 +402,13 @@ private: } m_flags; uint64_t m_amount; std::vector<crypto::public_key> m_additional_tx_keys; + uint32_t m_subaddr_index_major; + uint32_t m_subaddr_index_minor; BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) + VERSION_FIELD(1) + if (version < 1) + return false; FIELD(m_pubkey) VARINT_FIELD(m_internal_output_index) VARINT_FIELD(m_global_output_index) @@ -411,6 +416,8 @@ private: FIELD(m_flags.flags) VARINT_FIELD(m_amount) FIELD(m_additional_tx_keys) + VARINT_FIELD(m_subaddr_index_major) + VARINT_FIELD(m_subaddr_index_minor) END_SERIALIZE() }; @@ -667,16 +674,32 @@ private: struct unsigned_tx_set { std::vector<tx_construction_data> txes; - std::pair<size_t, wallet2::transfer_container> transfers; - std::pair<size_t, std::vector<wallet2::exported_transfer_details>> new_transfers; + std::tuple<uint64_t, uint64_t, wallet2::transfer_container> transfers; + std::tuple<uint64_t, uint64_t, std::vector<wallet2::exported_transfer_details>> new_transfers; BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(1) + VERSION_FIELD(2) FIELD(txes) - if (version >= 1) - FIELD(new_transfers) - else - FIELD(transfers) + if (version == 0) + { + std::pair<size_t, wallet2::transfer_container> v0_transfers; + FIELD(v0_transfers); + std::get<0>(transfers) = std::get<0>(v0_transfers); + std::get<1>(transfers) = std::get<0>(v0_transfers) + std::get<1>(v0_transfers).size(); + std::get<2>(transfers) = std::get<1>(v0_transfers); + return true; + } + if (version == 1) + { + std::pair<size_t, std::vector<wallet2::exported_transfer_details>> v1_transfers; + FIELD(v1_transfers); + std::get<0>(new_transfers) = std::get<0>(v1_transfers); + std::get<1>(new_transfers) = std::get<0>(v1_transfers) + std::get<1>(v1_transfers).size(); + std::get<2>(new_transfers) = std::get<1>(v1_transfers); + return true; + } + + FIELD(new_transfers) END_SERIALIZE() }; @@ -1069,7 +1092,9 @@ private: bool sign_multisig_tx_to_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector<crypto::hash> &txids); std::vector<pending_tx> create_unmixable_sweep_transactions(); void discard_unmixable_outputs(); - bool check_connection(uint32_t *version = NULL, bool *ssl = NULL, uint32_t timeout = 200000); + bool check_connection(uint32_t *version = NULL, bool *ssl = NULL, uint32_t timeout = 200000, bool *wallet_is_outdated = NULL, bool *daemon_is_outdated = NULL); + bool check_version(uint32_t *version, bool *wallet_is_outdated, bool *daemon_is_outdated); + bool check_hard_fork_version(cryptonote::network_type nettype, const std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, const uint64_t height, const uint64_t target_height, bool *wallet_is_outdated, bool *daemon_is_outdated); void get_transfers(wallet2::transfer_container& incoming_transfers) const; void get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height = 0, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; void get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height = (uint64_t)-1, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; @@ -1207,11 +1232,17 @@ private: if(ver < 29) return; a & m_rpc_client_secret_key; + if(ver < 30) + { + m_has_ever_refreshed_from_node = false; + return; + } + a & m_has_ever_refreshed_from_node; } BEGIN_SERIALIZE_OBJECT() MAGIC_FIELD("monero wallet cache") - VERSION_FIELD(0) + VERSION_FIELD(1) FIELD(m_blockchain) FIELD(m_transfers) FIELD(m_account_public_address) @@ -1237,6 +1268,12 @@ private: FIELD(m_device_last_key_image_sync) FIELD(m_cold_key_images) FIELD(m_rpc_client_secret_key) + if (version < 1) + { + m_has_ever_refreshed_from_node = false; + return true; + } + FIELD(m_has_ever_refreshed_from_node) END_SERIALIZE() /*! @@ -1324,6 +1361,8 @@ private: void credits_target(uint64_t threshold) { m_credits_target = threshold; } bool is_multisig_enabled() const { return m_enable_multisig; } void enable_multisig(bool enable) { m_enable_multisig = enable; } + bool is_mismatched_daemon_version_allowed() const { return m_allow_mismatched_daemon_version; } + void allow_mismatched_daemon_version(bool allow_mismatch) { m_allow_mismatched_daemon_version = allow_mismatch; } bool get_tx_key_cached(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const; void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const boost::optional<cryptonote::account_public_address> &single_destination_subaddress = boost::none); @@ -1448,10 +1487,10 @@ private: bool verify_with_public_key(const std::string &data, const crypto::public_key &public_key, const std::string &signature) const; // Import/Export wallet data - std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> export_outputs(bool all = false) const; - std::string export_outputs_to_str(bool all = false) const; - size_t import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs); - size_t import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs); + std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> export_outputs(bool all = false, uint32_t start = 0, uint32_t count = 0xffffffff) const; + std::string export_outputs_to_str(bool all = false, uint32_t start = 0, uint32_t count = 0xffffffff) const; + size_t import_outputs(const std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs); + size_t import_outputs(const std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs); size_t import_outputs_from_str(const std::string &outputs_st); payment_container export_payments() const; void import_payments(const payment_container &payments); @@ -1841,6 +1880,7 @@ private: rpc_payment_state_t m_rpc_payment_state; uint64_t m_credits_target; bool m_enable_multisig; + bool m_allow_mismatched_daemon_version; // Aux transaction data from device serializable_unordered_map<crypto::hash, std::string> m_tx_device; @@ -1884,11 +1924,13 @@ private: ExportFormat m_export_format; bool m_load_deprecated_formats; + bool m_has_ever_refreshed_from_node; + static boost::mutex default_daemon_address_lock; static std::string default_daemon_address; }; } -BOOST_CLASS_VERSION(tools::wallet2, 29) +BOOST_CLASS_VERSION(tools::wallet2, 30) BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 12) BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1) BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0) @@ -1899,7 +1941,7 @@ BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 8) BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 6) BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 18) BOOST_CLASS_VERSION(tools::wallet2::reserve_proof_entry, 0) -BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0) +BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 1) BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 1) BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 4) BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3) @@ -1909,6 +1951,17 @@ namespace boost { namespace serialization { + template<class Archive, class F, class S, class T> + inline void serialize( + Archive & ar, + std::tuple<F, S, T> & t, + const unsigned int /* file_version */ + ){ + ar & boost::serialization::make_nvp("f", std::get<0>(t)); + ar & boost::serialization::make_nvp("s", std::get<1>(t)); + ar & boost::serialization::make_nvp("t", std::get<2>(t)); + } + template <class Archive> inline typename std::enable_if<!Archive::is_loading::value, void>::type initialize_transfer_details(Archive &a, tools::wallet2::transfer_details &x, const boost::serialization::version_type ver) { @@ -2269,7 +2322,17 @@ namespace boost inline void serialize(Archive &a, tools::wallet2::unsigned_tx_set &x, const boost::serialization::version_type ver) { a & x.txes; - a & x.transfers; + if (ver == 0) + { + // load old version + std::pair<size_t, tools::wallet2::transfer_container> old_transfers; + a & old_transfers; + std::get<0>(x.transfers) = std::get<0>(old_transfers); + std::get<1>(x.transfers) = std::get<0>(old_transfers) + std::get<1>(old_transfers).size(); + std::get<2>(x.transfers) = std::get<1>(old_transfers); + return; + } + throw std::runtime_error("Boost serialization not supported for newest unsigned_tx_set"); } template <class Archive> diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index df594aa21..0b8512163 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -439,6 +439,16 @@ namespace tools std::string to_string() const { return refresh_error::to_string(); } }; //---------------------------------------------------------------------------------------------------- + struct incorrect_fork_version : public refresh_error + { + explicit incorrect_fork_version(std::string&& loc, const std::string& message) + : refresh_error(std::move(loc), message) + { + } + + std::string to_string() const { return refresh_error::to_string(); } + }; + //---------------------------------------------------------------------------------------------------- struct signature_check_failed : public wallet_logic_error { explicit signature_check_failed(std::string&& loc, const std::string& message) diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 7ec5fc7a1..1f0a1371f 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -2792,7 +2792,7 @@ namespace tools try { - res.outputs_data_hex = epee::string_tools::buff_to_hex_nodelimer(m_wallet->export_outputs_to_str(req.all)); + res.outputs_data_hex = epee::string_tools::buff_to_hex_nodelimer(m_wallet->export_outputs_to_str(req.all, req.start, req.count)); } catch (const std::exception &e) { diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index ecfc8e673..2cca323ee 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -1785,9 +1785,13 @@ namespace wallet_rpc struct request_t { bool all; + uint32_t start; + uint32_t count; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(all) + KV_SERIALIZE_OPT(start, 0u) + KV_SERIALIZE_OPT(count, 0xffffffffu) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; |