diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index b29b865b2e7..aa407b0ac5d 100755 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -268,11 +268,11 @@ namespace cryptonote crypto::public_key mnode_key; if (!cryptonote::get_master_node_pubkey_from_tx_extra(tx.extra, mnode_key)) return false; - + cryptonote::tx_extra_tx_key_image_unlock unlock; if (!cryptonote::get_field_from_tx_extra(tx.extra, unlock)) return false; - + uint64_t block_height = m_blockchain.get_current_blockchain_height(); if (!m_blockchain.get_master_node_list().is_master_node(mnode_key)) return false; @@ -297,7 +297,7 @@ namespace cryptonote return false; } } - } + } } } @@ -787,7 +787,9 @@ namespace cryptonote MINFO("Removing tx " << txid << " from txpool: weight: " << meta->weight << ", fee/byte: " << tx_fee); m_blockchain.remove_txpool_tx(txid); m_txpool_weight -= meta->weight; - remove_transaction_keyimages(tx, txid); + if (!remove_transaction_keyimages(tx, txid)) + MERROR("Failed to remove key images for tx " << txid << " being removed from the txpool; " + "the spent key image map may be inconsistent until restart"); m_txs_by_fee_and_receive_time.erase(it); return true; @@ -867,48 +869,77 @@ namespace cryptonote //--------------------------------------------------------------------------------- bool tx_memory_pool::insert_key_images(const transaction_prefix &tx, const crypto::hash &id, bool kept_by_block) { + std::vector key_images_to_insert; + key_images_to_insert.reserve(tx.vin.size()); + + std::unordered_set seen_key_images; + seen_key_images.reserve(tx.vin.size()); + for(const auto& in: tx.vin) { CHECKED_GET_SPECIFIC_VARIANT(in, txin_to_key, txin, false); - std::unordered_set& kei_image_set = m_spent_key_images[txin.k_image]; - CHECK_AND_ASSERT_MES(kept_by_block || kei_image_set.size() == 0, false, "internal error: kept_by_block=" << kept_by_block - << ", kei_image_set.size()=" << kei_image_set.size() << "\ntxin.k_image=" << txin.k_image - << "\ntx_id=" << id ); - auto ins_res = kei_image_set.insert(id); - CHECK_AND_ASSERT_MES(ins_res.second, false, "internal error: try to insert duplicate iterator in key_image set"); + CHECK_AND_ASSERT_MES(seen_key_images.insert(txin.k_image).second, + false, + "duplicate key image in transaction: " << txin.k_image + << "\ntx_id=" << id); + auto it = m_spent_key_images.find(txin.k_image); + if (it != m_spent_key_images.end()) + { + const std::unordered_set& kei_image_set = it->second; + CHECK_AND_ASSERT_MES(kept_by_block || kei_image_set.size() == 0, false, "internal error: kept_by_block=" << kept_by_block + << ", kei_image_set.size()=" << kei_image_set.size() << "\ntxin.k_image=" << txin.k_image + << "\ntx_id=" << id ); + CHECK_AND_ASSERT_MES(kei_image_set.count(id) == 0, false, "internal error: try to insert duplicate iterator in key_image set"); + } + key_images_to_insert.push_back(txin.k_image); } + + for (const crypto::key_image &k_image : key_images_to_insert) + m_spent_key_images[k_image].insert(id); + ++m_cookie; return true; } //--------------------------------------------------------------------------------- - //FIXME: Can return early before removal of all of the key images. - // At the least, need to make sure that a false return here - // is treated properly. Should probably not return early, however. bool tx_memory_pool::remove_transaction_keyimages(const transaction_prefix& tx, const crypto::hash &actual_hash) { auto locks = tools::unique_locks(m_transactions_lock, m_blockchain); - // ND: Speedup + std::vector key_images_to_erase; + key_images_to_erase.reserve(tx.vin.size()); + + std::unordered_set seen_key_images; + seen_key_images.reserve(tx.vin.size()); + for(const txin_v& vi: tx.vin) { CHECKED_GET_SPECIFIC_VARIANT(vi, txin_to_key, txin, false); + + CHECK_AND_ASSERT_MES(seen_key_images.insert(txin.k_image).second, false, "duplicate key image in transaction: " + << txin.k_image << "\ntransaction id = " << actual_hash); + auto it = m_spent_key_images.find(txin.k_image); CHECK_AND_ASSERT_MES(it != m_spent_key_images.end(), false, "failed to find transaction input in key images. img=" << txin.k_image << "\ntransaction id = " << actual_hash); - std::unordered_set& key_image_set = it->second; + const std::unordered_set& key_image_set = it->second; CHECK_AND_ASSERT_MES(key_image_set.size(), false, "empty key_image set, img=" << txin.k_image << "\ntransaction id = " << actual_hash); - auto it_in_set = key_image_set.find(actual_hash); - CHECK_AND_ASSERT_MES(it_in_set != key_image_set.end(), false, "transaction id not found in key_image set, img=" << txin.k_image + CHECK_AND_ASSERT_MES(key_image_set.count(actual_hash), false, "transaction id not found in key_image set, img=" << txin.k_image << "\ntransaction id = " << actual_hash); - key_image_set.erase(it_in_set); - if(!key_image_set.size()) + key_images_to_erase.push_back(txin.k_image); + } + + for (const crypto::key_image &k_image : key_images_to_erase) + { + auto it = m_spent_key_images.find(k_image); + if (it == m_spent_key_images.end()) + continue; + it->second.erase(actual_hash); + if (it->second.empty()) { - //it is now empty hash container for this key_image m_spent_key_images.erase(it); } - } ++m_cookie; return true; @@ -961,7 +992,9 @@ namespace cryptonote // remove first, in case this throws, so key images aren't removed m_blockchain.remove_txpool_tx(id); m_txpool_weight -= tx_weight; - remove_transaction_keyimages(tx, id); + if (!remove_transaction_keyimages(tx, id)) + MERROR("Failed to remove key images for tx " << id << " taken from the txpool; " + "the spent key image map may be inconsistent until restart"); lock.commit(); } catch (const std::exception &e) @@ -1045,7 +1078,9 @@ namespace cryptonote // remove first, so we only remove key images if the tx removal succeeds m_blockchain.remove_txpool_tx(txid); m_txpool_weight -= entry.second; - remove_transaction_keyimages(tx, txid); + if (!remove_transaction_keyimages(tx, txid)) + MERROR("Failed to remove key images for stuck tx " << txid << "; " + "the spent key image map may be inconsistent until restart"); } } catch (const std::exception &e) @@ -1292,7 +1327,6 @@ namespace cryptonote h.second += i2->second.second; } } - return stats; } //--------------------------------------------------------------------------------- @@ -1749,7 +1783,7 @@ namespace cryptonote // (otherwise the *block* will fail but validation won't, because validation here won't see the // earlier tx has having taken effect, but the block addition will). std::unordered_set bns_buys; - + LOG_PRINT_L2("Filling block template, median weight " << median_weight << ", " << m_txs_by_fee_and_receive_time.size() << " txes in the pool"); LockedTXN lock(m_blockchain); @@ -1920,7 +1954,9 @@ namespace cryptonote // remove tx from db first m_blockchain.remove_txpool_tx(txid); m_txpool_weight -= get_transaction_weight(tx, txblob.size()); - remove_transaction_keyimages(tx, txid); + if (!remove_transaction_keyimages(tx, txid)) + MERROR("Failed to remove key images for tx " << txid << "; " + "the spent key image map may be inconsistent until restart"); auto sorted_it = find_tx_in_sorted_container(txid); if (sorted_it == m_txs_by_fee_and_receive_time.end()) { diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 0664dbc6d2b..8257aa799da 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -13048,7 +13048,9 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s if (tx_key_data.tx_prefix_hash.empty()) { nlohmann::json get_transactions_params{ - {"txs_hashes", { tools::type_to_hex(txid) }} + {"txs_hashes", { tools::type_to_hex(txid) }}, + {"data", true}, + {"split", true} }; auto res = m_http_client.json_rpc("get_transactions", get_transactions_params); @@ -13811,7 +13813,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr 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 < rpc::version_t{1, 0}), error::wallet_internal_error, "Daemon RPC version is too old"); - THROW_WALLET_EXCEPTION_IF(tools::starts_with(sig_str, RESERVE_PROOF_MAGIC), error::wallet_internal_error, + THROW_WALLET_EXCEPTION_IF(!tools::starts_with(sig_str, RESERVE_PROOF_MAGIC), error::wallet_internal_error, "Signature header check error"); sig_str.remove_prefix(RESERVE_PROOF_MAGIC.size()); @@ -14332,6 +14334,7 @@ std::pair>> const transfer_details &td = m_transfers[n]; // get ephemeral public key + THROW_WALLET_EXCEPTION_IF(td.m_internal_output_index >= td.m_tx.vout.size(), error::wallet_internal_error, "tx output index out of bounds"); const cryptonote::tx_out &out = td.m_tx.vout[td.m_internal_output_index]; THROW_WALLET_EXCEPTION_IF(!std::holds_alternative(out.target), error::wallet_internal_error, "Output is not txout_to_key"); @@ -14455,6 +14458,7 @@ uint64_t wallet2::import_key_images(const std::vector= td.m_tx.vout.size(), error::wallet_internal_error, "tx output index out of bounds"); const cryptonote::tx_out &out = td.m_tx.vout[td.m_internal_output_index]; THROW_WALLET_EXCEPTION_IF(!std::holds_alternative(out.target), error::wallet_internal_error, "Non txout_to_key output found"); @@ -15288,6 +15292,7 @@ size_t wallet2::import_outputs(const std::pair= td.m_tx.vout.size(), error::wallet_internal_error, "tx output index out of bounds"); crypto::public_key tx_pub_key; if (!try_get_tx_pub_key_using_td(td, tx_pub_key)) { diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 18d9efa1127..16dda05212c 100755 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -2498,7 +2498,8 @@ namespace tools cryptonote::address_parse_info info = extract_account_addr(m_wallet->nettype(), req.address); entry.m_address = info.address; entry.m_is_subaddress = info.is_subaddress; - if (info.has_payment_id) + entry.m_has_payment_id = info.has_payment_id; + if (entry.m_has_payment_id) entry.m_payment_id = info.payment_id; } @@ -2768,15 +2769,30 @@ namespace { if (!viewkey_string.hex_to_pod(unwrap(unwrap(viewkey)))) throw wallet_rpc_error{error_code::UNKNOWN_ERROR, "Failed to parse view key secret key"}; + crypto::public_key pkey; + if (!crypto::secret_key_to_public_key(viewkey, pkey)) + throw wallet_rpc_error{error_code::UNKNOWN_ERROR, "Failed to verify view key secret key"}; + if (info.address.m_view_public_key != pkey) + throw wallet_rpc_error{error_code::UNKNOWN_ERROR, "View key does not match address"}; + + crypto::secret_key spendkey; + if (!req.spendkey.empty()) + { + epee::wipeable_string spendkey_string = req.spendkey; + if (!spendkey_string.hex_to_pod(unwrap(unwrap(spendkey)))) + throw wallet_rpc_error{error_code::UNKNOWN_ERROR, "Failed to parse spend key secret key"}; + + if (!crypto::secret_key_to_public_key(spendkey, pkey)) + throw wallet_rpc_error{error_code::UNKNOWN_ERROR, "Failed to verify spend key secret key"}; + if (info.address.m_spend_public_key != pkey) + throw wallet_rpc_error{error_code::UNKNOWN_ERROR, "Spend key does not match address"}; + } + close_wallet(req.autosave_current); { if (!req.spendkey.empty()) { - epee::wipeable_string spendkey_string = req.spendkey; - crypto::secret_key spendkey; - if (!spendkey_string.hex_to_pod(unwrap(unwrap(spendkey)))) - throw wallet_rpc_error{error_code::UNKNOWN_ERROR, "Failed to parse spend key secret key"}; wal->generate(wallet_file, std::move(rc.second).password(), info.address, spendkey, viewkey, false); res.info = "Wallet has been generated successfully."; }