diff --git a/src/common/va_args.h b/src/common/va_args.h new file mode 100644 index 0000000..3389b1c --- /dev/null +++ b/src/common/va_args.h @@ -0,0 +1,45 @@ +// Copyright (c) 2024, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +// Check for __VA_OPT__ support +// Apdated from cpplearner's StackOverflow answer: https://stackoverflow.com/a/48045656 +#define PP_THIRD_ARG(a,b,c,...) c +#define VA_OPT_SUPPORTED_I(...) PP_THIRD_ARG(__VA_OPT__(,),true,false,) +#define VA_OPT_SUPPORTED VA_OPT_SUPPORTED_I(?) + +// VA_ARGS_COMMAPREFIX(): VA_ARGS_COMMAPREFIX(__VA_ARGS__) expands to __VA_ARGS__ with a comma in +// front if more than one argument, else nothing. +// If __VA_OPT__ supported, use that. Else, use GCC's ,## hack +#if VA_OPT_SUPPORTED +# define VA_ARGS_COMMAPREFIX(...) __VA_OPT__(,) __VA_ARGS__ +#else +# define VA_ARGS_COMMAPREFIX(...) , ## __VA_ARGS__ +#endif + diff --git a/src/serialization/serialization.h b/src/serialization/serialization.h index de8e7ea..796a7c9 100644 --- a/src/serialization/serialization.h +++ b/src/serialization/serialization.h @@ -50,6 +50,8 @@ #include #include +#include "common/va_args.h" + /*! \struct is_blob_type * * \brief a descriptor for dispatching serialize @@ -139,6 +141,19 @@ inline bool do_serialize(Archive &ar, bool &v) template class Archive> \ bool do_serialize_object(Archive &ar){ + +/*! \macro BEGIN_SERIALIZE_OBJECT_FN + * + * \brief Begins the environment of the DSL as a free function in object-style + * + * Inside, instead of FIELD() and VARINT_FIELD(), use FIELD_F() and + * VARINT_FIELD_F(). Otherwise, this macro is similar to + * BEGIN_SERIALIZE_OBJECT(), as you should list only field serializations. + */ +#define BEGIN_SERIALIZE_OBJECT_FN(stype, ...) \ + template class Archive> \ + bool do_serialize_object(Archive &ar, stype &v VA_ARGS_COMMAPREFIX(__VA_ARGS__)) { + /*! \macro PREPARE_CUSTOM_VECTOR_SERIALIZATION */ #define PREPARE_CUSTOM_VECTOR_SERIALIZATION(size, vec) \ @@ -173,6 +188,12 @@ inline bool do_serialize(Archive &ar, bool &v) if (!r || !ar.good()) return false; \ } while(0); +/*! \macro FIELD_F(f) + * + * \brief tags the field with the variable name and then serializes it (for use in a free function) + */ +#define FIELD_F(f, ...) FIELD_N(#f, v.f VA_ARGS_COMMAPREFIX(__VA_ARGS__)) + /*! \macro FIELDS(f) * * \brief does not add a tag to the serialized value @@ -204,6 +225,12 @@ inline bool do_serialize(Archive &ar, bool &v) if (!ar.good()) return false; \ } while(0); +/*! \macro VARINT_FIELD_F(f) + * + * \brief tags and serializes the varint \a f (for use in a free function) + */ +#define VARINT_FIELD_F(f) VARINT_FIELD_N(#f, v.f) + /*! \macro MAGIC_FIELD(m) */ #define MAGIC_FIELD(m) \ diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index a0ee9bd..7858393 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -73,6 +73,7 @@ #include "common/json_util.h" #include "ringct/rctSigs.h" #include "multisig/multisig.h" +#include "wallet/tx_builder.h" #include "wallet/wallet_args.h" #include "version.h" #include @@ -1480,7 +1481,10 @@ bool simple_wallet::import_multisig_main(const std::vector &args, b bool simple_wallet::accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs) { std::string extra_message; - return accept_loaded_tx([&txs](){return txs.m_ptx.size();}, [&txs](size_t n)->const tools::wallet2::tx_construction_data&{return txs.m_ptx[n].construction_data;}, extra_message); + return accept_loaded_tx( + [&txs](){return txs.m_ptx.size();}, + [&txs](size_t n)->const auto&{return std::get(txs.m_ptx[n].construction_data);}, + extra_message); } bool simple_wallet::sign_multisig(const std::vector &args) @@ -6669,7 +6673,9 @@ bool simple_wallet::process_ring_members(const std::vector= rct::RCTTypeFcmpPlusPlus) + continue; + const auto& construction_data = std::get(ptx_vector[n].construction_data); if (verbose) ostr << boost::format(tr("\nTransaction %llu/%llu: txid=%s")) % (n + 1) % ptx_vector.size() % cryptonote::get_transaction_hash(tx); // for each input @@ -7284,8 +7290,8 @@ bool simple_wallet::transfer_main( { prompt << tr("\nTransaction ") << (n + 1) << "/" << ptx_vector.size() << ":\n"; subaddr_indices.clear(); - for (uint32_t i : ptx_vector[n].construction_data.subaddr_indices) - subaddr_indices.insert(i); + for (const std::size_t selected_transfer : ptx_vector.at(n).selected_transfers) + subaddr_indices.insert(m_wallet->get_transfer_details(selected_transfer).m_subaddr_index.minor); for (uint32_t i : subaddr_indices) prompt << boost::format(tr("Spending from address index %d\n")) % i; if (subaddr_indices.size() > 1) @@ -7847,8 +7853,8 @@ bool simple_wallet::sweep_main(uint32_t account, uint64_t below, bool locked, co { prompt << tr("\nTransaction ") << (n + 1) << "/" << ptx_vector.size() << ":\n"; subaddr_indices.clear(); - for (uint32_t i : ptx_vector[n].construction_data.subaddr_indices) - subaddr_indices.insert(i); + for (const std::size_t selected_transfer : ptx_vector.at(n).selected_transfers) + subaddr_indices.insert(m_wallet->get_transfer_details(selected_transfer).m_subaddr_index.minor); for (uint32_t i : subaddr_indices) prompt << boost::format(tr("Spending from address index %d\n")) % i; if (subaddr_indices.size() > 1) @@ -8976,7 +8982,10 @@ bool simple_wallet::accept_loaded_tx(const tools::wallet2::signed_tx_set &txs) std::string extra_message; if (!txs.key_images.empty()) extra_message = (boost::format("%u key images to import. ") % (unsigned)txs.key_images.size()).str(); - return accept_loaded_tx([&txs](){return txs.ptx.size();}, [&txs](size_t n)->const tools::wallet2::tx_construction_data&{return txs.ptx[n].construction_data;}, extra_message); + return accept_loaded_tx( + [&txs](){return txs.ptx.size();}, + [&txs](size_t n)->const auto&{return std::get(txs.ptx[n].construction_data);}, + extra_message); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::sign_transfer(const std::vector &args_) diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp index 70a7027..956d752 100644 --- a/src/wallet/api/pending_transaction.cpp +++ b/src/wallet/api/pending_transaction.cpp @@ -200,7 +200,13 @@ std::vector PendingTransactionImpl::subaddrAccount() const { std::vector result; for (const auto& ptx : m_pending_tx) - result.push_back(ptx.construction_data.subaddr_account); + { + const auto *pre_carrot_constr_data = std::get_if(&ptx.construction_data); + if (nullptr != pre_carrot_constr_data) + result.push_back(pre_carrot_constr_data->subaddr_account); + else + result.push_back(ptx.subaddr_account); + } return result; } @@ -208,7 +214,13 @@ std::vector> PendingTransactionImpl::subaddrIndices() const { std::vector> result; for (const auto& ptx : m_pending_tx) - result.push_back(ptx.construction_data.subaddr_indices); + { + const auto *pre_carrot_constr_data = std::get_if(&ptx.construction_data); + if (nullptr != pre_carrot_constr_data) + result.push_back(pre_carrot_constr_data->subaddr_indices); + else + result.push_back(ptx.subaddr_indices); + } return result; } diff --git a/src/wallet/tx_builder.cpp b/src/wallet/tx_builder.cpp index 2d58492..b11b35b 100644 --- a/src/wallet/tx_builder.cpp +++ b/src/wallet/tx_builder.cpp @@ -381,8 +381,6 @@ std::vector make_carrot_transaction_proposa const std::vector &extra, const std::uint32_t subaddr_account, const std::set &subaddr_indices, - const rct::xmr_amount ignore_above, - const rct::xmr_amount ignore_below, const wallet2::unique_index_container &subtract_fee_from_outputs) { wallet2::transfer_container transfers; @@ -407,8 +405,8 @@ std::vector make_carrot_transaction_proposa extra, subaddr_account, subaddr_indices, - ignore_above, - ignore_below, + w.ignore_outputs_above(), + w.ignore_outputs_below(), subtract_fee_from_outputs, top_block_index, w.get_account().get_keys()); @@ -663,5 +661,131 @@ std::vector make_carrot_transaction_proposa top_block_index, w.get_account().get_keys()); } +//------------------------------------------------------------------------------------------------------------------- +wallet2::pending_tx make_pending_carrot_tx(const carrot::CarrotTransactionProposalV1 &tx_proposal, + const wallet2::transfer_container &transfers, + const cryptonote::account_keys &acc_keys) +{ + const std::size_t n_inputs = tx_proposal.key_images_sorted.size(); + const std::size_t n_outputs = tx_proposal.normal_payment_proposals.size() + + tx_proposal.selfsend_payment_proposals.size(); + const bool shared_ephemeral_pubkey = n_outputs == 2; + + const crypto::key_image &tx_first_key_image = tx_proposal.key_images_sorted.at(0); + + // collect non-burned transfers + const std::unordered_map unburned_transfers_by_key_image = + collect_non_burned_transfers_by_key_image(transfers); + + // collect selected_transfers and key_images string + std::vector selected_transfers; + selected_transfers.reserve(n_inputs); + std::stringstream key_images_string; + for (size_t i = 0; i < n_inputs; ++i) + { + const crypto::key_image &ki = tx_proposal.key_images_sorted.at(i); + const auto ki_it = unburned_transfers_by_key_image.find(ki); + CHECK_AND_ASSERT_THROW_MES(ki_it != unburned_transfers_by_key_image.cend(), + "make_pending_carrot_tx: unrecognized key image in transfers list"); + selected_transfers.push_back(ki_it->second); + if (i) + key_images_string << ' '; + key_images_string << ki; + } + + //! @TODO: HW device + carrot::view_incoming_key_ram_borrowed_device k_view_dev(acc_keys.m_view_secret_key); + + // get order of payment proposals + std::vector output_enote_proposals; + carrot::encrypted_payment_id_t encrypted_payment_id; + std::vector> sorted_payment_proposal_indices; + carrot::get_output_enote_proposals_from_proposal_v1(tx_proposal, + /*s_view_balance_dev=*/nullptr, + &k_view_dev, + output_enote_proposals, + encrypted_payment_id, + &sorted_payment_proposal_indices); + + // calculate change_dst index based whether 2-out tx has a dummy output + // change_dst is set to dummy in 2-out self-send, otherwise last self-send + const bool has_2out_dummy = n_outputs == 2 + && tx_proposal.normal_payment_proposals.size() == 1 + && tx_proposal.normal_payment_proposals.at(0).amount == 0; + CHECK_AND_ASSERT_THROW_MES(!tx_proposal.selfsend_payment_proposals.empty(), + "make_pending_carrot_tx: carrot tx proposal missing a self-send proposal"); + const std::pair change_dst_index{!has_2out_dummy, + has_2out_dummy ? 0 : tx_proposal.selfsend_payment_proposals.size()-1}; + + // collect destinations and private tx keys for normal enotes + //! @TODO: payment proofs for special self-send, perhaps generate d_e deterministically + cryptonote::tx_destination_entry change_dts; + std::vector dests; + std::vector ephemeral_privkeys; + dests.reserve(n_outputs); + ephemeral_privkeys.reserve(n_outputs); + for (const std::pair &payment_idx : sorted_payment_proposal_indices) + { + cryptonote::tx_destination_entry dest; + + const bool is_selfsend = payment_idx.first; + if (is_selfsend) + { + dest = make_tx_destination_entry(tx_proposal.selfsend_payment_proposals.at(payment_idx.second), + k_view_dev); + ephemeral_privkeys.push_back(crypto::null_skey); + } + else // !is_selfsend + { + const carrot::CarrotPaymentProposalV1 &normal_payment_proposal = + tx_proposal.normal_payment_proposals.at(payment_idx.second); + dest = make_tx_destination_entry(normal_payment_proposal); + ephemeral_privkeys.push_back(carrot::get_enote_ephemeral_privkey(normal_payment_proposal, + carrot::make_carrot_input_context(tx_first_key_image))); + } + + if (payment_idx == change_dst_index) + change_dts = dest; + else + dests.push_back(dest); + } + + // collect subaddr account and minor indices + const std::uint32_t subaddr_account = transfers.at(selected_transfers.at(0)).m_subaddr_index.major; + std::set subaddr_indices; + for (const size_t selected_transfer : selected_transfers) + { + const wallet2::transfer_details &td = transfers.at(selected_transfer); + const std::uint32_t other_subaddr_account = td.m_subaddr_index.major; + if (other_subaddr_account != subaddr_account) + { + MWARNING("make_pending_carrot_tx: conflicting account indices: " << subaddr_account << " vs " + << other_subaddr_account); + } + subaddr_indices.insert(td.m_subaddr_index.minor); + } + + wallet2::pending_tx ptx; + ptx.tx.set_null(); + ptx.dust = 0; + ptx.fee = tx_proposal.fee; + ptx.dust_added_to_fee = false; + ptx.change_dts = change_dts; + ptx.selected_transfers = std::move(selected_transfers); + ptx.key_images = key_images_string.str(); + ptx.tx_key = shared_ephemeral_pubkey ? ephemeral_privkeys.at(0) : crypto::null_skey; + if (shared_ephemeral_pubkey) + ptx.additional_tx_keys = std::move(ephemeral_privkeys); + else + ptx.additional_tx_keys.clear(); + ptx.dests = std::move(dests); + ptx.multisig_sigs = {}; + ptx.multisig_tx_key_entropy = {}; + ptx.subaddr_account = subaddr_account; + ptx.subaddr_indices = std::move(subaddr_indices); + ptx.construction_data = tx_proposal; + return ptx; +} +//------------------------------------------------------------------------------------------------------------------- } //namespace wallet } //namespace tools diff --git a/src/wallet/tx_builder.h b/src/wallet/tx_builder.h index 52adfdd..ca97759 100644 --- a/src/wallet/tx_builder.h +++ b/src/wallet/tx_builder.h @@ -76,8 +76,6 @@ std::vector make_carrot_transaction_proposa const std::vector &extra, const std::uint32_t subaddr_account, const std::set &subaddr_indices, - const rct::xmr_amount ignore_above, - const rct::xmr_amount ignore_below, const wallet2::unique_index_container &subtract_fee_from_outputs); std::vector make_carrot_transaction_proposals_wallet2_sweep( @@ -123,5 +121,8 @@ std::vector make_carrot_transaction_proposa const std::vector &extra, const std::uint32_t subaddr_account, const std::set &subaddr_indices); +wallet2::pending_tx make_pending_carrot_tx(const carrot::CarrotTransactionProposalV1 &tx_proposal, + const wallet2::transfer_container &transfers, + const cryptonote::account_keys &acc_keys); } //namespace wallet } //namespace tools diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 6291ade..17ac5a8 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -923,9 +923,25 @@ bool get_short_payment_id(crypto::hash8 &payment_id8, const tools::wallet2::pend return false; } +static const tools::wallet2::tx_construction_data &get_construction_data(const tools::wallet2::pending_tx &ptx) +{ + THROW_WALLET_EXCEPTION_IF(!std::holds_alternative(ptx.construction_data), + tools::error::wallet_internal_error, + "Getting pre-carrot construction data only works for pre-carrot pending txs"); + return std::get(ptx.construction_data); +} + +static tools::wallet2::tx_construction_data &get_construction_data(tools::wallet2::pending_tx &ptx) +{ + THROW_WALLET_EXCEPTION_IF(!std::holds_alternative(ptx.construction_data), + tools::error::wallet_internal_error, + "Getting pre-carrot construction data only works for pre-carrot pending txs"); + return std::get(ptx.construction_data); +} + tools::wallet2::tx_construction_data get_construction_data_with_decrypted_short_payment_id(const tools::wallet2::pending_tx &ptx, hw::device &hwdev) { - tools::wallet2::tx_construction_data construction_data = ptx.construction_data; + tools::wallet2::tx_construction_data construction_data = get_construction_data(ptx); crypto::hash8 payment_id = null_hash8; if (get_short_payment_id(payment_id, ptx, hwdev)) { @@ -2052,7 +2068,7 @@ bool wallet2::frozen(const multisig_tx_set& txs) const std::unordered_set kis_to_sign; for (const auto& ptx : txs.m_ptx) { - const tools::wallet2::tx_construction_data& cd = ptx.construction_data; + const tools::wallet2::tx_construction_data& cd = get_construction_data(ptx); CHECK_AND_ASSERT_THROW_MES(cd.sources.size() == ptx.tx.vin.size(), "mismatched multisg tx set source sizes"); for (size_t src_idx = 0; src_idx < cd.sources.size(); ++src_idx) { @@ -7567,6 +7583,43 @@ void wallet2::commit_tx(pending_tx& ptx) crypto::hash txid; txid = get_transaction_hash(ptx.tx); + + // if it's already processed, bail + if (std::find_if(m_transfers.begin(), m_transfers.end(), [&txid](const transfer_details &td) { return td.m_txid == txid; }) != m_transfers.end()) + { + MDEBUG("Transaction " << txid << " already processed"); + return; + } + if (m_unconfirmed_txs.find(txid) != m_unconfirmed_txs.end()) + { + MDEBUG("Transaction " << txid << " already processed"); + return; + } + if (m_confirmed_txs.find(txid) != m_confirmed_txs.end()) + { + MDEBUG("Transaction " << txid << " already processed"); + return; + } + + // collect subaddr account and subaddr indices + std::optional subaddr_account_opt; + std::set subaddr_indices; + for (const size_t selected_transfer : ptx.selected_transfers) + { + const transfer_details &td = m_transfers.at(selected_transfer); + if (subaddr_account_opt && *subaddr_account_opt != td.m_subaddr_index.major) + { + MWARNING("Mismatched subaddr accounts in inputs: " << *subaddr_account_opt << " vs " << td.m_subaddr_index.major); + } + else + { + subaddr_account_opt = td.m_subaddr_index.major; + } + + subaddr_indices.insert(td.m_subaddr_index.minor); + } + const std::uint32_t subaddr_account = subaddr_account_opt.value_or(0); + crypto::hash payment_id = crypto::null_hash; std::vector dests; uint64_t amount_in = 0; @@ -7577,7 +7630,7 @@ void wallet2::commit_tx(pending_tx& ptx) for(size_t idx: ptx.selected_transfers) amount_in += m_transfers[idx].amount(); } - add_unconfirmed_tx(ptx.tx, amount_in, dests, payment_id, ptx.change_dts.amount, ptx.construction_data.subaddr_account, ptx.construction_data.subaddr_indices); + add_unconfirmed_tx(ptx.tx, amount_in, dests, payment_id, ptx.change_dts.amount, subaddr_account, subaddr_indices); if (store_tx_info() && ptx.tx_key != crypto::null_skey) { m_tx_keys[txid] = ptx.tx_key; @@ -7601,8 +7654,8 @@ void wallet2::commit_tx(pending_tx& ptx) //fee includes dust if dust policy specified it. LOG_PRINT_L1("Transaction successfully sent. <" << txid << ">" << ENDL << "Commission: " << print_money(ptx.fee) << " (dust sent to dust addr: " << print_money((ptx.dust_added_to_fee ? 0 : ptx.dust)) << ")" << ENDL - << "Balance: " << print_money(balance(ptx.construction_data.subaddr_account, source_asset, false)) << ENDL - << "Unlocked: " << print_money(unlocked_balance(ptx.construction_data.subaddr_account, source_asset, false)) << ENDL + << "Balance: " << print_money(balance(subaddr_account, source_asset, false)) << ENDL + << "Unlocked: " << print_money(unlocked_balance(subaddr_account, source_asset, false)) << ENDL << "Please, wait for confirmation for your balance to be unlocked."); } @@ -8112,7 +8165,7 @@ std::string wallet2::save_multisig_tx(multisig_tx_set txs) // txes generated, get rid of used k values for (size_t n = 0; n < txs.m_ptx.size(); ++n) - for (size_t idx: txs.m_ptx[n].construction_data.selected_transfers) + for (size_t idx: get_construction_data(txs.m_ptx[n]).selected_transfers) { memwipe(m_transfers[idx].m_multisig_k.data(), m_transfers[idx].m_multisig_k.size() * sizeof(m_transfers[idx].m_multisig_k[0])); m_transfers[idx].m_multisig_k.clear(); @@ -8121,7 +8174,7 @@ std::string wallet2::save_multisig_tx(multisig_tx_set txs) // zero out some data we don't want to share for (auto &ptx: txs.m_ptx) { - for (auto &e: ptx.construction_data.sources) + for (auto &e: get_construction_data(ptx).sources) memwipe(&e.multisig_kLRki.k, sizeof(e.multisig_kLRki.k)); } @@ -8234,10 +8287,10 @@ bool wallet2::parse_multisig_tx_from_str(std::string multisig_tx_st, multisig_tx CHECK_AND_ASSERT_MES(ptx.selected_transfers.size() == ptx.tx.vin.size(), false, "Mismatched selected_transfers/vin sizes"); for (size_t idx: ptx.selected_transfers) CHECK_AND_ASSERT_MES(idx < m_transfers.size(), false, "Transfer index out of range"); - CHECK_AND_ASSERT_MES(ptx.construction_data.selected_transfers.size() == ptx.tx.vin.size(), false, "Mismatched cd selected_transfers/vin sizes"); - for (size_t idx: ptx.construction_data.selected_transfers) + CHECK_AND_ASSERT_MES(get_construction_data(ptx).selected_transfers.size() == ptx.tx.vin.size(), false, "Mismatched cd selected_transfers/vin sizes"); + for (size_t idx: get_construction_data(ptx).selected_transfers) CHECK_AND_ASSERT_MES(idx < m_transfers.size(), false, "Transfer index out of range"); - CHECK_AND_ASSERT_MES(ptx.construction_data.sources.size() == ptx.tx.vin.size(), false, "Mismatched sources/vin sizes"); + CHECK_AND_ASSERT_MES(get_construction_data(ptx).sources.size() == ptx.tx.vin.size(), false, "Mismatched sources/vin sizes"); } return true; @@ -8325,7 +8378,7 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector #include #include +#include #include #include #include #include "include_base_utils.h" +#include "carrot_impl/carrot_offchain_serialization.h" #include "cryptonote_basic/account.h" #include "cryptonote_basic/account_boost_serialization.h" #include "cryptonote_basic/cryptonote_basic_impl.h" @@ -697,11 +699,17 @@ private: std::vector dests; std::vector multisig_sigs; crypto::secret_key multisig_tx_key_entropy; + uint32_t subaddr_account; // subaddress account of your wallet to be used in this transfer + std::set subaddr_indices; // set of address indices used as inputs in this transfer - tx_construction_data construction_data; + using tx_reconstruct_variant_t = std::variant< + tx_construction_data, + carrot::CarrotTransactionProposalV1 + >; + tx_reconstruct_variant_t construction_data; BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(1) + VERSION_FIELD(2) FIELD(tx) FIELD(dust) FIELD(fee) @@ -712,7 +720,20 @@ private: FIELD(tx_key) FIELD(additional_tx_keys) FIELD(dests) - FIELD(construction_data) + if (version < 2) + { + tx_construction_data pre_carrot_construction_data; + FIELD_N("construction_data", pre_carrot_construction_data) + construction_data = pre_carrot_construction_data; + subaddr_account = pre_carrot_construction_data.subaddr_account; + subaddr_indices = pre_carrot_construction_data.subaddr_indices; + } + else // version >= 2 + { + FIELD(construction_data) + FIELD(subaddr_account) + FIELD(subaddr_indices) + } FIELD(multisig_sigs) if (version < 1) { @@ -2229,7 +2250,7 @@ BOOST_CLASS_VERSION(tools::wallet2::reserve_proof_entry, 0) BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 1) BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 1) BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 4) -BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3) +BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 4) BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 1) BOOST_CLASS_VERSION(tools::wallet2::background_synced_tx_t, 0) BOOST_CLASS_VERSION(tools::wallet2::background_sync_data_t, 0) @@ -2730,7 +2751,16 @@ namespace boost a & x.key_images; a & x.tx_key; a & x.dests; - a & x.construction_data; + if (ver < 4) + { + tools::wallet2::tx_construction_data pre_carrot_construction_data; + a & pre_carrot_construction_data; + x.construction_data = pre_carrot_construction_data; + } + else // ver >= 4 + { + a & x.construction_data; + } if (ver < 1) return; a & x.additional_tx_keys; @@ -2740,6 +2770,10 @@ namespace boost if (ver < 3) return; a & x.multisig_sigs; + if (ver < 4) + return; + a & x.subaddr_account; + a & x.subaddr_indices; } template @@ -2822,3 +2856,6 @@ namespace tools } //---------------------------------------------------------------------------------------------------- } + +VARIANT_TAG(binary_archive, tools::wallet2::tx_construction_data, 0x21); +VARIANT_TAG(binary_archive, carrot::CarrotTransactionProposalV1, 0x22); diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index adb9c9c..bde6be3 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -1518,7 +1518,7 @@ namespace tools } for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n) { - tx_constructions.push_back(exported_txs.m_ptx[n].construction_data); + tx_constructions.push_back(std::get(exported_txs.m_ptx[n].construction_data)); } } catch (const std::exception &e) { diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index e90412d..cef1380 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -1136,7 +1136,7 @@ TEST(Serialization, portability_signed_tx) ASSERT_TRUE(ptx.dests[0].amount == 1400000000000); ASSERT_TRUE(cryptonote::get_account_address_as_str(nettype, false, ptx.dests[0].addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA"); // ptx.construction_data - auto& tcd = ptx.construction_data; + auto& tcd = std::get(ptx.construction_data); ASSERT_TRUE(tcd.sources.size() == 1); auto& tse = tcd.sources[0]; // ptx.construction_data.sources[0].outputs