aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/daemon/daemon.cpp19
-rw-r--r--src/net/i2p_address.cpp45
-rw-r--r--src/net/i2p_address.h9
-rw-r--r--src/net/parse.cpp2
-rw-r--r--src/p2p/net_node.h1
-rw-r--r--src/p2p/net_peerlist_boost_serialization.h2
-rw-r--r--src/ringct/rctTypes.h4
-rw-r--r--src/simplewallet/simplewallet.cpp83
-rw-r--r--src/wallet/wallet2.cpp172
-rw-r--r--src/wallet/wallet2.h5
-rw-r--r--src/wallet/wallet_errors.h10
-rw-r--r--src/wallet/wallet_rpc_server.cpp22
-rw-r--r--src/wallet/wallet_rpc_server.h4
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h19
14 files changed, 307 insertions, 90 deletions
diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp
index 97dad9357..1d4142c84 100644
--- a/src/daemon/daemon.cpp
+++ b/src/daemon/daemon.cpp
@@ -124,6 +124,25 @@ public:
core.get().set_txpool_listener(cryptonote::listener::zmq_pub::txpool_add{shared});
}
}
+ else // if --no-zmq specified
+ {
+ // Assert that none of --zmq-rpc-bind-port, --zmq-rpc-bind-ip, and --zmq-pub are specified b/c
+ // that does not make semantic sense with --no-zmq.
+ if (command_line::get_arg(vm, daemon_args::arg_zmq_rpc_bind_port) !=
+ daemon_args::arg_zmq_rpc_bind_port.default_value)
+ {
+ MWARNING("WARN: --zmq-rpc-bind-port has no effect because --no-zmq was specified");
+ }
+ else if (command_line::get_arg(vm, daemon_args::arg_zmq_rpc_bind_ip) !=
+ daemon_args::arg_zmq_rpc_bind_ip.default_value)
+ {
+ MWARNING("WARN: --zmq-rpc-bind-ip has no effect because --no-zmq was specified");
+ }
+ else if (!command_line::get_arg(vm, daemon_args::arg_zmq_pub).empty())
+ {
+ MWARNING("WARN: --zmq-pub has no effect because --no-zmq was specified");
+ }
+ }
}
};
diff --git a/src/net/i2p_address.cpp b/src/net/i2p_address.cpp
index a151e420b..d5ca8544f 100644
--- a/src/net/i2p_address.cpp
+++ b/src/net/i2p_address.cpp
@@ -71,7 +71,7 @@ namespace net
struct i2p_serialized
{
std::string host;
- std::uint16_t port;
+ std::uint16_t port; //! Leave for compatability with older clients
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(host)
@@ -80,8 +80,7 @@ namespace net
};
}
- i2p_address::i2p_address(const boost::string_ref host, const std::uint16_t port) noexcept
- : port_(port)
+ i2p_address::i2p_address(const boost::string_ref host) noexcept
{
// this is a private constructor, throw if moved to public
assert(host.size() < sizeof(host_));
@@ -97,27 +96,19 @@ namespace net
}
i2p_address::i2p_address() noexcept
- : port_(0)
{
static_assert(sizeof(unknown_host) <= sizeof(host_), "bad buffer size");
std::memcpy(host_, unknown_host, sizeof(unknown_host));
std::memset(host_ + sizeof(unknown_host), 0, sizeof(host_) - sizeof(unknown_host));
}
- expect<i2p_address> i2p_address::make(const boost::string_ref address, const std::uint16_t default_port)
+ expect<i2p_address> i2p_address::make(const boost::string_ref address)
{
boost::string_ref host = address.substr(0, address.rfind(':'));
- const boost::string_ref port =
- address.substr(host.size() + (host.size() == address.size() ? 0 : 1));
-
MONERO_CHECK(host_check(host));
- std::uint16_t porti = default_port;
- if (!port.empty() && !epee::string_tools::get_xtype_from_string(porti, std::string{port}))
- return {net::error::invalid_port};
-
static_assert(b32_length + sizeof(tld) == sizeof(i2p_address::host_), "bad internal host size");
- return i2p_address{host, porti};
+ return i2p_address{host};
}
bool i2p_address::_load(epee::serialization::portable_storage& src, epee::serialization::section* hparent)
@@ -127,23 +118,21 @@ namespace net
{
std::memcpy(host_, in.host.data(), in.host.size());
std::memset(host_ + in.host.size(), 0, sizeof(host_) - in.host.size());
- port_ = in.port;
return true;
}
static_assert(sizeof(unknown_host) <= sizeof(host_), "bad buffer size");
std::memcpy(host_, unknown_host, sizeof(unknown_host)); // include null terminator
- port_ = 0;
return false;
}
bool i2p_address::store(epee::serialization::portable_storage& dest, epee::serialization::section* hparent) const
{
- const i2p_serialized out{std::string{host_}, port_};
+ // Set port to 1 for backwards compatability; zero is invalid port
+ const i2p_serialized out{std::string{host_}, 1};
return out.store(dest, hparent);
}
i2p_address::i2p_address(const i2p_address& rhs) noexcept
- : port_(rhs.port_)
{
std::memcpy(host_, rhs.host_, sizeof(host_));
}
@@ -152,7 +141,6 @@ namespace net
{
if (this != std::addressof(rhs))
{
- port_ = rhs.port_;
std::memcpy(host_, rhs.host_, sizeof(host_));
}
return *this;
@@ -166,13 +154,12 @@ namespace net
bool i2p_address::equal(const i2p_address& rhs) const noexcept
{
- return port_ == rhs.port_ && is_same_host(rhs);
+ return is_same_host(rhs);
}
bool i2p_address::less(const i2p_address& rhs) const noexcept
{
- int res = std::strcmp(host_str(), rhs.host_str());
- return res < 0 || (res == 0 && port() < rhs.port());
+ return std::strcmp(host_str(), rhs.host_str()) < 0;
}
bool i2p_address::is_same_host(const i2p_address& rhs) const noexcept
@@ -182,20 +169,6 @@ namespace net
std::string i2p_address::str() const
{
- const std::size_t host_length = std::strlen(host_str());
- const std::size_t port_length =
- port_ == 0 ? 0 : std::numeric_limits<std::uint16_t>::digits10 + 2;
-
- std::string out{};
- out.reserve(host_length + port_length);
- out.assign(host_str(), host_length);
-
- if (port_ != 0)
- {
- out.push_back(':');
- namespace karma = boost::spirit::karma;
- karma::generate(std::back_inserter(out), karma::ushort_, port());
- }
- return out;
+ return host_str();
}
}
diff --git a/src/net/i2p_address.h b/src/net/i2p_address.h
index 4d342e67d..f52ccbb51 100644
--- a/src/net/i2p_address.h
+++ b/src/net/i2p_address.h
@@ -50,11 +50,10 @@ namespace net
//! b32 i2p address; internal format not condensed/decoded.
class i2p_address
{
- std::uint16_t port_;
char host_[61]; // null-terminated
//! Keep in private, `host.size()` has no runtime check
- i2p_address(boost::string_ref host, std::uint16_t port) noexcept;
+ i2p_address(boost::string_ref host) noexcept;
public:
//! \return Size of internal buffer for host.
@@ -74,7 +73,7 @@ namespace net
with `default_port` being used if port is not specified in
`address`.
*/
- static expect<i2p_address> make(boost::string_ref address, std::uint16_t default_port = 0);
+ static expect<i2p_address> make(boost::string_ref address);
//! Load from epee p2p format, and \return false if not valid tor address
bool _load(epee::serialization::portable_storage& src, epee::serialization::section* hparent);
@@ -103,8 +102,8 @@ namespace net
//! \return Null-terminated `x.b32.i2p` value or `unknown_str()`.
const char* host_str() const noexcept { return host_; }
- //! \return Port value or `0` if unspecified.
- std::uint16_t port() const noexcept { return port_; }
+ //! \return `1` to work with I2P socks which considers `0` error.
+ std::uint16_t port() const noexcept { return 1; }
static constexpr bool is_loopback() noexcept { return false; }
static constexpr bool is_local() noexcept { return false; }
diff --git a/src/net/parse.cpp b/src/net/parse.cpp
index 71d0d7b26..a6d665082 100644
--- a/src/net/parse.cpp
+++ b/src/net/parse.cpp
@@ -81,7 +81,7 @@ namespace net
if (host_str_ref.ends_with(".onion"))
return tor_address::make(address, default_port);
if (host_str_ref.ends_with(".i2p"))
- return i2p_address::make(address, default_port);
+ return i2p_address::make(address);
boost::system::error_code ec;
boost::asio::ip::address_v6 v6 = boost::asio::ip::address_v6::from_string(host_str, ec);
diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h
index cf2d212b3..459a6a396 100644
--- a/src/p2p/net_node.h
+++ b/src/p2p/net_node.h
@@ -48,7 +48,6 @@
#include "cryptonote_protocol/levin_notify.h"
#include "warnings.h"
#include "net/abstract_tcp_server2.h"
-#include "net/levin_protocol_handler.h"
#include "net/levin_protocol_handler_async.h"
#include "p2p_protocol_defs.h"
#include "storages/levin_abstract_invoke2.h"
diff --git a/src/p2p/net_peerlist_boost_serialization.h b/src/p2p/net_peerlist_boost_serialization.h
index 7f4aecce3..0b7a54860 100644
--- a/src/p2p/net_peerlist_boost_serialization.h
+++ b/src/p2p/net_peerlist_boost_serialization.h
@@ -196,7 +196,7 @@ namespace boost
if (std::strcmp(host, net::i2p_address::unknown_str()) == 0)
na = net::i2p_address::unknown();
else
- na = MONERO_UNWRAP(net::i2p_address::make(host, port));
+ na = MONERO_UNWRAP(net::i2p_address::make(host));
}
template <class Archive, class ver_type>
diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h
index 585d5fb49..c168c8571 100644
--- a/src/ringct/rctTypes.h
+++ b/src/ringct/rctTypes.h
@@ -325,6 +325,10 @@ namespace rct {
ctkeyV outPk;
xmr_amount txnFee; // contains b
+ rctSigBase() :
+ type(RCTTypeNull), message{}, mixRing{}, pseudoOuts{}, ecdhInfo{}, outPk{}, txnFee(0)
+ {}
+
template<bool W, template <bool> class Archive>
bool serialize_rctsig_base(Archive<W> &ar, size_t inputs, size_t outputs)
{
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index 6e5d0b1ec..bff62f1a8 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -188,7 +188,7 @@ namespace
const char* USAGE_INCOMING_TRANSFERS("incoming_transfers [available|unavailable] [verbose] [uses] [index=<N1>[,<N2>[,...]]]");
const char* USAGE_PAYMENTS("payments <PID_1> [<PID_2> ... <PID_N>]");
const char* USAGE_PAYMENT_ID("payment_id");
- const char* USAGE_TRANSFER("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <address> <amount>) [<payment_id>]");
+ const char* USAGE_TRANSFER("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <address> <amount>) [subtractfeefrom=<D0>[,<D1>,all,...]] [<payment_id>]");
const char* USAGE_LOCKED_TRANSFER("locked_transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <addr> <amount>) <lockblocks> [<payment_id (obsolete)>]");
const char* USAGE_LOCKED_SWEEP_ALL("locked_sweep_all [index=<N1>[,<N2>,...] | index=all] [<priority>] [<ring_size>] <address> <lockblocks> [<payment_id (obsolete)>]");
const char* USAGE_SWEEP_ALL("sweep_all [index=<N1>[,<N2>,...] | index=all] [<priority>] [<ring_size>] [outputs=<N>] <address> [<payment_id (obsolete)>]");
@@ -520,7 +520,52 @@ namespace
fail_msg_writer() << sw::tr("invalid format for subaddress lookahead; must be <major>:<minor>");
return r;
}
-}
+
+ static constexpr std::string_view SFFD_ARG_NAME{"subtractfeefrom="};
+
+ bool parse_subtract_fee_from_outputs
+ (
+ const std::string& arg,
+ tools::wallet2::unique_index_container& subtract_fee_from_outputs,
+ bool& subtract_fee_from_all,
+ bool& matches
+ )
+ {
+ matches = false;
+ if (!boost::string_ref{arg}.starts_with(SFFD_ARG_NAME.data())) // if arg doesn't match
+ return true;
+ matches = true;
+
+ const char* arg_end = arg.c_str() + arg.size();
+ for (const char* p = arg.c_str() + SFFD_ARG_NAME.size(); p < arg_end;)
+ {
+ const char* new_p = nullptr;
+ const unsigned long dest_index = strtoul(p, const_cast<char**>(&new_p), 10);
+ if (dest_index == 0 && new_p == p) // numerical conversion failed
+ {
+ if (0 != strncmp(p, "all", 3))
+ {
+ fail_msg_writer() << tr("Failed to parse subtractfeefrom list");
+ return false;
+ }
+ subtract_fee_from_all = true;
+ break;
+ }
+ else if (dest_index > std::numeric_limits<uint32_t>::max())
+ {
+ fail_msg_writer() << tr("Destination index is too large") << ": " << dest_index;
+ return false;
+ }
+ else
+ {
+ subtract_fee_from_outputs.insert(dest_index);
+ p = new_p + 1; // skip the comma
+ }
+ }
+
+ return true;
+ }
+} // anonymous namespace
void simple_wallet::handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon)
{
@@ -3080,7 +3125,7 @@ simple_wallet::simple_wallet()
tr("Show the blockchain height."));
m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::on_command, this, &simple_wallet::transfer, _1),
tr(USAGE_TRANSFER),
- tr("Transfer <amount> to <address>. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or <address_2> <amount_2> etcetera (before the payment ID, if it's included)"));
+ tr("Transfer <amount> to <address>. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or <address_2> <amount_2> etcetera (before the payment ID, if it's included). The \"subtractfeefrom=\" list allows you to choose which destinations to fund the tx fee from instead of the change output. The fee will be split across the chosen destinations proportionally equally. For example, to make 3 transfers where the fee is taken from the first and third destinations, one could do: \"transfer <addr1> 3 <addr2> 0.5 <addr3> 1 subtractfeefrom=0,2\". Let's say the tx fee is 0.1. The balance would drop by exactly 4.5 XMR including fees, and addr1 & addr3 would receive 2.925 & 0.975 XMR, respectively. Use \"subtractfeefrom=all\" to spread the fee across all destinations."));
m_cmd_binder.set_handler("locked_transfer",
boost::bind(&simple_wallet::on_command, this, &simple_wallet::locked_transfer,_1),
tr(USAGE_LOCKED_TRANSFER),
@@ -6349,6 +6394,27 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
local_args.pop_back();
}
+ // Parse subtractfeefrom destination list
+ tools::wallet2::unique_index_container subtract_fee_from_outputs;
+ bool subtract_fee_from_all = false;
+ for (auto it = local_args.begin(); it < local_args.end();)
+ {
+ bool matches = false;
+ if (!parse_subtract_fee_from_outputs(*it, subtract_fee_from_outputs, subtract_fee_from_all, matches))
+ {
+ return false;
+ }
+ else if (matches)
+ {
+ it = local_args.erase(it);
+ break;
+ }
+ else
+ {
+ ++it;
+ }
+ }
+
vector<cryptonote::address_parse_info> dsts_info;
vector<cryptonote::tx_destination_entry> dsts;
for (size_t i = 0; i < local_args.size(); )
@@ -6445,6 +6511,13 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
dsts.push_back(de);
}
+ if (subtract_fee_from_all)
+ {
+ subtract_fee_from_outputs.clear();
+ for (decltype(subtract_fee_from_outputs)::value_type i = 0; i < dsts.size(); ++i)
+ subtract_fee_from_outputs.insert(i);
+ }
+
SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;);
try
@@ -6463,13 +6536,13 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
return false;
}
unlock_block = bc_height + locked_blocks;
- ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices);
+ ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, subtract_fee_from_outputs);
break;
default:
LOG_ERROR("Unknown transfer method, using default");
/* FALLTHRU */
case Transfer:
- ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices);
+ ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, subtract_fee_from_outputs);
break;
}
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index ab6c86b04..5a739d50d 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -4261,9 +4261,6 @@ wallet2::detached_blockchain_data wallet2::detach_blockchain(uint64_t height, st
++it;
}
- if (m_callback)
- m_callback->on_reorg(height, blocks_detached, transfers_detached);
-
LOG_PRINT_L0("Detached blockchain on height " << height << ", transfers detached " << transfers_detached << ", blocks detached " << blocks_detached);
return dbd;
}
@@ -4275,7 +4272,10 @@ void wallet2::handle_reorg(uint64_t height, std::map<std::pair<uint64_t, uint64_
// C
THROW_WALLET_EXCEPTION_IF(height < m_blockchain.offset() && m_blockchain.size() > m_blockchain.offset(),
error::wallet_internal_error, "Daemon claims reorg below last checkpoint");
- detach_blockchain(height, output_tracker_cache);
+ detached_blockchain_data dbd = detach_blockchain(height, output_tracker_cache);
+
+ if (m_callback)
+ m_callback->on_reorg(height, dbd.detached_blockchain.size(), dbd.detached_tx_hashes.size());
}
//----------------------------------------------------------------------------------------------------
bool wallet2::deinit()
@@ -9860,7 +9860,7 @@ static uint32_t get_count_above(const std::vector<wallet2::transfer_details> &tr
// This system allows for sending (almost) the entire balance, since it does
// not generate spurious change in all txes, thus decreasing the instantaneous
// usable balance.
-std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices)
+std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const unique_index_container& subtract_fee_from_outputs)
{
//ensure device is let in NONE mode in any case
hw::device &hwdev = m_account.get_device();
@@ -9871,11 +9871,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
std::vector<std::pair<uint32_t, std::vector<size_t>>> unused_transfers_indices_per_subaddr;
std::vector<std::pair<uint32_t, std::vector<size_t>>> unused_dust_indices_per_subaddr;
- uint64_t needed_money;
+ uint64_t needed_money, total_needed_money; // 'needed_money' is the sum of the destination amounts, while 'total_needed_money' includes 'needed_money' plus the fee if not 'subtract_fee_from_outputs'
uint64_t accumulated_fee, accumulated_change;
struct TX {
std::vector<size_t> selected_transfers;
std::vector<cryptonote::tx_destination_entry> dsts;
+ std::vector<bool> dsts_are_fee_subtractable;
cryptonote::transaction tx;
pending_tx ptx;
size_t weight;
@@ -9885,9 +9886,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
TX() : weight(0), needed_fee(0) {}
/* Add an output to the transaction.
+ * If merge_destinations is true, when adding a destination with an existing address, to increment the amount of the existing tx output instead of creating a new one
+ * If subtracting_fee is true, when we generate a final list of destinations for transfer_selected[_rct], this destination will be used to fund the tx fee
* Returns True if the output was added, False if there are no more available output slots.
*/
- bool add(const cryptonote::tx_destination_entry &de, uint64_t amount, unsigned int original_output_index, bool merge_destinations, size_t max_dsts) {
+ bool add(const cryptonote::tx_destination_entry &de, uint64_t amount, unsigned int original_output_index, bool merge_destinations, size_t max_dsts, bool subtracting_fee) {
if (merge_destinations)
{
std::vector<cryptonote::tx_destination_entry>::iterator i;
@@ -9897,6 +9900,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
if (dsts.size() >= max_dsts)
return false;
dsts.push_back(de);
+ dsts_are_fee_subtractable.push_back(subtracting_fee);
i = dsts.end() - 1;
i->amount = 0;
}
@@ -9912,13 +9916,67 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
return false;
dsts.push_back(de);
dsts.back().amount = 0;
+ dsts_are_fee_subtractable.push_back(subtracting_fee);
}
THROW_WALLET_EXCEPTION_IF(memcmp(&dsts[original_output_index].addr, &de.addr, sizeof(de.addr)), error::wallet_internal_error, "Mismatched destination address");
dsts[original_output_index].amount += amount;
}
return true;
}
+
+ // Returns destinations adjusted for given fee if subtract_fee_from_outputs is enabled
+ std::vector<cryptonote::tx_destination_entry> get_adjusted_dsts(uint64_t needed_fee) const
+ {
+ uint64_t dest_total = 0;
+ uint64_t subtractable_dest_total = 0;
+ std::vector<size_t> subtractable_indices;
+ subtractable_indices.reserve(dsts.size());
+ for (size_t i = 0; i < dsts.size(); ++i)
+ {
+ dest_total += dsts[i].amount;
+ if (dsts_are_fee_subtractable[i])
+ {
+ subtractable_dest_total += dsts[i].amount;
+ subtractable_indices.push_back(i);
+ }
+ }
+
+ if (subtractable_indices.empty()) // if subtract_fee_from_outputs is not enabled for this tx
+ return dsts;
+
+ THROW_WALLET_EXCEPTION_IF(subtractable_dest_total < needed_fee, error::tx_not_possible,
+ subtractable_dest_total, dest_total, needed_fee);
+
+ std::vector<cryptonote::tx_destination_entry> res = dsts;
+
+ // subtract fees from destinations equally, rounded down, until dust is left where we subtract 1
+ uint64_t subtractable_remaining = needed_fee;
+ auto si_it = subtractable_indices.cbegin();
+ uint64_t amount_to_subtract = 0;
+ while (subtractable_remaining)
+ {
+ // Set the amount to subtract iterating at the beginning of the list so equal amounts are
+ // subtracted throughout the list of destinations. We use max(x, 1) so that we we still step
+ // forwards even when the amount remaining is less than the number of subtractable indices
+ if (si_it == subtractable_indices.cbegin())
+ amount_to_subtract = std::max<uint64_t>(subtractable_remaining / subtractable_indices.size(), 1);
+
+ cryptonote::tx_destination_entry& d = res[*si_it];
+ THROW_WALLET_EXCEPTION_IF(d.amount <= amount_to_subtract, error::zero_amount);
+
+ subtractable_remaining -= amount_to_subtract;
+ d.amount -= amount_to_subtract;
+ ++si_it;
+
+ // Wrap around to first subtractable index once we hit the end of the list
+ if (si_it == subtractable_indices.cend())
+ si_it = subtractable_indices.cbegin();
+ }
+
+ return res;
+ }
};
+
std::vector<TX> txes;
bool adding_fee; // true if new outputs go towards fee, rather than destinations
uint64_t needed_fee, available_for_fee = 0;
@@ -9941,6 +9999,14 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// throw if attempting a transaction with no destinations
THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination);
+ // throw if subtract_fee_from_outputs has a bad index
+ THROW_WALLET_EXCEPTION_IF(subtract_fee_from_outputs.size() && *subtract_fee_from_outputs.crbegin() >= dsts.size(),
+ error::subtract_fee_from_bad_index, *subtract_fee_from_outputs.crbegin());
+
+ // throw if subtract_fee_from_outputs is enabled and we have too many outputs to fit into one tx
+ THROW_WALLET_EXCEPTION_IF(subtract_fee_from_outputs.size() && dsts.size() > BULLETPROOF_MAX_OUTPUTS - 1,
+ error::wallet_internal_error, "subtractfeefrom transfers cannot be split over multiple transactions yet");
+
// calculate total amount being sent to all destinations
// throw if total amount overflows uint64_t
needed_money = 0;
@@ -9968,6 +10034,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// we could also check for being within FEE_PER_KB, but if the fee calculation
// ever changes, this might be missed, so let this go through
const uint64_t min_fee = (base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags));
+ total_needed_money = needed_money + (subtract_fee_from_outputs.size() ? 0 : min_fee);
uint64_t balance_subtotal = 0;
uint64_t unlocked_balance_subtotal = 0;
for (uint32_t index_minor : subaddr_indices)
@@ -9975,10 +10042,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
balance_subtotal += balance_per_subaddr[index_minor];
unlocked_balance_subtotal += unlocked_balance_per_subaddr[index_minor].first;
}
- THROW_WALLET_EXCEPTION_IF(needed_money + min_fee > balance_subtotal, error::not_enough_money,
+ THROW_WALLET_EXCEPTION_IF(total_needed_money > balance_subtotal || min_fee > balance_subtotal, error::not_enough_money,
balance_subtotal, needed_money, 0);
// first check overall balance is enough, then unlocked one, so we throw distinct exceptions
- THROW_WALLET_EXCEPTION_IF(needed_money + min_fee > unlocked_balance_subtotal, error::not_enough_unlocked_money,
+ THROW_WALLET_EXCEPTION_IF(total_needed_money > unlocked_balance_subtotal || min_fee > unlocked_balance_subtotal, error::not_enough_unlocked_money,
unlocked_balance_subtotal, needed_money, 0);
for (uint32_t i : subaddr_indices)
@@ -10081,7 +10148,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which
// will get us a known fee.
uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags, base_fee, fee_quantization_mask);
- preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices);
+ total_needed_money = needed_money + (subtract_fee_from_outputs.size() ? 0 : estimated_fee);
+ preferred_inputs = pick_preferred_rct_inputs(total_needed_money, subaddr_account, subaddr_indices);
if (!preferred_inputs.empty())
{
string s;
@@ -10114,7 +10182,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// - we have something to send
// - or we need to gather more fee
// - or we have just one input in that tx, which is rct (to try and make all/most rct txes 2/2)
- unsigned int original_output_index = 0;
+ unsigned int original_output_index = 0, destination_index = 0;
std::vector<size_t>* unused_transfers_indices = &unused_transfers_indices_per_subaddr[0].second;
std::vector<size_t>* unused_dust_indices = &unused_dust_indices_per_subaddr[0].second;
@@ -10197,7 +10265,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// we can fully pay that destination
LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) <<
" for " << print_money(dsts[0].amount));
- if (!tx.add(dsts[0], dsts[0].amount, original_output_index, m_merge_destinations, BULLETPROOF_MAX_OUTPUTS-1))
+ const bool subtract_fee_from_this_dest = subtract_fee_from_outputs.count(destination_index);
+ if (!tx.add(dsts[0], dsts[0].amount, original_output_index, m_merge_destinations, BULLETPROOF_MAX_OUTPUTS-1, subtract_fee_from_this_dest))
{
LOG_PRINT_L2("Didn't pay: ran out of output slots");
out_slots_exhausted = true;
@@ -10207,6 +10276,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
dsts[0].amount = 0;
pop_index(dsts, 0);
++original_output_index;
+ ++destination_index;
}
if (!out_slots_exhausted && available_amount > 0 && !dsts.empty() &&
@@ -10214,7 +10284,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// we can partially fill that destination
LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) <<
" for " << print_money(available_amount) << "/" << print_money(dsts[0].amount));
- if (tx.add(dsts[0], available_amount, original_output_index, m_merge_destinations, BULLETPROOF_MAX_OUTPUTS-1))
+ const bool subtract_fee_from_this_dest = subtract_fee_from_outputs.count(destination_index);
+ if (tx.add(dsts[0], available_amount, original_output_index, m_merge_destinations, BULLETPROOF_MAX_OUTPUTS-1, subtract_fee_from_this_dest))
{
dsts[0].amount -= available_amount;
available_amount = 0;
@@ -10293,9 +10364,13 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// Try to carve the estimated fee from the partial payment (if there is one)
available_for_fee = try_carving_from_partial_payment(needed_fee, available_for_fee);
- uint64_t inputs = 0, outputs = needed_fee;
+ uint64_t inputs = 0, outputs = 0;
for (size_t idx: tx.selected_transfers) inputs += m_transfers[idx].amount();
for (const auto &o: tx.dsts) outputs += o.amount;
+ if (subtract_fee_from_outputs.empty()) // if normal tx that doesn't subtract fees
+ {
+ outputs += needed_fee;
+ }
if (inputs < outputs)
{
@@ -10306,15 +10381,32 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " outputs and " <<
tx.selected_transfers.size() << " inputs");
+ auto tx_dsts = tx.get_adjusted_dsts(needed_fee);
if (use_rct)
- transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
+ transfer_selected_rct(tx_dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
test_tx, test_ptx, rct_config, use_view_tags);
else
- transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
+ transfer_selected(tx_dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags);
auto txBlob = t_serializable_object_to_blob(test_ptx.tx);
needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask);
- available_for_fee = test_ptx.fee + test_ptx.change_dts.amount + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0);
+
+ // Depending on the mode, we take extra fees from either our change output or the destination outputs for which subtract_fee_from_outputs is true
+ uint64_t output_available_for_fee = 0;
+ bool tx_has_subtractable_output = false;
+ for (size_t di = 0; di < tx.dsts.size(); ++di)
+ {
+ if (tx.dsts_are_fee_subtractable[di])
+ {
+ output_available_for_fee += tx.dsts[di].amount;
+ tx_has_subtractable_output = true;
+ }
+ }
+ if (!tx_has_subtractable_output)
+ {
+ output_available_for_fee = test_ptx.change_dts.amount;
+ }
+ available_for_fee = test_ptx.fee + output_available_for_fee + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0);
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)");
@@ -10330,18 +10422,24 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
else
{
LOG_PRINT_L2("We made a tx, adjusting fee and saving it, we need " << print_money(needed_fee) << " and we have " << print_money(test_ptx.fee));
- do {
+ size_t fee_tries;
+ for (fee_tries = 0; fee_tries < 10 && needed_fee > test_ptx.fee; ++fee_tries) {
+ tx_dsts = tx.get_adjusted_dsts(needed_fee);
+
if (use_rct)
- transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
+ transfer_selected_rct(tx_dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
test_tx, test_ptx, rct_config, use_view_tags);
else
- transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
+ transfer_selected(tx_dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags);
txBlob = t_serializable_object_to_blob(test_ptx.tx);
needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_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 and " << print_money(test_ptx.change_dts.amount) << " change");
- } while (needed_fee > test_ptx.fee);
+ };
+
+ THROW_WALLET_EXCEPTION_IF(fee_tries == 10, error::wallet_internal_error,
+ "Too many attempts to raise pending tx fee to level of needed fee");
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");
@@ -10394,10 +10492,13 @@ skip_tx:
for (std::vector<TX>::iterator i = txes.begin(); i != txes.end(); ++i)
{
TX &tx = *i;
+
+ const auto tx_dsts = tx.get_adjusted_dsts(tx.needed_fee);
+
cryptonote::transaction test_tx;
pending_tx test_ptx;
if (use_rct) {
- transfer_selected_rct(tx.dsts, /* NOMOD std::vector<cryptonote::tx_destination_entry> dsts,*/
+ transfer_selected_rct(tx_dsts, /* NOMOD std::vector<cryptonote::tx_destination_entry> dsts,*/
tx.selected_transfers, /* const std::list<size_t> selected_transfers */
fake_outs_count, /* CONST size_t fake_outputs_count, */
tx.outs, /* MOD std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, */
@@ -10410,7 +10511,7 @@ skip_tx:
rct_config,
use_view_tags); /* const bool use_view_tags */
} else {
- transfer_selected(tx.dsts,
+ transfer_selected(tx_dsts,
tx.selected_transfers,
fake_outs_count,
tx.outs,
@@ -10444,23 +10545,38 @@ skip_tx:
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");
+ THROW_WALLET_EXCEPTION_IF(!sanity_check(ptx_vector, original_dsts, subtract_fee_from_outputs), 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;
}
-bool wallet2::sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, std::vector<cryptonote::tx_destination_entry> dsts) const
+bool wallet2::sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, const std::vector<cryptonote::tx_destination_entry>& dsts, const unique_index_container& subtract_fee_from_outputs) const
{
- MDEBUG("sanity_check: " << ptx_vector.size() << " txes, " << dsts.size() << " destinations");
+ MDEBUG("sanity_check: " << ptx_vector.size() << " txes, " << dsts.size() << " destinations, subtract_fee_from_outputs " <<
+ (subtract_fee_from_outputs.size() ? "enabled" : "disabled"));
THROW_WALLET_EXCEPTION_IF(ptx_vector.empty(), error::wallet_internal_error, "No transactions");
+ THROW_WALLET_EXCEPTION_IF(!subtract_fee_from_outputs.empty() && ptx_vector.size() != 1,
+ error::wallet_internal_error, "feature subtractfeefrom not supported for split transactions");
+
+ // For destinations from where the fee is subtracted, the required amount has to be at least
+ // target amount - (tx fee / num_subtractable + 1). +1 since fee might not be evenly divisble by
+ // the number of subtractble destinations. For non-subtractable destinations, we need at least
+ // the target amount.
+ const size_t num_subtractable_dests = subtract_fee_from_outputs.size();
+ const uint64_t fee0 = ptx_vector[0].fee;
+ const uint64_t subtractable_fee_deduction = fee0 / std::max<size_t>(num_subtractable_dests, 1) + 1;
// check every party in there does receive at least the required amount
std::unordered_map<account_public_address, std::pair<uint64_t, bool>> required;
- for (const auto &d: dsts)
+ for (size_t i = 0; i < dsts.size(); ++i)
{
- required[d.addr].first += d.amount;
+ const cryptonote::tx_destination_entry& d = dsts[i];
+ const bool dest_is_subtractable = subtract_fee_from_outputs.count(i);
+ const uint64_t fee_deduction = dest_is_subtractable ? subtractable_fee_deduction : 0;
+ const uint64_t required_amount = d.amount - std::min(fee_deduction, d.amount);
+ required[d.addr].first += required_amount;
required[d.addr].second = d.is_subaddress;
}
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 310aadb94..5f884e374 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -591,6 +591,7 @@ private:
typedef std::vector<transfer_details> transfer_container;
typedef std::unordered_multimap<crypto::hash, payment_details> payment_container;
+ typedef std::set<uint32_t> unique_index_container;
struct multisig_sig
{
@@ -1103,11 +1104,11 @@ private:
bool parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsigned_tx_set &exported_txs) const;
bool load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func = NULL);
bool parse_tx_from_str(const std::string &signed_tx_st, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set &)> accept_func);
- std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices); // pass subaddr_indices by value on purpose
+ std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const unique_index_container& subtract_fee_from_outputs = {}); // pass subaddr_indices by value on purpose
std::vector<wallet2::pending_tx> 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<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices);
std::vector<wallet2::pending_tx> 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<uint8_t>& extra);
std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra);
- bool sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, std::vector<cryptonote::tx_destination_entry> dsts) const;
+ bool sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, const std::vector<cryptonote::tx_destination_entry>& dsts, const unique_index_container& subtract_fee_from_outputs = {}) const;
void cold_tx_aux_import(const std::vector<pending_tx>& ptx, const std::vector<std::string>& tx_device_aux);
void cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::vector<std::string> & tx_device_aux);
uint64_t cold_key_image_sync(uint64_t &spent, uint64_t &unspent);
diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h
index 6706e77ff..5166f868f 100644
--- a/src/wallet/wallet_errors.h
+++ b/src/wallet/wallet_errors.h
@@ -85,6 +85,7 @@ namespace tools
// tx_too_big
// zero_amount
// zero_destination
+ // subtract_fee_from_bad_index
// wallet_rpc_error *
// daemon_busy
// no_connection_to_daemon
@@ -779,6 +780,15 @@ namespace tools
}
};
//----------------------------------------------------------------------------------------------------
+ struct subtract_fee_from_bad_index : public transfer_error
+ {
+ explicit subtract_fee_from_bad_index(std::string&& loc, long bad_index)
+ : transfer_error(std::move(loc),
+ "subtractfeefrom: bad index: " + std::to_string(bad_index) + " (indexes are 0-based)")
+ {
+ }
+ };
+ //----------------------------------------------------------------------------------------------------
struct wallet_rpc_error : public wallet_logic_error
{
const std::string& request() const { return m_request; }
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 1d835a86b..d7aa80e0a 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -987,9 +987,9 @@ namespace tools
return amount;
}
//------------------------------------------------------------------------------------------------------------------------------
- template<typename Ts, typename Tu, typename Tk>
+ template<typename Ts, typename Tu, typename Tk, typename Ta>
bool wallet_rpc_server::fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector,
- bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, Tu &weight, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay,
+ bool get_tx_key, Ts& tx_key, Tu &amount, Ta &amounts_by_dest, Tu &fee, Tu &weight, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay,
Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, Tk &spent_key_images, epee::json_rpc::error &er)
{
for (const auto & ptx : ptx_vector)
@@ -1006,6 +1006,12 @@ namespace tools
fill(fee, ptx.fee);
fill(weight, cryptonote::get_transaction_weight(ptx.tx));
+ // add amounts by destination
+ tools::wallet_rpc::amounts_list abd;
+ for (const auto& dst : ptx.dests)
+ abd.amounts.push_back(dst.amount);
+ fill(amounts_by_dest, abd);
+
// add spent key images
tools::wallet_rpc::key_image_list key_image_list;
bool all_are_txin_to_key = std::all_of(ptx.tx.vin.begin(), ptx.tx.vin.end(), [&](const cryptonote::txin_v& s_e) -> bool
@@ -1086,7 +1092,7 @@ namespace tools
{
uint64_t mixin = m_wallet->adjust_mixin(req.ring_size ? req.ring_size - 1 : 0);
uint32_t priority = m_wallet->adjust_priority(req.priority);
- std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices);
+ std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices, req.subtract_fee_from_outputs);
if (ptx_vector.empty())
{
@@ -1103,7 +1109,7 @@ namespace tools
return false;
}
- return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.weight, res.multisig_txset, res.unsigned_txset, req.do_not_relay,
+ return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.amounts_by_dest, res.fee, res.weight, res.multisig_txset, res.unsigned_txset, req.do_not_relay,
res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, res.spent_key_images, er);
}
catch (const std::exception& e)
@@ -1151,7 +1157,7 @@ namespace tools
return false;
}
- return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.weight_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay,
+ return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.amounts_by_dest_list, res.fee_list, res.weight_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay,
res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, res.spent_key_images_list, er);
}
catch (const std::exception& e)
@@ -1540,7 +1546,7 @@ namespace tools
{
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions();
- return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.weight_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay,
+ return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.amounts_by_dest_list, res.fee_list, res.weight_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay,
res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, res.spent_key_images_list, er);
}
catch (const std::exception& e)
@@ -1600,7 +1606,7 @@ namespace tools
uint32_t priority = m_wallet->adjust_priority(req.priority);
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, req.outputs, mixin, req.unlock_time, priority, extra, req.account_index, subaddr_indices);
- return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.weight_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay,
+ return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.amounts_by_dest_list, res.fee_list, res.weight_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay,
res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, res.spent_key_images_list, er);
}
catch (const std::exception& e)
@@ -1677,7 +1683,7 @@ namespace tools
return false;
}
- return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.weight, res.multisig_txset, res.unsigned_txset, req.do_not_relay,
+ return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.amounts_by_dest, res.fee, res.weight, res.multisig_txset, res.unsigned_txset, req.do_not_relay,
res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, res.spent_key_images, er);
}
catch (const std::exception& e)
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
index 97fc0a278..bfb7013e2 100644
--- a/src/wallet/wallet_rpc_server.h
+++ b/src/wallet/wallet_rpc_server.h
@@ -262,9 +262,9 @@ namespace tools
bool not_open(epee::json_rpc::error& er);
void handle_rpc_exception(const std::exception_ptr& e, epee::json_rpc::error& er, int default_error_code);
- template<typename Ts, typename Tu, typename Tk>
+ template<typename Ts, typename Tu, typename Tk, typename Ta>
bool fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector,
- bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, Tu &weight, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay,
+ bool get_tx_key, Ts& tx_key, Tu &amount, Ta &amounts_by_dest, Tu &fee, Tu &weight, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay,
Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, Tk &spent_key_images, epee::json_rpc::error &er);
bool validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, bool at_least_one_destination, epee::json_rpc::error& er);
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index 2a1e5ac41..f9f534097 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -47,7 +47,7 @@
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define WALLET_RPC_VERSION_MAJOR 1
-#define WALLET_RPC_VERSION_MINOR 26
+#define WALLET_RPC_VERSION_MINOR 27
#define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR)
namespace tools
@@ -530,11 +530,23 @@ namespace wallet_rpc
END_KV_SERIALIZE_MAP()
};
+ struct amounts_list
+ {
+ std::list<uint64_t> amounts;
+
+ bool operator==(const amounts_list& other) const { return amounts == other.amounts; }
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(amounts)
+ END_KV_SERIALIZE_MAP()
+ };
+
struct single_transfer_response
{
std::string tx_hash;
std::string tx_key;
uint64_t amount;
+ amounts_list amounts_by_dest;
uint64_t fee;
uint64_t weight;
std::string tx_blob;
@@ -547,6 +559,7 @@ namespace wallet_rpc
KV_SERIALIZE(tx_hash)
KV_SERIALIZE(tx_key)
KV_SERIALIZE(amount)
+ KV_SERIALIZE_OPT(amounts_by_dest, decltype(amounts_by_dest)())
KV_SERIALIZE(fee)
KV_SERIALIZE(weight)
KV_SERIALIZE(tx_blob)
@@ -564,6 +577,7 @@ namespace wallet_rpc
std::list<transfer_destination> destinations;
uint32_t account_index;
std::set<uint32_t> subaddr_indices;
+ std::set<uint32_t> subtract_fee_from_outputs;
uint32_t priority;
uint64_t ring_size;
uint64_t unlock_time;
@@ -577,6 +591,7 @@ namespace wallet_rpc
KV_SERIALIZE(destinations)
KV_SERIALIZE(account_index)
KV_SERIALIZE(subaddr_indices)
+ KV_SERIALIZE_OPT(subtract_fee_from_outputs, decltype(subtract_fee_from_outputs)())
KV_SERIALIZE(priority)
KV_SERIALIZE_OPT(ring_size, (uint64_t)0)
KV_SERIALIZE(unlock_time)
@@ -598,6 +613,7 @@ namespace wallet_rpc
std::list<std::string> tx_hash_list;
std::list<std::string> tx_key_list;
std::list<uint64_t> amount_list;
+ std::list<amounts_list> amounts_by_dest_list;
std::list<uint64_t> fee_list;
std::list<uint64_t> weight_list;
std::list<std::string> tx_blob_list;
@@ -610,6 +626,7 @@ namespace wallet_rpc
KV_SERIALIZE(tx_hash_list)
KV_SERIALIZE(tx_key_list)
KV_SERIALIZE(amount_list)
+ KV_SERIALIZE_OPT(amounts_by_dest_list, decltype(amounts_by_dest_list)())
KV_SERIALIZE(fee_list)
KV_SERIALIZE(weight_list)
KV_SERIALIZE(tx_blob_list)