diff --git a/contrib/epee/include/epee/misc_log_ex.h b/contrib/epee/include/epee/misc_log_ex.h index c577365e330..e65f297399f 100755 --- a/contrib/epee/include/epee/misc_log_ex.h +++ b/contrib/epee/include/epee/misc_log_ex.h @@ -205,7 +205,8 @@ enum console_colors console_color_blue, console_color_cyan, console_color_magenta, - console_color_yellow + console_color_yellow, + console_color_orange }; bool is_stdout_a_tty(); diff --git a/contrib/epee/src/mlog.cpp b/contrib/epee/src/mlog.cpp index f6fc970c017..b2ac4cc83b7 100755 --- a/contrib/epee/src/mlog.cpp +++ b/contrib/epee/src/mlog.cpp @@ -443,7 +443,19 @@ void set_console_color(int color, bool bright) #endif } break; - + case console_color_orange: + { +#ifdef WIN32 + HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(h_stdout, FOREGROUND_RED | FOREGROUND_GREEN | (bright ? FOREGROUND_INTENSITY : 0)); +#else + if (bright) + std::cout << "\033[1;38;5;208m"; // Use ANSI escape code for bright orange (38;5;208). + else + std::cout << "\033[0;38;5;166m"; // Use ANSI escape code for normal orange (38;5;166). +#endif + } + break; } } diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index 92b854b031b..fcfc4bd6d71 100755 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -184,7 +184,7 @@ namespace cryptonote txversion version; txtype type; - bool is_transfer() const { return type == txtype::standard || type == txtype::stake || type == txtype::beldex_name_system; } + bool is_transfer() const { return type == txtype::standard || type == txtype::stake || type == txtype::beldex_name_system || type == txtype::coin_burn; } // not used after version 2, but remains for compatibility uint64_t unlock_time; //number of block (or time), used as a limitation like: spend this tx not early then block/time @@ -529,10 +529,11 @@ namespace cryptonote constexpr txtype transaction_prefix::get_max_type_for_hf(uint8_t hf_version) { txtype result = txtype::standard; - if (hf_version >= network_version_16) result = txtype::beldex_name_system; + if (hf_version >= network_version_18_bns) result = txtype::coin_burn; + else if (hf_version >= network_version_16) result = txtype::beldex_name_system; else if (hf_version >= network_version_15_flash) result = txtype::stake; else if (hf_version >= network_version_11_infinite_staking) result = txtype::key_image_unlock; - else if (hf_version >= network_version_9_master_nodes) result = txtype::state_change; + else if (hf_version >= network_version_9_master_nodes) result = txtype::state_change; return result; } @@ -557,7 +558,8 @@ namespace cryptonote case txtype::state_change: return "state_change"; case txtype::key_image_unlock: return "key_image_unlock"; case txtype::stake: return "stake"; - case txtype::beldex_name_system: return "beldex_name_system"; + case txtype::beldex_name_system: return "beldex_name_system"; + case txtype::coin_burn: return "coin_burn"; default: assert(false); return "xx_unhandled_type"; } } diff --git a/src/cryptonote_basic/txtypes.h b/src/cryptonote_basic/txtypes.h index 03968085196..d3f4b6cb9ff 100644 --- a/src/cryptonote_basic/txtypes.h +++ b/src/cryptonote_basic/txtypes.h @@ -18,6 +18,7 @@ enum class txversion : uint16_t { key_image_unlock, stake, beldex_name_system, + coin_burn, _count }; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 5821378bfc7..544dfaa2414 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -3267,7 +3267,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, if (tx.is_transfer()) { - if (tx.type != txtype::beldex_name_system && hf_version >= HF_VERSION_MIN_2_OUTPUTS && tx.vout.size() < 2) + if (tx.type != txtype::beldex_name_system && tx.type != txtype::coin_burn && hf_version >= HF_VERSION_MIN_2_OUTPUTS && tx.vout.size() < 2) { MERROR_VER("Tx " << get_transaction_hash(tx) << " has fewer than two outputs, which is not allowed as of hardfork " << +HF_VERSION_MIN_2_OUTPUTS); tvc.m_too_few_outputs = true; @@ -3557,6 +3557,17 @@ if (tx.version >= cryptonote::txversion::v2_ringct) return false; } } + else if (tx.type == txtype::coin_burn) + { + uint64_t burn = cryptonote::get_burned_amount_from_tx_extra(tx.extra); + if (burn == 0) + { + std::string fail_reason = "Burn amount must not equals to zero"; + MERROR_VER("Failed to validate Burn TX reason: " << fail_reason); + tvc.m_verbose_error = std::move(fail_reason); + return false; + } + } } } else diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 02a783d8750..910de9a1e09 100755 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -218,7 +218,7 @@ namespace cryptonote } else { - if (tx.type != txtype::standard && tx.type != txtype::stake) + if (tx.type != txtype::standard && tx.type != txtype::stake && tx.type != txtype::coin_burn) { // NOTE(beldex): This is a developer error. If we come across this in production, be conservative and just reject MERROR("Unrecognised transaction type: " << tx.type << " for tx: " << get_transaction_hash(tx)); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 7871dc21df0..95c08909d68 100755 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -263,6 +263,9 @@ namespace const char* USAGE_BNS_MAKE_UPDATE_MAPPING_SIGNATURE("bns_make_update_mapping_signature [owner=] [backup_owner=] "); const char* USAGE_BNS_BY_OWNER("bns_by_owner [ ...]"); const char* USAGE_BNS_LOOKUP("bns_lookup [ ...]"); + + const char* USAGE_COIN_BURN("coin_burn [index=[,,...]] [] "); + #if defined (BELDEX_ENABLE_INTEGRATION_TEST_HOOKS) std::string input_line(const std::string &prompt, bool yesno = false) @@ -3117,6 +3120,11 @@ Pending or Failed: "failed"|"pending", "out", Lock, Checkpointed, Time, Amount* [this](const auto& x) { return bns_make_update_mapping_signature(x); }, tr(USAGE_BNS_MAKE_UPDATE_MAPPING_SIGNATURE), tr(tools::wallet_rpc::BNS_MAKE_UPDATE_SIGNATURE::description)); + + m_cmd_binder.set_handler("coin_burn", + [this](const auto& x) { return coin_burn(x); }, + tr(USAGE_COIN_BURN), + tr(tools::wallet_rpc::COIN_BURN::description)); } simple_wallet::~simple_wallet() @@ -7203,6 +7211,119 @@ bool simple_wallet::bns_by_owner(const std::vector& args) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::coin_burn(std::vector args) +{ + if (!try_connect_to_daemon()) + return false; + + size_t outputs = 1; + // priority and subaddress fetch + uint32_t priority = 0; + std::set subaddr_indices = {}; + if (!parse_subaddr_indices_and_priority(*m_wallet, args, subaddr_indices, priority, m_current_subaddress_account)) return false; + + // get the burn amount from the argument + static constexpr auto BURN_PREFIX = "burn="sv; + uint64_t burn_amount = 0; + crypto::hash txid; + std::string burn_amount_str = eat_named_argument(args, BURN_PREFIX); + + if (!burn_amount_str.empty() && !cryptonote::parse_amount(burn_amount, burn_amount_str)) { + if (!tools::hex_to_type(burn_amount_str, txid)) + { + fail_msg_writer() << tr("failed to parse transactio ID or Invalid amount"); + return true; + } + } + + if(args.size() != 0){ + PRINT_USAGE(USAGE_COIN_BURN); + return false; + } + if(!txid && burn_amount == 0){ + fail_msg_writer() << tr("Burn amount equals to zero/not given."); + PRINT_USAGE(USAGE_COIN_BURN); + return false; + } + // unlock the wallet for getting the keys + SCOPED_WALLET_UNLOCK(); + // create_transaction2 try for burning + try + { + std::vector extra; + std::vector ptx_vector; + std::optional hf_version = m_wallet->get_hard_fork_version(); + if (!hf_version) + { + fail_msg_writer() << tools::ERR_MSG_NETWORK_VERSION_QUERY_FAILED; + return false; + } + // parms are constructed + beldex_construct_tx_params tx_params = tools::wallet2::construct_params(*hf_version, txtype::coin_burn, priority, burn_amount); + // transaction process called + if(burn_amount){ + ptx_vector = m_wallet->create_transactions_2({}, CRYPTONOTE_DEFAULT_TX_MIXIN, 0, priority, extra, m_current_subaddress_account, subaddr_indices, tx_params); + }else{ + tools::wallet2::transfer_container transfers; + bool available = false; + std::vector ki; + m_wallet->get_transfers(transfers); + + for (const auto& td : transfers) + { + if(td.m_txid == txid) + { + available = true; + if(!td.m_spent) + { + ki.push_back(td.m_key_image) ; + } + } + } + if(available && ki.size() == 0) + { + fail_msg_writer() << tr("The txid already spent."); + return false; + } + if(!available) + { + fail_msg_writer() << tr("No incoming available transfers"); + return false; + } + + ptx_vector = m_wallet->create_transactions_burn(ki, outputs, CRYPTONOTE_DEFAULT_TX_MIXIN, 0, priority, extra); + } + + if (ptx_vector.empty()) + { + fail_msg_writer() << tr("No outputs found, or daemon is not ready"); + return false; + } + + std::vector dsts; + cryptonote::address_parse_info info = {}; + info.address = m_wallet->get_subaddress({m_current_subaddress_account, 0}); + info.is_subaddress = m_current_subaddress_account != 0; + dsts.push_back(info); + + if (!confirm_and_send_tx(dsts, ptx_vector, priority == tools::tx_priority_flash)) + return false; + } + catch (const std::exception &e) + { + handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon()); + return true; + } + catch (...) + { + LOG_ERROR("unknown error"); + fail_msg_writer() << tr("unknown error"); + return true; + } + + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::sweep_unmixable(const std::vector &args_) { if (!try_connect_to_daemon()) @@ -8624,8 +8745,9 @@ bool simple_wallet::show_transfers(const std::vector &args_) case wallet::pay_type::governance: color = epee::console_color_cyan; break; case wallet::pay_type::stake: color = epee::console_color_blue; break; case wallet::pay_type::bns: color = epee::console_color_blue; break; - case wallet::pay_type::master_node: color = epee::console_color_cyan; break; - default: color = epee::console_color_magenta; break; + case wallet::pay_type::coin_burn: color = epee::console_color_orange; break; + case wallet::pay_type::master_node: color = epee::console_color_cyan; break; + default: color = epee::console_color_magenta; break; } } @@ -8647,6 +8769,8 @@ bool simple_wallet::show_transfers(const std::vector &args_) transfer.pay_type == wallet::pay_type::bns || transfer.pay_type == wallet::pay_type::miner) destinations += output.address.substr(0, 6); + else if (transfer.pay_type == wallet::pay_type::coin_burn){ + destinations = "-"; continue;} else destinations += output.address; diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index ce187049eaf..3965355ffad 100755 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -190,6 +190,8 @@ namespace cryptonote bool bns_by_owner(const std::vector &args); bool bns_lookup(std::vector args); + bool coin_burn(std::vector args); + enum class sweep_type_t { stake, register_stake, all_or_below, single }; bool sweep_main_internal(sweep_type_t sweep_type, std::vector &ptx_vector, cryptonote::address_parse_info const &dest, bool flash); bool sweep_main(uint32_t account, uint64_t below, Transfer transfer_type, const std::vector &args); diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp index d18a9befd15..8e5090a660c 100755 --- a/src/wallet/api/transaction_history.cpp +++ b/src/wallet/api/transaction_history.cpp @@ -145,7 +145,8 @@ void TransactionHistoryImpl::refresh() ti->m_hash = tools::type_to_hex(pd.m_tx_hash); ti->m_blockheight = pd.m_block_height; ti->m_is_stake = pd.m_type == wallet::pay_type::stake; - ti->m_is_bns = pd.m_type == wallet::pay_type::bns; + ti->m_is_bns = pd.m_type == wallet::pay_type::bns; + ti->m_is_coin_burn = pd.m_type == wallet::pay_type::coin_burn; ti->m_subaddrIndex = { pd.m_subaddr_index.minor }; ti->m_subaddrAccount = pd.m_subaddr_index.major; ti->m_label = w->get_subaddress_label(pd.m_subaddr_index); @@ -192,6 +193,7 @@ void TransactionHistoryImpl::refresh() ti->m_blockheight = pd.m_block_height; ti->m_is_stake = pd.m_pay_type == wallet::pay_type::stake; ti->m_is_bns = pd.m_pay_type == wallet::pay_type::bns; + ti->m_is_coin_burn = pd.m_pay_type == wallet::pay_type::coin_burn; ti->m_subaddrIndex = pd.m_subaddr_indices; ti->m_subaddrAccount = pd.m_subaddr_account; ti->m_label = pd.m_subaddr_indices.size() == 1 ? w->get_subaddress_label({pd.m_subaddr_account, *pd.m_subaddr_indices.begin()}) : ""; @@ -225,6 +227,7 @@ void TransactionHistoryImpl::refresh() ti->m_direction = TransactionInfo::Direction_Out; ti->m_is_stake = pd.m_pay_type == wallet::pay_type::stake; ti->m_is_bns = pd.m_pay_type == wallet::pay_type::bns; + ti->m_is_coin_burn = pd.m_pay_type == wallet::pay_type::coin_burn; ti->m_failed = is_failed; ti->m_pending = true; ti->m_hash = tools::type_to_hex(hash); @@ -253,6 +256,7 @@ void TransactionHistoryImpl::refresh() ti->m_blockheight = pd.m_block_height; ti->m_is_stake = pd.m_type == wallet::pay_type::stake; ti->m_is_bns = pd.m_type == wallet::pay_type::bns; + ti->m_is_coin_burn = pd.m_type == wallet::pay_type::coin_burn; ti->m_pending = true; ti->m_subaddrIndex = { pd.m_subaddr_index.minor }; ti->m_subaddrAccount = pd.m_subaddr_index.major; diff --git a/src/wallet/api/transaction_info.cpp b/src/wallet/api/transaction_info.cpp index aebae20b1ef..12123e6e97d 100755 --- a/src/wallet/api/transaction_info.cpp +++ b/src/wallet/api/transaction_info.cpp @@ -47,6 +47,7 @@ TransactionInfoImpl::TransactionInfoImpl() : m_direction(Direction_Out) , m_is_stake(false) , m_is_bns(false) + , m_is_coin_burn(false) , m_pending(false) , m_failed(false) , m_reward_type(reward_type::unspecified) diff --git a/src/wallet/api/transaction_info.h b/src/wallet/api/transaction_info.h index d96114997f7..9e2e3b61ad3 100755 --- a/src/wallet/api/transaction_info.h +++ b/src/wallet/api/transaction_info.h @@ -73,6 +73,7 @@ class TransactionInfoImpl : public TransactionInfo int m_direction; bool m_is_stake; bool m_is_bns; + bool m_is_coin_burn; bool m_pending; bool m_failed; reward_type m_reward_type; // may have a value rather than `unspecified` after hf 10 diff --git a/src/wallet/transfer_view.h b/src/wallet/transfer_view.h index f5ec76266bb..464122e2bf6 100755 --- a/src/wallet/transfer_view.h +++ b/src/wallet/transfer_view.h @@ -47,7 +47,8 @@ enum struct pay_type miner, master_node, governance, - bns + bns, + coin_burn }; inline const char *pay_type_string(pay_type type) @@ -62,6 +63,7 @@ inline const char *pay_type_string(pay_type type) case pay_type::bns: return "bns"; case pay_type::master_node: return "mnode"; case pay_type::governance: return "gov"; + case pay_type::coin_burn: return "burn"; default: assert(false); return "xxxxx"; } } @@ -72,6 +74,7 @@ inline pay_type pay_type_from_tx(const cryptonote::transaction tx) { case cryptonote::txtype::stake: return wallet::pay_type::stake; case cryptonote::txtype::beldex_name_system: return wallet::pay_type::bns; + case cryptonote::txtype::coin_burn: return wallet::pay_type::coin_burn; default: return wallet::pay_type::out; } } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 59d7997b8d5..65506f8f8cd 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -231,7 +231,7 @@ namespace { found_money += transfers[idx].amount(); if (found_money != needed_money) ++outputs; // change - if (outputs < (tx_params.tx_type == cryptonote::txtype::beldex_name_system ? 1 : 2)) + if (outputs < ((tx_params.tx_type == cryptonote::txtype::beldex_name_system || tx_params.tx_type == cryptonote::txtype::coin_burn) ? 1 : 2)) ++outputs; // extra 0 dummy output return outputs; } @@ -300,7 +300,9 @@ void do_prepare_file_names(const fs::path& file_path, fs::path& keys_file, fs::p uint64_t calculate_fee_from_weight(byte_and_output_fees base_fees, uint64_t weight, uint64_t outputs, uint64_t fee_percent, uint64_t fee_fixed, uint64_t fee_quantization_mask) { uint64_t fee = (weight * base_fees.first + outputs * base_fees.second) * fee_percent / 100; + LOG_PRINT_L1("fee(calculate_fee_from_weight) for rct tx: " << fee); fee = (fee + fee_quantization_mask - 1) / fee_quantization_mask * fee_quantization_mask + fee_fixed; + LOG_PRINT_L1("fee(calculate_fee_from_weight(AFTER)) for rct tx: " << fee); return fee; } @@ -788,6 +790,7 @@ uint64_t estimate_tx_weight(int n_inputs, int mixin, int n_outputs, size_t extra uint64_t estimate_fee(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool clsag, byte_and_output_fees base_fees, uint64_t fee_percent, uint64_t fee_fixed, uint64_t fee_quantization_mask) { const size_t estimated_tx_weight = estimate_tx_weight(n_inputs, mixin, n_outputs, extra_size, clsag); + LOG_PRINT_L1("estimated_tx_weight for rct tx: " << estimated_tx_weight); return calculate_fee_from_weight(base_fees, estimated_tx_weight, n_outputs, fee_percent, fee_fixed, fee_quantization_mask); } @@ -6334,6 +6337,8 @@ void wallet2::get_transfers(get_transfers_args_t args, std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector>> unlocked_balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account, false); std::map balance_per_subaddr = balance_per_subaddress(subaddr_account, false); @@ -10950,7 +10966,7 @@ std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_single(const crypt return create_transactions_from(address, is_subaddress, outputs, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, tx_type); } +std::vector wallet2::create_transactions_burn(const std::vector &ki, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra_base, cryptonote::txtype tx_type) +{ + std::vector unused_transfers_indices; + std::vector unused_dust_indices; + // find output with the given key image + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details& td = m_transfers[i]; + if (td.m_key_image_known && std::find(ki.begin(), ki.end(), td.m_key_image) != ki.end() && !is_spent(td, false) && !td.m_frozen && is_transfer_unlocked(td)) + { + if (td.is_rct()) + unused_transfers_indices.push_back(i); + else + unused_dust_indices.push_back(i); + } + } + + //ensure device is let in NONE mode in any case + hw::device &hwdev = m_account.get_device(); + std::unique_lock hwdev_lock{hwdev}; + hw::mode_resetter rst{hwdev}; + + std::vector dsts; + auto original_dsts = dsts; + + uint64_t accumulated_fee, accumulated_outputs, accumulated_change; + struct TX { + std::vector selected_transfers; + std::vector dsts; + cryptonote::transaction tx; + pending_tx ptx; + size_t weight; + uint64_t needed_fee; + std::vector> outs; + + TX() : weight(0), needed_fee(0) {} + }; + std::vector txes; + uint64_t needed_fee, available_for_fee = 0; + uint64_t upper_transaction_weight_limit = get_upper_transaction_weight_limit(); + std::vector> outs; + + const bool clsag = use_fork_rules(HF_VERSION_CLSAG, 0); + const rct::RCTConfig rct_config { rct::RangeProofType::PaddedBulletproof, clsag ? 3 : 2 }; + const auto base_fee = get_base_fees(); + const uint64_t fee_percent = get_fee_percent(priority, tx_type); + const uint64_t fee_quantization_mask = get_fee_quantization_mask(); + uint64_t fixed_fee = 0; + + std::optional hf_version = get_hard_fork_version(); + THROW_WALLET_EXCEPTION_IF(!hf_version, error::get_hard_fork_version_error, "Failed to query current hard fork version"); + + beldex_construct_tx_params beldex_tx_params = tools::wallet2::construct_params(*hf_version, tx_type, priority); + uint64_t burn_fixed = 0, burn_percent = 0; + + // Swap these out because we don't want them present for building intermediate temporary tx + // calculations (which we don't actually use); we'll set them again at the end before we build the + // real transactions. + std::swap(burn_fixed, beldex_tx_params.burn_fixed); + std::swap(burn_percent, beldex_tx_params.burn_percent); + THROW_WALLET_EXCEPTION_IF(beldex_tx_params.hf_version < HF_VERSION_FEE_BURNING, error::wallet_internal_error, "cannot construct transaction: cannot burn amounts under the current hard fork"); + { + std::vector extra_plus; // Copy and modified from input if modification needed + extra_plus = extra_base; + add_burned_amount_to_tx_extra(extra_plus, 0); + fixed_fee += burn_fixed; + THROW_WALLET_EXCEPTION_IF(burn_percent > fee_percent, error::wallet_internal_error, "invalid burn fees: cannot burn more than the tx fee"); + } + LOG_PRINT_L2("Starting with " << unused_transfers_indices.size() << " non-dust outputs and " << unused_dust_indices.size() << " dust outputs"); + + if (unused_dust_indices.empty() && unused_transfers_indices.empty()) + return std::vector(); + + // start with an empty tx + txes.push_back(TX()); + accumulated_fee = 0; + accumulated_outputs = 0; + accumulated_change = 0; + needed_fee = 0; + + // while we have something to send + hwdev.set_mode(hw::device::mode::TRANSACTION_CREATE_FAKE); + while (!unused_dust_indices.empty() || !unused_transfers_indices.empty()) { + TX &tx = txes.back(); + + // get a random unspent output and use it to pay next chunk. We try to alternate + // dust and non dust to ensure we never get with only dust, from which we might + // get a tx that can't pay for itself + uint64_t fee_dust_threshold; + { + const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra_base.size(), clsag); + fee_dust_threshold = calculate_fee_from_weight(base_fee, estimated_tx_weight_with_one_extra_output, outputs, fee_percent, fixed_fee, fee_quantization_mask); + } + + size_t idx = + unused_transfers_indices.empty() + ? pop_best_value(unused_dust_indices, tx.selected_transfers) + : unused_dust_indices.empty() + ? pop_best_value(unused_transfers_indices, tx.selected_transfers) + : ((tx.selected_transfers.size() & 1) || accumulated_outputs > fee_dust_threshold) + ? pop_best_value(unused_dust_indices, tx.selected_transfers) + : pop_best_value(unused_transfers_indices, tx.selected_transfers); + + const transfer_details &td = m_transfers[idx]; + LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount())); + + // add this output to the list to spend + tx.selected_transfers.push_back(idx); + uint64_t available_amount = td.amount(); + accumulated_outputs += available_amount; + + // clear any fake outs we'd already gathered, since we'll need a new set + outs.clear(); + + // here, check if we need to sent tx and start a new one + LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit " + << upper_transaction_weight_limit); + const size_t estimated_rct_tx_weight = estimate_tx_weight(tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra_base.size(), clsag); + bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || ( estimated_rct_tx_weight >= tx_weight_target(upper_transaction_weight_limit)); + LOG_PRINT_L2("Accumulated_outputs : " << accumulated_outputs); + if (try_tx) { + cryptonote::transaction test_tx; + pending_tx test_ptx; + + const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers, beldex_tx_params); + needed_fee = estimate_fee(tx.selected_transfers.size(), fake_outs_count, num_outputs, extra_base.size(), clsag, base_fee, fee_percent, fixed_fee, fee_quantization_mask); + + // add N - 1 outputs for correct initial fee estimation + for (size_t i = 0; i < ((outputs > 1) ? outputs - 1 : outputs); ++i) + tx.dsts.push_back(tx_destination_entry(0, account_public_address{}, false)); + + LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " << + tx.selected_transfers.size() << " outputs"); + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra_base, + test_tx, test_ptx, rct_config, beldex_tx_params); + auto txBlob = t_serializable_object_to_blob(test_ptx.tx); + needed_fee = calculate_fee(test_ptx.tx, txBlob.size(), base_fee, fee_percent, fixed_fee, fee_quantization_mask); + available_for_fee = test_ptx.fee + test_ptx.change_dts.amount; + for (auto &dt: test_ptx.dests) + available_for_fee += dt.amount; + LOG_PRINT_L2("Made a " << get_weight_string(test_ptx.tx, txBlob.size()) << " 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"); + + do { + LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); + // distribute total transferred amount between outputs + uint64_t amount_burned = available_for_fee - needed_fee; + fixed_fee = amount_burned; + needed_fee += fixed_fee; + uint64_t dt_amount = 0; + for (auto &dt: tx.dsts) + { + dt.amount = dt_amount; + } + beldex_tx_params.burn_fixed = amount_burned; + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra_base, + test_tx, test_ptx, rct_config, beldex_tx_params); + txBlob = t_serializable_object_to_blob(test_ptx.tx); + needed_fee = calculate_fee(test_ptx.tx, txBlob.size(), base_fee, fee_percent, fixed_fee, fee_quantization_mask); + LOG_PRINT_L2("Made an attempt at a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << + " fee " << print_money(needed_fee) << " needed fee " << "and " << print_money(test_ptx.change_dts.amount) << " change"); + } while (false); + + LOG_PRINT_L2("Made a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << + " fee and " << print_money(test_ptx.change_dts.amount) << " change"); + + tx.tx = test_tx; + tx.ptx = test_ptx; + tx.weight = get_transaction_weight(test_tx, txBlob.size()); + tx.outs = outs; + tx.needed_fee = test_ptx.fee; + accumulated_fee += test_ptx.fee; + accumulated_change += test_ptx.change_dts.amount; + if (!unused_transfers_indices.empty() || !unused_dust_indices.empty()) + { + LOG_PRINT_L2("We have more to pay, starting another tx"); + txes.push_back(TX()); + } + } + } + + LOG_PRINT_L1("Done creating " << txes.size() << " transactions, " << print_money(accumulated_fee) << + " total fee, " << print_money(accumulated_change) << " total change"); + hwdev.set_mode(hw::device::mode::TRANSACTION_CREATE_REAL); + for (auto &tx : txes) + { + cryptonote::transaction test_tx; + pending_tx test_ptx; + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, unlock_time, tx.needed_fee, extra_base, test_tx, test_ptx, rct_config, beldex_tx_params); + auto txBlob = t_serializable_object_to_blob(test_ptx.tx); + tx.tx = test_tx; + tx.ptx = test_ptx; + tx.weight = get_transaction_weight(test_tx, txBlob.size()); + } + std::vector ptx_vector; + for (std::vector::iterator i = txes.begin(); i != txes.end(); ++i) + { + TX &tx = *i; + uint64_t tx_money = 0; + 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() << + " " << get_transaction_hash(tx.ptx.tx) << ": " << get_weight_string(tx.weight) << ", sending " << print_money(tx_money) << " in " << tx.selected_transfers.size() << + " outputs to " << tx.dsts.size() << " destination(s), including " << + print_money(tx.ptx.fee) << " fee, " << print_money(tx.ptx.change_dts.amount) << " change"); + ptx_vector.push_back(tx.ptx); + } + THROW_WALLET_EXCEPTION_IF(!sanity_check(ptx_vector, original_dsts), error::wallet_internal_error, "Created transaction(s) failed sanity check"); + // if we made it this far, we're OK to actually send the transactions + return ptx_vector; +} + std::vector wallet2::create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, std::vector unused_transfers_indices, std::vector unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra_base, cryptonote::txtype tx_type) { //ensure device is let in NONE mode in any case diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 3ff3c4661c6..f3b72f2ee0e 100755 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -757,6 +757,7 @@ namespace tools std::vector create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, cryptonote::txtype tx_type = cryptonote::txtype::standard); std::vector create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, cryptonote::txtype tx_type = cryptonote::txtype::standard); std::vector create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, std::vector unused_transfers_indices, std::vector unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, cryptonote::txtype tx_type = cryptonote::txtype::standard); + std::vector create_transactions_burn(const std::vector &ki, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, cryptonote::txtype tx_type = cryptonote::txtype::coin_burn); bool sanity_check(const std::vector &ptx_vector, std::vector dsts) const; void cold_tx_aux_import(const std::vector& ptx, const std::vector& tx_device_aux); @@ -784,6 +785,7 @@ namespace tools bool out = false; bool stake = false; bool bns = false; + bool coin_burn = false; bool pending = false; bool failed = false; bool pool = false; diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index aee4db6c5bb..aeeddd1e909 100755 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -1063,6 +1063,79 @@ namespace tools return res; } //------------------------------------------------------------------------------------------------------------------------------ + COIN_BURN::response wallet_rpc_server::invoke(COIN_BURN::request&& req) + { + require_open(); + COIN_BURN::response res{}; + + std::vector ptx_vector; + std::vector extra; + + if(req.amount == 0 && req.tx_id.empty()) + throw wallet_rpc_error{error_code::TX_NOT_POSSIBLE, "Amount/Txid is required"}; + if(req.amount != 0 && !req.tx_id.empty()) + throw wallet_rpc_error{error_code::TX_NOT_POSSIBLE, "Only one field needed either Amount or Txid"}; + + LOG_PRINT_L3("on_burn_transfer starts"); + + if(req.amount) + { + std::optional hf_version = m_wallet->get_hard_fork_version(); + if (!hf_version) + throw wallet_rpc_error{error_code::HF_QUERY_FAILED, tools::ERR_MSG_NETWORK_VERSION_QUERY_FAILED}; + cryptonote::beldex_construct_tx_params tx_params = tools::wallet2::construct_params(*hf_version, cryptonote::txtype::coin_burn, req.priority, req.amount); + ptx_vector = m_wallet->create_transactions_2({}, CRYPTONOTE_DEFAULT_TX_MIXIN, 0, req.priority, extra, req.account_index, req.subaddr_indices, tx_params); + } + else + { + crypto::hash tx_hash; + if (!tools::hex_to_type(req.tx_id, tx_hash)) + throw wallet_rpc_error{error_code::WRONG_TXID, "failed to parse txid"}; + size_t outputs = 1; + tools::wallet2::transfer_container transfers; + bool available = false; + std::vector ki; + m_wallet->get_transfers(transfers); + for (const auto& td : transfers) + { + if (td.m_txid == tx_hash) + { + available = true; + if (!td.m_spent) + { + ki.push_back(td.m_key_image); + } + } + } + + if(available && ki.size() == 0) + throw wallet_rpc_error{error_code::TX_NOT_POSSIBLE, "The txid already spent."}; + if(!available) + throw wallet_rpc_error{error_code::TX_NOT_POSSIBLE, "No incoming available transfers"}; + ptx_vector = m_wallet->create_transactions_burn(ki, outputs, CRYPTONOTE_DEFAULT_TX_MIXIN, 0, req.priority, extra); + } + if (ptx_vector.empty()) + throw wallet_rpc_error{error_code::TX_NOT_POSSIBLE, "Failed to create coin_burn transaction:"}; + // reject proposed transactions if there are more than one. see on_transfer_split below. + if (ptx_vector.size() != 1) + throw wallet_rpc_error{error_code::TX_TOO_LARGE, "Transaction would be too large. try /transfer_split."}; + fill_response( ptx_vector, + req.get_tx_key, + res.tx_key, + res.amount, + res.fee, + res.multisig_txset, + res.unsigned_txset, + req.do_not_relay, + false /*flash*/, + res.tx_hash, + req.get_tx_hex, + res.tx_blob, + req.get_tx_metadata, + res.tx_metadata); + return res; + } + //------------------------------------------------------------------------------------------------------------------------------ TRANSFER_SPLIT::response wallet_rpc_server::invoke(TRANSFER_SPLIT::request&& req) { require_open(); @@ -1974,7 +2047,7 @@ namespace tools { res.in.push_back(std::move(entry)); } - else if (entry.pay_type == wallet::pay_type::out || entry.pay_type == wallet::pay_type::stake || entry.pay_type == wallet::pay_type::bns) + else if (entry.pay_type == wallet::pay_type::out || entry.pay_type == wallet::pay_type::stake || entry.pay_type == wallet::pay_type::bns || entry.pay_type == wallet::pay_type::coin_burn) { res.out.push_back(std::move(entry)); } diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index c834d0e9aa7..42ee4b69080 100755 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -167,6 +167,7 @@ namespace tools wallet_rpc::BNS_ADD_KNOWN_NAMES::response invoke(wallet_rpc::BNS_ADD_KNOWN_NAMES::request&& req); wallet_rpc::BNS_DECRYPT_VALUE::response invoke(wallet_rpc::BNS_DECRYPT_VALUE::request&& req); wallet_rpc::BNS_ENCRYPT_VALUE::response invoke(wallet_rpc::BNS_ENCRYPT_VALUE::request&& req); + wallet_rpc::COIN_BURN::response invoke(wallet_rpc::COIN_BURN::request&& req); wallet_rpc::QUERY_KEY::response invoke(wallet_rpc::QUERY_KEY::request&& req); private: diff --git a/src/wallet/wallet_rpc_server_commands_defs.cpp b/src/wallet/wallet_rpc_server_commands_defs.cpp index 26c486afcc5..017b463c8b5 100755 --- a/src/wallet/wallet_rpc_server_commands_defs.cpp +++ b/src/wallet/wallet_rpc_server_commands_defs.cpp @@ -1294,4 +1294,27 @@ KV_SERIALIZE_MAP_CODE_BEGIN(BNS_ENCRYPT_VALUE::response) KV_SERIALIZE(encrypted_value) KV_SERIALIZE_MAP_CODE_END() +KV_SERIALIZE_MAP_CODE_BEGIN(COIN_BURN::request) + KV_SERIALIZE(amount) + KV_SERIALIZE(tx_id) + KV_SERIALIZE(account_index) + KV_SERIALIZE(subaddr_indices) + KV_SERIALIZE(get_tx_key) + KV_SERIALIZE_OPT(priority, (uint32_t)0); + KV_SERIALIZE_OPT(do_not_relay, false) + KV_SERIALIZE_OPT(get_tx_hex, false) + KV_SERIALIZE_OPT(get_tx_metadata, false) +KV_SERIALIZE_MAP_CODE_END() + + +KV_SERIALIZE_MAP_CODE_BEGIN(COIN_BURN::response) + KV_SERIALIZE(tx_hash) + KV_SERIALIZE(tx_key) + KV_SERIALIZE(amount) + KV_SERIALIZE(fee) + KV_SERIALIZE(tx_blob) + KV_SERIALIZE(tx_metadata) + KV_SERIALIZE(multisig_txset) + KV_SERIALIZE(unsigned_txset) +KV_SERIALIZE_MAP_CODE_END() } diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index f486f5776c1..d18c6f51789 100755 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -2509,6 +2509,42 @@ This command is only required if the open wallet is one of the owners of a BNS r }; }; + BELDEX_RPC_DOC_INTROSPECT + struct COIN_BURN : RESTRICTED + { + static constexpr auto names() { return NAMES("burn" , "coin_burn"); } + static constexpr const char *description = R"(A coin burn is a process in which a predetermined amount of cryptocurrency is permanently removed from circulation. Once burned, the specified amount of coins is eliminated from the blockchain, making them inaccessible and unrecoverable.)"; + + struct request + { + uint64_t amount; // burn amount + std::string tx_id; // Transaction ID used to find the transfer. + uint32_t account_index; // (Optional) Transfer from this account index. (Defaults to 0) + std::set subaddr_indices; // (Optional) Transfer from this set of subaddresses. (Defaults to 0). + uint32_t priority; + bool get_tx_key; // (Optional) Return the transaction key after sending. + bool do_not_relay; // (Optional) If true, the newly created transaction will not be relayed to the beldex network. (Defaults to false) + bool get_tx_hex; // Return the transaction as hex string after sending. (Defaults to false) + bool get_tx_metadata; // Return the metadata needed to relay the transaction. (Defaults to false) + + KV_MAP_SERIALIZABLE + }; + + struct response + { + std::string tx_hash; // Publicly searchable transaction hash. + std::string tx_key; // Transaction key if get_tx_key is true, otherwise, blank string. + uint64_t amount; // Amount transferred for the transaction. + uint64_t fee; // Fee charged for the txn. + std::string tx_blob; // Raw transaction represented as hex string, if get_tx_hex is true. + std::string tx_metadata; // Set of transaction metadata needed to relay this transfer later, if get_tx_metadata is true. + std::string multisig_txset; // Set of multisig transactions in the process of being signed (empty for non-multisig). + std::string unsigned_txset; // Set of unsigned tx for cold-signing purposes. + + KV_MAP_SERIALIZABLE + }; + }; + /// List of all supported rpc command structs to allow compile-time enumeration of all supported /// RPC types. Every type added above that has an RPC endpoint needs to be added here, and needs /// a core_rpc_server::invoke() overload that takes a ::request and returns a @@ -2612,7 +2648,8 @@ This command is only required if the open wallet is one of the owners of a BNS r BNS_KNOWN_NAMES, BNS_ADD_KNOWN_NAMES, BNS_DECRYPT_VALUE, - BNS_ENCRYPT_VALUE + BNS_ENCRYPT_VALUE, + COIN_BURN >; }