diff options
45 files changed, 1454 insertions, 365 deletions
diff --git a/README.i18n b/README.i18n index c4eabaf15..755c5cf38 100644 --- a/README.i18n +++ b/README.i18n @@ -19,7 +19,7 @@ To edit translations for Spanish: linguist translations/monero_es.ts -To build translations after modiying them: +To build translations after modifying them: ./utils/translations/build-translations.sh @@ -19,7 +19,7 @@ Monero is a private, secure, untraceable, decentralised digital currency. You ar **Privacy:** Monero uses a cryptographically sound system to allow you to send and receive funds without your transactions being easily revealed on the blockchain (the ledger of transactions that everyone has). This ensures that your purchases, receipts, and all transfers remain absolutely private by default. -**Security:** Using the power of a distributed peer-to-peer consensus network, every transaction on the network is cryptographically secured. Individual wallets have a 24 word mnemonic seed that is only displayed once, and can be written down to backup the wallet. Wallet files are encrypted with a passphrase to ensure they are useless if stolen. +**Security:** Using the power of a distributed peer-to-peer consensus network, every transaction on the network is cryptographically secured. Individual wallets have a 25 word mnemonic seed that is only displayed once, and can be written down to backup the wallet. Wallet files are encrypted with a passphrase to ensure they are useless if stolen. **Untraceability:** By taking advantage of ring signatures, a special property of a certain type of cryptography, Monero is able to ensure that transactions are not only untraceable, but have an optional measure of ambiguity that ensures that transactions cannot easily be tied back to an individual user or computer. @@ -164,11 +164,7 @@ application. * Download and install the [MSYS2 installer](http://msys2.github.io), either the 64-bit or the 32-bit package, depending on your system. * Open the MSYS shell via the `MSYS2 Shell` shortcut -* Update the core packages in your MSYS2 install: - - update-core - -* Exit the MSYS shell using Alt+F4, then restart MSYS and update packages using pacman: +* Update packages using pacman: pacman -Syuu diff --git a/contrib/epee/include/net/abstract_tcp_server2.h b/contrib/epee/include/net/abstract_tcp_server2.h index cb387d39f..f0a1ddd7c 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.h +++ b/contrib/epee/include/net/abstract_tcp_server2.h @@ -300,7 +300,6 @@ namespace net_utils boost::thread::id m_main_thread_id; critical_section m_threads_lock; volatile uint32_t m_thread_index; // TODO change to std::atomic - void detach_threads(); t_connection_type m_connection_type; diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl index 94dbe7458..dbdfbc74d 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.inl +++ b/contrib/epee/include/net/abstract_tcp_server2.inl @@ -827,7 +827,7 @@ POP_WARNINGS } CRITICAL_REGION_END(); // Wait for all threads in the pool to exit. - if (wait) // && ! ::cryptonote::core::get_is_stopping()) // TODO fast_exit + if (wait) { _fact("JOINING all threads"); for (std::size_t i = 0; i < m_threads.size(); ++i) { @@ -897,10 +897,6 @@ POP_WARNINGS template<class t_protocol_handler> void boosted_tcp_server<t_protocol_handler>::send_stop_signal() { - if (::cryptonote::core::get_fast_exit() == true) - { - detach_threads(); - } m_stop_signal_sent = true; TRY_ENTRY(); connections_mutex.lock(); @@ -1144,14 +1140,6 @@ POP_WARNINGS return true; CATCH_ENTRY_L0("boosted_tcp_server<t_protocol_handler>::connect_async", false); } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - void boosted_tcp_server<t_protocol_handler>::detach_threads() - { - for (auto thread : m_threads) - thread->detach(); - } - } // namespace } // namespace diff --git a/contrib/rlwrap/monerocommands_bitmonerod.txt b/contrib/rlwrap/monerocommands_bitmonerod.txt new file mode 100644 index 000000000..1c97cfedf --- /dev/null +++ b/contrib/rlwrap/monerocommands_bitmonerod.txt @@ -0,0 +1,34 @@ +ban +bans +diff +exit +flush_txpool +hard_fork_info +help +hide_hr +is_key_image_spent +limit +limit_down +limit_up +out_peers +output_histogram +print_bc +print_block +print_cn +print_height +print_pl +print_pool +print_pool_sh +print_status +print_tx +q +save +set_log +show_hr +start_mining +start_save_graph +status +stop_daemon +stop_mining +stop_save_graph +unban diff --git a/contrib/rlwrap/monerocommands_monero-wallet-cli.txt b/contrib/rlwrap/monerocommands_monero-wallet-cli.txt new file mode 100644 index 000000000..c5e4c0323 --- /dev/null +++ b/contrib/rlwrap/monerocommands_monero-wallet-cli.txt @@ -0,0 +1,33 @@ +address +balance +bc_height +check_tx_key +export_key_images +get_tx_key +get_tx_note +help +import_key_images +incoming_transfers +integrated_address +payments +refresh +rescan_bc +rescan_spent +save +save_bc +save_watch_only +seed +set +set_tx_note +show_transfers +sign +spendkey +start_mining +status +stop_mining +sweep_all +sweep_unmixable +transfer +transfer_original +verify +viewkey diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 21ed8f4da..acb7d2cf6 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -1132,7 +1132,7 @@ void BlockchainLMDB::open(const std::string& filename, const int mdb_flags) if (!(mdb_flags & MDB_RDONLY)) { result = mdb_drop(txn, m_hf_starting_heights, 1); - if (result) + if (result && result != MDB_NOTFOUND) throw0(DB_ERROR(lmdb_error("Failed to drop m_hf_starting_heights: ", result).c_str())); } diff --git a/src/common/command_line.cpp b/src/common/command_line.cpp index fc096abe5..b3f488447 100644 --- a/src/common/command_line.cpp +++ b/src/common/command_line.cpp @@ -30,6 +30,7 @@ #include "command_line.h" #include "string_tools.h" +#include "cryptonote_config.h" namespace command_line { @@ -92,4 +93,9 @@ namespace command_line , "Show time-stats when processing blocks/txs and disk synchronization." , 0 }; + const command_line::arg_descriptor<size_t> arg_block_sync_size = { + "block-sync-size" + , "How many blocks to sync at once during chain synchronization." + , BLOCKS_SYNCHRONIZING_DEFAULT_COUNT + }; } diff --git a/src/common/command_line.h b/src/common/command_line.h index 731b8b0bb..0ea749168 100644 --- a/src/common/command_line.h +++ b/src/common/command_line.h @@ -216,4 +216,5 @@ namespace command_line extern const arg_descriptor<uint64_t> arg_prep_blocks_threads; extern const arg_descriptor<uint64_t> arg_db_auto_remove_logs; extern const arg_descriptor<uint64_t> arg_show_time_stats; + extern const arg_descriptor<size_t> arg_block_sync_size; } diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index badb1a335..74e1419c8 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -387,7 +387,7 @@ bool Blockchain::init(BlockchainDB* db, HardFork*& hf, const bool testnet) //------------------------------------------------------------------ bool Blockchain::store_blockchain() { - LOG_PRINT_YELLOW("Blockchain::" << __func__, LOG_LEVEL_3); + LOG_PRINT_L3("Blockchain::" << __func__); // lock because the rpc_thread command handler also calls this CRITICAL_REGION_LOCAL(m_db->m_synchronization_lock); @@ -419,9 +419,10 @@ bool Blockchain::deinit() { LOG_PRINT_L3("Blockchain::" << __func__); - LOG_PRINT_L0("Closing IO Service."); - // stop async service - m_async_work_idle.reset(); + LOG_PRINT_L1("Stopping blockchain read/write activity"); + + // stop async service + m_async_work_idle.reset(); m_async_pool.join_all(); m_async_service.stop(); @@ -436,14 +437,15 @@ bool Blockchain::deinit() try { m_db->close(); + LOG_PRINT_L1("Local blockchain read/write activity stopped successfully"); } catch (const std::exception& e) { - LOG_PRINT_L0(std::string("Error closing blockchain db: ") + e.what()); + LOG_ERROR(std::string("Error closing blockchain db: ") + e.what()); } catch (...) { - LOG_PRINT_L0("There was an issue closing/storing the blockchain, shutting down now to prevent issues!"); + LOG_ERROR("There was an issue closing/storing the blockchain, shutting down now to prevent issues!"); } delete m_hardfork; @@ -1828,14 +1830,6 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc return false; } - // if split_height remains 0, we didn't have any but the genesis block in common - // which is only fine if the blocks just have the genesis block - if(split_height == 0 && qblock_ids.size() > 1) - { - LOG_ERROR("Ours and foreign blockchain have only genesis block in common... o.O"); - return false; - } - //we start to put block ids INCLUDING last known id, just to make other side be sure starter_offset = split_height; return true; diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index c289f297b..149fb09df 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -142,6 +142,7 @@ namespace cryptonote command_line::add_arg(desc, command_line::arg_db_sync_mode); command_line::add_arg(desc, command_line::arg_show_time_stats); command_line::add_arg(desc, command_line::arg_db_auto_remove_logs); + command_line::add_arg(desc, command_line::arg_block_sync_size); } //----------------------------------------------------------------------------------------------- bool core::handle_command_line(const boost::program_options::variables_map& vm) @@ -227,14 +228,14 @@ namespace cryptonote LOG_PRINT_L1("Locking " << lock_path.string()); if (!db_lock.try_lock()) { - LOG_PRINT_L0("Failed to lock " << lock_path.string()); + LOG_ERROR("Failed to lock " << lock_path.string()); return false; } return true; } catch (const std::exception &e) { - LOG_PRINT_L0("Error trying to lock " << lock_path.string() << ": " << e.what()); + LOG_ERROR("Error trying to lock " << lock_path.string() << ": " << e.what()); return false; } } @@ -243,6 +244,7 @@ namespace cryptonote { db_lock.unlock(); db_lock = boost::interprocess::file_lock(); + LOG_PRINT_L1("Blockchain directory successfully unlocked"); return true; } //----------------------------------------------------------------------------------------------- @@ -386,7 +388,7 @@ namespace cryptonote } catch (const DB_ERROR& e) { - LOG_PRINT_L0("Error opening database: " << e.what()); + LOG_ERROR("Error opening database: " << e.what()); return false; } @@ -403,6 +405,10 @@ namespace cryptonote m_blockchain_storage.set_show_time_stats(show_time_stats); CHECK_AND_ASSERT_MES(r, false, "Failed to initialize blockchain storage"); + block_sync_size = command_line::get_arg(vm, command_line::arg_block_sync_size); + if (block_sync_size == 0) + block_sync_size = BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; + // load json & DNS checkpoints, and verify them // with respect to what blocks we already have CHECK_AND_ASSERT_MES(update_checkpoints(), false, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); @@ -428,24 +434,11 @@ namespace cryptonote { m_miner.stop(); m_mempool.deinit(); - if (!m_fast_exit) - { - m_blockchain_storage.deinit(); - } + m_blockchain_storage.deinit(); unlock_db_directory(); return true; } //----------------------------------------------------------------------------------------------- - void core::set_fast_exit() - { - m_fast_exit = true; - } - //----------------------------------------------------------------------------------------------- - bool core::get_fast_exit() - { - return m_fast_exit; - } - //----------------------------------------------------------------------------------------------- void core::test_drop_download() { m_test_drop_download = false; @@ -965,10 +958,7 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- void core::set_target_blockchain_height(uint64_t target_blockchain_height) { - if (target_blockchain_height > m_target_blockchain_height) - { - m_target_blockchain_height = target_blockchain_height; - } + m_target_blockchain_height = target_blockchain_height; } //----------------------------------------------------------------------------------------------- uint64_t core::get_target_blockchain_height() const @@ -980,6 +970,4 @@ namespace cryptonote { raise(SIGTERM); } - - std::atomic<bool> core::m_fast_exit(false); } diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index d16bd6553..6727d6b04 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -230,29 +230,11 @@ namespace cryptonote * * Uninitializes the miner instance, transaction pool, and Blockchain * - * if m_fast_exit is set, the call to Blockchain::deinit() is not made. - * * @return true */ bool deinit(); /** - * @brief sets fast exit flag - * - * @note see deinit() - */ - static void set_fast_exit(); - - /** - * @brief gets the current state of the fast exit flag - * - * @return the fast exit flag - * - * @note see deinit() - */ - static bool get_fast_exit(); - - /** * @brief sets to drop blocks downloaded (for testing) */ void test_drop_download(); @@ -611,6 +593,13 @@ namespace cryptonote */ bool are_key_images_spent(const std::vector<crypto::key_image>& key_im, std::vector<bool> &spent) const; + /** + * @brief get the number of blocks to sync in one go + * + * @return the number of blocks to sync in one go + */ + size_t get_block_sync_size() const { return block_sync_size; } + private: /** @@ -757,8 +746,6 @@ namespace cryptonote */ bool unlock_db_directory(); - static std::atomic<bool> m_fast_exit; //!< whether or not to deinit Blockchain on exit - bool m_test_drop_download = true; //!< whether or not to drop incoming blocks (for testing) uint64_t m_test_drop_download_height = 0; //!< height under which to drop incoming blocks, if doing so @@ -798,6 +785,8 @@ namespace cryptonote std::atomic_flag m_checkpoints_updating; //!< set if checkpoints are currently updating to avoid multiple threads attempting to update at once boost::interprocess::file_lock db_lock; //!< a lock object for a file lock in the db directory + + size_t block_sync_size; }; } diff --git a/src/cryptonote_core/cryptonote_format_utils.cpp b/src/cryptonote_core/cryptonote_format_utils.cpp index d1ccfc7d1..64f8eb924 100644 --- a/src/cryptonote_core/cryptonote_format_utils.cpp +++ b/src/cryptonote_core/cryptonote_format_utils.cpp @@ -366,7 +366,7 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool remove_extra_nonce_tx_extra(std::vector<uint8_t>& tx_extra) + bool remove_field_from_tx_extra(std::vector<uint8_t>& tx_extra, const std::type_info &type) { std::string extra_str(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size()); std::istringstream iss(extra_str); @@ -380,7 +380,7 @@ namespace cryptonote tx_extra_field field; bool r = ::do_serialize(ar, field); CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to deserialize extra field. extra = " << string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size()))); - if (field.type() != typeid(tx_extra_nonce)) + if (field.type() != type) ::do_serialize(newar, field); std::ios_base::iostate state = iss.rdstate(); @@ -472,10 +472,7 @@ namespace cryptonote bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct) { std::vector<rct::key> amount_keys; - tx.vin.clear(); - tx.vout.clear(); - tx.signatures.clear(); - tx.rct_signatures.type = rct::RCTTypeNull; + tx.set_null(); amount_keys.clear(); tx.version = rct ? 2 : 1; @@ -512,7 +509,7 @@ namespace cryptonote std::string extra_nonce; set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); - remove_extra_nonce_tx_extra(tx.extra); + remove_field_from_tx_extra(tx.extra, typeid(tx_extra_fields)); if (!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) { LOG_ERROR("Failed to add encrypted payment id to tx extra"); @@ -615,6 +612,14 @@ namespace cryptonote return false; } + // check for watch only wallet + bool zero_secret_key = true; + for (size_t i = 0; i < sizeof(sender_account_keys.m_spend_secret_key); ++i) + zero_secret_key &= (sender_account_keys.m_spend_secret_key.data[i] == 0); + if (zero_secret_key) + { + LOG_PRINT_L1("Null secret key, skipping signatures"); + } if (tx.version == 1) { @@ -641,7 +646,8 @@ namespace cryptonote tx.signatures.push_back(std::vector<crypto::signature>()); std::vector<crypto::signature>& sigs = tx.signatures.back(); sigs.resize(src_entr.outputs.size()); - crypto::generate_ring_signature(tx_prefix_hash, boost::get<txin_to_key>(tx.vin[i]).k_image, keys_ptrs, in_contexts[i].in_ephemeral.sec, src_entr.real_output, sigs.data()); + if (!zero_secret_key) + crypto::generate_ring_signature(tx_prefix_hash, boost::get<txin_to_key>(tx.vin[i]).k_image, keys_ptrs, in_contexts[i].in_ephemeral.sec, src_entr.real_output, sigs.data()); ss_ring_s << "signatures:" << ENDL; std::for_each(sigs.begin(), sigs.end(), [&](const crypto::signature& s){ss_ring_s << s << ENDL;}); ss_ring_s << "prefix_hash:" << tx_prefix_hash << ENDL << "in_ephemeral_key: " << in_contexts[i].in_ephemeral.sec << ENDL << "real_output: " << src_entr.real_output; diff --git a/src/cryptonote_core/cryptonote_format_utils.h b/src/cryptonote_core/cryptonote_format_utils.h index e6a3bfba4..24db8008e 100644 --- a/src/cryptonote_core/cryptonote_format_utils.h +++ b/src/cryptonote_core/cryptonote_format_utils.h @@ -62,6 +62,16 @@ namespace cryptonote rct::key mask; //ringct amount mask void push_output(uint64_t idx, const crypto::public_key &k, uint64_t amount) { outputs.push_back(std::make_pair(idx, rct::ctkey({rct::pk2rct(k), rct::zeroCommit(amount)}))); } + + BEGIN_SERIALIZE_OBJECT() + FIELD(outputs) + VARINT_FIELD(real_output) + FIELD(real_out_tx_key) + VARINT_FIELD(real_output_in_tx_index) + VARINT_FIELD(amount) + FIELD(rct) + FIELD(mask) + END_SERIALIZE() }; struct tx_destination_entry @@ -71,6 +81,11 @@ namespace cryptonote tx_destination_entry() : amount(0), addr(AUTO_VAL_INIT(addr)) { } tx_destination_entry(uint64_t a, const account_public_address &ad) : amount(a), addr(ad) { } + + BEGIN_SERIALIZE_OBJECT() + VARINT_FIELD(amount) + FIELD(addr) + END_SERIALIZE() }; //--------------------------------------------------------------- @@ -94,7 +109,7 @@ namespace cryptonote crypto::public_key get_tx_pub_key_from_extra(const transaction& tx); bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key); bool add_extra_nonce_to_tx_extra(std::vector<uint8_t>& tx_extra, const blobdata& extra_nonce); - bool remove_extra_nonce_tx_extra(std::vector<uint8_t>& tx_extra); + bool remove_field_from_tx_extra(std::vector<uint8_t>& tx_extra, const std::type_info &type); void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id); void set_encrypted_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash8& payment_id); bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id); diff --git a/src/cryptonote_core/miner.cpp b/src/cryptonote_core/miner.cpp index ec717a13d..6f4e706ed 100644 --- a/src/cryptonote_core/miner.cpp +++ b/src/cryptonote_core/miner.cpp @@ -278,8 +278,13 @@ namespace cryptonote //----------------------------------------------------------------------------------------------------- bool miner::stop() { + LOG_PRINT_L1("Miner has received stop signal"); + if (!is_mining()) + { + LOG_PRINT_L1("Not mining - nothing to stop" ); return true; + } send_stop_signal(); CRITICAL_REGION_LOCAL(m_threads_lock); diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 46fab4dcf..5bfa7eca6 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -689,7 +689,7 @@ namespace cryptonote bool res = tools::unserialize_obj_from_file(*this, state_file_path); if(!res) { - LOG_PRINT_L1("Failed to load memory pool from file " << state_file_path); + LOG_ERROR("Failed to load memory pool from file " << state_file_path); m_transactions.clear(); m_txs_by_fee.clear(); @@ -710,12 +710,17 @@ namespace cryptonote //TODO: investigate whether only ever returning true is correct bool tx_memory_pool::deinit() { + LOG_PRINT_L1("Received signal to deactivate memory pool store"); + if (m_config_folder.empty()) + { + LOG_PRINT_L1("Memory pool store already empty"); return true; + } if (!tools::create_directories_if_necessary(m_config_folder)) { - LOG_PRINT_L1("Failed to create data directory: " << m_config_folder); + LOG_ERROR("Failed to create memory pool data directory: " << m_config_folder); return false; } @@ -723,8 +728,14 @@ namespace cryptonote bool res = tools::serialize_obj_to_file(*this, state_file_path); if(!res) { - LOG_PRINT_L1("Failed to serialize memory pool to file " << state_file_path); + LOG_ERROR("Failed to serialize memory pool to file " << state_file_path); + return false; } - return true; + else + { + LOG_PRINT_L1("Memory pool store deactivated successfully"); + return true; + } + } } diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index f16dad281..1d1cd3631 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -75,8 +75,6 @@ namespace cryptonote template<class t_core> bool t_cryptonote_protocol_handler<t_core>::deinit() { - - return true; } //------------------------------------------------------------------------------------------------------------------------ @@ -263,7 +261,7 @@ namespace cryptonote if(context.m_state == cryptonote_connection_context::state_synchronizing) return true; - if(m_core.have_block(hshd.top_id) && !(hshd.current_height < m_core.get_target_blockchain_height())) + if(m_core.have_block(hshd.top_id)) { context.m_state = cryptonote_connection_context::state_normal; if(is_inital) @@ -650,9 +648,9 @@ namespace cryptonote size_t count = 0; auto it = context.m_needed_objects.begin(); - size_t count_limit = BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; + const size_t count_limit = m_core.get_block_sync_size(); _note_c("net/req-calc" , "Setting count_limit: " << count_limit); - while(it != context.m_needed_objects.end() && count < BLOCKS_SYNCHRONIZING_DEFAULT_COUNT) + while(it != context.m_needed_objects.end() && count < count_limit) { if( !(check_having_blocks && m_core.have_block(*it))) { diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 83892c661..6ea862b56 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -336,12 +336,6 @@ bool t_command_parser_executor::set_limit_down(const std::vector<std::string>& a return m_executor.set_limit_down(limit); } -bool t_command_parser_executor::fast_exit(const std::vector<std::string>& args) -{ - if (!args.empty()) return false; - return m_executor.fast_exit(); -} - bool t_command_parser_executor::out_peers(const std::vector<std::string>& args) { if (args.empty()) return false; diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index e59f51cdb..7819bd261 100644 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -98,8 +98,6 @@ public: bool set_limit_down(const std::vector<std::string>& args); - bool fast_exit(const std::vector<std::string>& args); - bool out_peers(const std::vector<std::string>& args); bool start_save_graph(const std::vector<std::string>& args); diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 2c3c54841..cb54d1966 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -171,11 +171,6 @@ t_command_server::t_command_server( , "limit <kB/s> - Set download limit" ); m_command_lookup.set_handler( - "fast_exit" - , std::bind(&t_command_parser_executor::fast_exit, &m_parser, p::_1) - , "Exit" - ); - m_command_lookup.set_handler( "out_peers" , std::bind(&t_command_parser_executor::out_peers, &m_parser, p::_1) , "Set max number of out peers" diff --git a/src/daemon/executor.cpp b/src/daemon/executor.cpp index 3db3cb68d..9583fd056 100644 --- a/src/daemon/executor.cpp +++ b/src/daemon/executor.cpp @@ -56,7 +56,7 @@ namespace daemonize boost::program_options::variables_map const & vm ) { - LOG_PRINT_L0("Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL); + LOG_PRINT_L0("Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ") Daemonised"); return t_daemon{vm}; } diff --git a/src/daemon/protocol.h b/src/daemon/protocol.h index 8e2add4a2..eb894fb81 100644 --- a/src/daemon/protocol.h +++ b/src/daemon/protocol.h @@ -69,12 +69,13 @@ public: ~t_protocol() { - LOG_PRINT_L0("Deinitializing cryptonote_protocol..."); + LOG_PRINT_L0("Stopping cryptonote protocol..."); try { m_protocol.deinit(); m_protocol.set_p2p_endpoint(nullptr); + LOG_PRINT_L0("Cryptonote protocol stopped successfully"); } catch (...) { - LOG_PRINT_L0("Failed to deinitialize protocol..."); + LOG_ERROR("Failed to stop cryptonote protocol!"); } } }; diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index ad6041fca..2289bf37d 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -57,7 +57,7 @@ namespace { tools::msg_writer() << boost::format("%-10s %-25s %-25s %s") % prefix % id_str % addr_str % elapsed; } - void print_block_header(cryptonote::block_header_responce const & header) + void print_block_header(cryptonote::block_header_response const & header) { tools::success_msg_writer() << "timestamp: " << boost::lexical_cast<std::string>(header.timestamp) << std::endl @@ -296,6 +296,16 @@ static std::string get_fork_extra_info(uint64_t t, uint64_t now, uint64_t block_ return ""; } +static float get_sync_percentage(const cryptonote::COMMAND_RPC_GET_INFO::response &ires) +{ + uint64_t height = ires.height; + uint64_t target_height = ires.target_height ? ires.target_height < ires.height ? ires.height : ires.target_height : ires.height; + float pc = 100.0f * height / target_height; + if (height < target_height && pc > 99.9f) + return 99.9f; // to avoid 100% when not fully synced + return pc; +} + bool t_rpc_command_executor::show_status() { cryptonote::COMMAND_RPC_GET_INFO::request ireq; cryptonote::COMMAND_RPC_GET_INFO::response ires; @@ -356,7 +366,7 @@ bool t_rpc_command_executor::show_status() { tools::success_msg_writer() << boost::format("Height: %llu/%llu (%.1f%%) on %s, %s, net hash %s, v%u%s, %s, %u+%u connections") % (unsigned long long)ires.height % (unsigned long long)(ires.target_height >= ires.height ? ires.target_height : ires.height) - % (100.0f * ires.height / (ires.target_height ? ires.target_height < ires.height ? ires.height : ires.target_height : ires.height)) + % get_sync_percentage(ires) % (ires.testnet ? "testnet" : "mainnet") % (mining_busy ? "syncing" : mres.active ? "mining at " + get_mining_speed(mres.speed) : "not mining") % get_mining_speed(ires.difficulty / ires.target) @@ -979,34 +989,6 @@ bool t_rpc_command_executor::set_limit_down(int limit) return true; } -bool t_rpc_command_executor::fast_exit() -{ - cryptonote::COMMAND_RPC_FAST_EXIT::request req; - cryptonote::COMMAND_RPC_FAST_EXIT::response res; - - std::string fail_message = "Daemon did not stop"; - - if (m_is_rpc) - { - if (!m_rpc_client->rpc_request(req, res, "/fast_exit", fail_message.c_str())) - { - return true; - } - } - - else - { - if (!m_rpc_server->on_fast_exit(req, res) || res.status != CORE_RPC_STATUS_OK) - { - tools::fail_msg_writer() << fail_message.c_str(); - return true; - } - } - - tools::success_msg_writer() << "Daemon stopped"; - return true; -} - bool t_rpc_command_executor::out_peers(uint64_t limit) { cryptonote::COMMAND_RPC_OUT_PEERS::request req; diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index afb2b5f36..5eed44353 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -116,8 +116,6 @@ public: bool set_limit_down(int limit); - bool fast_exit(); - bool out_peers(uint64_t limit); bool start_save_graph(); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index f6431a018..27c1dfddb 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -830,19 +830,19 @@ namespace cryptonote return reward; } //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::fill_block_header_responce(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_responce& responce) + bool core_rpc_server::fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response) { - responce.major_version = blk.major_version; - responce.minor_version = blk.minor_version; - responce.timestamp = blk.timestamp; - responce.prev_hash = string_tools::pod_to_hex(blk.prev_id); - responce.nonce = blk.nonce; - responce.orphan_status = orphan_status; - responce.height = height; - responce.depth = m_core.get_current_blockchain_height() - height - 1; - responce.hash = string_tools::pod_to_hex(hash); - responce.difficulty = m_core.get_blockchain_storage().block_difficulty(height); - responce.reward = get_block_reward(blk); + response.major_version = blk.major_version; + response.minor_version = blk.minor_version; + response.timestamp = blk.timestamp; + response.prev_hash = string_tools::pod_to_hex(blk.prev_id); + response.nonce = blk.nonce; + response.orphan_status = orphan_status; + response.height = height; + response.depth = m_core.get_current_blockchain_height() - height - 1; + response.hash = string_tools::pod_to_hex(hash); + response.difficulty = m_core.get_blockchain_storage().block_difficulty(height); + response.reward = get_block_reward(blk); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -871,8 +871,8 @@ namespace cryptonote error_resp.message = "Internal error: can't get last block."; return false; } - bool responce_filled = fill_block_header_responce(last_block, false, last_block_height, last_block_hash, res.block_header); - if (!responce_filled) + bool response_filled = fill_block_header_response(last_block, false, last_block_height, last_block_hash, res.block_header); + if (!response_filled) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Internal error: can't produce valid response."; @@ -912,8 +912,8 @@ namespace cryptonote return false; } uint64_t block_height = boost::get<txin_gen>(blk.miner_tx.vin.front()).height; - bool responce_filled = fill_block_header_responce(blk, false, block_height, block_hash, res.block_header); - if (!responce_filled) + bool response_filled = fill_block_header_response(blk, false, block_height, block_hash, res.block_header); + if (!response_filled) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Internal error: can't produce valid response."; @@ -945,8 +945,8 @@ namespace cryptonote error_resp.message = "Internal error: can't get block by height. Height = " + std::to_string(req.height) + '.'; return false; } - bool responce_filled = fill_block_header_responce(blk, false, req.height, block_hash, res.block_header); - if (!responce_filled) + bool response_filled = fill_block_header_response(blk, false, req.height, block_hash, res.block_header); + if (!response_filled) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Internal error: can't produce valid response."; @@ -999,8 +999,8 @@ namespace cryptonote return false; } uint64_t block_height = boost::get<txin_gen>(blk.miner_tx.vin.front()).height; - bool responce_filled = fill_block_header_responce(blk, false, block_height, block_hash, res.block_header); - if (!responce_filled) + bool response_filled = fill_block_header_response(blk, false, block_height, block_hash, res.block_header); + if (!response_filled) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Internal error: can't produce valid response."; @@ -1215,14 +1215,6 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::on_fast_exit(const COMMAND_RPC_FAST_EXIT::request& req, COMMAND_RPC_FAST_EXIT::response& res) - { - cryptonote::core::set_fast_exit(); - m_p2p.deinit(); - m_core.deinit(); - return true; - } - //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_out_peers(const COMMAND_RPC_OUT_PEERS::request& req, COMMAND_RPC_OUT_PEERS::response& res) { // TODO diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 9885aa0ff..a4ca8bf1b 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -94,7 +94,6 @@ namespace cryptonote MAP_URI_AUTO_JON2("/get_transaction_pool", on_get_transaction_pool, COMMAND_RPC_GET_TRANSACTION_POOL) MAP_URI_AUTO_JON2_IF("/stop_daemon", on_stop_daemon, COMMAND_RPC_STOP_DAEMON, !m_restricted) MAP_URI_AUTO_JON2("/getinfo", on_get_info, COMMAND_RPC_GET_INFO) - MAP_URI_AUTO_JON2_IF("/fast_exit", on_fast_exit, COMMAND_RPC_FAST_EXIT, !m_restricted) MAP_URI_AUTO_JON2_IF("/out_peers", on_out_peers, COMMAND_RPC_OUT_PEERS, !m_restricted) MAP_URI_AUTO_JON2_IF("/start_save_graph", on_start_save_graph, COMMAND_RPC_START_SAVE_GRAPH, !m_restricted) MAP_URI_AUTO_JON2_IF("/stop_save_graph", on_stop_save_graph, COMMAND_RPC_STOP_SAVE_GRAPH, !m_restricted) @@ -138,7 +137,6 @@ namespace cryptonote bool on_set_log_level(const COMMAND_RPC_SET_LOG_LEVEL::request& req, COMMAND_RPC_SET_LOG_LEVEL::response& res); bool on_get_transaction_pool(const COMMAND_RPC_GET_TRANSACTION_POOL::request& req, COMMAND_RPC_GET_TRANSACTION_POOL::response& res); bool on_stop_daemon(const COMMAND_RPC_STOP_DAEMON::request& req, COMMAND_RPC_STOP_DAEMON::response& res); - bool on_fast_exit(const COMMAND_RPC_FAST_EXIT::request& req, COMMAND_RPC_FAST_EXIT::response& res); bool on_out_peers(const COMMAND_RPC_OUT_PEERS::request& req, COMMAND_RPC_OUT_PEERS::response& res); bool on_start_save_graph(const COMMAND_RPC_START_SAVE_GRAPH::request& req, COMMAND_RPC_START_SAVE_GRAPH::response& res); bool on_stop_save_graph(const COMMAND_RPC_STOP_SAVE_GRAPH::request& req, COMMAND_RPC_STOP_SAVE_GRAPH::response& res); @@ -172,7 +170,7 @@ private: //utils uint64_t get_block_reward(const block& blk); - bool fill_block_header_responce(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_responce& responce); + bool fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response); core& m_core; nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >& m_p2p; diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 8cba53943..dd2116e51 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -631,7 +631,7 @@ namespace cryptonote }; }; - struct block_header_responce + struct block_header_response { uint8_t major_version; uint8_t minor_version; @@ -671,7 +671,7 @@ namespace cryptonote struct response { std::string status; - block_header_responce block_header; + block_header_response block_header; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(block_header) @@ -695,7 +695,7 @@ namespace cryptonote struct response { std::string status; - block_header_responce block_header; + block_header_response block_header; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(block_header) @@ -719,7 +719,7 @@ namespace cryptonote struct response { std::string status; - block_header_responce block_header; + block_header_response block_header; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(block_header) @@ -745,7 +745,7 @@ namespace cryptonote struct response { std::string status; - block_header_responce block_header; + block_header_response block_header; std::vector<std::string> tx_hashes; std::string blob; std::string json; @@ -940,7 +940,7 @@ namespace cryptonote struct response { std::string status; - std::vector<block_header_responce> headers; + std::vector<block_header_response> headers; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(status) diff --git a/src/serialization/list.h b/src/serialization/list.h new file mode 100644 index 000000000..d0fb72163 --- /dev/null +++ b/src/serialization/list.h @@ -0,0 +1,100 @@ +// Copyright (c) 2014-2015, 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 "serialization.h" + +namespace serialization +{ + namespace detail + { + template <typename Archive, class T> + bool serialize_list_element(Archive& ar, T& e) + { + return ::do_serialize(ar, e); + } + + template <typename Archive> + bool serialize_list_element(Archive& ar, uint64_t& e) + { + ar.serialize_varint(e); + return true; + } + } +} + +template <template <bool> class Archive, class T> +bool do_serialize(Archive<false> &ar, std::list<T> &l) +{ + size_t cnt; + ar.begin_array(cnt); + if (!ar.stream().good()) + return false; + l.clear(); + + // very basic sanity check + if (ar.remaining_bytes() < cnt) { + ar.stream().setstate(std::ios::failbit); + return false; + } + + for (size_t i = 0; i < cnt; i++) { + if (i > 0) + ar.delimit_array(); + l.push_back(T()); + T &t = l.back(); + if (!::serialization::detail::serialize_list_element(ar, t)) + return false; + if (!ar.stream().good()) + return false; + } + ar.end_array(); + return true; +} + +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::list<T> &l) +{ + size_t cnt = l.size(); + ar.begin_array(cnt); + for (typename std::list<T>::iterator i = l.begin(); i != l.end(); ++i) { + if (!ar.stream().good()) + return false; + if (i != l.begin()) + ar.delimit_array(); + if(!::serialization::detail::serialize_list_element(ar, *i)) + return false; + if (!ar.stream().good()) + return false; + } + ar.end_array(); + return true; +} diff --git a/src/serialization/pair.h b/src/serialization/pair.h new file mode 100644 index 000000000..4913a74d6 --- /dev/null +++ b/src/serialization/pair.h @@ -0,0 +1,96 @@ +// Copyright (c) 2014-2015, 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_pair_element(Archive& ar, T& e) + { + return ::do_serialize(ar, e); + } + + template <typename Archive> + bool serialize_pair_element(Archive& ar, uint64_t& e) + { + ar.serialize_varint(e); + return true; + } + } +} + +template <template <bool> class Archive, class F, class S> +inline bool do_serialize(Archive<false>& ar, std::pair<F,S>& p) +{ + size_t cnt; + ar.begin_array(cnt); + if (!ar.stream().good()) + return false; + if (cnt != 2) + return false; + + if (!::serialization::detail::serialize_pair_element(ar, p.first)) + return false; + if (!ar.stream().good()) + return false; + ar.delimit_array(); + if (!::serialization::detail::serialize_pair_element(ar, p.second)) + return false; + if (!ar.stream().good()) + return false; + + ar.end_array(); + return true; +} + +template <template <bool> class Archive, class F, class S> +inline bool do_serialize(Archive<true>& ar, std::pair<F,S>& p) +{ + ar.begin_array(2); + if (!ar.stream().good()) + return false; + if(!::serialization::detail::serialize_pair_element(ar, p.first)) + return false; + if (!ar.stream().good()) + return false; + ar.delimit_array(); + if(!::serialization::detail::serialize_pair_element(ar, p.second)) + return false; + if (!ar.stream().good()) + return false; + ar.end_array(); + return true; +} + diff --git a/src/serialization/serialization.h b/src/serialization/serialization.h index 177cdf33a..dac43720b 100644 --- a/src/serialization/serialization.h +++ b/src/serialization/serialization.h @@ -41,6 +41,7 @@ #pragma once #include <vector> +#include <list> #include <string> #include <boost/type_traits/is_integral.hpp> #include <boost/type_traits/integral_constant.hpp> @@ -59,6 +60,16 @@ struct is_blob_type { typedef boost::false_type type; }; template <class T> struct has_free_serializer { typedef boost::true_type type; }; +/*! \struct is_pair_type + * + * \brief a descriptor for dispatching serialize + */ +template <class T> +struct is_pair_type { typedef boost::false_type type; }; + +template<typename F, typename S> +struct is_pair_type<std::pair<F,S>> { typedef boost::true_type type; }; + /*! \struct serializer * * \brief ... wouldn't a class be better? @@ -75,20 +86,26 @@ struct has_free_serializer { typedef boost::true_type type; }; template <class Archive, class T> struct serializer{ static bool serialize(Archive &ar, T &v) { - return serialize(ar, v, typename boost::is_integral<T>::type(), typename is_blob_type<T>::type()); + return serialize(ar, v, typename boost::is_integral<T>::type(), typename is_blob_type<T>::type(), typename is_pair_type<T>::type()); } - static bool serialize(Archive &ar, T &v, boost::false_type, boost::true_type) { + template<typename A> + static bool serialize(Archive &ar, T &v, boost::false_type, boost::true_type, A a) { ar.serialize_blob(&v, sizeof(v)); return true; } - static bool serialize(Archive &ar, T &v, boost::true_type, boost::false_type) { + template<typename A> + static bool serialize(Archive &ar, T &v, boost::true_type, boost::false_type, A a) { ar.serialize_int(v); return true; } - static bool serialize(Archive &ar, T &v, boost::false_type, boost::false_type) { + static bool serialize(Archive &ar, T &v, boost::false_type, boost::false_type, boost::false_type) { //serialize_custom(ar, v, typename has_free_serializer<T>::type()); return v.do_serialize(ar); } + static bool serialize(Archive &ar, T &v, boost::false_type, boost::false_type, boost::true_type) { + //serialize_custom(ar, v, typename has_free_serializer<T>::type()); + return do_serialize(ar, v); + } static void serialize_custom(Archive &ar, T &v, boost::true_type) { } }; @@ -328,3 +345,5 @@ namespace serialization { #include "string.h" #include "vector.h" +#include "list.h" +#include "pair.h" diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 279a5fa41..a7477c93b 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -389,11 +389,6 @@ bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = s bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { bool success = false; - if (m_wallet->watch_only()) - { - fail_msg_writer() << tr("wallet is watch-only and cannot transfer"); - return true; - } tools::password_container pwd_container(m_wallet_file.empty()); success = pwd_container.read_password(); if (!success) @@ -650,10 +645,12 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("incoming_transfers", boost::bind(&simple_wallet::show_incoming_transfers, this, _1), tr("incoming_transfers [available|unavailable] - Show incoming transfers, all or filtered by availability")); m_cmd_binder.set_handler("payments", boost::bind(&simple_wallet::show_payments, this, _1), tr("payments <PID_1> [<PID_2> ... <PID_N>] - Show payments for given payment ID[s]")); m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), tr("Show blockchain height")); - m_cmd_binder.set_handler("transfer_original", boost::bind(&simple_wallet::transfer, this, _1), tr("transfer [<mixin_count>] <addr_1> <amount_1> [<addr_2> <amount_2> ... <addr_N> <amount_N>] [payment_id] - Transfer <amount_1>,... <amount_N> to <address_1>,... <address_N>, respectively. <mixin_count> is the number of extra inputs to include for untraceability (from 0 to maximum available)")); + m_cmd_binder.set_handler("transfer_original", boost::bind(&simple_wallet::transfer, this, _1), tr("transfer [<mixin_count>] <addr_1> <amount_1> [<addr_2> <amount_2> ... <addr_N> <amount_N>] [payment_id] - Transfer <amount_1>,... <amount_N> to <address_1>,... <address_N>, respectively. <mixin_count> is the number of extra inputs to include for untraceability (from 2 to maximum available)")); m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer_new, this, _1), tr("Same as transfer_original, but using a new transaction building algorithm")); m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::sweep_unmixable, this, _1), tr("Send all unmixable outputs to yourself with mixin 0")); m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), tr("Send all unlocked balance an address")); + m_cmd_binder.set_handler("sign_transfer", boost::bind(&simple_wallet::sign_transfer, this, _1), tr("Sign a transaction from a file")); + m_cmd_binder.set_handler("submit_transfer", boost::bind(&simple_wallet::submit_transfer, this, _1), tr("Submit a signed transaction from a file")); m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log <level> - Change current log detail level, <0-4>")); m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), tr("Show current wallet public address")); m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::print_integrated_address, this, _1), tr("integrated_address [PID] - Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID")); @@ -2313,12 +2310,6 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri return true; } - if(m_wallet->watch_only()) - { - fail_msg_writer() << tr("this is a watch only wallet"); - return true; - } - std::vector<uint8_t> extra; bool payment_id_seen = false; if (1 == local_args.size() % 2) @@ -2455,7 +2446,19 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri } // actually commit the transactions - while (!ptx_vector.empty()) + if (m_wallet->watch_only()) + { + bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "unsigned_monero_tx"; + } + } + else while (!ptx_vector.empty()) { auto & ptx = ptx_vector.back(); m_wallet->commit_tx(ptx); @@ -2562,12 +2565,6 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) if (!try_connect_to_daemon()) return true; - if(m_wallet->watch_only()) - { - fail_msg_writer() << tr("this is a watch only wallet"); - return true; - } - LOCK_IDLE_SCOPE(); try { @@ -2617,7 +2614,19 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) } // actually commit the transactions - while (!ptx_vector.empty()) + if (m_wallet->watch_only()) + { + bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "unsigned_monero_tx"; + } + } + else while (!ptx_vector.empty()) { auto & ptx = ptx_vector.back(); m_wallet->commit_tx(ptx); @@ -2714,12 +2723,6 @@ bool simple_wallet::sweep_all(const std::vector<std::string> &args_) if (!try_connect_to_daemon()) return true; - if(m_wallet->watch_only()) - { - fail_msg_writer() << tr("this is a watch only wallet"); - return true; - } - std::vector<std::string> local_args = args_; size_t fake_outs_count; @@ -2849,7 +2852,19 @@ bool simple_wallet::sweep_all(const std::vector<std::string> &args_) } // actually commit the transactions - while (!ptx_vector.empty()) + if (m_wallet->watch_only()) + { + bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "unsigned_monero_tx"; + } + } + else while (!ptx_vector.empty()) { auto & ptx = ptx_vector.back(); m_wallet->commit_tx(ptx); @@ -2941,6 +2956,235 @@ bool simple_wallet::sweep_all(const std::vector<std::string> &args_) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs) +{ + // gather info to ask the user + uint64_t amount = 0, amount_to_dests = 0, change = 0; + size_t min_mixin = ~0; + std::unordered_map<std::string, uint64_t> dests; + const std::string wallet_address = m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + for (size_t n = 0; n < txs.txes.size(); ++n) + { + const tools::wallet2::tx_construction_data &cd = txs.txes[n]; + for (size_t s = 0; s < cd.sources.size(); ++s) + { + amount += cd.sources[s].amount; + size_t mixin = cd.sources[s].outputs.size() - 1; + if (mixin < min_mixin) + min_mixin = mixin; + } + for (size_t d = 0; d < cd.destinations.size(); ++d) + { + const tx_destination_entry &entry = cd.destinations[d]; + std::string address = get_account_address_as_str(m_wallet->testnet(), entry.addr); + std::unordered_map<std::string,uint64_t>::iterator i = dests.find(address); + if (i == dests.end()) + dests.insert(std::make_pair(address, entry.amount)); + else + i->second += entry.amount; + amount_to_dests += entry.amount; + } + if (cd.change_dts.amount > 0) + { + dests.insert(std::make_pair(get_account_address_as_str(m_wallet->testnet(), cd.change_dts.addr), cd.change_dts.amount)); + amount_to_dests += cd.change_dts.amount; + change += cd.change_dts.amount; + } + } + std::string dest_string; + for (std::unordered_map<std::string, uint64_t>::const_iterator i = dests.begin(); i != dests.end(); ) + { + dest_string += (boost::format(tr("sending %s to %s")) % print_money(i->second) % i->first).str(); + ++i; + if (i != dests.end()) + dest_string += ", "; + } + if (dest_string.empty()) + dest_string = tr("with no destinations"); + + uint64_t fee = amount - amount_to_dests; + std::string prompt_str = (boost::format(tr("Loaded %lu transactions, for %s, fee %s, change %s, %s, with min mixin %lu (full details in log file). Is this okay? (Y/Yes/N/No)")) % (unsigned long)txs.txes.size() % print_money(amount) % print_money(fee) % print_money(change) % dest_string % (unsigned long)min_mixin).str(); + std::string accepted = command_line::input_line(prompt_str); + return is_it_true(accepted); +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::sign_transfer(const std::vector<std::string> &args_) +{ + if(m_wallet->watch_only()) + { + fail_msg_writer() << tr("This is a watch only wallet"); + return true; + } + + try + { + bool r = m_wallet->sign_tx("unsigned_monero_tx", "signed_monero_tx", [&](const tools::wallet2::unsigned_tx_set &tx){ return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to sign transaction"); + return true; + } + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to sign transaction: ") << e.what(); + return true; + } + + success_msg_writer(true) << tr("Transaction successfully signed to file: ") << "signed_monero_tx"; + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::submit_transfer(const std::vector<std::string> &args_) +{ + if (!try_connect_to_daemon()) + return true; + + try + { + std::vector<tools::wallet2::pending_tx> ptx_vector; + bool r = m_wallet->load_tx("signed_monero_tx", ptx_vector); + if (!r) + { + fail_msg_writer() << tr("Failed to load transaction from file"); + return true; + } + + // if more than one tx necessary, prompt user to confirm + if (m_wallet->always_confirm_transfers()) + { + uint64_t total_fee = 0; + uint64_t dust_not_in_fee = 0; + uint64_t dust_in_fee = 0; + for (size_t n = 0; n < ptx_vector.size(); ++n) + { + total_fee += ptx_vector[n].fee; + + if (ptx_vector[n].dust_added_to_fee) + dust_in_fee += ptx_vector[n].dust; + else + dust_not_in_fee += ptx_vector[n].dust; + } + + std::stringstream prompt; + if (ptx_vector.size() > 1) + { + prompt << boost::format(tr("Your transaction needs to be split into %llu transactions. " + "This will result in a transaction fee being applied to each transaction, for a total fee of %s")) % + ((unsigned long long)ptx_vector.size()) % print_money(total_fee); + } + else + { + prompt << boost::format(tr("The transaction fee is %s")) % + print_money(total_fee); + } + if (dust_in_fee != 0) prompt << boost::format(tr(", of which %s is dust from change")) % print_money(dust_in_fee); + if (dust_not_in_fee != 0) prompt << tr(".") << ENDL << boost::format(tr("A total of %s from dust change will be sent to dust address")) + % print_money(dust_not_in_fee); + prompt << tr(".") << ENDL << tr("Is this okay? (Y/Yes/N/No)"); + + std::string accepted = command_line::input_line(prompt.str()); + if (std::cin.eof()) + return true; + if (accepted != "Y" && accepted != "y" && accepted != "Yes" && accepted != "yes") + { + fail_msg_writer() << tr("transaction cancelled."); + + // would like to return false, because no tx made, but everything else returns true + // and I don't know what returning false might adversely affect. *sigh* + return true; + } + } + + // actually commit the transactions + while (!ptx_vector.empty()) + { + auto & ptx = ptx_vector.back(); + m_wallet->commit_tx(ptx); + success_msg_writer(true) << tr("Money successfully sent, transaction: ") << get_transaction_hash(ptx.tx); + + // if no exception, remove element from vector + ptx_vector.pop_back(); + } + } + catch (const tools::error::daemon_busy&) + { + fail_msg_writer() << tr("daemon is busy. Please try later"); + } + catch (const tools::error::no_connection_to_daemon&) + { + fail_msg_writer() << tr("no connection to daemon. Please, make sure daemon is running."); + } + catch (const tools::error::wallet_rpc_error& e) + { + LOG_ERROR("Unknown RPC error: " << e.to_string()); + fail_msg_writer() << tr("RPC error: ") << e.what(); + } + catch (const tools::error::get_random_outs_error&) + { + fail_msg_writer() << tr("failed to get random outputs to mix"); + } + catch (const tools::error::not_enough_money& e) + { + fail_msg_writer() << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) % + print_money(e.available()) % + print_money(e.tx_amount() + e.fee()) % + print_money(e.tx_amount()) % + print_money(e.fee()); + } + catch (const tools::error::not_enough_outs_to_mix& e) + { + auto writer = fail_msg_writer(); + writer << tr("not enough outputs for specified mixin_count") << " = " << e.mixin_count() << ":"; + for (std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs()) + { + writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to mix") << " = " << outs_for_amount.second; + } + } + catch (const tools::error::tx_not_constructed&) + { + fail_msg_writer() << tr("transaction was not constructed"); + } + catch (const tools::error::tx_rejected& e) + { + fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); + } + catch (const tools::error::tx_sum_overflow& e) + { + fail_msg_writer() << e.what(); + } + catch (const tools::error::zero_destination&) + { + fail_msg_writer() << tr("one of destinations is zero"); + } + catch (const tools::error::tx_too_big& e) + { + fail_msg_writer() << tr("Failed to find a suitable way to split transactions"); + } + catch (const tools::error::transfer_error& e) + { + LOG_ERROR("unknown transfer error: " << e.to_string()); + fail_msg_writer() << tr("unknown transfer error: ") << e.what(); + } + catch (const tools::error::wallet_internal_error& e) + { + LOG_ERROR("internal error: " << e.to_string()); + fail_msg_writer() << tr("internal error: ") << e.what(); + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + fail_msg_writer() << tr("unexpected error: ") << e.what(); + } + catch (...) + { + LOG_ERROR("Unknown error"); + fail_msg_writer() << tr("unknown error"); + } + + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) { std::vector<std::string> local_args = args_; diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 6eb18ed9a..61e956ce9 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -125,6 +125,8 @@ namespace cryptonote bool transfer_new(const std::vector<std::string> &args); bool sweep_all(const std::vector<std::string> &args); bool sweep_unmixable(const std::vector<std::string> &args); + bool sign_transfer(const std::vector<std::string> &args); + bool submit_transfer(const std::vector<std::string> &args); std::vector<std::vector<cryptonote::tx_destination_entry>> split_amounts( std::vector<cryptonote::tx_destination_entry> dsts, size_t num_splits ); @@ -152,6 +154,7 @@ namespace cryptonote uint64_t get_daemon_blockchain_height(std::string& err); bool try_connect_to_daemon(bool silent = false); bool ask_wallet_create_if_needed(); + bool accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs); bool get_address_from_str(const std::string &str, cryptonote::account_public_address &address, bool &has_payment_id, crypto::hash8 &payment_id); /*! diff --git a/src/wallet/api/utils.cpp b/src/wallet/api/utils.cpp index aa85323f0..1bf35197c 100644 --- a/src/wallet/api/utils.cpp +++ b/src/wallet/api/utils.cpp @@ -56,18 +56,22 @@ bool isAddressLocal(const std::string &address) LOG_PRINT_L1("Failed to determine whether daemon is local, assuming not"); return false; } - - // resolve to IP - boost::asio::io_service io_service; - boost::asio::ip::tcp::resolver resolver(io_service); - boost::asio::ip::tcp::resolver::query query(u_c.host, ""); - boost::asio::ip::tcp::resolver::iterator i = resolver.resolve(query); - while (i != boost::asio::ip::tcp::resolver::iterator()) - { - const boost::asio::ip::tcp::endpoint &ep = *i; - if (ep.address().is_loopback()) - return true; - ++i; + // resolver::resolve can throw an exception + try { + // resolve to IP + boost::asio::io_service io_service; + boost::asio::ip::tcp::resolver resolver(io_service); + boost::asio::ip::tcp::resolver::query query(u_c.host, ""); + boost::asio::ip::tcp::resolver::iterator i = resolver.resolve(query); + while (i != boost::asio::ip::tcp::resolver::iterator()) + { + const boost::asio::ip::tcp::endpoint &ep = *i; + if (ep.address().is_loopback()) + return true; + ++i; + } + } catch (const boost::system::system_error &e) { + LOG_ERROR("Failed to resolve " << address << ", :" << e.what()); } return false; diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 49ccceb13..16321a5a2 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -46,7 +46,9 @@ namespace Bitmonero { namespace { // copy-pasted from simplewallet static const size_t DEFAULT_MIXIN = 4; - static const int DEFAULT_REFRESH_INTERVAL_SECONDS = 10; + static const int DEFAULT_REFRESH_INTERVAL_MILLIS = 1000 * 10; + // limit maximum refresh interval as one minute + static const int MAX_REFRESH_INTERVAL_MILLIS = 1000 * 60 * 1; } struct Wallet2CallbackImpl : public tools::i_wallet2_callback @@ -75,8 +77,12 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback virtual void on_new_block(uint64_t height, const cryptonote::block& block) { - // TODO; LOG_PRINT_L3(__FUNCTION__ << ": new block. height: " << height); + + if (m_listener) { + m_listener->newBlock(height); + // m_listener->updated(); + } } virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, uint64_t amount) @@ -161,7 +167,7 @@ uint64_t Wallet::maximumAllowedAmount() ///////////////////////// WalletImpl implementation //////////////////////// WalletImpl::WalletImpl(bool testnet) :m_wallet(nullptr), m_status(Wallet::Status_Ok), m_trustedDaemon(false), - m_wallet2Callback(nullptr) + m_wallet2Callback(nullptr), m_recoveringFromSeed(false) { m_wallet = new tools::wallet2(testnet); m_history = new TransactionHistoryImpl(this); @@ -170,7 +176,8 @@ WalletImpl::WalletImpl(bool testnet) m_refreshThreadDone = false; m_refreshEnabled = false; - m_refreshIntervalSeconds = DEFAULT_REFRESH_INTERVAL_SECONDS; + + m_refreshIntervalMillis = DEFAULT_REFRESH_INTERVAL_MILLIS; m_refreshThread = boost::thread([this] () { this->refreshThreadFunc(); @@ -190,7 +197,7 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co { clearStatus(); - + m_recoveringFromSeed = false; bool keys_file_exists; bool wallet_file_exists; tools::wallet2::wallet_exists(path, keys_file_exists, wallet_file_exists); @@ -227,6 +234,7 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co bool WalletImpl::open(const std::string &path, const std::string &password) { clearStatus(); + m_recoveringFromSeed = false; try { // TODO: handle "deprecated" m_wallet->load(path, password); @@ -251,6 +259,7 @@ bool WalletImpl::recover(const std::string &path, const std::string &seed) return false; } + m_recoveringFromSeed = true; crypto::secret_key recovery_key; std::string old_language; if (!crypto::ElectrumWords::words_to_bytes(seed, recovery_key, old_language)) { @@ -263,6 +272,7 @@ bool WalletImpl::recover(const std::string &path, const std::string &seed) m_wallet->set_seed_language(old_language); m_wallet->generate(path, "", recovery_key, true, false); // TODO: wallet->init(daemon_address); + } catch (const std::exception &e) { m_status = Status_Error; m_errorString = e.what(); @@ -379,11 +389,7 @@ string WalletImpl::keysFilename() const bool WalletImpl::init(const std::string &daemon_address, uint64_t upper_transaction_size_limit) { clearStatus(); - - m_wallet->init(daemon_address, upper_transaction_size_limit); - if (Utils::isAddressLocal(daemon_address)) { - this->setTrustedDaemon(true); - } + doInit(daemon_address, upper_transaction_size_limit); bool result = this->refresh(); // enabling background refresh thread startRefresh(); @@ -394,10 +400,7 @@ bool WalletImpl::init(const std::string &daemon_address, uint64_t upper_transact void WalletImpl::initAsync(const string &daemon_address, uint64_t upper_transaction_size_limit) { clearStatus(); - m_wallet->init(daemon_address, upper_transaction_size_limit); - if (Utils::isAddressLocal(daemon_address)) { - this->setTrustedDaemon(true); - } + doInit(daemon_address, upper_transaction_size_limit); startRefresh(); } @@ -413,6 +416,27 @@ uint64_t WalletImpl::unlockedBalance() const return m_wallet->unlocked_balance(); } +uint64_t WalletImpl::blockChainHeight() const +{ + return m_wallet->get_blockchain_current_height(); +} + +uint64_t WalletImpl::daemonBlockChainHeight() const +{ + std::string err; + uint64_t result = m_wallet->get_daemon_blockchain_height(err); + if (!err.empty()) { + LOG_ERROR(__FUNCTION__ << ": " << err); + result = 0; + m_errorString = err; + m_status = Status_Error; + + } else { + m_status = Status_Ok; + m_errorString = ""; + } + return result; +} bool WalletImpl::refresh() { @@ -428,14 +452,20 @@ void WalletImpl::refreshAsync() m_refreshCV.notify_one(); } -void WalletImpl::setAutoRefreshInterval(int seconds) +void WalletImpl::setAutoRefreshInterval(int millis) { - m_refreshIntervalSeconds = seconds; + if (millis > MAX_REFRESH_INTERVAL_MILLIS) { + LOG_ERROR(__FUNCTION__<< ": invalid refresh interval " << millis + << " ms, maximum allowed is " << MAX_REFRESH_INTERVAL_MILLIS << " ms"); + m_refreshIntervalMillis = MAX_REFRESH_INTERVAL_MILLIS; + } else { + m_refreshIntervalMillis = millis; + } } int WalletImpl::autoRefreshInterval() const { - return m_refreshIntervalSeconds; + return m_refreshIntervalMillis; } // TODO: @@ -652,8 +682,8 @@ void WalletImpl::refreshThreadFunc() LOG_PRINT_L3(__FUNCTION__ << ": waiting for refresh..."); // if auto refresh enabled, we wait for the "m_refreshIntervalSeconds" interval. // if not - we wait forever - if (m_refreshIntervalSeconds > 0) { - boost::posix_time::milliseconds wait_for_ms(m_refreshIntervalSeconds * 1000); + if (m_refreshIntervalMillis > 0) { + boost::posix_time::milliseconds wait_for_ms(m_refreshIntervalMillis); m_refreshCV.timed_wait(lock, wait_for_ms); } else { m_refreshCV.wait(lock); @@ -715,4 +745,27 @@ void WalletImpl::pauseRefresh() } +bool WalletImpl::isNewWallet() const +{ + // in case wallet created without daemon connection, closed and opened again, + // it's the same case as if it created from scratch, i.e. we need "fast sync" + // with the daemon (pull hashes instead of pull blocks) + return !(blockChainHeight() > 1 || m_recoveringFromSeed); +} + +void WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction_size_limit) +{ + m_wallet->init(daemon_address, upper_transaction_size_limit); + + // in case new wallet, this will force fast-refresh (pulling hashes instead of blocks) + if (isNewWallet()) { + m_wallet->set_refresh_from_block_height(daemonBlockChainHeight()); + } + + if (Utils::isAddressLocal(daemon_address)) { + this->setTrustedDaemon(true); + } + +} + } // namespace diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 11880d555..5706b2bf5 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -75,9 +75,11 @@ public: bool trustedDaemon() const; uint64_t balance() const; uint64_t unlockedBalance() const; + uint64_t blockChainHeight() const; + uint64_t daemonBlockChainHeight() const; bool refresh(); void refreshAsync(); - void setAutoRefreshInterval(int seconds); + void setAutoRefreshInterval(int millis); int autoRefreshInterval() const; @@ -99,14 +101,16 @@ private: void startRefresh(); void stopRefresh(); void pauseRefresh(); + bool isNewWallet() const; + void doInit(const std::string &daemon_address, uint64_t upper_transaction_size_limit); private: friend class PendingTransactionImpl; friend class TransactionHistoryImpl; tools::wallet2 * m_wallet; - std::atomic<int> m_status; - std::string m_errorString; + mutable std::atomic<int> m_status; + mutable std::string m_errorString; std::string m_password; TransactionHistoryImpl * m_history; bool m_trustedDaemon; @@ -116,7 +120,7 @@ private: // multi-threaded refresh stuff std::atomic<bool> m_refreshEnabled; std::atomic<bool> m_refreshThreadDone; - std::atomic<int> m_refreshIntervalSeconds; + std::atomic<int> m_refreshIntervalMillis; // synchronizing refresh loop; boost::mutex m_refreshMutex; @@ -124,7 +128,10 @@ private: boost::mutex m_refreshMutex2; boost::condition_variable m_refreshCV; boost::thread m_refreshThread; - + // flag indicating wallet is recovering from seed + // so it shouldn't be considered as new and pull blocks (slow-refresh) + // instead of pulling hashes (fast-refresh) + bool m_recoveringFromSeed; }; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 3d4f93aff..ab82755fc 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -74,6 +74,9 @@ using namespace cryptonote; // arbitrary, used to generate different hashes from the same input #define CHACHA8_KEY_TAIL 0x8c +#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\001" +#define SIGNED_TX_PREFIX "Monero signed tx set\001" + #define KILL_IOSERVICE() \ do { \ work.reset(); \ @@ -173,15 +176,17 @@ bool wallet2::is_deprecated() const return is_old_file_format; } //---------------------------------------------------------------------------------------------------- -void wallet2::set_spent(transfer_details &td, uint64_t height) +void wallet2::set_spent(size_t idx, uint64_t height) { + transfer_details &td = m_transfers[idx]; LOG_PRINT_L2("Setting SPENT at " << height << ": ki " << td.m_key_image << ", amount " << print_money(td.m_amount)); td.m_spent = true; td.m_spent_height = height; } //---------------------------------------------------------------------------------------------------- -void wallet2::set_unspent(transfer_details &td) +void wallet2::set_unspent(size_t idx) { + transfer_details &td = m_transfers[idx]; LOG_PRINT_L2("Setting UNSPENT: ki " << td.m_key_image << ", amount " << print_money(td.m_amount)); td.m_spent = false; td.m_spent_height = 0; @@ -501,7 +506,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s td.m_mask = rct::identity(); td.m_rct = false; } - set_unspent(td); + set_unspent(m_transfers.size()-1); m_key_images[td.m_key_image] = m_transfers.size()-1; LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid()); if (0 != m_callback) @@ -580,7 +585,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s amount = td.amount(); LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << txid()); tx_money_spent_in_ins += amount; - set_spent(td, height); + set_spent(it->second, height); if (0 != m_callback) m_callback->on_money_spent(height, tx, amount, tx); } @@ -997,12 +1002,13 @@ void wallet2::update_pool_state() if (pit->second.m_tx.vin[vini].type() == typeid(txin_to_key)) { txin_to_key &tx_in_to_key = boost::get<txin_to_key>(pit->second.m_tx.vin[vini]); - for (auto &td: m_transfers) + for (size_t i = 0; i < m_transfers.size(); ++i) { + const transfer_details &td = m_transfers[i]; if (td.m_key_image == tx_in_to_key.k_image) { LOG_PRINT_L1("Resetting spent status for output " << vini << ": " << td.m_key_image); - set_unspent(td); + set_unspent(i); break; } } @@ -1301,7 +1307,7 @@ void wallet2::detach_blockchain(uint64_t height) if (td.m_spent && td.m_spent_height >= height) { LOG_PRINT_L1("Resetting spent status for output " << i << ": " << td.m_key_image); - set_unspent(td); + set_unspent(i); } } @@ -1590,7 +1596,8 @@ bool wallet2::verify_password(const std::string& password) const const cryptonote::account_keys& keys = account_data_check.get_keys(); r = r && verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); - r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); + if(!m_watch_only) + r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); return r; } @@ -2128,13 +2135,13 @@ void wallet2::rescan_spent() if (td.m_spent) { LOG_PRINT_L0("Marking output " << i << "(" << td.m_key_image << ") as unspent, it was marked as spent"); - set_unspent(td); + set_unspent(i); td.m_spent_height = 0; } else { LOG_PRINT_L0("Marking output " << i << "(" << td.m_key_image << ") as spent, it was marked as unspent"); - set_spent(td, td.m_spent_height); + set_spent(i, td.m_spent_height); // unknown height, if this gets reorged, it might still be missed } } @@ -2260,7 +2267,7 @@ float wallet2::get_output_relatedness(const transfer_details &td0, const transfe return 0.0f; } //---------------------------------------------------------------------------------------------------- -size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_indices, const std::list<transfer_container::iterator>& selected_transfers) const +size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_indices, const std::list<size_t>& selected_transfers) const { std::vector<size_t> candidates; float best_relatedness = 1.0f; @@ -2268,9 +2275,9 @@ size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::ve { const transfer_details &candidate = transfers[unused_indices[n]]; float relatedness = 0.0f; - for (const auto &i: selected_transfers) + for (size_t i = 0; i < selected_transfers.size(); ++i) { - float r = get_output_relatedness(candidate, *i); + float r = get_output_relatedness(candidate, transfers[i]); if (r > relatedness) { relatedness = r; @@ -2292,7 +2299,7 @@ size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::ve return pop_index (unused_indices, candidates[idx]); } //---------------------------------------------------------------------------------------------------- -size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::list<transfer_container::iterator>& selected_transfers) const +size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::list<size_t>& selected_transfers) const { return pop_best_value_from(m_transfers, unused_indices, selected_transfers); } @@ -2301,7 +2308,7 @@ size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::l // returns: // direct return: amount of money found // modified reference: selected_transfers, a list of iterators/indices of input sources -uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<transfer_container::iterator>& selected_transfers, bool trusted_daemon) +uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<size_t>& selected_transfers, bool trusted_daemon) { uint64_t found_money = 0; while (found_money < needed_money && !unused_transfers_indices.empty()) @@ -2309,7 +2316,7 @@ uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> un size_t idx = pop_best_value(unused_transfers_indices, selected_transfers); transfer_container::iterator it = m_transfers.begin() + idx; - selected_transfers.push_back(it); + selected_transfers.push_back(idx); found_money += it->amount(); } @@ -2513,8 +2520,8 @@ void wallet2::commit_tx(pending_tx& ptx) { payment_id = get_payment_id(ptx); dests = ptx.dests; - BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers) - amount_in += it->amount(); + BOOST_FOREACH(size_t idx, ptx.selected_transfers) + amount_in += m_transfers[idx].amount(); } add_unconfirmed_tx(ptx.tx, amount_in, dests, payment_id, ptx.change_dts.amount); if (store_tx_info()) @@ -2524,9 +2531,9 @@ void wallet2::commit_tx(pending_tx& ptx) LOG_PRINT_L2("transaction " << txid << " generated ok and sent to daemon, key_images: [" << ptx.key_images << "]"); - BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers) + BOOST_FOREACH(size_t idx, ptx.selected_transfers) { - set_spent(*it, 0); + set_spent(idx, 0); } //fee includes dust if dust policy specified it. @@ -2544,7 +2551,153 @@ void wallet2::commit_tx(std::vector<pending_tx>& ptx_vector) commit_tx(ptx); } } +//---------------------------------------------------------------------------------------------------- +bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) +{ + LOG_PRINT_L0("saving " << ptx_vector.size() << " transactions"); + unsigned_tx_set txs; + for (auto &tx: ptx_vector) + txs.txes.push_back(tx.construction_data); + std::string s = obj_to_json_str(txs); + if (s.empty()) + return false; + LOG_PRINT_L2("Saving unsigned tx data: " << s); + // save as binary as there's no implementation of loading a json_archive + if (!::serialization::dump_binary(txs, s)) + return false; + return epee::file_io_utils::save_string_to_file(filename, std::string(UNSIGNED_TX_PREFIX) + s); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::function<bool(const unsigned_tx_set&)> accept_func) +{ + std::string s; + boost::system::error_code errcode; + + if (!boost::filesystem::exists(unsigned_filename, errcode)) + { + LOG_PRINT_L0("File " << unsigned_filename << " does not exist: " << errcode); + return false; + } + if (!epee::file_io_utils::load_file_to_string(unsigned_filename.c_str(), s)) + { + LOG_PRINT_L0("Failed to load from " << unsigned_filename); + return false; + } + const size_t magiclen = strlen(UNSIGNED_TX_PREFIX); + if (strncmp(s.c_str(), UNSIGNED_TX_PREFIX, magiclen)) + { + LOG_PRINT_L0("Bad magic from " << unsigned_filename); + return false; + } + unsigned_tx_set exported_txs; + if (!::serialization::parse_binary(std::string(s.c_str() + magiclen, s.size() - magiclen), exported_txs)) + { + LOG_PRINT_L0("Failed to parse data from " << unsigned_filename); + return false; + } + LOG_PRINT_L1("Loaded tx unsigned data from binary: " << exported_txs.txes.size() << " transactions"); + + if (accept_func && !accept_func(exported_txs)) + { + LOG_PRINT_L1("Transactions rejected by callback"); + return false; + } + + // sign the transactions + signed_tx_set signed_txes; + for (size_t n = 0; n < exported_txs.txes.size(); ++n) + { + const tools::wallet2::tx_construction_data &sd = exported_txs.txes[n]; + LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, mixin " << (sd.sources[0].outputs.size()-1)); + signed_txes.ptx.push_back(pending_tx()); + tools::wallet2::pending_tx &ptx = signed_txes.ptx.back(); + crypto::secret_key tx_key; + std::vector<cryptonote::tx_destination_entry> dests = sd.destinations; + if (sd.change_dts.amount > 0) + dests.push_back(sd.change_dts); + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sd.sources, dests, sd.extra, ptx.tx, sd.unlock_time, tx_key, sd.use_rct); + THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.destinations, sd.unlock_time, m_testnet); + // we don't test tx size, because we don't know the current limit, due to not having a blockchain, + // and it's a bit pointless to fail there anyway, since it'd be a (good) guess only. We sign anyway, + // and if we really go over limit, the daemon will reject when it gets submitted. Chances are it's + // OK anyway since it was generated in the first place, and rerolling should be within a few bytes. + + // normally, the tx keys are saved in commit_tx, when the tx is actually sent to the daemon. + // we can't do that here since the tx will be sent from the compromised wallet, which we don't want + // to see that info, so we save it here + if (store_tx_info()) + { + const crypto::hash txid = get_transaction_hash(ptx.tx); + m_tx_keys.insert(std::make_pair(txid, tx_key)); + } + + std::string key_images; + bool all_are_txin_to_key = std::all_of(ptx.tx.vin.begin(), ptx.tx.vin.end(), [&](const txin_v& s_e) -> bool + { + CHECKED_GET_SPECIFIC_VARIANT(s_e, const txin_to_key, in, false); + key_images += boost::to_string(in.k_image) + " "; + return true; + }); + THROW_WALLET_EXCEPTION_IF(!all_are_txin_to_key, error::unexpected_txin_type, ptx.tx); + + ptx.key_images = key_images; + ptx.fee = 0; + for (const auto &i: sd.sources) ptx.fee += i.amount; + for (const auto &i: dests) ptx.fee -= i.amount; + ptx.dust = 0; + ptx.dust_added_to_fee = false; + ptx.change_dts = sd.change_dts; +// ptx.selected_transfers = selected_transfers; + ptx.tx_key = rct::rct2sk(rct::identity()); // don't send it back to the untrusted view wallet + ptx.dests = sd.destinations; + ptx.construction_data = sd; + } + + s = obj_to_json_str(signed_txes); + if (s.empty()) + return false; + LOG_PRINT_L2("Saving signed tx data: " << s); + // save as binary as there's no implementation of loading a json_archive + if (!::serialization::dump_binary(signed_txes, s)) + return false; + return epee::file_io_utils::save_string_to_file(signed_filename, std::string(SIGNED_TX_PREFIX) + s); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx) +{ + std::string s; + boost::system::error_code errcode; + signed_tx_set signed_txs; + + if (!boost::filesystem::exists(signed_filename, errcode)) + { + LOG_PRINT_L0("File " << signed_filename << " does not exist: " << errcode); + return false; + } + + if (!epee::file_io_utils::load_file_to_string(signed_filename.c_str(), s)) + { + LOG_PRINT_L0("Failed to load from " << signed_filename); + return false; + } + const size_t magiclen = strlen(SIGNED_TX_PREFIX); + if (strncmp(s.c_str(), SIGNED_TX_PREFIX, magiclen)) + { + LOG_PRINT_L0("Bad magic from " << signed_filename); + return false; + } + if (!::serialization::parse_binary(std::string(s.c_str() + magiclen, s.size() - magiclen), signed_txs)) + { + LOG_PRINT_L0("Failed to parse data from " << signed_filename); + return false; + } + LOG_PRINT_L1("Loaded signed tx data from binary: " << signed_txs.ptx.size() << " transactions"); + + ptx = signed_txs.ptx; + return true; +} +//---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_fee_multiplier(uint32_t priority, bool use_new_fee) const { static const uint64_t old_multipliers[3] = {1, 2, 3}; @@ -2563,7 +2716,6 @@ uint64_t wallet2::get_fee_multiplier(uint32_t priority, bool use_new_fee) const THROW_WALLET_EXCEPTION_IF (false, error::invalid_priority); return 1; } - //---------------------------------------------------------------------------------------------------- // separated the call(s) to wallet2::transfer into their own function // @@ -2613,9 +2765,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto ptx_vector.push_back(ptx); // mark transfers to be used as "spent" - BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers) + BOOST_FOREACH(size_t idx, ptx.selected_transfers) { - set_spent(*it, 0); + set_spent(idx, 0); } } @@ -2625,9 +2777,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto for (auto & ptx : ptx_vector) { // mark transfers to be used as not spent - BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) + BOOST_FOREACH(size_t idx2, ptx.selected_transfers) { - set_unspent(*it2); + set_unspent(idx2); } } @@ -2644,9 +2796,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto for (auto & ptx : ptx_vector) { // mark transfers to be used as not spent - BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) + BOOST_FOREACH(size_t idx2, ptx.selected_transfers) { - set_unspent(*it2); + set_unspent(idx2); } } @@ -2663,9 +2815,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto for (auto & ptx : ptx_vector) { // mark transfers to be used as not spent - BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) + BOOST_FOREACH(size_t idx2, ptx.selected_transfers) { - set_unspent(*it2); + set_unspent(idx2); } } @@ -2675,7 +2827,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto } template<typename entry> -void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<transfer_container::iterator> &selected_transfers, size_t fake_outputs_count) +void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count) { LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count); outs.clear(); @@ -2688,8 +2840,8 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr req_t.jsonrpc = "2.0"; req_t.id = epee::serialization::storage_entry(0); req_t.method = "get_output_histogram"; - for(auto it: selected_transfers) - req_t.params.amounts.push_back(it->is_rct() ? 0 : it->amount()); + for(size_t idx: selected_transfers) + req_t.params.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount()); std::sort(req_t.params.amounts.begin(), req_t.params.amounts.end()); auto end = std::unique(req_t.params.amounts.begin(), req_t.params.amounts.end()); req_t.params.amounts.resize(std::distance(req_t.params.amounts.begin(), end)); @@ -2708,12 +2860,13 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr COMMAND_RPC_GET_OUTPUTS::request req = AUTO_VAL_INIT(req); COMMAND_RPC_GET_OUTPUTS::response daemon_resp = AUTO_VAL_INIT(daemon_resp); - for(transfer_container::iterator it: selected_transfers) + for(size_t idx: selected_transfers) { - const uint64_t amount = it->is_rct() ? 0 : it->amount(); + const transfer_details &td = m_transfers[idx]; + const uint64_t amount = td.is_rct() ? 0 : td.amount(); std::unordered_set<uint64_t> seen_indices; // request more for rct in base recent (locked) coinbases are picked, since they're locked for longer - size_t requested_outputs_count = base_requested_outputs_count + (it->is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); + size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); size_t start = req.outputs.size(); // if there are just enough outputs to mix with, use all of them. @@ -2745,8 +2898,8 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr { // start with real one uint64_t num_found = 1; - seen_indices.emplace(it->m_global_output_index); - req.outputs.push_back({amount, it->m_global_output_index}); + seen_indices.emplace(td.m_global_output_index); + req.outputs.push_back({amount, td.m_global_output_index}); // while we still need more mixins while (num_found < requested_outputs_count) @@ -2798,15 +2951,16 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr std::unordered_map<uint64_t, uint64_t> scanty_outs; size_t base = 0; outs.reserve(selected_transfers.size()); - for(transfer_container::iterator it: selected_transfers) + for(size_t idx: selected_transfers) { - size_t requested_outputs_count = base_requested_outputs_count + (it->is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); + const transfer_details &td = m_transfers[idx]; + size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); outs.push_back(std::vector<entry>()); outs.back().reserve(fake_outputs_count + 1); - const rct::key mask = it->is_rct() ? rct::commit(it->amount(), it->m_mask) : rct::zeroCommit(it->amount()); + const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount()); // pick real out first (it will be sorted when done) - outs.back().push_back(std::make_tuple(it->m_global_output_index, boost::get<txout_to_key>(it->m_tx.vout[it->m_internal_output_index].target).key, mask)); + outs.back().push_back(std::make_tuple(td.m_global_output_index, boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, mask)); // then pick others in random order till we reach the required number // since we use an equiprobable pick here, we don't upset the triangular distribution @@ -2816,12 +2970,12 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr order[n] = n; std::shuffle(order.begin(), order.end(), std::default_random_engine(crypto::rand<unsigned>())); - LOG_PRINT_L2("Looking for " << (fake_outputs_count+1) << " outputs of size " << print_money(it->is_rct() ? 0 : it->amount())); + LOG_PRINT_L2("Looking for " << (fake_outputs_count+1) << " outputs of size " << print_money(td.is_rct() ? 0 : td.amount())); for (size_t o = 0; o < requested_outputs_count && outs.back().size() < fake_outputs_count + 1; ++o) { size_t i = base + order[o]; - LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << it->m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key); - if (req.outputs[i].index == it->m_global_output_index) // don't re-add real one + LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << td.m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key); + if (req.outputs[i].index == td.m_global_output_index) // don't re-add real one continue; if (!daemon_resp.outs[i].unlocked) // don't add locked outs continue; @@ -2832,7 +2986,7 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr } if (outs.back().size() < fake_outputs_count + 1) { - scanty_outs[it->is_rct() ? 0 : it->amount()] = outs.back().size(); + scanty_outs[td.is_rct() ? 0 : td.amount()] = outs.back().size(); } else { @@ -2845,18 +2999,19 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr } else { - for (transfer_container::iterator it: selected_transfers) + for (size_t idx: selected_transfers) { + const transfer_details &td = m_transfers[idx]; std::vector<entry> v; - const rct::key mask = it->is_rct() ? rct::commit(it->amount(), it->m_mask) : rct::zeroCommit(it->amount()); - v.push_back(std::make_tuple(it->m_global_output_index, boost::get<txout_to_key>(it->m_tx.vout[it->m_internal_output_index].target).key, mask)); + const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount()); + v.push_back(std::make_tuple(td.m_global_output_index, boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, mask)); outs.push_back(v); } } } template<typename T> -void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, +void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx) { using namespace cryptonote; @@ -2878,9 +3033,9 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent } uint64_t found_money = 0; - BOOST_FOREACH(auto it, selected_transfers) + BOOST_FOREACH(size_t idx, selected_transfers) { - found_money += it->amount(); + found_money += m_transfers[idx].amount(); } LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee)); @@ -2894,11 +3049,11 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent typedef cryptonote::tx_source_entry::output_entry tx_output_entry; size_t i = 0, out_index = 0; std::vector<cryptonote::tx_source_entry> sources; - BOOST_FOREACH(transfer_container::iterator it, selected_transfers) + BOOST_FOREACH(size_t idx, selected_transfers) { sources.resize(sources.size()+1); cryptonote::tx_source_entry& src = sources.back(); - transfer_details& td = *it; + const transfer_details& td = m_transfers[idx]; src.amount = td.amount(); src.rct = td.is_rct(); //paste keys (fake and real) @@ -2983,9 +3138,15 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent ptx.selected_transfers = selected_transfers; ptx.tx_key = tx_key; ptx.dests = dsts; + ptx.construction_data.sources = sources; + ptx.construction_data.destinations = dsts; + ptx.construction_data.change_dts = change_dts; + ptx.construction_data.extra = tx.extra; + ptx.construction_data.unlock_time = unlock_time; + ptx.construction_data.use_rct = false; } -void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, +void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx) { using namespace cryptonote; @@ -3007,9 +3168,9 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry } uint64_t found_money = 0; - BOOST_FOREACH(auto it, selected_transfers) + BOOST_FOREACH(size_t idx, selected_transfers) { - found_money += it->amount(); + found_money += m_transfers[idx].amount(); } LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee)); @@ -3022,11 +3183,11 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry //prepare inputs size_t i = 0, out_index = 0; std::vector<cryptonote::tx_source_entry> sources; - BOOST_FOREACH(transfer_container::iterator it, selected_transfers) + BOOST_FOREACH(size_t idx, selected_transfers) { sources.resize(sources.size()+1); cryptonote::tx_source_entry& src = sources.back(); - transfer_details& td = *it; + const transfer_details& td = m_transfers[idx]; src.amount = td.amount(); src.rct = td.is_rct(); //paste mixin transaction @@ -3096,6 +3257,12 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry ptx.selected_transfers = selected_transfers; ptx.tx_key = tx_key; ptx.dests = dsts; + ptx.construction_data.sources = sources; + ptx.construction_data.destinations = dsts; + ptx.construction_data.change_dts = change_dts; + ptx.construction_data.extra = tx.extra; + ptx.construction_data.unlock_time = unlock_time; + ptx.construction_data.use_rct = true; } static size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs) @@ -3225,7 +3392,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp uint64_t needed_money; uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { - std::list<transfer_container::iterator> selected_transfers; + std::list<size_t> selected_transfers; std::vector<cryptonote::tx_destination_entry> dsts; cryptonote::transaction tx; pending_tx ptx; @@ -3332,7 +3499,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount())); // add this output to the list to spend - tx.selected_transfers.push_back(m_transfers.begin() + idx); + tx.selected_transfers.push_back(idx); uint64_t available_amount = td.amount(); accumulated_outputs += available_amount; @@ -3400,7 +3567,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); available_for_fee = test_ptx.fee + test_ptx.change_dts.amount + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0); - LOG_PRINT_L2("Made a " << txBlob.size() << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << + LOG_PRINT_L2("Made a " << ((txBlob.size() + 1023) / 1024) << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << print_money(needed_fee) << " needed)"); if (needed_fee > available_for_fee && dsts[0].amount > 0) @@ -3472,8 +3639,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp { TX &tx = *i; uint64_t tx_money = 0; - for (std::list<transfer_container::iterator>::const_iterator mi = tx.selected_transfers.begin(); mi != tx.selected_transfers.end(); ++mi) - tx_money += (*mi)->amount(); + for (size_t idx: tx.selected_transfers) + tx_money += m_transfers[idx].amount(); LOG_PRINT_L1(" Transaction " << (1+std::distance(txes.begin(), i)) << "/" << txes.size() << ": " << (tx.bytes+1023)/1024 << " kB, sending " << print_money(tx_money) << " in " << tx.selected_transfers.size() << " outputs to " << tx.dsts.size() << " destination(s), including " << @@ -3491,7 +3658,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono std::vector<size_t> unused_dust_indices; uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { - std::list<transfer_container::iterator> selected_transfers; + std::list<size_t> selected_transfers; std::vector<cryptonote::tx_destination_entry> dsts; cryptonote::transaction tx; pending_tx ptx; @@ -3543,7 +3710,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount())); // add this output to the list to spend - tx.selected_transfers.push_back(m_transfers.begin() + idx); + tx.selected_transfers.push_back(idx); uint64_t available_amount = td.amount(); accumulated_outputs += available_amount; @@ -3576,7 +3743,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); available_for_fee = test_ptx.fee + test_ptx.dests[0].amount + test_ptx.change_dts.amount; - LOG_PRINT_L2("Made a " << txBlob.size() << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << + LOG_PRINT_L2("Made a " << ((txBlob.size() + 1023) / 1024) << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << print_money(needed_fee) << " needed)"); THROW_WALLET_EXCEPTION_IF(needed_fee > available_for_fee, error::wallet_internal_error, "Transaction cannot pay for itself"); @@ -3620,8 +3787,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono { TX &tx = *i; uint64_t tx_money = 0; - for (std::list<transfer_container::iterator>::const_iterator mi = tx.selected_transfers.begin(); mi != tx.selected_transfers.end(); ++mi) - tx_money += (*mi)->amount(); + for (size_t idx: tx.selected_transfers) + tx_money += m_transfers[idx].amount(); LOG_PRINT_L1(" Transaction " << (1+std::distance(txes.begin(), i)) << "/" << txes.size() << ": " << (tx.bytes+1023)/1024 << " kB, sending " << print_money(tx_money) << " in " << tx.selected_transfers.size() << " outputs to " << tx.dsts.size() << " destination(s), including " << @@ -3658,14 +3825,14 @@ void wallet2::transfer_from(const std::vector<size_t> &outs, size_t num_outputs, // select all dust inputs for transaction // throw if there are none uint64_t money = 0; - std::list<transfer_container::iterator> selected_transfers; + std::list<size_t> selected_transfers; #if 1 for (size_t n = 0; n < outs.size(); ++n) { const transfer_details& td = m_transfers[outs[n]]; if (!td.m_spent) { - selected_transfers.push_back (m_transfers.begin() + outs[n]); + selected_transfers.push_back (outs[n]); money += td.amount(); if (selected_transfers.size() >= num_outputs) break; @@ -3693,11 +3860,11 @@ void wallet2::transfer_from(const std::vector<size_t> &outs, size_t num_outputs, //prepare inputs size_t i = 0; std::vector<cryptonote::tx_source_entry> sources; - BOOST_FOREACH(transfer_container::iterator it, selected_transfers) + BOOST_FOREACH(size_t idx, selected_transfers) { sources.resize(sources.size()+1); cryptonote::tx_source_entry& src = sources.back(); - transfer_details& td = *it; + const transfer_details& td = m_transfers[idx]; src.amount = td.amount(); src.rct = td.is_rct(); @@ -3754,6 +3921,12 @@ void wallet2::transfer_from(const std::vector<size_t> &outs, size_t num_outputs, ptx.selected_transfers = selected_transfers; ptx.tx_key = tx_key; ptx.dests = dsts; + ptx.construction_data.sources = sources; + ptx.construction_data.destinations = dsts; + ptx.construction_data.change_dts = change_dts; + ptx.construction_data.extra = tx.extra; + ptx.construction_data.unlock_time = unlock_time; + ptx.construction_data.use_rct = false; } //---------------------------------------------------------------------------------------------------- @@ -3906,13 +4079,15 @@ uint64_t wallet2::get_num_rct_outputs() std::vector<size_t> wallet2::select_available_unmixable_outputs(bool trusted_daemon) { // request all outputs with less than 3 instances - return select_available_outputs_from_histogram(3, false, true, trusted_daemon); + const size_t min_mixin = use_fork_rules(5, 10) ? 2 : 4; // v5 increases min mixin from 2 to 4 + return select_available_outputs_from_histogram(min_mixin + 1, false, true, trusted_daemon); } //---------------------------------------------------------------------------------------------------- std::vector<size_t> wallet2::select_available_mixable_outputs(bool trusted_daemon) { // request all outputs with at least 3 instances, so we can use mixin 2 with - return select_available_outputs_from_histogram(3, true, true, trusted_daemon); + const size_t min_mixin = use_fork_rules(5, 10) ? 2 : 4; // v5 increases min mixin from 2 to 4 + return select_available_outputs_from_histogram(min_mixin + 1, true, true, trusted_daemon); } //---------------------------------------------------------------------------------------------------- std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bool trusted_daemon) @@ -3969,9 +4144,9 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo ptx_vector.push_back(ptx); // mark transfers to be used as "spent" - BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers) + BOOST_FOREACH(size_t idx, ptx.selected_transfers) { - set_spent(*it, 0); + set_spent(idx, 0); } } @@ -3981,9 +4156,9 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo for (auto & ptx : ptx_vector) { // mark transfers to be used as not spent - BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) + BOOST_FOREACH(size_t idx2, ptx.selected_transfers) { - set_unspent(*it2); + set_unspent(idx2); } } @@ -3999,9 +4174,9 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo for (auto & ptx : ptx_vector) { // mark transfers to be used as not spent - BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) + BOOST_FOREACH(size_t idx2, ptx.selected_transfers) { - set_unspent(*it2); + set_unspent(idx2); } } @@ -4018,9 +4193,9 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo for (auto & ptx : ptx_vector) { // mark transfers to be used as not spent - BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) + BOOST_FOREACH(size_t idx2, ptx.selected_transfers) { - set_unspent(*it2); + set_unspent(idx2); } } @@ -4053,6 +4228,38 @@ std::string wallet2::get_daemon_address() const return m_daemon_address; } +uint64_t wallet2::get_daemon_blockchain_height(string &err) +{ + // XXX: DRY violation. copy-pasted from simplewallet.cpp:get_daemon_blockchain_height() + // consider to move it from simplewallet to wallet2 ? + COMMAND_RPC_GET_HEIGHT::request req; + COMMAND_RPC_GET_HEIGHT::response res = boost::value_initialized<COMMAND_RPC_GET_HEIGHT::response>(); + m_daemon_rpc_mutex.lock(); + bool ok = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/getheight", req, res, m_http_client); + m_daemon_rpc_mutex.unlock(); + // XXX: DRY violation. copy-pasted from simplewallet.cpp:interpret_rpc_response() + if (ok) + { + if (res.status == CORE_RPC_STATUS_BUSY) + { + err = "daemon is busy. Please try again later."; + } + else if (res.status != CORE_RPC_STATUS_OK) + { + err = res.status; + } + else // success, cleaning up error message + { + err = ""; + } + } + else + { + err = "possibly lost connection to daemon"; + } + return res.height; +} + void wallet2::set_tx_note(const crypto::hash &txid, const std::string ¬e) { m_tx_notes[txid] = note; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 89b613d34..c1c8edcd8 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -151,6 +151,25 @@ namespace tools m_amount_in(utd.m_amount_in), m_amount_out(utd.m_amount_out), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp) {} }; + struct tx_construction_data + { + std::vector<cryptonote::tx_source_entry> sources; + std::vector<cryptonote::tx_destination_entry> destinations; + cryptonote::tx_destination_entry change_dts; + std::vector<uint8_t> extra; + uint64_t unlock_time; + bool use_rct; + + BEGIN_SERIALIZE_OBJECT() + FIELD(sources) + FIELD(destinations) + FIELD(change_dts) + FIELD(extra) + VARINT_FIELD(unlock_time) + FIELD(use_rct) + END_SERIALIZE() + }; + typedef std::vector<transfer_details> transfer_container; typedef std::unordered_multimap<crypto::hash, payment_details> payment_container; @@ -160,10 +179,41 @@ namespace tools uint64_t dust, fee; bool dust_added_to_fee; cryptonote::tx_destination_entry change_dts; - std::list<transfer_container::iterator> selected_transfers; + std::list<size_t> selected_transfers; std::string key_images; crypto::secret_key tx_key; std::vector<cryptonote::tx_destination_entry> dests; + + tx_construction_data construction_data; + + BEGIN_SERIALIZE_OBJECT() + FIELD(tx) + VARINT_FIELD(dust) + VARINT_FIELD(fee) + FIELD(dust_added_to_fee) + FIELD(change_dts) + FIELD(selected_transfers) + FIELD(key_images) + FIELD(tx_key) + FIELD(dests) + FIELD(construction_data) + END_SERIALIZE() + }; + + struct unsigned_tx_set + { + std::vector<tx_construction_data> txes; + BEGIN_SERIALIZE_OBJECT() + FIELD(txes) + END_SERIALIZE() + }; + + struct signed_tx_set + { + std::vector<pending_tx> ptx; + BEGIN_SERIALIZE_OBJECT() + FIELD(ptx) + END_SERIALIZE() }; struct keys_file_data @@ -298,13 +348,16 @@ namespace tools template<typename T> void transfer_from(const std::vector<size_t> &outs, size_t num_outputs, uint64_t unlock_time, uint64_t needed_fee, T destination_split_strategy, const tx_dust_policy& dust_policy, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx); template<typename T> - void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, + void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx); - void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, + void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx); void commit_tx(pending_tx& ptx_vector); void commit_tx(std::vector<pending_tx>& ptx_vector); + bool save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename); + bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::function<bool(const unsigned_tx_set&)> accept_func = NULL); + bool load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx); std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_all(const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); @@ -400,14 +453,15 @@ namespace tools std::string get_wallet_file() const; std::string get_keys_file() const; std::string get_daemon_address() const; + uint64_t get_daemon_blockchain_height(std::string& err); std::vector<size_t> select_available_outputs_from_histogram(uint64_t count, bool atleast, bool unlocked, bool trusted_daemon); std::vector<size_t> select_available_outputs(const std::function<bool(const transfer_details &td)> &f); std::vector<size_t> select_available_unmixable_outputs(bool trusted_daemon); std::vector<size_t> select_available_mixable_outputs(bool trusted_daemon); - size_t pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_dust_indices, const std::list<transfer_container::iterator>& selected_transfers) const; - size_t pop_best_value(std::vector<size_t> &unused_dust_indices, const std::list<transfer_container::iterator>& selected_transfers) const; + size_t pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_dust_indices, const std::list<size_t>& selected_transfers) const; + size_t pop_best_value(std::vector<size_t> &unused_dust_indices, const std::list<size_t>& selected_transfers) const; void set_tx_note(const crypto::hash &txid, const std::string ¬e); std::string get_tx_note(const crypto::hash &txid) const; @@ -445,7 +499,7 @@ namespace tools void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history); void pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::list<cryptonote::block_complete_entry> &prev_blocks, std::list<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, bool &error); void process_blocks(uint64_t start_height, const std::list<cryptonote::block_complete_entry> &blocks, const std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t& blocks_added); - uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<transfer_container::iterator>& selected_transfers, bool trusted_daemon); + uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<size_t>& selected_transfers, bool trusted_daemon); bool prepare_file_names(const std::string& file_path); void process_unconfirmed(const cryptonote::transaction& tx, uint64_t height); void process_outgoing(const cryptonote::transaction& tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received); @@ -461,10 +515,10 @@ namespace tools uint64_t get_fee_multiplier(uint32_t priority, bool use_new_fee) const; float get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const; std::vector<size_t> pick_prefered_rct_inputs(uint64_t needed_money) const; - void set_spent(transfer_details &td, uint64_t height); - void set_unspent(transfer_details &td); + void set_spent(size_t idx, uint64_t height); + void set_unspent(size_t idx); template<typename entry> - void get_outs(std::vector<std::vector<entry>> &outs, const std::list<transfer_container::iterator> &selected_transfers, size_t fake_outputs_count); + void get_outs(std::vector<std::vector<entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count); cryptonote::account_base m_account; std::string m_daemon_address; @@ -736,7 +790,7 @@ namespace tools // randomly select inputs for transaction // throw if requested send amount is greater than amount available to send - std::list<transfer_container::iterator> selected_transfers; + std::list<size_t> selected_transfers; uint64_t found_money = select_transfers(needed_money, unused_transfers_indices, selected_transfers, trusted_daemon); THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee); @@ -748,8 +802,9 @@ namespace tools { COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request req = AUTO_VAL_INIT(req); req.outs_count = fake_outputs_count + 1;// add one to make possible (if need) to skip real output key - BOOST_FOREACH(transfer_container::iterator it, selected_transfers) + BOOST_FOREACH(size_t idx, selected_transfers) { + const transfer_container::const_iterator it = m_transfers.begin() + idx; THROW_WALLET_EXCEPTION_IF(it->m_tx.vout.size() <= it->m_internal_output_index, error::wallet_internal_error, "m_internal_output_index = " + std::to_string(it->m_internal_output_index) + " is greater or equal to outputs count = " + std::to_string(it->m_tx.vout.size())); @@ -780,11 +835,11 @@ namespace tools //prepare inputs size_t i = 0; std::vector<cryptonote::tx_source_entry> sources; - BOOST_FOREACH(transfer_container::iterator it, selected_transfers) + BOOST_FOREACH(size_t idx, selected_transfers) { sources.resize(sources.size()+1); cryptonote::tx_source_entry& src = sources.back(); - transfer_details& td = *it; + const transfer_details& td = m_transfers[idx]; src.amount = td.amount(); src.rct = false; //paste mixin transaction @@ -871,6 +926,12 @@ namespace tools ptx.selected_transfers = selected_transfers; ptx.tx_key = tx_key; ptx.dests = dsts; + ptx.construction_data.sources = sources; + ptx.construction_data.destinations = dsts; + ptx.construction_data.change_dts = change_dts; + ptx.construction_data.extra = tx.extra; + ptx.construction_data.unlock_time = unlock_time; + ptx.construction_data.use_rct = false; } diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h index 2d2877856..08e2ae16b 100644 --- a/src/wallet/wallet2_api.h +++ b/src/wallet/wallet2_api.h @@ -114,11 +114,35 @@ struct TransactionHistory struct WalletListener { virtual ~WalletListener() = 0; + /** + * @brief moneySpent - called when money spent + * @param txId - transaction id + * @param amount - amount + */ virtual void moneySpent(const std::string &txId, uint64_t amount) = 0; + + /** + * @brief moneyReceived - called when money received + * @param txId - transaction id + * @param amount - amount + */ virtual void moneyReceived(const std::string &txId, uint64_t amount) = 0; - // generic callback, called when any event (sent/received/block reveived/etc) happened with the wallet; + + /** + * @brief newBlock - called when new block received + * @param height - block height + */ + virtual void newBlock(uint64_t height) = 0; + + /** + * @brief updated - generic callback, called when any event (sent/received/block reveived/etc) happened with the wallet; + */ virtual void updated() = 0; - // called when wallet refreshed by background thread or explicitly called be calling "refresh" synchronously + + + /** + * @brief refreshed - called when wallet refreshed by background thread or explicitly refreshed by calling "refresh" synchronously + */ virtual void refreshed() = 0; }; @@ -211,6 +235,20 @@ struct Wallet virtual uint64_t balance() const = 0; virtual uint64_t unlockedBalance() const = 0; + /** + * @brief blockChainHeight - returns current blockchain height + * @return + */ + virtual uint64_t blockChainHeight() const = 0; + + /** + * @brief daemonBlockChainHeight - returns daemon blockchain height + * @return 0 - in case error communicating with the daemon. + * status() will return Status_Error and errorString() will return verbose error description + */ + virtual uint64_t daemonBlockChainHeight() const = 0; + + static std::string displayAmount(uint64_t amount); static uint64_t amountFromString(const std::string &amount); static uint64_t amountFromDouble(double amount); @@ -231,12 +269,12 @@ struct Wallet /** * @brief setAutoRefreshInterval - setup interval for automatic refresh. - * @param seconds - interval in seconds. if zero or less than zero - automatic refresh disabled; + * @param seconds - interval in millis. if zero or less than zero - automatic refresh disabled; */ - virtual void setAutoRefreshInterval(int seconds) = 0; + virtual void setAutoRefreshInterval(int millis) = 0; /** - * @brief autoRefreshInterval - returns automatic refresh interval in seconds + * @brief autoRefreshInterval - returns automatic refresh interval in millis * @return */ virtual int autoRefreshInterval() const = 0; diff --git a/tests/core_proxy/core_proxy.h b/tests/core_proxy/core_proxy.h index 0f6d6571e..995b0c650 100644 --- a/tests/core_proxy/core_proxy.h +++ b/tests/core_proxy/core_proxy.h @@ -87,5 +87,6 @@ namespace tests bool prepare_handle_incoming_blocks(const std::list<cryptonote::block_complete_entry> &blocks) { return true; } bool cleanup_handle_incoming_blocks(bool force_sync = false) { return true; } uint64_t get_target_blockchain_height() const { return 1; } + size_t get_block_sync_size() const { return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; } }; } diff --git a/tests/libwallet_api_tests/main.cpp b/tests/libwallet_api_tests/main.cpp index 94f374285..45a94b4dd 100644 --- a/tests/libwallet_api_tests/main.cpp +++ b/tests/libwallet_api_tests/main.cpp @@ -58,6 +58,7 @@ namespace Consts // TODO: get rid of hardcoded paths const char * WALLET_NAME = "testwallet"; +const char * WALLET_NAME_MAINNET = "testwallet_mainnet"; const char * WALLET_NAME_COPY = "testwallet_copy"; const char * WALLET_NAME_WITH_DIR = "walletdir/testwallet_test"; const char * WALLET_NAME_WITH_DIR_NON_WRITABLE = "/var/walletdir/testwallet_test"; @@ -85,6 +86,7 @@ const uint64_t AMOUNT_1XMR = 1000000000000L; const std::string PAYMENT_ID_EMPTY = ""; std::string TESTNET_DAEMON_ADDRESS = "localhost:38081"; +std::string MAINNET_DAEMON_ADDRESS = "localhost:18081"; } @@ -157,6 +159,25 @@ struct WalletManagerTest : public testing::Test }; +struct WalletManagerMainnetTest : public testing::Test +{ + Bitmonero::WalletManager * wmgr; + + + WalletManagerMainnetTest() + { + std::cout << __FUNCTION__ << std::endl; + wmgr = Bitmonero::WalletManagerFactory::getWalletManager(); + Utils::deleteWallet(WALLET_NAME_MAINNET); + } + + + ~WalletManagerMainnetTest() + { + std::cout << __FUNCTION__ << std::endl; + } + +}; struct WalletTest1 : public testing::Test { @@ -473,8 +494,6 @@ TEST_F(WalletTest1, WalletGeneratesIntegratedAddress) TEST_F(WalletTest1, WalletShowsBalance) { - // TODO: temporary disabled; - return; Bitmonero::Wallet * wallet1 = wmgr->openWallet(CURRENT_SRC_WALLET, TESTNET_WALLET_PASS, true); ASSERT_TRUE(wallet1->balance() > 0); ASSERT_TRUE(wallet1->unlockedBalance() > 0); @@ -491,6 +510,32 @@ TEST_F(WalletTest1, WalletShowsBalance) ASSERT_TRUE(wmgr->closeWallet(wallet2)); } +TEST_F(WalletTest1, WalletReturnsCurrentBlockHeight) +{ + Bitmonero::Wallet * wallet1 = wmgr->openWallet(CURRENT_SRC_WALLET, TESTNET_WALLET_PASS, true); + ASSERT_TRUE(wallet1->blockChainHeight() > 0); + wmgr->closeWallet(wallet1); +} + + +TEST_F(WalletTest1, WalletReturnsDaemonBlockHeight) +{ + Bitmonero::Wallet * wallet1 = wmgr->openWallet(CURRENT_SRC_WALLET, TESTNET_WALLET_PASS, true); + // wallet not connected to daemon + ASSERT_TRUE(wallet1->daemonBlockChainHeight() == 0); + ASSERT_TRUE(wallet1->status() != Bitmonero::Wallet::Status_Ok); + ASSERT_FALSE(wallet1->errorString().empty()); + wmgr->closeWallet(wallet1); + + wallet1 = wmgr->openWallet(CURRENT_SRC_WALLET, TESTNET_WALLET_PASS, true); + // wallet connected to daemon + wallet1->init(TESTNET_DAEMON_ADDRESS, 0); + ASSERT_TRUE(wallet1->daemonBlockChainHeight() > 0); + std::cout << "daemonBlockChainHeight: " << wallet1->daemonBlockChainHeight() << std::endl; + wmgr->closeWallet(wallet1); +} + + TEST_F(WalletTest1, WalletRefresh) { @@ -742,8 +787,10 @@ struct MyWalletListener : public Bitmonero::WalletListener std::condition_variable cv_receive; std::condition_variable cv_update; std::condition_variable cv_refresh; + std::condition_variable cv_newblock; bool send_triggered; bool receive_triggered; + bool newblock_triggered; bool update_triggered; bool refresh_triggered; @@ -781,6 +828,18 @@ struct MyWalletListener : public Bitmonero::WalletListener cv_receive.notify_one(); } + virtual void newBlock(uint64_t height) + { +// std::cout << "wallet: " << wallet->address() +// <<", new block received, blockHeight: " << height << std::endl; + static int bc_height = wallet->daemonBlockChainHeight(); + std::cout << height + << " / " << bc_height/* 0*/ + << std::endl; + newblock_triggered = true; + cv_newblock.notify_one(); + } + virtual void updated() { std::cout << __FUNCTION__ << "Wallet updated"; @@ -837,6 +896,7 @@ TEST_F(WalletTest2, WalletCallBackRefreshedAsync) + TEST_F(WalletTest2, WalletCallbackSent) { @@ -879,17 +939,18 @@ TEST_F(WalletTest2, WalletCallbackSent) TEST_F(WalletTest2, WalletCallbackReceived) { - Bitmonero::Wallet * wallet_src = wmgr->openWallet(TESTNET_WALLET5_NAME, TESTNET_WALLET_PASS, true); + Bitmonero::Wallet * wallet_src = wmgr->openWallet(CURRENT_SRC_WALLET, TESTNET_WALLET_PASS, true); // make sure testnet daemon is running ASSERT_TRUE(wallet_src->init(TESTNET_DAEMON_ADDRESS, 0)); ASSERT_TRUE(wallet_src->refresh()); - std::cout << "** Balance: " << wallet_src->displayAmount(wallet_src->balance()) << std::endl; + std::cout << "** Balance src1: " << wallet_src->displayAmount(wallet_src->balance()) << std::endl; Bitmonero::Wallet * wallet_dst = wmgr->openWallet(CURRENT_DST_WALLET, TESTNET_WALLET_PASS, true); ASSERT_TRUE(wallet_dst->init(TESTNET_DAEMON_ADDRESS, 0)); ASSERT_TRUE(wallet_dst->refresh()); uint64_t balance = wallet_dst->balance(); - MyWalletListener * wallet_dst_listener = new MyWalletListener(wallet_dst); + std::cout << "** Balance dst1: " << wallet_dst->displayAmount(wallet_dst->balance()) << std::endl; + std::unique_ptr<MyWalletListener> wallet_dst_listener (new MyWalletListener(wallet_dst)); uint64_t amount = AMOUNT_1XMR * 5; std::cout << "** Sending " << Bitmonero::Wallet::displayAmount(amount) << " to " << wallet_dst->address(); @@ -910,7 +971,10 @@ TEST_F(WalletTest2, WalletCallbackReceived) std::cerr << "TEST: receive lock acquired...\n"; ASSERT_TRUE(wallet_dst_listener->receive_triggered); ASSERT_TRUE(wallet_dst_listener->update_triggered); - std::cout << "** Balance: " << wallet_dst->displayAmount(wallet_src->balance()) << std::endl; + + std::cout << "** Balance src2: " << wallet_dst->displayAmount(wallet_src->balance()) << std::endl; + std::cout << "** Balance dst2: " << wallet_dst->displayAmount(wallet_dst->balance()) << std::endl; + ASSERT_TRUE(wallet_dst->balance() > balance); wmgr->closeWallet(wallet_src); @@ -918,15 +982,157 @@ TEST_F(WalletTest2, WalletCallbackReceived) } + +TEST_F(WalletTest2, WalletCallbackNewBlock) +{ + + Bitmonero::Wallet * wallet_src = wmgr->openWallet(TESTNET_WALLET5_NAME, TESTNET_WALLET_PASS, true); + // make sure testnet daemon is running + ASSERT_TRUE(wallet_src->init(TESTNET_DAEMON_ADDRESS, 0)); + ASSERT_TRUE(wallet_src->refresh()); + uint64_t bc1 = wallet_src->blockChainHeight(); + std::cout << "** Block height: " << bc1 << std::endl; + + + std::unique_ptr<MyWalletListener> wallet_listener (new MyWalletListener(wallet_src)); + + // wait max 4 min for new block + std::chrono::seconds wait_for = std::chrono::seconds(60*4); + std::unique_lock<std::mutex> lock (wallet_listener->mutex); + std::cerr << "TEST: waiting on newblock lock...\n"; + wallet_listener->cv_newblock.wait_for(lock, wait_for); + std::cerr << "TEST: newblock lock acquired...\n"; + ASSERT_TRUE(wallet_listener->newblock_triggered); + uint64_t bc2 = wallet_src->blockChainHeight(); + std::cout << "** Block height: " << bc2 << std::endl; + ASSERT_TRUE(bc2 > bc1); + wmgr->closeWallet(wallet_src); + +} + +TEST_F(WalletManagerMainnetTest, CreateOpenAndRefreshWalletMainNetSync) +{ + + Bitmonero::Wallet * wallet = wmgr->createWallet(WALLET_NAME_MAINNET, "", WALLET_LANG); + std::unique_ptr<MyWalletListener> wallet_listener (new MyWalletListener(wallet)); + wallet->init(MAINNET_DAEMON_ADDRESS, 0); + std::cerr << "TEST: waiting on refresh lock...\n"; + //wallet_listener->cv_refresh.wait_for(lock, wait_for); + std::cerr << "TEST: refresh lock acquired...\n"; + ASSERT_TRUE(wallet_listener->refresh_triggered); + ASSERT_TRUE(wallet->connected()); + ASSERT_TRUE(wallet->blockChainHeight() == wallet->daemonBlockChainHeight()); + std::cerr << "TEST: closing wallet...\n"; + wmgr->closeWallet(wallet); +} + + +TEST_F(WalletManagerMainnetTest, CreateAndRefreshWalletMainNetAsync) +{ + // supposing 120 seconds should be enough for fast refresh + int SECONDS_TO_REFRESH = 120; + + Bitmonero::Wallet * wallet = wmgr->createWallet(WALLET_NAME_MAINNET, "", WALLET_LANG); + std::unique_ptr<MyWalletListener> wallet_listener (new MyWalletListener(wallet)); + + std::chrono::seconds wait_for = std::chrono::seconds(SECONDS_TO_REFRESH); + std::unique_lock<std::mutex> lock (wallet_listener->mutex); + wallet->initAsync(MAINNET_DAEMON_ADDRESS, 0); + // wallet->init(MAINNET_DAEMON_ADDRESS, 0); + std::cerr << "TEST: waiting on refresh lock...\n"; + wallet_listener->cv_refresh.wait_for(lock, wait_for); + std::cerr << "TEST: refresh lock acquired...\n"; + ASSERT_TRUE(wallet->status() == Bitmonero::Wallet::Status_Ok); + ASSERT_TRUE(wallet_listener->refresh_triggered); + ASSERT_TRUE(wallet->connected()); + ASSERT_TRUE(wallet->blockChainHeight() == wallet->daemonBlockChainHeight()); + std::cerr << "TEST: closing wallet...\n"; + wmgr->closeWallet(wallet); +} + +TEST_F(WalletManagerMainnetTest, OpenAndRefreshWalletMainNetAsync) +{ + + // supposing 120 seconds should be enough for fast refresh + int SECONDS_TO_REFRESH = 120; + Bitmonero::Wallet * wallet = wmgr->createWallet(WALLET_NAME_MAINNET, "", WALLET_LANG); + wmgr->closeWallet(wallet); + wallet = wmgr->openWallet(WALLET_NAME_MAINNET, ""); + + std::unique_ptr<MyWalletListener> wallet_listener (new MyWalletListener(wallet)); + + std::chrono::seconds wait_for = std::chrono::seconds(SECONDS_TO_REFRESH); + std::unique_lock<std::mutex> lock (wallet_listener->mutex); + wallet->initAsync(MAINNET_DAEMON_ADDRESS, 0); + // wallet->init(MAINNET_DAEMON_ADDRESS, 0); + std::cerr << "TEST: waiting on refresh lock...\n"; + wallet_listener->cv_refresh.wait_for(lock, wait_for); + std::cerr << "TEST: refresh lock acquired...\n"; + ASSERT_TRUE(wallet->status() == Bitmonero::Wallet::Status_Ok); + ASSERT_TRUE(wallet_listener->refresh_triggered); + ASSERT_TRUE(wallet->connected()); + ASSERT_TRUE(wallet->blockChainHeight() == wallet->daemonBlockChainHeight()); + std::cerr << "TEST: closing wallet...\n"; + wmgr->closeWallet(wallet); + +} + +TEST_F(WalletManagerMainnetTest, RecoverAndRefreshWalletMainNetAsync) +{ + + // supposing 120 seconds should be enough for fast refresh + int SECONDS_TO_REFRESH = 120; + Bitmonero::Wallet * wallet = wmgr->createWallet(WALLET_NAME_MAINNET, "", WALLET_LANG); + std::string seed = wallet->seed(); + std::string address = wallet->address(); + wmgr->closeWallet(wallet); + + // deleting wallet files + Utils::deleteWallet(WALLET_NAME_MAINNET); + // ..and recovering wallet from seed + + wallet = wmgr->recoveryWallet(WALLET_NAME_MAINNET, seed); + ASSERT_TRUE(wallet->status() == Bitmonero::Wallet::Status_Ok); + ASSERT_TRUE(wallet->address() == address); + std::unique_ptr<MyWalletListener> wallet_listener (new MyWalletListener(wallet)); + std::chrono::seconds wait_for = std::chrono::seconds(SECONDS_TO_REFRESH); + std::unique_lock<std::mutex> lock (wallet_listener->mutex); + wallet->initAsync(MAINNET_DAEMON_ADDRESS, 0); + // wallet->init(MAINNET_DAEMON_ADDRESS, 0); + std::cerr << "TEST: waiting on refresh lock...\n"; + + // here we wait for 120 seconds and test if wallet doesn't syncrnonize blockchain completely, + // as it needs much more than 120 seconds for mainnet + + wallet_listener->cv_refresh.wait_for(lock, wait_for); + ASSERT_TRUE(wallet->status() == Bitmonero::Wallet::Status_Ok); + ASSERT_FALSE(wallet_listener->refresh_triggered); + ASSERT_TRUE(wallet->connected()); + ASSERT_FALSE(wallet->blockChainHeight() == wallet->daemonBlockChainHeight()); + std::cerr << "TEST: closing wallet...\n"; + wmgr->closeWallet(wallet); + std::cerr << "TEST: wallet closed\n"; + +} + + + int main(int argc, char** argv) { // we can override default values for "TESTNET_DAEMON_ADDRESS" and "WALLETS_ROOT_DIR" - const char * monero_daemon_addr = std::getenv("TESTNET_DAEMON_ADDRESS"); - if (monero_daemon_addr) { - TESTNET_DAEMON_ADDRESS = monero_daemon_addr; + const char * testnet_daemon_addr = std::getenv("TESTNET_DAEMON_ADDRESS"); + if (testnet_daemon_addr) { + TESTNET_DAEMON_ADDRESS = testnet_daemon_addr; + } + + const char * mainnet_daemon_addr = std::getenv("MAINNET_DAEMON_ADDRESS"); + if (mainnet_daemon_addr) { + MAINNET_DAEMON_ADDRESS = mainnet_daemon_addr; } + + const char * wallets_root_dir = std::getenv("WALLETS_ROOT_DIR"); if (wallets_root_dir) { WALLETS_ROOT_DIR = wallets_root_dir; diff --git a/tests/libwallet_api_tests/scripts/send_funds.sh b/tests/libwallet_api_tests/scripts/send_funds.sh index b7f282b71..3ce923353 100755 --- a/tests/libwallet_api_tests/scripts/send_funds.sh +++ b/tests/libwallet_api_tests/scripts/send_funds.sh @@ -1,7 +1,5 @@ #!/bin/bash - - function send_funds { local amount=$1 local dest=$(cat "$2.address.txt") diff --git a/tests/unit_tests/ban.cpp b/tests/unit_tests/ban.cpp index a5ce244d9..85b0298b7 100644 --- a/tests/unit_tests/ban.cpp +++ b/tests/unit_tests/ban.cpp @@ -62,6 +62,7 @@ public: bool prepare_handle_incoming_blocks(const std::list<cryptonote::block_complete_entry> &blocks) { return true; } bool cleanup_handle_incoming_blocks(bool force_sync = false) { return true; } uint64_t get_target_blockchain_height() const { return 1; } + size_t get_block_sync_size() const { return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; } }; typedef nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<test_core>> Server; diff --git a/tests/unit_tests/output_selection.cpp b/tests/unit_tests/output_selection.cpp index 4344d1ffc..d26f5ae1a 100644 --- a/tests/unit_tests/output_selection.cpp +++ b/tests/unit_tests/output_selection.cpp @@ -56,14 +56,14 @@ static tools::wallet2::transfer_container make_transfers_container(size_t N) auto i = std::find(unused_indices.begin(), unused_indices.end(), idx); \ ASSERT_TRUE(i != unused_indices.end()); \ unused_indices.erase(i); \ - selected.push_back(transfers.begin() + idx); \ + selected.push_back(idx); \ } while(0) #define PICK(expected) \ do { \ size_t idx = w.pop_best_value_from(transfers, unused_indices, selected); \ ASSERT_EQ(expected, idx); \ - selected.push_back(transfers.begin() + idx); \ + selected.push_back(idx); \ } while(0) TEST(select_outputs, one_out_of_N) @@ -76,7 +76,7 @@ TEST(select_outputs, one_out_of_N) tools::wallet2::transfer_container transfers = make_transfers_container(10); transfers[6].m_block_height = 700; std::vector<size_t> unused_indices({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); - std::list<tools::wallet2::transfer_container::iterator> selected; + std::list<size_t> selected; SELECT(2); PICK(6); } @@ -93,7 +93,7 @@ TEST(select_outputs, order) transfers[3].m_block_height = 716; transfers[4].m_block_height = 701; std::vector<size_t> unused_indices({0, 1, 2, 3, 4}); - std::list<tools::wallet2::transfer_container::iterator> selected; + std::list<size_t> selected; SELECT(0); PICK(3); // first the one that's far away PICK(2); // then the one that's close diff --git a/translations/monero.ts b/translations/monero.ts index e245c168b..018b4f1f0 100644 --- a/translations/monero.ts +++ b/translations/monero.ts @@ -90,7 +90,7 @@ </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="355"/> - <source>transfer [<mixin_count>] <addr_1> <amount_1> [<addr_2> <amount_2> ... <addr_N> <amount_N>] [payment_id] - Transfer <amount_1>,... <amount_N> to <address_1>,... <address_N>, respectively. <mixin_count> is the number of transactions yours is indistinguishable from (from 0 to maximum available)</source> + <source>transfer [<mixin_count>] <addr_1> <amount_1> [<addr_2> <amount_2> ... <addr_N> <amount_N>] [payment_id] - Transfer <amount_1>,... <amount_N> to <address_1>,... <address_N>, respectively. <mixin_count> is the number of transactions yours is indistinguishable from (from 2 to maximum available)</source> <translation type="unfinished"></translation> </message> <message> diff --git a/utils/gpg_keys/luigi1111.asc b/utils/gpg_keys/luigi1111.asc new file mode 100644 index 000000000..4ac3de627 --- /dev/null +++ b/utils/gpg_keys/luigi1111.asc @@ -0,0 +1,30 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mQENBFdodc4BCAC02POyrMUol+nTXeZ2nCKM9G1Q2oU5jQLaQLNLUU88PHLdOVGd +nKh0QA4Uc4CA2wWU5zdIVFEzowUUNyKeSLJZhvOXbpN+pm6n9XiyNSZSDJhvMtAs +FEcyWyAjPAQT7MFS2SJgG7HhcoMAgr3ypp8FpAK2YwbfjfPqs5VFVdrYrVPfiSVJ +NTSoJMW+2oOUdTR/L6z8lzUZu0CH8aJK1Qqr0yyxbJelayPsos3aRQ9TQAu6u/zQ +PxURHaLTe8RsB6oa6wXnpZtXcl1xaigvlkNVpeHPbroBvhJoCbAFHC/edaBNBLiP +I3WCRGiCvvP2kIfuZlyv48gFX+E3NWSB10Q1ABEBAAG0IGx1aWdpMTExMSA8bHVp +Z2kxMTExd0BnbWFpbC5jb20+iQE4BBMBAgAiBQJXaHXOAhsDBgsJCAcDAgYVCAIJ +CgsEFgIDAQIeAQIXgAAKCRD0rKAYNkHgEAyoB/9agBv/xdNUgTVheW/O6kWG4DYG +/1mMfuTF1xi8CSFbUID3dRhnjXtncuSCaY7030CSLtnC/Pl8MnKHmcBZcoCiARzI +bNFK4dEdJjDU8yiqD+M0IAe58xCYQ7I/RX5TXJJ+WTS3lwi1zBqf9D4XVTQxSm+u +3tVH5mUu35pRWVCg1OSwWo1IGkSDkeGq8ySdYUJdM535caKf8L1ICNir1H7pWNCa +Jya/b3PRtSefw4tNHcsFtndl4S52iMdXwNUdwRKTP/hDa9KYxQqTGDtJ6nKjTy6p +dgZxAiF2mL9dVYg6c+GHlrtZMUZd5NQZcIwu9jXzAiQKZMmtL6/RauzXRuz9uQEN +BFdodc4BCADACy78WJBKCR4MrUcy4L0G4P5n+cqao2Lf+oc53xJudhcgufowsvzs +nixKG9HC+6SoZCZwKYfglu0JxjvqpZC5U9nCwIWuf52Qu4pDO4xYTeuhVr+Lcjvg +tqtJf5M9QaVJwAz8jTKaEAWUSXuKnljVC76zU8LTprYrTEdOyHO9E6Z94MrBUwS3 +6IVNlJ+q5wZyoj9noWbm7X0SIRER9/shr/UlfkSx0Kpnyj3ludkdg7TQR7jp9rGy +GqRmOKH3eGv2aFO1fo+RDacn+R9Fh1vHIdUX6FZil+yft9lXg5jtxcNXvhIJtsoC +NuVIWEYaQuzSFqtblTrM6IwJ8HrimEBPABEBAAGJAR8EGAECAAkFAldodc4CGwwA +CgkQ9KygGDZB4BBnEAf+OSvWLNJs0VtKRIO5oHP7Ia6oS8v0IyXFLx2/p/JW7jMR +3LVor2wvao52omW+JzoIj4J8cnaEEt2ptHpmzZApSQydZhHO+108hPDYbeWPrXa8 +aSXCtI++s2occAemsMvgrnzI7yuMFaAwOWOYL5bmR5pJnnuQQUj1vfXkqBEYYFyF +u7dvJ3hLHLbxBFvIbDBMfzev+g6wOGkhT2/7osUACoLwTwzkbXZNSoQHiZ72YA/P +9f3SA5Zc5hGopft96sAAnzk6sm/xCfFO1uj6Yk/H/fd/ZjVzQW+XBUvwM/jIqpuX +Ftwy4Ulp0YZT5BpXkHDhosL6o4GelJULMbxreh7nNQ== +=j0V6 +-----END PGP PUBLIC KEY BLOCK-----
\ No newline at end of file |