diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 921489ec..c12edb60 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,5 +1,9 @@ ## Release Notes +### v3.3.3 + +- Fixed bug when `walletd` fails to create transactions for certain coins. + ### v3.3.2 - Fixed bug when an invalid transaction may persist in the payment queue. diff --git a/src/Core/BlockChainState.cpp b/src/Core/BlockChainState.cpp index 1dc88ea5..4c49febe 100644 --- a/src/Core/BlockChainState.cpp +++ b/src/Core/BlockChainState.cpp @@ -1083,24 +1083,6 @@ std::vector BlockChainState::get_random_outputs( std::vector result; uint32_t total_count = next_global_index_for_amount(amount); // We might need better algorithm if we have lots of locked amounts - if (total_count <= output_count) { - for (uint32_t i = 0; i != total_count; ++i) { - api::Output item; - UnlockTimePublickKeyHeightSpent unp; - item.amount = amount; - item.index = i; - invariant(read_amount_output(amount, i, &unp), "global amount < total_count not found"); - item.unlock_block_or_timestamp = unp.unlock_block_or_timestamp; - item.public_key = unp.public_key; - item.height = unp.height; - if (unp.spent || unp.height > confirmed_height) - continue; - if (!m_currency.is_transaction_spend_time_unlocked(item.unlock_block_or_timestamp, confirmed_height, time)) - continue; - result.push_back(item); - } - return result; - } std::set tried_or_added; crypto::random_engine generator; std::lognormal_distribution distribution(1.9, 1.0); // Magic params here @@ -1122,14 +1104,8 @@ std::vector BlockChainState::get_random_outputs( } if (!tried_or_added.insert(num).second) continue; - api::Output item; UnlockTimePublickKeyHeightSpent unp; - item.amount = amount; - item.index = num; invariant(read_amount_output(amount, num, &unp), "num < total_count not found"); - item.unlock_block_or_timestamp = unp.unlock_block_or_timestamp; - item.public_key = unp.public_key; - item.height = unp.height; if (unp.height > confirmed_height) { if (confirmed_height + 128 < get_tip_height()) total_count = num; @@ -1140,10 +1116,40 @@ std::vector BlockChainState::get_random_outputs( } if (unp.spent) continue; - if (!m_currency.is_transaction_spend_time_unlocked(item.unlock_block_or_timestamp, confirmed_height, time)) + if (!m_currency.is_transaction_spend_time_unlocked(unp.unlock_block_or_timestamp, confirmed_height, time)) continue; + api::Output item; + item.amount = amount; + item.index = num; + item.unlock_block_or_timestamp = unp.unlock_block_or_timestamp; + item.public_key = unp.public_key; + item.height = unp.height; result.push_back(item); } + if(result.size() < output_count){ + // Read the whole index. + size_t attempts = 0; + for (DB::Cursor cur = m_db.rbegin(AMOUNT_OUTPUT_PREFIX + common::write_varint_sqlite4(amount)); result.size() < output_count && attempts < 10000 && !cur.end(); cur.next(), ++attempts) { // TODO - 10000 + const char *be = cur.get_suffix().data(); + const char *en = be + cur.get_suffix().size(); + uint32_t global_index = common::integer_cast(common::read_varint_sqlite4(be, en)); + if (tried_or_added.count(global_index) != 0) + continue; + UnlockTimePublickKeyHeightSpent unp; + seria::from_binary(unp, cur.get_value_array()); + if (unp.spent || unp.height > confirmed_height) + continue; + if (!m_currency.is_transaction_spend_time_unlocked(unp.unlock_block_or_timestamp, confirmed_height, time)) + continue; + api::Output item; + item.amount = amount; + item.index = global_index; + item.unlock_block_or_timestamp = unp.unlock_block_or_timestamp; + item.public_key = unp.public_key; + item.height = unp.height; + result.push_back(item); + } + } return result; } diff --git a/src/Core/TransactionBuilder.cpp b/src/Core/TransactionBuilder.cpp index a1227c7a..492e9a2c 100644 --- a/src/Core/TransactionBuilder.cpp +++ b/src/Core/TransactionBuilder.cpp @@ -177,28 +177,30 @@ void UnspentSelector::add_mixed_inputs(const SecretKey &view_secret_key, const W api::bytecoind::GetRandomOutputs::Response &&ra_response) { for (const auto &uu : m_used_unspents) { std::vector mix_outputs; - auto &our_ra_outputs = ra_response.outputs[uu.amount]; - while (mix_outputs.size() < anonymity + 1) { - if (our_ra_outputs.empty()) - throw json_rpc::Error(api::walletd::CreateTransaction::NOT_ENOUGH_ANONYMITY, - "Requested anonymity too high for amount " + common::to_string(uu.amount)); - mix_outputs.push_back(std::move(our_ra_outputs.back())); - our_ra_outputs.pop_back(); - } - std::sort(mix_outputs.begin(), mix_outputs.end(), APIOutputLessGlobalIndex); - mix_outputs.erase( - std::unique(mix_outputs.begin(), mix_outputs.end(), APIOutputEqualGlobalIndex), mix_outputs.end()); - int best_distance = 0; - size_t best_index = mix_outputs.size(); - for (size_t i = 0; i != mix_outputs.size(); ++i) { - int distance = abs(int(uu.index) - int(mix_outputs[i].index)); - if (best_index == mix_outputs.size() || distance < best_distance) { - best_index = i; - best_distance = distance; + if(anonymity != 0){ + auto &our_ra_outputs = ra_response.outputs[uu.amount]; + while (mix_outputs.size() < anonymity + 1) { + if (our_ra_outputs.empty()) + throw json_rpc::Error(api::walletd::CreateTransaction::NOT_ENOUGH_ANONYMITY, + "Requested anonymity too high for amount " + common::to_string(uu.amount)); + mix_outputs.push_back(std::move(our_ra_outputs.back())); + our_ra_outputs.pop_back(); + } + std::sort(mix_outputs.begin(), mix_outputs.end(), APIOutputLessGlobalIndex); + mix_outputs.erase( + std::unique(mix_outputs.begin(), mix_outputs.end(), APIOutputEqualGlobalIndex), mix_outputs.end()); + int best_distance = 0; + size_t best_index = mix_outputs.size(); + for (size_t i = 0; i != mix_outputs.size(); ++i) { + int distance = abs(int(uu.index) - int(mix_outputs[i].index)); + if (best_index == mix_outputs.size() || distance < best_distance) { + best_index = i; + best_distance = distance; + } } + invariant(best_index != mix_outputs.size(), ""); + mix_outputs.erase(mix_outputs.begin() + best_index); } - invariant(best_index != mix_outputs.size(), ""); - mix_outputs.erase(mix_outputs.begin() + best_index); AccountKeys sender_keys; sender_keys.view_secret_key = view_secret_key; if (!m_currency.parse_account_address_string(uu.address, &sender_keys.address)) diff --git a/src/Core/WalletNode.cpp b/src/Core/WalletNode.cpp index b13e9a73..f1d06c88 100644 --- a/src/Core/WalletNode.cpp +++ b/src/Core/WalletNode.cpp @@ -461,30 +461,28 @@ bool WalletNode::handle_create_transaction(http::Client *who, http::RequestData request.transaction.anonymity + 1; // Ask excess output for the case of collision with our output ra_request.amounts = selector.get_ra_amounts(); api::bytecoind::GetRandomOutputs::Response ra_response; - if (m_inproc_node) { - m_inproc_node->on_get_random_outputs( - nullptr, http::RequestData(raw_request), json_rpc::Request(), std::move(ra_request), ra_response); + if (m_inproc_node || request.transaction.anonymity == 0) { + if(request.transaction.anonymity != 0) + m_inproc_node->on_get_random_outputs( + nullptr, http::RequestData(raw_request), json_rpc::Request(), std::move(ra_request), ra_response); selector.add_mixed_inputs(m_wallet_state.get_wallet().get_view_secret_key(), request.any_spend_address ? &m_wallet_state.get_wallet() : nullptr, only_records, &builder, request.transaction.anonymity, std::move(ra_response)); Transaction tx = builder.sign(m_wallet_state.get_wallet().get_tx_derivation_seed()); response.binary_transaction = seria::to_binary(tx); - Hash transaction_hash = get_transaction_hash(tx); - if (request.save_history && !m_wallet_state.get_wallet().save_history(transaction_hash, history)) { + const Hash tx_hash = get_transaction_hash(tx); + if (request.save_history && !m_wallet_state.get_wallet().save_history(tx_hash, history)) { m_log(logging::ERROR) << "Saving transaction history failed, you will need to pass list of destination addresses to generate sending proof for tx=" - << transaction_hash << std::endl; + << tx_hash << std::endl; response.save_history_error = true; } - api::Transaction ptx{}; - if (!m_wallet_state.parse_raw_transaction(ptx, tx, transaction_hash)) { - // TODO - process error - } + if (!m_wallet_state.parse_raw_transaction(ptx, tx, tx_hash)) + throw json_rpc::Error(json_rpc::INTERNAL_ERROR, "Created trsnsaction cannot be parsed"); response.transaction = ptx; return true; } - api::walletd::CreateTransaction::Request request_copy = request; // TODO ??? http::RequestData new_request = json_rpc::create_request(api::bytecoind::url(), api::bytecoind::GetRandomOutputs::method(), ra_request); @@ -499,7 +497,6 @@ bool WalletNode::handle_create_transaction(http::Client *who, http::RequestData } Transaction tx{}; api::walletd::CreateTransaction::Response last_response; - Hash tx_hash{}; json_rpc::Response json_resp(random_response.body); api::bytecoind::GetRandomOutputs::Response ra_response; json_resp.get_result(ra_response); @@ -508,7 +505,7 @@ bool WalletNode::handle_create_transaction(http::Client *who, http::RequestData request.transaction.anonymity, std::move(ra_response)); tx = builder.sign(m_wallet_state.get_wallet().get_tx_derivation_seed()); last_response.binary_transaction = seria::to_binary(tx); - tx_hash = get_transaction_hash(tx); + const Hash tx_hash = get_transaction_hash(tx); if (request.save_history && !m_wallet_state.get_wallet().save_history(tx_hash, history)) { m_log(logging::ERROR) << "Saving transaction history failed, you will need to pass list of destination addresses to generate sending proof for tx=" diff --git a/src/version.hpp b/src/version.hpp index af79c926..f2a1af67 100644 --- a/src/version.hpp +++ b/src/version.hpp @@ -4,8 +4,8 @@ #pragma once // defines are for Windows resource compiler -#define bytecoin_VERSION_WINDOWS_COMMA 3, 18, 10, 9 -#define bytecoin_VERSION_STRING "3.3.2" +#define bytecoin_VERSION_WINDOWS_COMMA 3, 18, 11, 22 +#define bytecoin_VERSION_STRING "3.3.3" #ifndef RC_INVOKED // Windows resource compiler