mirror of
https://codeberg.org/wownero/lwsf-wow.git
synced 2026-10-17 05:41:30 +02:00
More on subaddr+error handling AND working tx construction
This commit is contained in:
@@ -81,6 +81,7 @@ set_property(TARGET monero::libraries PROPERTY
|
||||
common
|
||||
epee
|
||||
mnemonics
|
||||
qrcodegen
|
||||
wallet_api
|
||||
)
|
||||
|
||||
|
||||
@@ -28,11 +28,19 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "wallet/api/wallet2_api.h" // monero/src
|
||||
|
||||
namespace lwsf
|
||||
{
|
||||
std::string displayAmount(std::uint64_t amount);
|
||||
std::uint64_t amountFromString(const std::string &amount);
|
||||
bool addressValid(const std::string &str, Monero::NetworkType nettype);
|
||||
|
||||
std::vector<std::vector<std::uint8_t>> qrcode(Monero::Wallet const* wal, std::uint32_t major, std::uint32_t minor);
|
||||
|
||||
struct WalletManagerFactory
|
||||
{
|
||||
// logging levels for underlying library
|
||||
|
||||
+110
-21
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "backend.h"
|
||||
|
||||
#include <boost/numeric/conversion/cast.hpp>
|
||||
#include <boost/thread/lock_guard.hpp>
|
||||
#include "crypto/crypto.h" // monero/src
|
||||
#include "crypto/crypto-ops.h" // monero/src
|
||||
@@ -212,14 +213,20 @@ namespace lwsf { namespace internal { namespace backend
|
||||
template<typename F, typename T>
|
||||
void map_transaction(F& format, T& self)
|
||||
{
|
||||
//! TODO complete payment_id storage
|
||||
// make timestamp storage as portable as possible
|
||||
using time_point = std::chrono::system_clock::time_point;
|
||||
std::optional<std::int64_t> timestamp;
|
||||
if (self.timestamp)
|
||||
timestamp = boost::numeric_cast<std::int64_t>(self.timestamp->time_since_epoch().count());
|
||||
|
||||
auto payment_id = wire::variant(std::ref(self.payment_id));
|
||||
wire::object(format,
|
||||
WIRE_FIELD(raw_bytes),
|
||||
wire::optional_field("spends", wire::trusted_array(std::ref(self.spends))),
|
||||
wire::optional_field("receives", wire::trusted_array(std::ref(self.receives))),
|
||||
wire::optional_field("transfers", wire::trusted_array(std::ref(self.transfers))),
|
||||
WIRE_FIELD(description),
|
||||
WIRE_OPTIONAL_FIELD(timestamp),
|
||||
wire::optional_field("timestamp", std::ref(timestamp)),
|
||||
WIRE_FIELD(amount),
|
||||
WIRE_FIELD(fee),
|
||||
WIRE_OPTIONAL_FIELD(height),
|
||||
@@ -230,8 +237,16 @@ namespace lwsf { namespace internal { namespace backend
|
||||
WIRE_OPTION("payment_id32", crypto::hash, payment_id),
|
||||
WIRE_FIELD(id),
|
||||
WIRE_FIELD(prefix),
|
||||
WIRE_FIELD(coinbase)
|
||||
WIRE_FIELD(coinbase),
|
||||
WIRE_FIELD_DEFAULTED(failed, false)
|
||||
);
|
||||
|
||||
if constexpr (!std::is_const<T>())
|
||||
{
|
||||
self.timestamp = std::nullopt;
|
||||
if (timestamp)
|
||||
self.timestamp = time_point{time_point::duration{boost::numeric_cast<time_point::rep>(*timestamp)}};
|
||||
}
|
||||
}
|
||||
} // anonymous
|
||||
|
||||
@@ -268,6 +283,10 @@ namespace lwsf { namespace internal { namespace backend
|
||||
wire::optional_field("attributes", wire::trusted_array(std::ref(self.attributes))),
|
||||
WIRE_FIELD(scan_height),
|
||||
WIRE_FIELD(restore_height),
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
WIRE_FIELD(requested_start),
|
||||
>>>>>>> Stashed changes
|
||||
WIRE_FIELD_DEFAULTED(lookahead, config::default_lookahead),
|
||||
WIRE_FIELD_DEFAULTED(type, Monero::MAINNET),
|
||||
WIRE_FIELD(view),
|
||||
@@ -318,11 +337,12 @@ namespace lwsf { namespace internal { namespace backend
|
||||
return out.recipient;
|
||||
}
|
||||
|
||||
rpc::address_meta update_spend(transfer_spend& out, const rpc::transaction_spend& source)
|
||||
rpc::address_meta update_spend(transfer_spend& out, const rpc::transaction_spend& source, const crypto::public_key& output_pub)
|
||||
{
|
||||
out.amount = std::uint64_t(source.amount);
|
||||
out.sender = source.sender.value_or(rpc::address_meta{});
|
||||
out.tx_pub = source.tx_pub_key;
|
||||
out.output_pub = output_pub;
|
||||
return out.sender;
|
||||
}
|
||||
|
||||
@@ -357,7 +377,10 @@ namespace lwsf { namespace internal { namespace backend
|
||||
std::vector<rpc::address_meta> meta;
|
||||
|
||||
out.id = source.hash;
|
||||
out.timestamp = source.timestamp;
|
||||
if (source.timestamp)
|
||||
out.timestamp = std::chrono::system_clock::from_time_t(*source.timestamp);
|
||||
else
|
||||
out.timestamp = std::nullopt;
|
||||
out.fee = std::uint64_t(source.fee.value_or(rpc::uint64_string(0)));
|
||||
out.height = source.height;
|
||||
out.unlock_time = source.unlock_time;
|
||||
@@ -382,7 +405,7 @@ namespace lwsf { namespace internal { namespace backend
|
||||
{
|
||||
/* Frontend will typically know about spend before backend. So only
|
||||
merge and never erase spends. */
|
||||
const rpc::address_meta sub = update_spend(out.spends.try_emplace(image).first->second, spend);
|
||||
const rpc::address_meta sub = update_spend(out.spends.try_emplace(image).first->second, spend, output_pub);
|
||||
if (need_expansion(self, sub))
|
||||
meta.push_back(sub);
|
||||
total_spent += std::uint64_t(spend.amount);
|
||||
@@ -484,7 +507,10 @@ namespace lwsf { namespace internal { namespace backend
|
||||
if (!rescanning)
|
||||
{
|
||||
iter->second->height = std::nullopt;
|
||||
<<<<<<< Updated upstream
|
||||
iter->second->timestamp = std::nullopt;
|
||||
=======
|
||||
>>>>>>> Stashed changes
|
||||
iter->second->failed = false;
|
||||
}
|
||||
}
|
||||
@@ -716,7 +742,8 @@ namespace lwsf { namespace internal { namespace backend
|
||||
}
|
||||
|
||||
transaction::transaction()
|
||||
: spends(),
|
||||
: raw_bytes(),
|
||||
spends(),
|
||||
receives(),
|
||||
transfers(),
|
||||
description(),
|
||||
@@ -733,15 +760,33 @@ namespace lwsf { namespace internal { namespace backend
|
||||
failed(false)
|
||||
{}
|
||||
|
||||
transaction::transaction(const transaction& rhs)
|
||||
: raw_bytes(rhs.raw_bytes.clone()), // required because of this
|
||||
spends(rhs.spends),
|
||||
receives(rhs.receives),
|
||||
transfers(rhs.transfers),
|
||||
description(rhs.description),
|
||||
timestamp(rhs.timestamp),
|
||||
height(rhs.height),
|
||||
amount(rhs.amount),
|
||||
fee(rhs.fee),
|
||||
unlock_time(rhs.unlock_time),
|
||||
direction(rhs.direction),
|
||||
payment_id(rhs.payment_id),
|
||||
id(rhs.id),
|
||||
prefix(rhs.prefix),
|
||||
coinbase(rhs.coinbase),
|
||||
failed(rhs.failed)
|
||||
{}
|
||||
|
||||
bool transaction::is_unlocked(const std::uint64_t chain_height, const Monero::NetworkType type) const
|
||||
{
|
||||
const std::uint64_t the_height = height.value_or(std::numeric_limits<std::uint64_t>::max());
|
||||
if(!is_tx_spendtime_unlocked(chain_height, unlock_time, the_height, type))
|
||||
if (!height)
|
||||
return false;
|
||||
|
||||
if(the_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE > chain_height)
|
||||
if(!is_tx_spendtime_unlocked(chain_height, unlock_time, *height, type))
|
||||
return false;
|
||||
if(*height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE > chain_height)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -751,6 +796,10 @@ namespace lwsf { namespace internal { namespace backend
|
||||
primary{},
|
||||
refresh_error(),
|
||||
lookahead_error(default_subaddr_state),
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
import_error(),
|
||||
>>>>>>> Stashed changes
|
||||
last_sync(),
|
||||
blockchain_height(0),
|
||||
per_byte_fee(0),
|
||||
@@ -783,17 +832,26 @@ namespace lwsf { namespace internal { namespace backend
|
||||
return rct::rct2pk(rct::addKeys(rct::pk2rct(primary.spend.pub), rct::pk2rct(M)));
|
||||
}
|
||||
|
||||
std::string wallet::get_spend_address(const rpc::address_meta& index) const
|
||||
cryptonote::account_public_address wallet::get_spend_account(const rpc::address_meta& index) const
|
||||
{
|
||||
const bool is_subaddress = !index.is_default();
|
||||
const auto spend_public = get_spend_public(index);
|
||||
const crypto::public_key view_public = is_subaddress ?
|
||||
rct::rct2pk(rct::scalarmultKey(rct::pk2rct(spend_public), rct::sk2rct(primary.view.sec))) : primary.view.pub;
|
||||
return {spend_public, view_public};
|
||||
}
|
||||
|
||||
cryptonote::account_keys wallet::get_primary_keys() const
|
||||
{
|
||||
return {
|
||||
{primary.view.pub, primary.spend.pub}, primary.spend.sec, primary.view.sec
|
||||
};
|
||||
}
|
||||
|
||||
std::string wallet::get_spend_address(const rpc::address_meta& index) const
|
||||
{
|
||||
return cryptonote::get_account_address_as_str(
|
||||
get_net_type(),
|
||||
is_subaddress,
|
||||
cryptonote::account_public_address{spend_public, view_public}
|
||||
get_net_type(), !index.is_default(), get_spend_account(index)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -830,8 +888,17 @@ namespace lwsf { namespace internal { namespace backend
|
||||
boost::unique_lock<boost::mutex> lock{sync};
|
||||
{
|
||||
passed_login = false;
|
||||
<<<<<<< Updated upstream
|
||||
server_lookahead = 0;
|
||||
lookahead_error = {error::subaddr_disabled};
|
||||
=======
|
||||
blockchain_height = 0;
|
||||
per_byte_fee = 0;
|
||||
fee_mask = 0;
|
||||
server_lookahead = 0;
|
||||
import_error = {};
|
||||
lookahead_error = error::subaddr_disabled;
|
||||
>>>>>>> Stashed changes
|
||||
const rpc::login_request login{
|
||||
primary.address, primary.view.sec, true, primary.generated_locally
|
||||
};
|
||||
@@ -905,6 +972,7 @@ namespace lwsf { namespace internal { namespace backend
|
||||
if (now - last_sync < config::refresh_interval_min)
|
||||
return refresh_error;
|
||||
}
|
||||
last_sync = now;
|
||||
|
||||
struct call_refreshed
|
||||
{
|
||||
@@ -930,7 +998,10 @@ namespace lwsf { namespace internal { namespace backend
|
||||
if (failed_login)
|
||||
{
|
||||
lock.lock();
|
||||
<<<<<<< Updated upstream
|
||||
last_sync = now;
|
||||
=======
|
||||
>>>>>>> Stashed changes
|
||||
return refresh_error = failed_login;
|
||||
}
|
||||
}
|
||||
@@ -944,7 +1015,6 @@ namespace lwsf { namespace internal { namespace backend
|
||||
}
|
||||
lock.lock();
|
||||
|
||||
last_sync = now;
|
||||
passed_login = bool(txs_response) && bool(outs_response); // reset state
|
||||
|
||||
if (!txs_response)
|
||||
@@ -1191,7 +1261,13 @@ namespace lwsf { namespace internal { namespace backend
|
||||
{
|
||||
boost::unique_lock<boost::mutex> lock{sync};
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
if (primary.requested_start < height)
|
||||
=======
|
||||
if (primary.restore_height <= height)
|
||||
return import_error = {};
|
||||
if (import_error && import_error == error::import_pending)
|
||||
>>>>>>> Stashed changes
|
||||
return import_error;
|
||||
|
||||
if (!passed_login)
|
||||
@@ -1224,6 +1300,11 @@ namespace lwsf { namespace internal { namespace backend
|
||||
case 0:
|
||||
return import_error = error::import_pending;
|
||||
case 1:
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
if (import->import_fee.value_or(rpc::uint64_string(0)) == rpc::uint64_string(0))
|
||||
return import_error = error::import_pending;
|
||||
>>>>>>> Stashed changes
|
||||
return import_error = error::import_invalid;
|
||||
case 2:
|
||||
break;
|
||||
@@ -1258,14 +1339,22 @@ namespace lwsf { namespace internal { namespace backend
|
||||
primary.addressbook[i] = address_book_entry{std::move(*import->payment_address), std::move(payment_id), std::move(description)};
|
||||
|
||||
return import_error = {};
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
}
|
||||
|
||||
expect<std::vector<rpc::random_outputs>> wallet::get_decoys(const rpc::get_random_outs_request& req)
|
||||
{
|
||||
auto resp = rpc::invoke<rpc::get_random_outs_response>(client, req);
|
||||
if (!resp)
|
||||
return resp.error();
|
||||
return {std::move(resp->amount_outs)};
|
||||
>>>>>>> Stashed changes
|
||||
}
|
||||
|
||||
std::error_code wallet::send_tx(epee::byte_slice tx_bytes)
|
||||
{
|
||||
const rpc::submit_raw_tx_request request{std::move(tx_bytes)};
|
||||
auto response = rpc::invoke<rpc::submit_raw_tx_response>(client, request);
|
||||
if (!response)
|
||||
return response.error();
|
||||
return {};
|
||||
return rpc::invoke<rpc::submit_raw_tx_response>(client, request).error();
|
||||
}
|
||||
}}} // lwsf // internal // backend
|
||||
|
||||
+20
-3
@@ -48,6 +48,7 @@
|
||||
#include "crypto/crypto.h" // monero/src
|
||||
#include "crypto/hash.h" // monero/src
|
||||
#include "cryptonote_config.h" // monero/src
|
||||
#include "cryptonote_basic/account.h" // monero/src
|
||||
#include "lwsf_config.h"
|
||||
#include "net/http_client.h" // monero/contrib/epee/include
|
||||
#include "ringct/rctTypes.h" // monero/src
|
||||
@@ -152,6 +153,10 @@ namespace lwsf { namespace internal { namespace backend
|
||||
transfer_out()
|
||||
: address(), amount(0), secret{}
|
||||
{}
|
||||
|
||||
transfer_out(std::string address, const std::uint64_t amount)
|
||||
: address(std::move(address)), amount(amount), secret{}
|
||||
{}
|
||||
};
|
||||
WIRE_DECLARE_OBJECT(transfer_out);
|
||||
|
||||
@@ -160,20 +165,20 @@ namespace lwsf { namespace internal { namespace backend
|
||||
transaction();
|
||||
|
||||
transaction(transaction&&) = default;
|
||||
transaction(const transaction&) = default;
|
||||
transaction(const transaction& rhs);
|
||||
transaction& operator=(transaction&&) = default;
|
||||
transaction& operator=(const transaction&) = default;
|
||||
|
||||
bool is_unlocked(std::uint64_t chain_height, Monero::NetworkType type) const;
|
||||
|
||||
/*! flat_map is used here for faster copies/merging. A single allocation
|
||||
is needed in the copy (done every refresh interval), instead of an
|
||||
allocation per key. */
|
||||
epee::byte_slice raw_bytes; // for sends held for 100 confirmations
|
||||
boost::container::flat_map<crypto::key_image, transfer_spend, memory> spends;
|
||||
boost::container::flat_map<crypto::public_key, transfer_in, memory> receives; //!< Key is output pub
|
||||
std::vector<transfer_out> transfers;
|
||||
std::string description;
|
||||
std::optional<std::time_t> timestamp;
|
||||
std::optional<std::chrono::system_clock::time_point> timestamp; //!< guaranteed to be unix epoch in C++20
|
||||
std::optional<std::uint64_t> height;
|
||||
std::uint64_t amount;
|
||||
std::uint64_t fee;
|
||||
@@ -237,10 +242,21 @@ namespace lwsf { namespace internal { namespace backend
|
||||
|
||||
wallet();
|
||||
|
||||
// `sync` mutex is NOT acquired for this group
|
||||
|
||||
cryptonote::network_type get_net_type() const;
|
||||
crypto::public_key get_spend_public(const rpc::address_meta& index) const;
|
||||
cryptonote::account_keys get_primary_keys() const;
|
||||
cryptonote::account_public_address get_spend_account(const rpc::address_meta& index) const;
|
||||
std::string get_spend_address(const rpc::address_meta& index) const;
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
// End GROUP
|
||||
|
||||
// `sync` mutex IS acquired for this group (until end). Do not hold this mutex
|
||||
|
||||
>>>>>>> Stashed changes
|
||||
//! Serialize `this` wallet to msgpack. Locks contents.
|
||||
expect<epee::byte_slice> to_bytes() const;
|
||||
|
||||
@@ -266,6 +282,7 @@ namespace lwsf { namespace internal { namespace backend
|
||||
|
||||
std::error_code restore_height(const std::uint64_t height);
|
||||
|
||||
expect<std::vector<rpc::random_outputs>> get_decoys(const rpc::get_random_outs_request& req);
|
||||
std::error_code send_tx(epee::byte_slice tx_bytes);
|
||||
};
|
||||
}}} // lwsf // internal // backend
|
||||
|
||||
@@ -55,6 +55,29 @@ namespace lwsf
|
||||
return "server has subaddresses disabled";
|
||||
case error::subaddr_local:
|
||||
return "lwsf (local) limits on subaddresses too small";
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
case error::tx_critical:
|
||||
return "Tx construction had critical error";
|
||||
case error::tx_decoys:
|
||||
return "Tx construction failed due to invalid decoy response";
|
||||
case error::tx_extra:
|
||||
return "Tx construction failed to generate tx extra";
|
||||
case error::tx_failed:
|
||||
return "Tx construction failed (construct_tx failure)";
|
||||
case error::tx_invalid_address:
|
||||
return "Tx construction was given invalid address";
|
||||
case error::tx_long_pid:
|
||||
return "Tx construction requested with deprecated long payment id";
|
||||
case error::tx_low_funds:
|
||||
return "Tx construction failed due to insufficient funds";
|
||||
case error::tx_size_mismatch:
|
||||
return "Tx construction requested with amount and destination mismatch";
|
||||
case error::tx_sweep:
|
||||
return "Tx construction sweep with multiple destinations";
|
||||
case error::tx_two_pid:
|
||||
return "Tx construction given two payment ids";
|
||||
>>>>>>> Stashed changes
|
||||
case error::unexpected_userinfo:
|
||||
return "Unexpected user+pass field in URL";
|
||||
|
||||
|
||||
+13
@@ -45,6 +45,19 @@ namespace lwsf
|
||||
subaddr_ahead, //!< Server limits on subaddresses affects lookahead
|
||||
subaddr_disabled, //!< Server has subaddresses disabled
|
||||
subaddr_local, //!< Local limits on subaddresses too small
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
tx_critical, //!< Tx creation had critical error
|
||||
tx_decoys, //!< Tx creation failed due to invalid decoy response
|
||||
tx_extra, //!< Tx creation failed to generate tx extra
|
||||
tx_failed, //!< Tx creation failed (contruct_tx failure)
|
||||
tx_invalid_address, //!< Tx creation was given invalid address
|
||||
tx_long_pid, //!< Tx creation with long payment_id (deprecated)
|
||||
tx_low_funds, //!< Tx creation failed due to insufficient funds
|
||||
tx_size_mismatch, //!< Tx creation mismatch on amounts and destinations
|
||||
tx_two_pid, //!< Tx creation given two payment ids
|
||||
tx_sweep, //!< Tx creation requested sweep to multiple dests
|
||||
>>>>>>> Stashed changes
|
||||
unexpected_userinfo,//!< Unexpected user+pass provided
|
||||
};
|
||||
|
||||
|
||||
+8
-1
@@ -40,6 +40,10 @@ namespace lwsf { namespace config
|
||||
std::uint32_t minor = 0;
|
||||
};
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
constexpr const std::size_t bulletproof_version = 4;
|
||||
>>>>>>> Stashed changes
|
||||
constexpr const std::chrono::seconds connect_timeout{5};
|
||||
constexpr const std::chrono::seconds daemon_status_cache{10};
|
||||
constexpr const std::string_view default_account_name{"Untitled account"};
|
||||
@@ -47,8 +51,11 @@ namespace lwsf { namespace config
|
||||
constexpr const lookahead default_minimal_lookahead{5, 15};
|
||||
constexpr const std::string_view default_primary_name{"Primary account"};
|
||||
constexpr const std::size_t initial_buffer_size = 1024 * 64; // 64 KiB
|
||||
constexpr const std::size_t max_inputs = 512;
|
||||
constexpr const std::size_t max_ring_size = 128;
|
||||
constexpr const std::size_t max_txes_in_rpc = 2048;
|
||||
constexpr const std::uint32_t mixin_default = 16;
|
||||
constexpr const std::size_t minimum_outputs = 2;
|
||||
constexpr const std::uint32_t mixin_default = 15;
|
||||
constexpr const std::chrono::seconds refresh_interval{30};
|
||||
constexpr const std::chrono::seconds refresh_interval_min{5};
|
||||
constexpr const std::chrono::seconds rpc_timeout{5};
|
||||
|
||||
+49
-11
@@ -32,13 +32,23 @@
|
||||
#include "backend.h"
|
||||
#include "byte_slice.h" // monero/contrib/epee/include
|
||||
#include "cryptonote_basic/cryptonote_format_utils.h" // monero/src
|
||||
#include "error.h"
|
||||
#include "string_tools.h" // monero/contrib/epee/include
|
||||
|
||||
|
||||
namespace lwsf { namespace internal
|
||||
{
|
||||
pending_transaction::pending_transaction(std::shared_ptr<backend::wallet> wallet, cryptonote::transaction&& source, Priority priority)
|
||||
: wallet_(std::move(wallet)), source_(std::move(source)), error_(), priority_(priority)
|
||||
namespace
|
||||
{
|
||||
cryptonote::transaction get_tx(expect<cryptonote::transaction>&& source)
|
||||
{
|
||||
if (source)
|
||||
return {std::move(*source)};
|
||||
return {};
|
||||
}
|
||||
}
|
||||
pending_transaction::pending_transaction(std::shared_ptr<backend::wallet> wallet, expect<cryptonote::transaction> source, std::shared_ptr<backend::transaction> local)
|
||||
: wallet_(std::move(wallet)), error_(source.error()), local_(std::move(local))
|
||||
{
|
||||
if (!wallet_)
|
||||
throw std::invalid_argument{"lwsf::internal::pending_transction cannot be given nullptr backend"};
|
||||
@@ -47,30 +57,58 @@ namespace lwsf { namespace internal
|
||||
pending_transaction::~pending_transaction()
|
||||
{}
|
||||
|
||||
int pending_transaction::status() const
|
||||
{
|
||||
return error_ ? Status_Error : Status_Ok;
|
||||
}
|
||||
|
||||
std::string pending_transaction::errorString() const
|
||||
{
|
||||
if (!error_)
|
||||
return {};
|
||||
return error_.message();
|
||||
if (error_)
|
||||
return error_.message();
|
||||
return {};
|
||||
}
|
||||
|
||||
bool pending_transaction::commit(const std::string &filename, bool)
|
||||
{
|
||||
// saving to file not yet implemented
|
||||
if (!filename.empty())
|
||||
if (error_ || !filename.empty())
|
||||
return false;
|
||||
|
||||
error_ = wallet_->send_tx(
|
||||
epee::byte_slice{cryptonote::t_serializable_object_to_blob(source_)}
|
||||
);
|
||||
if (local_)
|
||||
error_ = wallet_->send_tx(local_->raw_bytes.clone());
|
||||
else if (!error_)
|
||||
error_ = error::tx_failed;
|
||||
|
||||
if (!error_ && local_)
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{wallet_->sync};
|
||||
auto entry = wallet_->primary.txes.try_emplace(local_->id).first;
|
||||
if (!entry->second)
|
||||
entry->second = local_;
|
||||
}
|
||||
|
||||
return !error_;
|
||||
}
|
||||
|
||||
std::uint64_t pending_transaction::amount() const
|
||||
{
|
||||
if (!local_)
|
||||
return 0;
|
||||
return local_->amount;
|
||||
}
|
||||
|
||||
std::uint64_t pending_transaction::fee() const
|
||||
{ return get_tx_fee(source_); }
|
||||
{
|
||||
if (!local_)
|
||||
return 0;
|
||||
return local_->fee;
|
||||
}
|
||||
|
||||
std::vector<std::string> pending_transaction::txid() const
|
||||
{
|
||||
return {epee::string_tools::pod_to_hex(get_transaction_hash(source_))};
|
||||
if (!local_)
|
||||
return {};
|
||||
return {epee::to_hex::string(epee::as_byte_span(local_->id))};
|
||||
}
|
||||
}} // lwsf // internal
|
||||
|
||||
+17
-11
@@ -32,22 +32,28 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "common/expect.h" // monero/src
|
||||
#include "cryptonote_basic/cryptonote_basic.h" // monero/src
|
||||
#include "wallet/api/wallet2_api.h" // moneor/src
|
||||
|
||||
namespace lwsf { namespace internal
|
||||
{
|
||||
namespace backend { struct wallet; }
|
||||
namespace backend
|
||||
{
|
||||
struct transaction;
|
||||
struct wallet;
|
||||
}
|
||||
|
||||
class pending_transaction final : public Monero::PendingTransaction
|
||||
{
|
||||
const std::shared_ptr<backend::wallet> wallet_;
|
||||
const cryptonote::transaction source_;
|
||||
std::error_code error_;
|
||||
const Priority priority_;
|
||||
const std::shared_ptr<backend::transaction> local_;
|
||||
|
||||
public:
|
||||
pending_transaction(std::shared_ptr<backend::wallet> wallet, cryptonote::transaction&& source, Priority priority);
|
||||
pending_transaction(std::shared_ptr<backend::wallet> wallet, expect<cryptonote::transaction> source, std::shared_ptr<backend::transaction> local = {});
|
||||
|
||||
pending_transaction(pending_transaction&&) = delete;
|
||||
pending_transaction(const pending_transaction&) = delete;
|
||||
@@ -57,16 +63,16 @@ namespace lwsf { namespace internal
|
||||
pending_transaction& operator=(pending_transaction&&) = delete;
|
||||
pending_transaction& operator=(const pending_transaction&) = delete;
|
||||
|
||||
virtual int status() const override final { return priority_; }
|
||||
virtual int status() const override final;
|
||||
virtual std::string errorString() const override final;
|
||||
virtual bool commit(const std::string &filename = "", bool overwrite = false) override final;
|
||||
virtual std::uint64_t amount() const = 0;
|
||||
virtual std::uint64_t amount() const override final;
|
||||
virtual std::uint64_t dust() const override final { return 0; }
|
||||
virtual std::uint64_t fee() const override final;
|
||||
virtual std::vector<std::string> txid() const override final;
|
||||
virtual std::uint64_t txCount() const override final { return 1; }
|
||||
virtual std::vector<uint32_t> subaddrAccount() const = 0;
|
||||
virtual std::vector<std::set<uint32_t>> subaddrIndices() const = 0;
|
||||
virtual std::vector<uint32_t> subaddrAccount() const override final { return {}; }
|
||||
virtual std::vector<std::set<uint32_t>> subaddrIndices() const override final { return {}; }
|
||||
|
||||
/**
|
||||
* @brief multisigSignData
|
||||
@@ -84,12 +90,12 @@ namespace lwsf { namespace internal
|
||||
* pendingTransaction->signMultisigTx();
|
||||
* pendingTransaction->commit();
|
||||
*/
|
||||
virtual std::string multisigSignData() = 0;
|
||||
virtual void signMultisigTx() = 0;
|
||||
virtual std::string multisigSignData() override final { return {}; }
|
||||
virtual void signMultisigTx() override final {}
|
||||
/**
|
||||
* @brief signersKeys
|
||||
* @return vector of base58-encoded signers' public keys
|
||||
*/
|
||||
virtual std::vector<std::string> signersKeys() const = 0;
|
||||
virtual std::vector<std::string> signersKeys() const override final { return {}; }
|
||||
};
|
||||
}} // lwsf // internal
|
||||
|
||||
+33
@@ -246,6 +246,39 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
}
|
||||
|
||||
|
||||
void read_bytes(wire::json_reader& source, random_output& self)
|
||||
{
|
||||
epee::byte_slice rct;
|
||||
wire::object(source,
|
||||
WIRE_FIELD(global_index),
|
||||
WIRE_FIELD(public_key),
|
||||
wire::field("rct", std::ref(rct))
|
||||
);
|
||||
|
||||
if (rct.size() < sizeof(self.rct))
|
||||
WIRE_DLOG_THROW_(wire::error::schema::fixed_binary);
|
||||
std::memcpy(std::addressof(self.rct), rct.data(), sizeof(self.rct));
|
||||
}
|
||||
|
||||
void read_bytes(wire::json_reader& source, random_outputs& self)
|
||||
{
|
||||
using max_ring = wire::max_element_count<config::max_ring_size>;
|
||||
wire::object(source, WIRE_FIELD_ARRAY(outputs, max_ring), WIRE_FIELD(amount));
|
||||
}
|
||||
|
||||
void write_bytes(wire::json_writer& dest, const get_random_outs_request& self)
|
||||
{
|
||||
using unused = wire::max_element_count<0>;
|
||||
wire::object(dest, WIRE_FIELD_ARRAY(amounts, unused), WIRE_FIELD(count));
|
||||
}
|
||||
|
||||
void read_bytes(wire::json_reader& source, get_random_outs_response& self)
|
||||
{
|
||||
using max_inputs = wire::max_element_count<config::max_inputs>;
|
||||
wire::object(source, WIRE_FIELD_ARRAY(amount_outs, max_inputs));
|
||||
}
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
//! A wrapper that simulates an array by only using the first element
|
||||
|
||||
@@ -227,6 +227,48 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
void read_bytes(wire::json_reader&, get_address_txs&);
|
||||
|
||||
|
||||
struct random_output
|
||||
{
|
||||
random_output()
|
||||
: global_index(uint64_string(0)), public_key{}, rct{}
|
||||
{}
|
||||
|
||||
uint64_string global_index;
|
||||
rct::key public_key;
|
||||
rct::key rct;
|
||||
};
|
||||
void read_bytes(wire::json_reader&, random_output&);
|
||||
|
||||
struct random_outputs
|
||||
{
|
||||
random_outputs()
|
||||
: outputs(), amount(uint64_string(0))
|
||||
{}
|
||||
|
||||
std::vector<random_output> outputs;
|
||||
uint64_string amount;
|
||||
};
|
||||
void read_bytes(wire::json_reader&, random_outputs&);
|
||||
|
||||
struct get_random_outs_request
|
||||
{
|
||||
get_random_outs_request() = delete;
|
||||
|
||||
std::vector<uint64_string> amounts;
|
||||
std::uint32_t count; // mixin
|
||||
};
|
||||
void write_bytes(wire::json_writer&, const get_random_outs_request&);
|
||||
|
||||
struct get_random_outs_response
|
||||
{
|
||||
get_random_outs_response() = delete;
|
||||
static constexpr const char* endpoint() noexcept { return "/get_random_outs"; }
|
||||
|
||||
std::vector<random_outputs> amount_outs;
|
||||
};
|
||||
void read_bytes(wire::json_reader&, get_random_outs_response&);
|
||||
|
||||
|
||||
struct subaddrs
|
||||
{
|
||||
constexpr subaddrs() noexcept
|
||||
|
||||
@@ -119,13 +119,6 @@ namespace lwsf { namespace internal
|
||||
balance.unlocked += receive.second.amount;
|
||||
}
|
||||
|
||||
if (!tx.second->spends.empty())
|
||||
{
|
||||
auto& balance = balances[tx.second->spends.begin()->second.sender.maj_i];
|
||||
balance.unlocked -= tx.second->fee;
|
||||
balance.total -= tx.second->fee;
|
||||
}
|
||||
|
||||
for (const auto& spend : tx.second->spends)
|
||||
{
|
||||
auto& balance = balances[spend.second.sender.maj_i];
|
||||
|
||||
@@ -75,6 +75,15 @@ namespace lwsf { namespace internal
|
||||
update_transfers();
|
||||
}
|
||||
|
||||
std::uint64_t transaction_info::blockHeight() const
|
||||
{
|
||||
if (data_->height)
|
||||
return *data_->height;
|
||||
if (isFailed())
|
||||
return 0;
|
||||
return std::numeric_limits<std::uint64_t>::max();
|
||||
}
|
||||
|
||||
std::string transaction_info::description() const
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{wallet_->sync};
|
||||
@@ -151,6 +160,15 @@ namespace lwsf { namespace internal
|
||||
return epee::to_hex::string(epee::as_byte_span(data_->id));
|
||||
}
|
||||
|
||||
std::time_t transaction_info::timestamp() const
|
||||
{
|
||||
if (data_->timestamp)
|
||||
return std::chrono::system_clock::to_time_t(*data_->timestamp);
|
||||
if (isFailed())
|
||||
return std::numeric_limits<std::time_t>::min();
|
||||
return std::numeric_limits<std::time_t>::max();
|
||||
}
|
||||
|
||||
std::string transaction_info::paymentId() const
|
||||
{
|
||||
return std::visit(payment_id_to_hex{}, data_->payment_id);
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace lwsf { namespace internal
|
||||
virtual bool isCoinbase() const override { return data_->coinbase; }
|
||||
virtual std::uint64_t amount() const override { return data_->amount; }
|
||||
virtual std::uint64_t fee() const override { return data_->fee; }
|
||||
virtual std::uint64_t blockHeight() const override { return data_->height.value_or(std::numeric_limits<std::uint64_t>::max()); }
|
||||
virtual std::uint64_t blockHeight() const override;
|
||||
virtual std::string description() const override;
|
||||
virtual std::set<uint32_t> subaddrIndex() const override;
|
||||
virtual std::uint32_t subaddrAccount() const override;
|
||||
@@ -72,7 +72,7 @@ namespace lwsf { namespace internal
|
||||
virtual std::uint64_t confirmations() const override;
|
||||
virtual std::uint64_t unlockTime() const override { return data_->unlock_time; }
|
||||
virtual std::string hash() const override;
|
||||
virtual std::time_t timestamp() const override { return data_->timestamp.value_or(std::numeric_limits<std::time_t>::max()); }
|
||||
virtual std::time_t timestamp() const override;
|
||||
virtual std::string paymentId() const override;
|
||||
virtual const std::vector<Transfer>& transfers() const override { return transfers_; }
|
||||
};
|
||||
|
||||
+598
-47
@@ -1,21 +1,21 @@
|
||||
// Copyright (c) 2014-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
|
||||
@@ -43,6 +43,7 @@
|
||||
#include "address_book.h"
|
||||
#include "backend.h"
|
||||
#include "cryptonote_basic/cryptonote_format_utils.h" // monero/src
|
||||
#include "cryptonote_core/cryptonote_tx_utils.h" // monero/src
|
||||
#include "error.h"
|
||||
#include "hex.h" // monero/contrib/epee/include
|
||||
#include "lwsf_config.h"
|
||||
@@ -51,6 +52,7 @@
|
||||
#include "net/parse.h" // monero/src
|
||||
#include "net/socks.h" // monero/src
|
||||
#include "net/socks_connect.h" // monero/src
|
||||
#include "pending_transaction.h"
|
||||
#include "subaddress_account.h"
|
||||
#include "subaddress_minor.h"
|
||||
#include "transaction_history.h"
|
||||
@@ -58,8 +60,7 @@
|
||||
#include "wire/msgpack.h"
|
||||
|
||||
namespace lwsf { namespace internal
|
||||
{
|
||||
|
||||
{
|
||||
namespace
|
||||
{
|
||||
//! The stored wallet file always has this at beginning
|
||||
@@ -76,6 +77,94 @@ namespace lwsf { namespace internal
|
||||
}
|
||||
};
|
||||
|
||||
std::uint64_t calculate_fee_from_weight(const std::uint64_t base_fee, const std::uint64_t weight, const std::uint64_t fee_quantization_mask)
|
||||
{
|
||||
uint64_t fee = weight * base_fee;
|
||||
fee = (fee + fee_quantization_mask - 1) / fee_quantization_mask * fee_quantization_mask;
|
||||
return fee;
|
||||
}
|
||||
|
||||
std::size_t estimate_rct_tx_size(const std::size_t n_inputs, const std::size_t n_outputs, const std::uint32_t mixin, const std::size_t extra_size)
|
||||
{
|
||||
std::size_t size = 0;
|
||||
|
||||
// tx prefix
|
||||
|
||||
// first few bytes
|
||||
size += 1 + 6;
|
||||
|
||||
// vin
|
||||
size += n_inputs * (1+6+(mixin+1)*2+32);
|
||||
|
||||
// vout
|
||||
size += n_outputs * (6+32);
|
||||
|
||||
// extra
|
||||
size += extra_size;
|
||||
|
||||
// rct signatures
|
||||
|
||||
// type
|
||||
size += 1;
|
||||
|
||||
// rangeSigs
|
||||
//if (bulletproof || bulletproof_plus)
|
||||
{
|
||||
std::size_t log_padded_outputs = 0;
|
||||
while ((1<<log_padded_outputs) < n_outputs)
|
||||
++log_padded_outputs;
|
||||
size += (2 * (6 + log_padded_outputs) + /*(bulletproof_plus ? */ 6 /* : (4 + 5))*/) * 32 + 3;
|
||||
}
|
||||
//else
|
||||
// size += (2*64*32+32+64*32) * n_outputs;
|
||||
|
||||
// MGs/CLSAGs
|
||||
//if (clsag)
|
||||
size += n_inputs * (32 * (mixin+1) + 64);
|
||||
//else
|
||||
// size += n_inputs * (64 * (mixin+1) + 32);
|
||||
|
||||
//if (use_view_tags)
|
||||
size += n_outputs * sizeof(crypto::view_tag);
|
||||
|
||||
// mixRing - not serialized, can be reconstructed
|
||||
/* size += 2 * 32 * (mixin+1) * n_inputs; */
|
||||
|
||||
// pseudoOuts
|
||||
size += 32 * n_inputs;
|
||||
// ecdhInfo
|
||||
size += 8 * n_outputs;
|
||||
// outPk - only commitment is saved
|
||||
size += 32 * n_outputs;
|
||||
// txnFee
|
||||
size += 4;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
std::uint64_t estimate_tx_weight(const std::size_t n_inputs, const std::size_t n_outputs, const std::uint32_t mixin, const std::size_t extra_size)
|
||||
{
|
||||
size_t size = estimate_rct_tx_size(n_inputs, n_outputs, mixin, extra_size);
|
||||
if (/*use_rct && (bulletproof || bulletproof_plus) && */n_outputs > 2)
|
||||
{
|
||||
const uint64_t bp_base = (32 * (/*(bulletproof_plus ?*/ 6 /* : 9)*/ + 7 * 2)) / 2; // notional size of a 2 output proof, normalized to 1 proof (ie, divided by 2)
|
||||
size_t log_padded_outputs = 2;
|
||||
while ((1<<log_padded_outputs) < n_outputs)
|
||||
++log_padded_outputs;
|
||||
uint64_t nlr = 2 * (6 + log_padded_outputs);
|
||||
const uint64_t bp_size = 32 * (/*(bulletproof_plus ? */ 6 /* : 9)*/ + nlr);
|
||||
const uint64_t bp_clawback = (bp_base * (1<<log_padded_outputs) - bp_size) * 4 / 5;
|
||||
MDEBUG("clawback on size " << size << ": " << bp_clawback);
|
||||
size += bp_clawback;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
std::uint64_t estimate_fee(const std::uint64_t base_fee, const std::size_t n_inputs, const std::size_t n_outputs, const std::uint32_t mixin, const std::size_t extra_size, const std::uint64_t fee_mask)
|
||||
{
|
||||
return calculate_fee_from_weight(base_fee, estimate_tx_weight(n_inputs, n_outputs, mixin, extra_size), fee_mask);
|
||||
}
|
||||
|
||||
struct encrypted_file
|
||||
{
|
||||
std::string cipher; //!< Name of cipher+authentication used
|
||||
@@ -132,7 +221,7 @@ namespace lwsf { namespace internal
|
||||
static constexpr std::size_t nonce_size() noexcept
|
||||
{
|
||||
return crypto_aead_chacha20poly1305_ietf_NPUBBYTES;
|
||||
}
|
||||
}
|
||||
static epee::byte_slice get_nonce() { return get_random(nonce_size()); }
|
||||
|
||||
//! \pre `sodium_init()` has been called
|
||||
@@ -168,7 +257,7 @@ namespace lwsf { namespace internal
|
||||
get_nonce(),
|
||||
nullptr,
|
||||
std::max(iterations, ops_min()),
|
||||
memory_limit()
|
||||
memory_limit()
|
||||
};
|
||||
|
||||
const auto key = out.get_key(password);
|
||||
@@ -210,7 +299,7 @@ namespace lwsf { namespace internal
|
||||
|
||||
const auto key = get_key(password);
|
||||
static_assert(key_size() == key.size());
|
||||
|
||||
|
||||
epee::byte_stream buffer;
|
||||
buffer.put_n(0, epayload.size());
|
||||
|
||||
@@ -279,7 +368,7 @@ namespace lwsf { namespace internal
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
|
||||
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
|
||||
bool atomic_file_write(const std::filesystem::path& filename, const std::filesystem::path& directory, epee::byte_slice contents) noexcept
|
||||
{
|
||||
const int fd =
|
||||
@@ -388,7 +477,7 @@ namespace lwsf { namespace internal
|
||||
{
|
||||
struct set_stop_
|
||||
{
|
||||
void operator()(state* val) const noexcept
|
||||
void operator()(state* val) const noexcept
|
||||
{
|
||||
if (val)
|
||||
*val = state::stop;
|
||||
@@ -413,7 +502,7 @@ namespace lwsf { namespace internal
|
||||
|
||||
const bool mandatory_refresh = mandatory_refresh_;
|
||||
mandatory_refresh_ = false;
|
||||
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (mandatory_refresh_ || (refresh_interval_ <= now - last_refresh && thread_state_ == state::run))
|
||||
{
|
||||
@@ -424,12 +513,12 @@ namespace lwsf { namespace internal
|
||||
lock.lock();
|
||||
}
|
||||
else if (thread_state_ == state::skip_once)
|
||||
thread_state_ = state::run;
|
||||
thread_state_ = state::run;
|
||||
|
||||
// check while holding lock and before a wait call
|
||||
if (thread_state_ == state::stop)
|
||||
return;
|
||||
|
||||
|
||||
const auto last_state = thread_state_;
|
||||
refresh_notify_.wait_for(
|
||||
lock, to_boost(refresh_interval_), [this, last_state] () {
|
||||
@@ -441,7 +530,7 @@ namespace lwsf { namespace internal
|
||||
{
|
||||
set_critical(e);
|
||||
}
|
||||
catch (...)
|
||||
catch (...)
|
||||
{
|
||||
set_critical(unknown_exception{});
|
||||
}
|
||||
@@ -475,7 +564,7 @@ namespace lwsf { namespace internal
|
||||
std::filesystem::path work_dir(path);
|
||||
if(!std::filesystem::is_directory(path))
|
||||
return out;
|
||||
|
||||
|
||||
std::filesystem::recursive_directory_iterator end_itr;
|
||||
for (std::filesystem::recursive_directory_iterator itr(path); itr != end_itr; ++itr)
|
||||
{
|
||||
@@ -494,7 +583,7 @@ namespace lwsf { namespace internal
|
||||
addressbook_(),
|
||||
history_(),
|
||||
subaddresses_(),
|
||||
subaddress_minor_(),
|
||||
subaddress_minor_(),
|
||||
filename_(std::move(filename)),
|
||||
password_(std::move(password)),
|
||||
work_queue_(),
|
||||
@@ -602,11 +691,11 @@ namespace lwsf { namespace internal
|
||||
exception_error_ = "Electrum-style word list failed verification";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!seed_offset.empty())
|
||||
recovery = cryptonote::decrypt_key(recovery, seed_offset);
|
||||
|
||||
init_wallet(*data_, recovery, nettype, false);
|
||||
init_wallet(*data_, recovery, nettype, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -660,11 +749,11 @@ namespace lwsf { namespace internal
|
||||
exception_error_ = "spend_pub could not be computed";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (data_->get_spend_address({0, 0}) != address_string)
|
||||
exception_error_ = "view_key, spend_key, and address_string do not match";
|
||||
}
|
||||
|
||||
|
||||
wallet::~wallet() { stop_refresh_loop(); }
|
||||
|
||||
void wallet::add_subaddress(const std::uint32_t accountIndex, std::string label)
|
||||
@@ -716,7 +805,7 @@ namespace lwsf { namespace internal
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
return data_->primary.language;
|
||||
}
|
||||
|
||||
|
||||
void wallet::setSeedLanguage(const std::string &arg)
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
@@ -758,7 +847,7 @@ namespace lwsf { namespace internal
|
||||
errorString.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool wallet::setPassword(const std::string &password)
|
||||
{
|
||||
password_ = password;
|
||||
@@ -891,7 +980,7 @@ namespace lwsf { namespace internal
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
return data_->primary.restore_height;
|
||||
}
|
||||
|
||||
|
||||
void wallet::setSubaddressLookahead(uint32_t major, uint32_t minor)
|
||||
{
|
||||
queue_work([this, major, minor] () { return data_->set_lookahead(major, minor); });
|
||||
@@ -956,9 +1045,6 @@ namespace lwsf { namespace internal
|
||||
balance += receive.second.amount;
|
||||
}
|
||||
|
||||
if (!tx.second->spends.empty() && tx.second->spends.begin()->second.sender.maj_i == accountIndex)
|
||||
balance -= tx.second->fee;
|
||||
|
||||
for (const auto& spend : tx.second->spends)
|
||||
{
|
||||
if (spend.second.sender.maj_i == accountIndex)
|
||||
@@ -970,7 +1056,7 @@ namespace lwsf { namespace internal
|
||||
}
|
||||
|
||||
uint64_t wallet::unlockedBalance(const uint32_t accountIndex) const
|
||||
{
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
const Monero::NetworkType net_type = data_->primary.type;
|
||||
const std::uint32_t chain_height = data_->blockchain_height;
|
||||
@@ -988,9 +1074,6 @@ namespace lwsf { namespace internal
|
||||
balance += receive.second.amount;
|
||||
}
|
||||
|
||||
if (!tx.second->spends.empty() && tx.second->spends.begin()->second.sender.maj_i == accountIndex)
|
||||
balance -= tx.second->fee;
|
||||
|
||||
for (const auto& spend : tx.second->spends)
|
||||
{
|
||||
if (spend.second.sender.maj_i == accountIndex)
|
||||
@@ -1010,14 +1093,14 @@ namespace lwsf { namespace internal
|
||||
uint64_t wallet::daemonBlockChainHeight() const
|
||||
{
|
||||
const boost::unique_lock<boost::mutex> lock{data_->sync};
|
||||
return data_->blockchain_height;
|
||||
return data_->blockchain_height;
|
||||
}
|
||||
|
||||
uint64_t wallet::daemonBlockChainTargetHeight() const
|
||||
{ return daemonBlockChainHeight(); }
|
||||
|
||||
bool wallet::synchronized() const
|
||||
{
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
return data_->blockchain_height == data_->primary.scan_height;
|
||||
}
|
||||
@@ -1025,8 +1108,10 @@ namespace lwsf { namespace internal
|
||||
void wallet::startRefresh()
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{thread_sync_};
|
||||
state old_state = state::stop;
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock2{refresh_sync_};
|
||||
old_state = thread_state_;
|
||||
const bool no_refresh =
|
||||
refresh_interval_ <= std::chrono::milliseconds{0};
|
||||
if (no_refresh)
|
||||
@@ -1035,17 +1120,17 @@ namespace lwsf { namespace internal
|
||||
if (!mandatory_refresh_ && work_queue_.empty())
|
||||
return;
|
||||
}
|
||||
|
||||
const state old_state = thread_state_;
|
||||
thread_state_ = no_refresh ? state::stop : state::run;
|
||||
if (old_state == state::stop)
|
||||
{
|
||||
if (thread_.joinable())
|
||||
thread_.join();
|
||||
thread_ = boost::thread{[this] () { this->refresh_loop(); }};
|
||||
}
|
||||
else
|
||||
thread_state_ = state::run;
|
||||
}
|
||||
refresh_notify_.notify_all();
|
||||
|
||||
if (old_state == state::stop)
|
||||
{
|
||||
if (thread_.joinable())
|
||||
thread_.join();
|
||||
thread_ = boost::thread{[this] () { this->refresh_loop(); }};
|
||||
}
|
||||
}
|
||||
|
||||
void wallet::pauseRefresh()
|
||||
@@ -1149,6 +1234,472 @@ namespace lwsf { namespace internal
|
||||
set_critical(std::runtime_error{"setSubaddressLabel failed, " + std::to_string(accountIndex) + "," + std::to_string(addressIndex) + " does not exist"});
|
||||
}
|
||||
|
||||
Monero::PendingTransaction* wallet::createTransactionMultDest(const std::vector<std::string> &dst_addr, const std::string &payment_id,
|
||||
Monero::optional<std::vector<uint64_t>> amount, uint32_t mixin_count,
|
||||
Monero::PendingTransaction::Priority priority,
|
||||
uint32_t subaddr_account,
|
||||
std::set<uint32_t> subaddr_indices)
|
||||
{
|
||||
if (!mixin_count)
|
||||
mixin_count = mixin_;
|
||||
|
||||
std::unique_ptr<internal::pending_transaction> out;
|
||||
const auto tx_error = [this] (std::error_code error)
|
||||
{
|
||||
return std::make_unique<internal::pending_transaction>(data_, std::move(error));
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
/* This design is funky, but guards against exceptions in destructors
|
||||
of stack elements. Everything on the stack (except for arguments) is
|
||||
cleaned up before the uniue_ptr is released. */
|
||||
out = [&, this] ()
|
||||
{
|
||||
if (!amount && dst_addr.size() > 1)
|
||||
return tx_error(error::tx_sweep);
|
||||
if (amount && amount->size() != dst_addr.size())
|
||||
return tx_error(error::tx_size_mismatch);
|
||||
if (!payment_id.empty())
|
||||
return tx_error(error::tx_long_pid);
|
||||
|
||||
cryptonote::network_type ctype{};
|
||||
Monero::NetworkType mtype{};
|
||||
std::uint64_t per_byte_fee = 0;
|
||||
std::uint64_t fee_mask = 0;
|
||||
std::string change_address;
|
||||
cryptonote::account_keys keys{};
|
||||
cryptonote::account_public_address change_account{};
|
||||
std::unordered_map<crypto::public_key, std::pair<std::pair<std::uint64_t, std::uint64_t>, std::shared_ptr<const backend::transaction>>> unspent;
|
||||
{
|
||||
data_->refresh(); // get latest outputs, block height, and fee info
|
||||
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
per_byte_fee = data_->per_byte_fee;
|
||||
fee_mask = data_->fee_mask;
|
||||
|
||||
if (!per_byte_fee || !fee_mask || !data_->passed_login)
|
||||
return tx_error(data_->refresh_error);
|
||||
|
||||
mtype = data_->primary.type;
|
||||
ctype = data_->get_net_type();
|
||||
change_address = data_->get_spend_address({subaddr_account, 0});
|
||||
keys = data_->get_primary_keys();
|
||||
change_account = data_->get_spend_account({subaddr_account, 0});
|
||||
const std::uint64_t height = data_->blockchain_height;
|
||||
|
||||
if (!amount && subaddr_indices.empty())
|
||||
{
|
||||
const std::uint32_t max = data_->primary.subaccounts.at(subaddr_account).last;
|
||||
for (std::uint64_t index = 0; index <= max; ++index)
|
||||
subaddr_indices.insert(std::uint32_t(index));
|
||||
}
|
||||
|
||||
for (const auto& tx : data_->primary.txes)
|
||||
{
|
||||
if (!tx.second->is_unlocked(height, mtype))
|
||||
continue; // ignore locked amounts
|
||||
|
||||
for (const auto& receive : tx.second->receives)
|
||||
{
|
||||
/* Subtract by max for map below - that map will sort by amount
|
||||
then reverse global index. This will have a bias towards newer
|
||||
outputs, which is standard behavior. */
|
||||
static constexpr std::uint64_t max =
|
||||
std::numeric_limits<std::uint64_t>::max();
|
||||
const std::uint64_t amount = receive.second.amount;
|
||||
const rpc::address_meta source = receive.second.recipient;
|
||||
if (amount && source.maj_i == subaddr_account && (subaddr_indices.empty() || subaddr_indices.count(source.min_i)))
|
||||
unspent.try_emplace(receive.first).first->second = {{amount, max - receive.second.global_index}, tx.second};
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& tx : data_->primary.txes)
|
||||
{
|
||||
for (const auto& spend : tx.second->spends)
|
||||
unspent.erase(spend.second.output_pub);
|
||||
}
|
||||
}
|
||||
|
||||
// actually sorted by amount then reverse index (see comment above)
|
||||
std::map<std::pair<std::uint64_t, std::uint64_t>, std::pair<crypto::public_key, std::shared_ptr<const backend::transaction>>> unspent_by_amount_then_index;
|
||||
for (const auto& tx : unspent)
|
||||
unspent_by_amount_then_index.try_emplace(tx.second.first).first->second = {tx.first, tx.second.second};
|
||||
unspent.clear(); // free up some memory
|
||||
|
||||
const bool subtract_from_dest = !amount;
|
||||
if (subtract_from_dest)
|
||||
{
|
||||
std::uint64_t total_unspent = 0;
|
||||
for (const auto& tx : unspent)
|
||||
{
|
||||
const auto output = tx.second.second->receives.find(tx.first);
|
||||
if (output == tx.second.second->receives.end())
|
||||
throw std::logic_error{"Expected output pub"};
|
||||
total_unspent += output->second.amount;
|
||||
}
|
||||
amount = std::vector<std::uint64_t>{total_unspent};
|
||||
}
|
||||
|
||||
if (amount->size() < dst_addr.size())
|
||||
throw std::logic_error{"Sanity check failed: address vec <= amount vec"};
|
||||
|
||||
std::string extra_nonce;
|
||||
std::vector<cryptonote::tx_destination_entry> dests;
|
||||
std::multiset<std::pair<std::uint64_t, std::string>> transfers;
|
||||
for (std::size_t i = 0; i < dst_addr.size(); ++i)
|
||||
{
|
||||
cryptonote::address_parse_info info{};
|
||||
if (!cryptonote::get_account_address_from_str(info, ctype, dst_addr.at(i)))
|
||||
return tx_error(error::tx_invalid_address);
|
||||
|
||||
if (info.has_payment_id)
|
||||
{
|
||||
if (!extra_nonce.empty())
|
||||
return tx_error(error::tx_two_pid);
|
||||
cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id);
|
||||
}
|
||||
|
||||
transfers.insert({amount->at(i), dst_addr.at(i)});
|
||||
|
||||
auto& dest = dests.emplace_back();
|
||||
dest.original = dst_addr.at(i);
|
||||
dest.addr = info.address;
|
||||
dest.amount = amount->at(i);
|
||||
dest.is_subaddress = info.is_subaddress;
|
||||
dest.is_integrated = info.has_payment_id;
|
||||
}
|
||||
|
||||
for (std::size_t i = dst_addr.size(); i < amount->size(); ++i)
|
||||
{
|
||||
auto& dest = dests.emplace_back();
|
||||
dest.original = change_address;
|
||||
dest.addr = change_account;
|
||||
dest.amount = amount->at(i);
|
||||
dest.is_subaddress = subaddr_account;
|
||||
dest.is_integrated = false;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> extra;
|
||||
if (!extra_nonce.empty() && !cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce))
|
||||
return tx_error(error::tx_extra);
|
||||
|
||||
std::vector<std::pair<crypto::public_key, std::shared_ptr<const backend::transaction>>> spending;
|
||||
{
|
||||
std::uint64_t fee = 0;
|
||||
std::uint64_t remaining =
|
||||
std::accumulate(amount->begin(), amount->end(), std::uint64_t(0));
|
||||
|
||||
while (bool(remaining + fee))
|
||||
{
|
||||
if (unspent_by_amount_then_index.empty())
|
||||
{
|
||||
if (subtract_from_dest && !remaining)
|
||||
break;
|
||||
return tx_error(error::tx_low_funds);
|
||||
}
|
||||
|
||||
/* Check if we can omit change address for more efficiency. We
|
||||
must have at least 2 outputs for uniformity/privacy. */
|
||||
if (1 < dests.size())
|
||||
{
|
||||
fee = estimate_fee(per_byte_fee, spending.size() + 1, dests.size(), mixin_count, extra.size(), fee_mask);
|
||||
const std::uint64_t needed = remaining + fee;
|
||||
const auto output = unspent_by_amount_then_index.lower_bound({needed, 0});
|
||||
if (output != unspent_by_amount_then_index.end() && needed == output->first.first)
|
||||
{
|
||||
remaining = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check
|
||||
fee = estimate_fee(per_byte_fee, spending.size() + 1, dests.size() + 1, mixin_count, extra.size(), fee_mask);
|
||||
|
||||
const std::uint64_t needed = remaining + fee;
|
||||
auto output = unspent_by_amount_then_index.lower_bound({needed, 0});
|
||||
if (output == unspent_by_amount_then_index.end())
|
||||
--output;
|
||||
|
||||
const std::uint64_t this_amount = output->first.first;
|
||||
const bool complete = needed <= this_amount;
|
||||
spending.emplace_back(output->second.first, output->second.second);
|
||||
unspent_by_amount_then_index.erase(output);
|
||||
|
||||
remaining -= this_amount;
|
||||
if (complete)
|
||||
break;
|
||||
}
|
||||
|
||||
if (subtract_from_dest)
|
||||
{
|
||||
if (dests.size() != 1)
|
||||
throw std::logic_error{"Sanity check: subtract_from_dest && dest.size() == 1"};
|
||||
if (dests.back().amount < fee)
|
||||
return tx_error(error::tx_low_funds);
|
||||
dests.back().amount -= fee;
|
||||
fee = 0;
|
||||
}
|
||||
|
||||
const std::uint64_t change = (std::uint64_t(0) - remaining) - fee;
|
||||
if (change || dests.size() == 1)
|
||||
{
|
||||
auto& change_dest = dests.emplace_back();
|
||||
change_dest.original = change_address;
|
||||
change_dest.addr = change_account;
|
||||
change_dest.amount = change;
|
||||
change_dest.is_subaddress = bool(subaddr_account);
|
||||
change_dest.is_integrated = false;
|
||||
}
|
||||
|
||||
if (dests.size() < config::minimum_outputs)
|
||||
throw std::logic_error{"Sanity check: config::minium_outputs <= dests.size()"};
|
||||
|
||||
remaining = 0;
|
||||
for (const auto& source : spending)
|
||||
{
|
||||
const auto output = source.second->receives.find(source.first);
|
||||
if (output == source.second->receives.end())
|
||||
throw std::logic_error{"Failed sanity check: source output does not exist"};
|
||||
remaining += output->second.amount;
|
||||
}
|
||||
|
||||
for (const auto& dest : dests)
|
||||
remaining -= dest.amount;
|
||||
|
||||
if (fee != remaining)
|
||||
throw std::logic_error{"Sanity check: inputs.amounts == outputs.amounts + fee"};
|
||||
}
|
||||
unspent_by_amount_then_index.clear(); // free some memory
|
||||
|
||||
const auto rct_amount = [] (const backend::transfer_in& source)
|
||||
{
|
||||
return source.rct_mask ? 0 : source.amount;
|
||||
};
|
||||
|
||||
std::vector<rpc::random_outputs> decoys;
|
||||
{
|
||||
rpc::get_random_outs_request req{};
|
||||
req.count = mixin_count;
|
||||
for (const auto& spend : spending)
|
||||
req.amounts.push_back(rpc::uint64_string(rct_amount(spend.second->receives.at(spend.first))));
|
||||
|
||||
auto resp = data_->get_decoys(req);
|
||||
if (!resp)
|
||||
return tx_error(resp.error());
|
||||
decoys = std::move(*resp);
|
||||
}
|
||||
|
||||
struct by_amount
|
||||
{
|
||||
bool operator()(const rpc::random_outputs& lhs, const rpc::random_outputs& rhs) const noexcept
|
||||
{
|
||||
return lhs.amount < rhs.amount;
|
||||
}
|
||||
bool operator()(const rpc::random_outputs& lhs, const std::uint64_t rhs) const noexcept
|
||||
{
|
||||
return lhs.amount < rpc::uint64_string(rhs);
|
||||
}
|
||||
};
|
||||
std::sort(decoys.begin(), decoys.end(), by_amount{});
|
||||
|
||||
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subs;
|
||||
std::vector<cryptonote::tx_source_entry> sources;
|
||||
for (const auto& spend : spending)
|
||||
{
|
||||
const auto& source = spend.second->receives.at(spend.first);
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
subs.insert({data_->get_spend_public(source.recipient), {source.recipient.maj_i, source.recipient.min_i}});
|
||||
}
|
||||
|
||||
auto& entry = sources.emplace_back();
|
||||
if (source.rct_mask)
|
||||
entry.outputs.emplace_back(source.global_index, rct::ctkey{rct::pk2rct(spend.first), rct::commit(source.amount, *source.rct_mask)});
|
||||
else
|
||||
entry.push_output(source.global_index, spend.first, source.amount);
|
||||
|
||||
{
|
||||
const std::uint64_t amount = rct_amount(source);
|
||||
const auto ring = std::lower_bound(decoys.begin(), decoys.end(), amount, by_amount{});
|
||||
if (ring == decoys.end() || ring->amount != rpc::uint64_string(amount))
|
||||
return tx_error(error::tx_decoys);
|
||||
|
||||
for (const auto& decoy : ring->outputs)
|
||||
entry.outputs.emplace_back(std::uint64_t(decoy.global_index), rct::ctkey{decoy.public_key, decoy.rct});
|
||||
|
||||
decoys.erase(ring);
|
||||
}
|
||||
|
||||
entry.real_output_in_tx_index = source.index;
|
||||
entry.real_out_tx_key = source.tx_pub;
|
||||
entry.amount = source.amount;
|
||||
entry.rct = bool(source.rct_mask);
|
||||
if (entry.rct)
|
||||
entry.mask = *source.rct_mask;
|
||||
}
|
||||
|
||||
const auto ring_sort = [] (const auto& lhs, const auto& rhs)
|
||||
{
|
||||
return lhs.first < rhs.first;
|
||||
};
|
||||
const auto ring_compare = [] (const auto& lhs, const auto& rhs)
|
||||
{
|
||||
return lhs.first == rhs.first;
|
||||
};
|
||||
|
||||
std::shuffle(sources.begin(), sources.end(), crypto::random_device{});
|
||||
std::shuffle(dests.begin(), dests.end(), crypto::random_device{});
|
||||
for (auto& source : sources)
|
||||
{
|
||||
if (source.outputs.empty())
|
||||
throw std::runtime_error{"Sanity check: ring is !empty"};
|
||||
|
||||
const std::uint64_t real_source = source.outputs.front().first;
|
||||
std::sort(source.outputs.begin(), source.outputs.end(), ring_sort);
|
||||
if (std::unique(source.outputs.begin(), source.outputs.end(), ring_compare) != source.outputs.end())
|
||||
throw std::logic_error{"Sanity check: each ring destination is unique"};
|
||||
|
||||
for (std::size_t i = 0; i < source.outputs.size(); ++i)
|
||||
{
|
||||
if (source.outputs.at(i).first == real_source)
|
||||
{
|
||||
source.real_output = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr const rct::RCTConfig config{
|
||||
rct::RangeProofPaddedBulletproof, config::bulletproof_version
|
||||
};
|
||||
|
||||
crypto::secret_key tx_key;
|
||||
std::vector<crypto::secret_key> tx_keys;
|
||||
cryptonote::transaction tx;
|
||||
if (!cryptonote::construct_tx_and_get_tx_key(keys, subs, sources, dests, change_account, extra, tx, tx_key, tx_keys, true /* rct */, config, true /* view_tags */))
|
||||
return tx_error(error::tx_failed);
|
||||
|
||||
auto details = std::make_shared<backend::transaction>();
|
||||
details->raw_bytes = epee::byte_slice{cryptonote::t_serializable_object_to_blob(tx)};
|
||||
details->timestamp = std::chrono::system_clock::now();
|
||||
details->amount = std::accumulate(amount->begin(), amount->end(), std::uint64_t(0));
|
||||
details->fee = get_tx_fee(tx);
|
||||
details->direction = Monero::TransactionInfo::Direction_Out;
|
||||
if (extra_nonce.size() == sizeof(crypto::hash8))
|
||||
{
|
||||
crypto::hash8 pid{};
|
||||
std::memcpy(std::addressof(pid), extra_nonce.data(), sizeof(pid));
|
||||
details->payment_id = pid;
|
||||
}
|
||||
get_transaction_hash(tx, details->id);
|
||||
get_transaction_prefix_hash(tx, details->prefix);
|
||||
|
||||
if (sources.size() != tx.vin.size())
|
||||
throw std::logic_error{"Sanity check: sources.size() == tx.vin.size()"};
|
||||
|
||||
for (std::size_t i = 0; i < sources.size(); ++i)
|
||||
{
|
||||
const auto& source = sources.at(i);
|
||||
auto spend = details->spends.try_emplace(boost::get<cryptonote::txin_to_key>(tx.vin.at(i)).k_image).first;
|
||||
spend->second.amount = source.amount;
|
||||
spend->second.output_pub = rct2pk(source.outputs.at(source.real_output).second.dest);
|
||||
|
||||
const auto elem = std::find_if(spending.begin(), spending.end(), [&] (const auto& e) {
|
||||
return e.first == spend->second.output_pub;
|
||||
});
|
||||
if (elem == spending.end())
|
||||
throw std::logic_error{"Sanity check spend not found"};
|
||||
|
||||
const auto& base = elem->second->receives.at(spend->second.output_pub);
|
||||
spend->second.sender = base.recipient;
|
||||
spend->second.tx_pub = base.tx_pub;
|
||||
}
|
||||
|
||||
if (dests.size() != tx.vout.size())
|
||||
throw std::logic_error{"Sanity check: dests.size() == tx.vout.size()"};
|
||||
if (dests.size() != tx.rct_signatures.ecdhInfo.size())
|
||||
throw std::logic_error{"Sanity check: dests.size() == ecdhInfo.size()"};
|
||||
if (!tx_keys.empty() && tx_keys.size() != dests.size())
|
||||
throw std::logic_error{"Sanity check tx_keys.empty() || tx_keys.size() == dest.size()"};
|
||||
|
||||
struct get_output_pub
|
||||
{
|
||||
crypto::public_key operator()(const cryptonote::txout_to_script&) const noexcept { return {}; }
|
||||
crypto::public_key operator()(const cryptonote::txout_to_scripthash&) const noexcept { return {}; }
|
||||
crypto::public_key operator()(const cryptonote::txout_to_key& src) const noexcept { return src.key; }
|
||||
crypto::public_key operator()(const cryptonote::txout_to_tagged_key& src) const noexcept { return src.key; }
|
||||
};
|
||||
|
||||
for (std::size_t i = 0; i < dests.size(); ++i)
|
||||
{
|
||||
const auto& dest = dests.at(i);
|
||||
if (dest.original != change_address)
|
||||
continue;
|
||||
|
||||
auto receive = details->receives.try_emplace(
|
||||
boost::apply_visitor(get_output_pub{}, tx.vout.at(i).target)
|
||||
).first;
|
||||
|
||||
receive->second.global_index = std::numeric_limits<std::uint64_t>::max();
|
||||
receive->second.amount = dest.amount;
|
||||
receive->second.recipient = {subaddr_account, 0};
|
||||
receive->second.index = i;
|
||||
receive->second.rct_mask = tx.rct_signatures.ecdhInfo.at(i).mask;
|
||||
|
||||
if (tx_keys.empty())
|
||||
crypto::secret_key_to_public_key(tx_key, receive->second.tx_pub);
|
||||
else
|
||||
crypto::secret_key_to_public_key(tx_keys.at(i), receive->second.tx_pub);
|
||||
}
|
||||
|
||||
if (!tx_keys.empty())
|
||||
{
|
||||
if (tx_keys.size() != dests.size())
|
||||
throw std::logic_error{"Failed sanity check: tx_keys.size() == dests.size()"};
|
||||
|
||||
for (std::size_t i = 0; i < tx_keys.size(); ++i)
|
||||
{
|
||||
const auto& dest = dests.at(i);
|
||||
const auto is_transfer = transfers.find({dest.amount, dest.original});
|
||||
if (is_transfer == transfers.end())
|
||||
continue;
|
||||
|
||||
details->transfers.emplace_back(dest.original, dest.amount).secret = tx_keys.at(i);
|
||||
transfers.erase(is_transfer);
|
||||
}
|
||||
|
||||
if (!transfers.empty())
|
||||
throw std::logic_error{"Sanity check: missing secret key for outgoing transfer"};
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto& transfer : transfers)
|
||||
details->transfers.emplace_back(transfer.second, transfer.first).secret = tx_key;
|
||||
transfers.clear();
|
||||
}
|
||||
|
||||
return std::make_unique<internal::pending_transaction>(data_, std::move(tx), std::move(details));
|
||||
}();
|
||||
}
|
||||
catch (const std::exception & e)
|
||||
{
|
||||
set_critical(e);
|
||||
out = tx_error(error::tx_critical);
|
||||
}
|
||||
|
||||
return out.release();
|
||||
}
|
||||
|
||||
Monero::PendingTransaction* wallet::createTransaction(const std::string &dst_addr, const std::string &payment_id,
|
||||
std::optional<uint64_t> amount, uint32_t mixin_count,
|
||||
Monero::PendingTransaction::Priority priority,
|
||||
uint32_t subaddr_account,
|
||||
std::set<uint32_t> subaddr_indices)
|
||||
{
|
||||
return createTransactionMultDest({dst_addr}, payment_id, amount ? std::optional<std::vector<uint64_t>>{{*amount}} : std::nullopt, mixin_count, priority, subaddr_account, subaddr_indices);
|
||||
}
|
||||
|
||||
void wallet::disposeTransaction(Monero::PendingTransaction * t)
|
||||
{
|
||||
delete t;
|
||||
@@ -1159,12 +1710,12 @@ namespace lwsf { namespace internal
|
||||
{
|
||||
throw std::runtime_error{"Not Implemented yet"};
|
||||
}
|
||||
|
||||
|
||||
bool wallet::exportKeyImages(const std::string &filename, bool all)
|
||||
{
|
||||
throw std::runtime_error{"exportKeyImages not implemented"};
|
||||
}
|
||||
|
||||
|
||||
bool wallet::importKeyImages(const std::string &filename)
|
||||
{
|
||||
throw std::runtime_error{"importKeyImages not implemented"};
|
||||
@@ -1204,7 +1755,7 @@ namespace lwsf { namespace internal
|
||||
if (!history_)
|
||||
history_ = std::make_unique<transaction_history>(data_);
|
||||
return history_.get();
|
||||
}
|
||||
}
|
||||
|
||||
Monero::SubaddressAccount* wallet::subaddressAccount()
|
||||
{
|
||||
|
||||
+4
-4
@@ -340,12 +340,12 @@ namespace internal
|
||||
* @brief rescanBlockchain - rescans the wallet, updating transactions from daemon
|
||||
* @return - true if refreshed successfully;
|
||||
*/
|
||||
virtual bool rescanBlockchain() override { /* TODO */ return false; }
|
||||
virtual bool rescanBlockchain() override { return refresh(); }
|
||||
|
||||
/**
|
||||
* @brief rescanBlockchainAsync - rescans wallet asynchronously, starting from genesys
|
||||
*/
|
||||
virtual void rescanBlockchainAsync() override { /* TODO */ }
|
||||
virtual void rescanBlockchainAsync() override { refreshAsync(); }
|
||||
|
||||
/**
|
||||
* @brief setAutoRefreshInterval - setup interval for automatic refresh.
|
||||
@@ -468,7 +468,7 @@ namespace internal
|
||||
Monero::optional<std::vector<uint64_t>> amount, uint32_t mixin_count,
|
||||
Monero::PendingTransaction::Priority = Monero::PendingTransaction::Priority_Low,
|
||||
uint32_t subaddr_account = 0,
|
||||
std::set<uint32_t> subaddr_indices = {}) override { /* TODO */ return nullptr; }
|
||||
std::set<uint32_t> subaddr_indices = {}) override;
|
||||
|
||||
/*!
|
||||
* \brief createTransaction creates transaction. if dst_addr is an integrated address, payment_id is ignored
|
||||
@@ -487,7 +487,7 @@ namespace internal
|
||||
std::optional<uint64_t> amount, uint32_t mixin_count,
|
||||
Monero::PendingTransaction::Priority = Monero::PendingTransaction::Priority_Low,
|
||||
uint32_t subaddr_account = 0,
|
||||
std::set<uint32_t> subaddr_indices = {}) override { /* TODO */ return nullptr; }
|
||||
std::set<uint32_t> subaddr_indices = {}) override;
|
||||
|
||||
/*!
|
||||
* \brief createSweepUnmixableTransaction creates transaction with unmixable outputs.
|
||||
|
||||
+49
-17
@@ -36,11 +36,13 @@
|
||||
#include <system_error>
|
||||
#include "backend.h"
|
||||
#include "common/dns_utils.h" // monero/src
|
||||
#include "cryptonote_basic/cryptonote_format_utils.h" // monero/src
|
||||
#include "lwsf_config.h"
|
||||
#include "lws_frontend.h"
|
||||
#include "net/http_client.h" // monero/contrib/epee/include
|
||||
#include "net/parse.h" // monero/src
|
||||
#include "net/socks_connect.h" // monero/src
|
||||
#include "QrCode.hpp" // monero/src/external
|
||||
#include "rpc.h"
|
||||
#include "wallet.h"
|
||||
#include "wallet/api/wallet2_api.h" // monero/src
|
||||
@@ -359,23 +361,10 @@ namespace lwsf
|
||||
|
||||
std::string resolveOpenAlias(const std::string &address, bool &dnssec_valid) const override
|
||||
{
|
||||
static constexpr const std::string_view prefix{"oa1:xmr "};
|
||||
std::string real_address =
|
||||
tools::DNSResolver::instance().get_dns_format_from_oa_address(address);
|
||||
|
||||
bool dnssec_available = false;
|
||||
std::vector<std::string> records =
|
||||
tools::DNSResolver::instance().get_txt_record(real_address, dnssec_available, dnssec_valid);
|
||||
for (std::string_view record : records)
|
||||
{
|
||||
if (record.substr(0, prefix.size()) == prefix)
|
||||
{
|
||||
record = record.substr(prefix.size());
|
||||
return std::string{record.data(), record.size()};
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
std::vector<std::string> addresses = tools::dns_utils::addresses_from_url(address, dnssec_valid);
|
||||
if (addresses.empty())
|
||||
return {};
|
||||
return {std::move(addresses.front())};
|
||||
}
|
||||
|
||||
//! checks for an update and returns version, hash and url
|
||||
@@ -399,6 +388,49 @@ namespace lwsf
|
||||
};
|
||||
} // internal
|
||||
|
||||
std::string displayAmount(std::uint64_t amount)
|
||||
{
|
||||
return cryptonote::print_money(amount);
|
||||
}
|
||||
|
||||
std::uint64_t amountFromString(const std::string &amount)
|
||||
{
|
||||
std::uint64_t result = 0;
|
||||
cryptonote::parse_amount(result, amount);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool addressValid(const std::string &str, Monero::NetworkType nettype)
|
||||
{
|
||||
cryptonote::address_parse_info info;
|
||||
return get_account_address_from_str(info, static_cast<cryptonote::network_type>(nettype), str);
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::uint8_t>> qrcode(Monero::Wallet const* const wal, const std::uint32_t major, const std::uint32_t minor)
|
||||
{
|
||||
if (!wal)
|
||||
return {};
|
||||
|
||||
const std::string uri = "monero:" + wal->address(major, minor);
|
||||
const qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(uri.c_str(), qrcodegen::QrCode::Ecc::LOW);
|
||||
const int size = qr.getSize();
|
||||
if (size < 0 || std::numeric_limits<std::size_t>::max() < size)
|
||||
return {};
|
||||
|
||||
std::vector<std::vector<std::uint8_t>> out;
|
||||
out.resize(size);
|
||||
for (std::size_t y = 0; y < size; ++y)
|
||||
{
|
||||
auto& row = out.at(y);
|
||||
row.resize(size);
|
||||
|
||||
for (std::size_t x = 0; x < size; ++x)
|
||||
row.at(x) = qr.getModule(x, y);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
Monero::WalletManager* WalletManagerFactory::getWalletManager()
|
||||
{
|
||||
return new internal::wallet_manager{};
|
||||
|
||||
Reference in New Issue
Block a user