diff options
Diffstat (limited to 'src/wallet/wallet2.cpp')
-rw-r--r-- | src/wallet/wallet2.cpp | 490 |
1 files changed, 418 insertions, 72 deletions
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 2af41f588..f83aad63d 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -223,7 +223,7 @@ boost::optional<tools::password_container> get_password(const boost::program_opt THROW_WALLET_EXCEPTION_IF(!password_prompter, tools::error::wallet_internal_error, tools::wallet2::tr("no password specified; use --prompt-for-password to prompt for a password")); - return password_prompter(verify ? tr("Enter new wallet password") : tr("Wallet password"), verify); + return password_prompter(verify ? tr("Enter a new password for the wallet") : tr("Wallet password"), verify); } std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, const boost::program_options::variables_map& vm, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) @@ -357,7 +357,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, const bool deprecated_wallet = restore_deterministic_wallet && ((old_language == crypto::ElectrumWords::old_language_name) || crypto::ElectrumWords::get_is_old_style_seed(field_seed)); THROW_WALLET_EXCEPTION_IF(deprecated_wallet, tools::error::wallet_internal_error, - tools::wallet2::tr("Cannot create deprecated wallets from JSON")); + tools::wallet2::tr("Cannot generate deprecated wallets from JSON")); wallet.reset(make_basic(vm, opts, password_prompter).release()); wallet->set_refresh_from_block_height(field_scan_from_height); @@ -532,20 +532,16 @@ size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, si return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES + extra_size; } -uint8_t get_bulletproof_fork(bool testnet) +uint8_t get_bulletproof_fork() { - if (testnet) - return 7; - else - return 255; // TODO + return 8; } crypto::hash8 get_short_payment_id(const tools::wallet2::pending_tx &ptx) { crypto::hash8 payment_id8 = null_hash8; std::vector<tx_extra_field> tx_extra_fields; - if(!parse_tx_extra(ptx.tx.extra, tx_extra_fields)) - return payment_id8; + parse_tx_extra(ptx.tx.extra, tx_extra_fields); // ok if partially parsed cryptonote::tx_extra_nonce extra_nonce; if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) { @@ -612,6 +608,8 @@ wallet2::wallet2(bool testnet, bool restricted): m_merge_destinations(false), m_confirm_backlog(true), m_confirm_backlog_threshold(0), + m_confirm_export_overwrite(true), + m_auto_low_priority(true), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), @@ -1011,7 +1009,7 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation & } } //---------------------------------------------------------------------------------------------------- -void wallet2::scan_output(const cryptonote::account_keys &keys, const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs) +void wallet2::scan_output(const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs) const { THROW_WALLET_EXCEPTION_IF(i >= tx.vout.size(), error::wallet_internal_error, "Invalid vout index"); if (m_multisig) @@ -1022,7 +1020,7 @@ void wallet2::scan_output(const cryptonote::account_keys &keys, const cryptonote } else { - bool r = cryptonote::generate_key_image_helper_precomp(keys, boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki); + bool r = cryptonote::generate_key_image_helper_precomp(m_account.get_keys(), boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); @@ -1127,7 +1125,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote THROW_WALLET_EXCEPTION_IF(tx_scan_info[i].error, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); if (tx_scan_info[i].received) { - scan_output(keys, tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); + scan_output(tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); } } } @@ -1145,7 +1143,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote THROW_WALLET_EXCEPTION_IF(tx_scan_info[i].error, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); if (tx_scan_info[i].received) { - scan_output(keys, tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); + scan_output(tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); } } } @@ -1157,7 +1155,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote THROW_WALLET_EXCEPTION_IF(tx_scan_info[i].error, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); if (tx_scan_info[i].received) { - scan_output(keys, tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); + scan_output(tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); } } } @@ -1332,6 +1330,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } } + uint64_t fee = miner_tx ? 0 : tx.version == 1 ? tx_money_spent_in_ins - get_outs_money_amount(tx) : tx.rct_signatures.txnFee; + if (tx_money_spent_in_ins > 0 && !pool) { uint64_t self_received = std::accumulate<decltype(tx_money_got_in_outs.begin()), uint64_t>(tx_money_got_in_outs.begin(), tx_money_got_in_outs.end(), 0, @@ -1341,7 +1341,6 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote }); process_outgoing(txid, tx, height, ts, tx_money_spent_in_ins, self_received, *subaddr_account, subaddr_indices); // if sending to yourself at the same subaddress account, set the outgoing payment amount to 0 so that it's less confusing - uint64_t fee = tx.version == 1 ? tx_money_spent_in_ins - get_outs_money_amount(tx) : tx.rct_signatures.txnFee; if (tx_money_spent_in_ins == self_received + fee) { auto i = m_confirmed_txs.find(txid); @@ -1406,6 +1405,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote { payment_details payment; payment.m_tx_hash = txid; + payment.m_fee = fee; payment.m_amount = i.second; payment.m_block_height = height; payment.m_unlock_time = tx.unlock_time; @@ -1460,14 +1460,12 @@ void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::trans entry.first->second.m_change = received; std::vector<tx_extra_field> tx_extra_fields; - if(parse_tx_extra(tx.extra, tx_extra_fields)) + parse_tx_extra(tx.extra, tx_extra_fields); // ok if partially parsed + tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) { - tx_extra_nonce extra_nonce; - if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) - { - // we do not care about failure here - get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, entry.first->second.m_payment_id); - } + // we do not care about failure here + get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, entry.first->second.m_payment_id); } entry.first->second.m_subaddr_account = subaddr_account; entry.first->second.m_subaddr_indices = subaddr_indices; @@ -2444,6 +2442,12 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value2.SetUint(m_confirm_backlog_threshold); json.AddMember("confirm_backlog_threshold", value2, json.GetAllocator()); + value2.SetInt(m_confirm_export_overwrite ? 1 :0); + json.AddMember("confirm_export_overwrite", value2, json.GetAllocator()); + + value2.SetInt(m_auto_low_priority ? 1 : 0); + json.AddMember("auto_low_priority", value2, json.GetAllocator()); + value2.SetInt(m_testnet ? 1 :0); json.AddMember("testnet", value2, json.GetAllocator()); @@ -2525,6 +2529,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_merge_destinations = false; m_confirm_backlog = true; m_confirm_backlog_threshold = 0; + m_confirm_export_overwrite = true; + m_auto_low_priority = true; } else if(json.IsObject()) { @@ -2624,6 +2630,10 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_confirm_backlog = field_confirm_backlog; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, confirm_backlog_threshold, uint32_t, Uint, false, 0); m_confirm_backlog_threshold = field_confirm_backlog_threshold; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, confirm_export_overwrite, int, Int, false, true); + m_confirm_export_overwrite = field_confirm_export_overwrite; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, m_auto_low_priority, int, Int, false, true); + m_auto_low_priority = field_m_auto_low_priority; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, testnet, int, Int, false, m_testnet); // Wallet is being opened with testnet flag, but is saved as a mainnet wallet THROW_WALLET_EXCEPTION_IF(m_testnet && !field_testnet, error::wallet_internal_error, "Mainnet wallet can not be opened as testnet wallet"); @@ -2983,6 +2993,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& cryptonote::block b; generate_genesis(b); m_blockchain.push_back(get_block_hash(b)); + add_subaddress_account(tr("Primary account")); if (!wallet_.empty()) store(); @@ -3412,16 +3423,6 @@ bool wallet2::parse_payment_id(const std::string& payment_id_str, crypto::hash& return false; } //---------------------------------------------------------------------------------------------------- -void wallet2::set_default_decimal_point(unsigned int decimal_point) -{ - cryptonote::set_default_decimal_point(decimal_point); -} -//---------------------------------------------------------------------------------------------------- -unsigned int wallet2::get_default_decimal_point() const -{ - return cryptonote::get_default_decimal_point(); -} -//---------------------------------------------------------------------------------------------------- bool wallet2::prepare_file_names(const std::string& file_path) { do_prepare_file_names(file_path, m_keys_file, m_wallet_file); @@ -4137,7 +4138,7 @@ size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::v // 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::vector<size_t>& selected_transfers, bool trusted_daemon) +uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::vector<size_t>& selected_transfers, bool trusted_daemon) const { uint64_t found_money = 0; selected_transfers.reserve(unused_transfers_indices.size()); @@ -4145,7 +4146,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; + const transfer_container::const_iterator it = m_transfers.begin() + idx; selected_transfers.push_back(idx); found_money += it->amount(); } @@ -4239,8 +4240,7 @@ std::vector<std::vector<cryptonote::tx_destination_entry>> split_amounts( crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const { std::vector<tx_extra_field> tx_extra_fields; - if(!parse_tx_extra(ptx.tx.extra, tx_extra_fields)) - return crypto::null_hash; + parse_tx_extra(ptx.tx.extra, tx_extra_fields); // ok if partially parsed tx_extra_nonce extra_nonce; crypto::hash payment_id = null_hash; if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) @@ -4353,7 +4353,7 @@ void wallet2::commit_tx(std::vector<pending_tx>& ptx_vector) } } //---------------------------------------------------------------------------------------------------- -bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) +bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) const { LOG_PRINT_L0("saving " << ptx_vector.size() << " transactions"); unsigned_tx_set txs; @@ -4382,7 +4382,7 @@ bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::stri return epee::file_io_utils::save_string_to_file(filename, std::string(UNSIGNED_TX_PREFIX) + ciphertext); } //---------------------------------------------------------------------------------------------------- -bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs) +bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs) const { std::string s; boost::system::error_code errcode; @@ -4956,7 +4956,7 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto return sign_multisig_tx_to_file(exported_txs, filename, txids); } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm) +uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm) const { static const uint64_t old_multipliers[3] = {1, 2, 3}; static const uint64_t new_multipliers[3] = {1, 20, 166}; @@ -4993,7 +4993,7 @@ uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm) return 1; } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_dynamic_per_kb_fee_estimate() +uint64_t wallet2::get_dynamic_per_kb_fee_estimate() const { uint64_t fee; boost::optional<std::string> result = m_node_rpc_proxy.get_dynamic_per_kb_fee_estimate(FEE_ESTIMATE_GRACE_BLOCKS, fee); @@ -5003,7 +5003,7 @@ uint64_t wallet2::get_dynamic_per_kb_fee_estimate() return FEE_PER_KB; } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_per_kb_fee() +uint64_t wallet2::get_per_kb_fee() const { if(m_light_wallet) return m_light_wallet_per_kb_fee; @@ -5014,7 +5014,7 @@ uint64_t wallet2::get_per_kb_fee() return get_dynamic_per_kb_fee_estimate(); } //---------------------------------------------------------------------------------------------------- -int wallet2::get_fee_algorithm() +int wallet2::get_fee_algorithm() const { // changes at v3 and v5 if (use_fork_rules(5, 0)) @@ -5024,7 +5024,7 @@ int wallet2::get_fee_algorithm() return 0; } //------------------------------------------------------------------------------------------------------------------------------ -uint64_t wallet2::adjust_mixin(uint64_t mixin) +uint64_t wallet2::adjust_mixin(uint64_t mixin) const { if (mixin < 4 && use_fork_rules(6, 10)) { MWARNING("Requested ring size " << (mixin + 1) << " too low for hard fork 6, using 5"); @@ -5037,6 +5037,90 @@ uint64_t wallet2::adjust_mixin(uint64_t mixin) return mixin; } //---------------------------------------------------------------------------------------------------- +uint32_t wallet2::adjust_priority(uint32_t priority) +{ + if (priority == 0 && get_default_priority() != 1 && auto_low_priority()) + { + try + { + // check if there's a backlog in the tx pool + const double fee_level = get_fee_multiplier(1) * get_per_kb_fee() * (12/(double)13) / (double)1024; + const std::vector<std::pair<uint64_t, uint64_t>> blocks = estimate_backlog({std::make_pair(fee_level, fee_level)}); + if (blocks.size() != 1) + { + MERROR("Bad estimated backlog array size"); + return priority; + } + else if (blocks[0].first > 0) + { + MINFO("We don't use the low priority because there's a backlog in the tx pool."); + return priority; + } + + // get the current full reward zone + epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_INFO::request> getinfo_req = AUTO_VAL_INIT(getinfo_req); + epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_INFO::response, std::string> getinfo_res = AUTO_VAL_INIT(getinfo_res); + m_daemon_rpc_mutex.lock(); + getinfo_req.jsonrpc = "2.0"; + getinfo_req.id = epee::serialization::storage_entry(0); + getinfo_req.method = "get_info"; + bool r = net_utils::invoke_http_json("/json_rpc", getinfo_req, getinfo_res, m_http_client); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_info"); + THROW_WALLET_EXCEPTION_IF(getinfo_res.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_info"); + THROW_WALLET_EXCEPTION_IF(getinfo_res.result.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error); + const uint64_t full_reward_zone = getinfo_res.result.block_size_limit / 2; + + // get the last N block headers and sum the block sizes + const size_t N = 10; + if (m_blockchain.size() < N) + { + MERROR("The blockchain is too short"); + return priority; + } + epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::request> getbh_req = AUTO_VAL_INIT(getbh_req); + epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response, std::string> getbh_res = AUTO_VAL_INIT(getbh_res); + m_daemon_rpc_mutex.lock(); + getbh_req.jsonrpc = "2.0"; + getbh_req.id = epee::serialization::storage_entry(0); + getbh_req.method = "getblockheadersrange"; + getbh_req.params.start_height = m_blockchain.size() - N; + getbh_req.params.end_height = m_blockchain.size() - 1; + r = net_utils::invoke_http_json("/json_rpc", getbh_req, getbh_res, m_http_client, rpc_timeout); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getblockheadersrange"); + THROW_WALLET_EXCEPTION_IF(getbh_res.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getblockheadersrange"); + THROW_WALLET_EXCEPTION_IF(getbh_res.result.status != CORE_RPC_STATUS_OK, error::get_blocks_error, getbh_res.result.status); + if (getbh_res.result.headers.size() != N) + { + MERROR("Bad blockheaders size"); + return priority; + } + size_t block_size_sum = 0; + for (const cryptonote::block_header_response &i : getbh_res.result.headers) + { + block_size_sum += i.block_size; + } + + // estimate how 'full' the last N blocks are + const size_t P = 100 * block_size_sum / (N * full_reward_zone); + MINFO((boost::format("The last %d blocks fill roughly %d%% of the full reward zone.") % N % P).str()); + if (P > 80) + { + MINFO("We don't use the low priority because recent blocks are quite full."); + return priority; + } + MINFO("We'll use the low priority because probably it's safe to do so."); + return 1; + } + catch (const std::exception &e) + { + MERROR(e.what()); + } + } + return priority; +} +//---------------------------------------------------------------------------------------------------- // separated the call(s) to wallet2::transfer into their own function // // this function will make multiple calls to wallet2::transfer if multiple @@ -6312,6 +6396,7 @@ void wallet2::light_wallet_get_address_txs() address_tx.m_tx_hash = tx_hash; address_tx.m_incoming = incoming; address_tx.m_amount = incoming ? total_received - total_sent : total_sent - total_received; + address_tx.m_fee = 0; // TODO address_tx.m_block_height = t.height; address_tx.m_unlock_time = t.unlock_time; address_tx.m_timestamp = t.timestamp; @@ -6325,6 +6410,7 @@ void wallet2::light_wallet_get_address_txs() payment_details payment; payment.m_tx_hash = tx_hash; payment.m_amount = total_received - total_sent; + payment.m_fee = 0; // TODO payment.m_block_height = t.height; payment.m_unlock_time = t.unlock_time; payment.m_timestamp = t.timestamp; @@ -6553,7 +6639,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp uint64_t needed_fee, available_for_fee = 0; uint64_t upper_transaction_size_limit = get_upper_transaction_size_limit(); const bool use_rct = use_fork_rules(4, 0); - const bool bulletproof = use_fork_rules(get_bulletproof_fork(m_testnet), 0); + const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); const uint64_t fee_per_kb = get_per_kb_fee(); const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); @@ -7062,7 +7148,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton std::vector<std::vector<get_outs_entry>> outs; const bool use_rct = fake_outs_count > 0 && use_fork_rules(4, 0); - const bool bulletproof = use_fork_rules(get_bulletproof_fork(m_testnet), 0); + const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); const uint64_t fee_per_kb = get_per_kb_fee(); const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); @@ -7129,7 +7215,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton THROW_WALLET_EXCEPTION_IF(needed_fee > available_for_fee, error::wallet_internal_error, "Transaction cannot pay for itself"); - while (needed_fee > test_ptx.fee) { + do { LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); tx.dsts[0].amount = available_for_fee - needed_fee; if (use_rct) @@ -7142,7 +7228,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); LOG_PRINT_L2("Made an attempt at a final " << get_size_string(txBlob) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); - } + } while (needed_fee > test_ptx.fee); LOG_PRINT_L2("Made a final " << get_size_string(txBlob) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); @@ -7181,13 +7267,13 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton return ptx_vector; } //---------------------------------------------------------------------------------------------------- -void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height) +void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height) const { boost::optional<std::string> result = m_node_rpc_proxy.get_earliest_height(version, earliest_height); throw_on_rpc_response_error(result, "get_hard_fork_info"); } //---------------------------------------------------------------------------------------------------- -bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks) +bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks) const { // TODO: How to get fork rule info from light wallet node? if(m_light_wallet) @@ -7206,7 +7292,7 @@ bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks) return close_enough; } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_upper_transaction_size_limit() +uint64_t wallet2::get_upper_transaction_size_limit() const { if (m_upper_transaction_size_limit > 0) return m_upper_transaction_size_limit; @@ -7214,7 +7300,7 @@ uint64_t wallet2::get_upper_transaction_size_limit() return full_reward_zone - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; } //---------------------------------------------------------------------------------------------------- -std::vector<size_t> wallet2::select_available_outputs(const std::function<bool(const transfer_details &td)> &f) +std::vector<size_t> wallet2::select_available_outputs(const std::function<bool(const transfer_details &td)> &f) const { std::vector<size_t> outputs; size_t n = 0; @@ -7232,7 +7318,7 @@ std::vector<size_t> wallet2::select_available_outputs(const std::function<bool(c return outputs; } //---------------------------------------------------------------------------------------------------- -std::vector<uint64_t> wallet2::get_unspent_amounts_vector() +std::vector<uint64_t> wallet2::get_unspent_amounts_vector() const { std::set<uint64_t> set; for (const auto &td: m_transfers) @@ -7947,6 +8033,251 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account return false; } +std::string wallet2::get_reserve_proof(const boost::optional<std::pair<uint32_t, uint64_t>> &account_minreserve, const std::string &message) +{ + THROW_WALLET_EXCEPTION_IF(m_watch_only || m_multisig, error::wallet_internal_error, "Reserve proof can only be generated by a full wallet"); + THROW_WALLET_EXCEPTION_IF(balance_all() == 0, error::wallet_internal_error, "Zero balance"); + THROW_WALLET_EXCEPTION_IF(account_minreserve && balance(account_minreserve->first) < account_minreserve->second, error::wallet_internal_error, + "Not enough balance in this account for the requested minimum reserve amount"); + + // determine which outputs to include in the proof + std::vector<size_t> selected_transfers; + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details &td = m_transfers[i]; + if (!td.m_spent && (!account_minreserve || account_minreserve->first == td.m_subaddr_index.major)) + selected_transfers.push_back(i); + } + + if (account_minreserve) + { + // minimize the number of outputs included in the proof, by only picking the N largest outputs that can cover the requested min reserve amount + std::sort(selected_transfers.begin(), selected_transfers.end(), [&](const size_t a, const size_t b) + { return m_transfers[a].amount() > m_transfers[b].amount(); }); + while (selected_transfers.size() >= 2 && m_transfers[selected_transfers[1]].amount() >= account_minreserve->second) + selected_transfers.erase(selected_transfers.begin()); + size_t sz = 0; + uint64_t total = 0; + while (total < account_minreserve->second) + { + total += m_transfers[selected_transfers[sz]].amount(); + ++sz; + } + selected_transfers.resize(sz); + } + + // compute signature prefix hash + std::string prefix_data = message; + prefix_data.append((const char*)&m_account.get_keys().m_account_address, sizeof(cryptonote::account_public_address)); + for (size_t i = 0; i < selected_transfers.size(); ++i) + { + prefix_data.append((const char*)&m_transfers[selected_transfers[i]].m_key_image, sizeof(crypto::key_image)); + } + crypto::hash prefix_hash; + crypto::cn_fast_hash(prefix_data.data(), prefix_data.size(), prefix_hash); + + // generate proof entries + std::vector<reserve_proof_entry> proofs(selected_transfers.size()); + std::unordered_set<cryptonote::subaddress_index> subaddr_indices = { {0,0} }; + for (size_t i = 0; i < selected_transfers.size(); ++i) + { + const transfer_details &td = m_transfers[selected_transfers[i]]; + reserve_proof_entry& proof = proofs[i]; + proof.txid = td.m_txid; + proof.index_in_tx = td.m_internal_output_index; + proof.key_image = td.m_key_image; + subaddr_indices.insert(td.m_subaddr_index); + + // get tx pub key + const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(td.m_tx, td.m_pk_index); + THROW_WALLET_EXCEPTION_IF(tx_pub_key == crypto::null_pkey, error::wallet_internal_error, "The tx public key isn't found"); + const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); + + // determine which tx pub key was used for deriving the output key + const crypto::public_key *tx_pub_key_used = &tx_pub_key; + for (int i = 0; i < 2; ++i) + { + proof.shared_secret = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(*tx_pub_key_used), rct::sk2rct(m_account.get_keys().m_view_secret_key))); + crypto::key_derivation derivation; + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(proof.shared_secret, rct::rct2sk(rct::I), derivation), + error::wallet_internal_error, "Failed to generate key derivation"); + crypto::public_key subaddress_spendkey; + THROW_WALLET_EXCEPTION_IF(!derive_subaddress_public_key(td.get_public_key(), derivation, proof.index_in_tx, subaddress_spendkey), + error::wallet_internal_error, "Failed to derive subaddress public key"); + if (m_subaddresses.count(subaddress_spendkey) == 1) + break; + THROW_WALLET_EXCEPTION_IF(additional_tx_pub_keys.empty(), error::wallet_internal_error, + "Normal tx pub key doesn't derive the expected output, while the additional tx pub keys are empty"); + THROW_WALLET_EXCEPTION_IF(i == 1, error::wallet_internal_error, + "Neither normal tx pub key nor additional tx pub key derive the expected output key"); + tx_pub_key_used = &additional_tx_pub_keys[proof.index_in_tx]; + } + + // generate signature for shared secret + crypto::generate_tx_proof(prefix_hash, m_account.get_keys().m_account_address.m_view_public_key, *tx_pub_key_used, boost::none, proof.shared_secret, m_account.get_keys().m_view_secret_key, proof.shared_secret_sig); + + // derive ephemeral secret key + crypto::key_image ki; + cryptonote::keypair ephemeral; + const bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, td.get_public_key(), tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, ephemeral, ki); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); + THROW_WALLET_EXCEPTION_IF(ephemeral.pub != td.get_public_key(), error::wallet_internal_error, "Derived public key doesn't agree with the stored one"); + + // generate signature for key image + const std::vector<const crypto::public_key*> pubs = { &ephemeral.pub }; + crypto::generate_ring_signature(prefix_hash, td.m_key_image, &pubs[0], 1, ephemeral.sec, 0, &proof.key_image_sig); + } + + // collect all subaddress spend keys that received those outputs and generate their signatures + std::unordered_map<crypto::public_key, crypto::signature> subaddr_spendkeys; + for (const cryptonote::subaddress_index &index : subaddr_indices) + { + crypto::secret_key subaddr_spend_skey = m_account.get_keys().m_spend_secret_key; + if (!index.is_zero()) + { + crypto::secret_key m = cryptonote::get_subaddress_secret_key(m_account.get_keys().m_view_secret_key, index); + crypto::secret_key tmp = subaddr_spend_skey; + sc_add((unsigned char*)&subaddr_spend_skey, (unsigned char*)&m, (unsigned char*)&tmp); + } + crypto::public_key subaddr_spend_pkey; + secret_key_to_public_key(subaddr_spend_skey, subaddr_spend_pkey); + crypto::generate_signature(prefix_hash, subaddr_spend_pkey, subaddr_spend_skey, subaddr_spendkeys[subaddr_spend_pkey]); + } + + // serialize & encode + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + ar << proofs << subaddr_spendkeys; + return "ReserveProofV1" + tools::base58::encode(oss.str()); +} + +bool wallet2::check_reserve_proof(const cryptonote::account_public_address &address, const std::string &message, const std::string &sig_str, uint64_t &total, uint64_t &spent) +{ + uint32_t rpc_version; + THROW_WALLET_EXCEPTION_IF(!check_connection(&rpc_version), error::wallet_internal_error, "Failed to connect to daemon: " + get_daemon_address()); + THROW_WALLET_EXCEPTION_IF(rpc_version < MAKE_CORE_RPC_VERSION(1, 0), error::wallet_internal_error, "Daemon RPC version is too old"); + + static constexpr char header[] = "ReserveProofV1"; + THROW_WALLET_EXCEPTION_IF(!boost::string_ref{sig_str}.starts_with(header), error::wallet_internal_error, + "Signature header check error"); + + std::string sig_decoded; + THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(std::strlen(header)), sig_decoded), error::wallet_internal_error, + "Signature decoding error"); + + std::istringstream iss(sig_decoded); + boost::archive::portable_binary_iarchive ar(iss); + std::vector<reserve_proof_entry> proofs; + std::unordered_map<crypto::public_key, crypto::signature> subaddr_spendkeys; + ar >> proofs >> subaddr_spendkeys; + + THROW_WALLET_EXCEPTION_IF(subaddr_spendkeys.count(address.m_spend_public_key) == 0, error::wallet_internal_error, + "The given address isn't found in the proof"); + + // compute signature prefix hash + std::string prefix_data = message; + prefix_data.append((const char*)&address, sizeof(cryptonote::account_public_address)); + for (size_t i = 0; i < proofs.size(); ++i) + { + prefix_data.append((const char*)&proofs[i].key_image, sizeof(crypto::key_image)); + } + crypto::hash prefix_hash; + crypto::cn_fast_hash(prefix_data.data(), prefix_data.size(), prefix_hash); + + // fetch txes from daemon + COMMAND_RPC_GET_TRANSACTIONS::request gettx_req; + COMMAND_RPC_GET_TRANSACTIONS::response gettx_res; + for (size_t i = 0; i < proofs.size(); ++i) + gettx_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(proofs[i].txid)); + m_daemon_rpc_mutex.lock(); + bool ok = net_utils::invoke_http_json("/gettransactions", gettx_req, gettx_res, m_http_client); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!ok || gettx_res.txs.size() != proofs.size(), + error::wallet_internal_error, "Failed to get transaction from daemon"); + + // check spent status + COMMAND_RPC_IS_KEY_IMAGE_SPENT::request kispent_req; + COMMAND_RPC_IS_KEY_IMAGE_SPENT::response kispent_res; + for (size_t i = 0; i < proofs.size(); ++i) + kispent_req.key_images.push_back(epee::string_tools::pod_to_hex(proofs[i].key_image)); + m_daemon_rpc_mutex.lock(); + ok = epee::net_utils::invoke_http_json("/is_key_image_spent", kispent_req, kispent_res, m_http_client, rpc_timeout); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!ok || kispent_res.spent_status.size() != proofs.size(), + error::wallet_internal_error, "Failed to get key image spent status from daemon"); + + total = spent = 0; + for (size_t i = 0; i < proofs.size(); ++i) + { + const reserve_proof_entry& proof = proofs[i]; + THROW_WALLET_EXCEPTION_IF(gettx_res.txs[i].in_pool, error::wallet_internal_error, "Tx is unconfirmed"); + + cryptonote::blobdata tx_data; + ok = string_tools::parse_hexstr_to_binbuff(gettx_res.txs[i].as_hex, tx_data); + THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); + + crypto::hash tx_hash, tx_prefix_hash; + cryptonote::transaction tx; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, + "Failed to validate transaction from daemon"); + THROW_WALLET_EXCEPTION_IF(tx_hash != proof.txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); + + THROW_WALLET_EXCEPTION_IF(proof.index_in_tx >= tx.vout.size(), error::wallet_internal_error, "index_in_tx is out of bound"); + + const cryptonote::txout_to_key* const out_key = boost::get<cryptonote::txout_to_key>(std::addressof(tx.vout[proof.index_in_tx].target)); + THROW_WALLET_EXCEPTION_IF(!out_key, error::wallet_internal_error, "Output key wasn't found") + + // get tx pub key + const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + THROW_WALLET_EXCEPTION_IF(tx_pub_key == crypto::null_pkey, error::wallet_internal_error, "The tx public key isn't found"); + const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); + + // check singature for shared secret + ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, proof.shared_secret, proof.shared_secret_sig); + if (!ok && additional_tx_pub_keys.size() == tx.vout.size()) + ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[proof.index_in_tx], boost::none, proof.shared_secret, proof.shared_secret_sig); + if (!ok) + return false; + + // check signature for key image + const std::vector<const crypto::public_key*> pubs = { &out_key->key }; + ok = crypto::check_ring_signature(prefix_hash, proof.key_image, &pubs[0], 1, &proof.key_image_sig); + if (!ok) + return false; + + // check if the address really received the fund + crypto::key_derivation derivation; + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(proof.shared_secret, rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation"); + crypto::public_key subaddr_spendkey; + crypto::derive_subaddress_public_key(out_key->key, derivation, proof.index_in_tx, subaddr_spendkey); + THROW_WALLET_EXCEPTION_IF(subaddr_spendkeys.count(subaddr_spendkey) == 0, error::wallet_internal_error, + "The address doesn't seem to have received the fund"); + + // check amount + uint64_t amount = tx.vout[proof.index_in_tx].amount; + if (amount == 0) + { + // decode rct + crypto::secret_key shared_secret; + crypto::derivation_to_scalar(derivation, proof.index_in_tx, shared_secret); + rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[proof.index_in_tx]; + rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret)); + amount = rct::h2d(ecdh_info.amount); + } + total += amount; + if (kispent_res.spent_status[i]) + spent += amount; + } + + // check signatures for all subaddress spend keys + for (const auto &i : subaddr_spendkeys) + { + if (!crypto::check_signature(prefix_hash, i.first, i.second)) + return false; + } + return true; +} + std::string wallet2::get_wallet_file() const { return m_wallet_file; @@ -7962,7 +8293,7 @@ std::string wallet2::get_daemon_address() const return m_daemon_address; } -uint64_t wallet2::get_daemon_blockchain_height(string &err) +uint64_t wallet2::get_daemon_blockchain_height(string &err) const { uint64_t height; @@ -8020,8 +8351,9 @@ uint64_t wallet2::get_approximate_blockchain_height() const // Calculated blockchain height uint64_t approx_blockchain_height = fork_block + (time(NULL) - fork_time)/seconds_per_block; // testnet got some huge rollbacks, so the estimation is way off - if (m_testnet && approx_blockchain_height > 105000) - approx_blockchain_height -= 105000; + static const uint64_t approximate_testnet_rolled_back_blocks = 148540; + if (m_testnet && approx_blockchain_height > approximate_testnet_rolled_back_blocks) + approx_blockchain_height -= approximate_testnet_rolled_back_blocks; LOG_PRINT_L2("Calculated blockchain height: " << approx_blockchain_height); return approx_blockchain_height; } @@ -8192,7 +8524,7 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle return crypto::null_pkey; } -bool wallet2::export_key_images(const std::string &filename) +bool wallet2::export_key_images(const std::string &filename) const { std::vector<std::pair<crypto::key_image, crypto::signature>> ski = export_key_images(); std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC)); @@ -8597,7 +8929,7 @@ void wallet2::import_blockchain(const std::tuple<size_t, crypto::hash, std::vect m_blockchain.clear(); if (std::get<0>(bc)) { - for (size_t n = std::get<0>(bc); n > 0; ++n) + for (size_t n = std::get<0>(bc); n > 0; --n) m_blockchain.push_back(std::get<1>(bc)); m_blockchain.trim(std::get<0>(bc)); } @@ -8637,11 +8969,8 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail // the hot wallet wouldn't have known about key images (except if we already exported them) cryptonote::keypair in_ephemeral; - std::vector<tx_extra_field> tx_extra_fields; THROW_WALLET_EXCEPTION_IF(td.m_tx.vout.empty(), error::wallet_internal_error, "tx with no outputs at index " + boost::lexical_cast<std::string>(i)); - THROW_WALLET_EXCEPTION_IF(!parse_tx_extra(td.m_tx.extra, tx_extra_fields), error::wallet_internal_error, - "Transaction extra has unsupported format at index " + boost::lexical_cast<std::string>(i)); crypto::public_key tx_pub_key = get_tx_pub_key_from_received_outs(td); const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); @@ -8999,7 +9328,7 @@ std::string wallet2::decrypt_with_view_secret_key(const std::string &ciphertext, return decrypt(ciphertext, get_account().get_keys().m_view_secret_key, authenticated); } //---------------------------------------------------------------------------------------------------- -std::string wallet2::make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name, std::string &error) +std::string wallet2::make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name, std::string &error) const { cryptonote::address_parse_info info; if(!get_account_address_from_str(info, testnet(), address)) @@ -9233,13 +9562,12 @@ bool wallet2::is_synced() const return get_blockchain_current_height() >= height; } //---------------------------------------------------------------------------------------------------- -std::vector<std::pair<uint64_t, uint64_t>> wallet2::estimate_backlog(uint64_t min_blob_size, uint64_t max_blob_size, const std::vector<uint64_t> &fees) +std::vector<std::pair<uint64_t, uint64_t>> wallet2::estimate_backlog(const std::vector<std::pair<double, double>> &fee_levels) { - THROW_WALLET_EXCEPTION_IF(min_blob_size == 0, error::wallet_internal_error, "Invalid 0 fee"); - THROW_WALLET_EXCEPTION_IF(max_blob_size == 0, error::wallet_internal_error, "Invalid 0 fee"); - for (uint64_t fee: fees) + for (const auto &fee_level: fee_levels) { - THROW_WALLET_EXCEPTION_IF(fee == 0, error::wallet_internal_error, "Invalid 0 fee"); + THROW_WALLET_EXCEPTION_IF(fee_level.first == 0.0, error::wallet_internal_error, "Invalid 0 fee"); + THROW_WALLET_EXCEPTION_IF(fee_level.second == 0.0, error::wallet_internal_error, "Invalid 0 fee"); } // get txpool backlog @@ -9269,9 +9597,10 @@ std::vector<std::pair<uint64_t, uint64_t>> wallet2::estimate_backlog(uint64_t mi uint64_t full_reward_zone = resp_t.result.block_size_limit / 2; std::vector<std::pair<uint64_t, uint64_t>> blocks; - for (uint64_t fee: fees) + for (const auto &fee_level: fee_levels) { - double our_fee_byte_min = fee / (double)min_blob_size, our_fee_byte_max = fee / (double)max_blob_size; + const double our_fee_byte_min = fee_level.first; + const double our_fee_byte_max = fee_level.second; uint64_t priority_size_min = 0, priority_size_max = 0; for (const auto &i: res.result.backlog) { @@ -9289,15 +9618,32 @@ std::vector<std::pair<uint64_t, uint64_t>> wallet2::estimate_backlog(uint64_t mi uint64_t nblocks_min = priority_size_min / full_reward_zone; uint64_t nblocks_max = priority_size_max / full_reward_zone; - MDEBUG("estimate_backlog: priority_size " << priority_size_min << " - " << priority_size_max << " for " << fee - << " (" << our_fee_byte_min << " - " << our_fee_byte_max << " piconero byte fee), " + MDEBUG("estimate_backlog: priority_size " << priority_size_min << " - " << priority_size_max << " for " + << our_fee_byte_min << " - " << our_fee_byte_max << " piconero byte fee, " << nblocks_min << " - " << nblocks_max << " blocks at block size " << full_reward_zone); blocks.push_back(std::make_pair(nblocks_min, nblocks_max)); } return blocks; } //---------------------------------------------------------------------------------------------------- -void wallet2::generate_genesis(cryptonote::block& b) { +std::vector<std::pair<uint64_t, uint64_t>> wallet2::estimate_backlog(uint64_t min_blob_size, uint64_t max_blob_size, const std::vector<uint64_t> &fees) +{ + THROW_WALLET_EXCEPTION_IF(min_blob_size == 0, error::wallet_internal_error, "Invalid 0 fee"); + THROW_WALLET_EXCEPTION_IF(max_blob_size == 0, error::wallet_internal_error, "Invalid 0 fee"); + for (uint64_t fee: fees) + { + THROW_WALLET_EXCEPTION_IF(fee == 0, error::wallet_internal_error, "Invalid 0 fee"); + } + std::vector<std::pair<double, double>> fee_levels; + for (uint64_t fee: fees) + { + double our_fee_byte_min = fee / (double)min_blob_size, our_fee_byte_max = fee / (double)max_blob_size; + fee_levels.emplace_back(our_fee_byte_min, our_fee_byte_max); + } + return estimate_backlog(fee_levels); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::generate_genesis(cryptonote::block& b) const { if (m_testnet) { cryptonote::generate_genesis_block(b, config::testnet::GENESIS_TX, config::testnet::GENESIS_NONCE); |