mirror of
https://codeberg.org/wownero/lwsf-wow.git
synced 2026-10-17 05:41:30 +02:00
Complete subaddresses and restore height. Fix error reporting
This commit is contained in:
+230
-99
@@ -32,7 +32,9 @@
|
||||
#include "crypto/crypto.h" // monero/src
|
||||
#include "crypto/crypto-ops.h" // monero/src
|
||||
#include "cryptonote_basic/cryptonote_basic_impl.h" // monero/src
|
||||
#include "cryptonote_basic/cryptonote_format_utils.h" // monero/src
|
||||
#include "error.h"
|
||||
#include "hex.h" // monero/src
|
||||
#include "lwsf_config.h"
|
||||
#include "ringct/rctOps.h"
|
||||
#include "wire.h"
|
||||
@@ -49,6 +51,26 @@ namespace Monero
|
||||
WIRE_AS_INTEGER(Monero::NetworkType);
|
||||
}
|
||||
|
||||
namespace lwsf { namespace config
|
||||
{
|
||||
namespace
|
||||
{
|
||||
template<typename F, typename T>
|
||||
void map_lookahead(F& format, T& self)
|
||||
{
|
||||
wire::object(format, WIRE_FIELD(major), WIRE_FIELD(minor));
|
||||
}
|
||||
} // anonymous
|
||||
WIRE_DEFINE_OBJECT(lookahead, map_lookahead);
|
||||
|
||||
constexpr bool operator==(const lookahead& lhs, const lookahead& rhs) noexcept
|
||||
{ return lhs.major == rhs.major && lhs.minor == rhs.minor; }
|
||||
|
||||
constexpr bool operator!=(const lookahead& lhs, const lookahead& rhs) noexcept
|
||||
{ return lhs.major != rhs.major || lhs.minor != rhs.minor; }
|
||||
|
||||
}} // lwsf // config
|
||||
|
||||
namespace lwsf { namespace internal { namespace backend
|
||||
{
|
||||
namespace
|
||||
@@ -125,6 +147,12 @@ namespace lwsf { namespace internal { namespace backend
|
||||
return m;
|
||||
}
|
||||
|
||||
//! \return If user requested {0,0} lookahead with no known subaddresses
|
||||
bool no_subaddresses(const epee::span<const sub_account> subaccounts, const config::lookahead lookahead) noexcept
|
||||
{
|
||||
return !lookahead.major && !lookahead.minor && subaccounts.size() == 1 && !subaccounts[0].last;
|
||||
}
|
||||
|
||||
template<typename F, typename T>
|
||||
void map_address_book_entry(F& format, T& self)
|
||||
{
|
||||
@@ -240,12 +268,11 @@ 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),
|
||||
WIRE_FIELD_DEFAULTED(maj_lookahead, config::subaddr_major_lookahead),
|
||||
WIRE_FIELD_DEFAULTED(min_lookahead, config::subaddr_minor_lookahead),
|
||||
WIRE_FIELD(type),
|
||||
WIRE_FIELD_DEFAULTED(lookahead, config::default_lookahead),
|
||||
WIRE_FIELD_DEFAULTED(type, Monero::MAINNET),
|
||||
WIRE_FIELD(view),
|
||||
WIRE_FIELD(spend),
|
||||
WIRE_FIELD(generated_locally)
|
||||
WIRE_FIELD_DEFAULTED(generated_locally, true)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -440,20 +467,27 @@ namespace lwsf { namespace internal { namespace backend
|
||||
{
|
||||
if (tx.second)
|
||||
{
|
||||
if (!tx.second->transfers.empty())
|
||||
const bool rescanning =
|
||||
source.scanned_block_height < tx.second->height.value_or(0);
|
||||
if (rescanning || !tx.second->description.empty() || !tx.second->transfers.empty())
|
||||
{
|
||||
const auto iter = updated_txes.emplace_hint(
|
||||
updated_txes.end(), tx.first, nullptr
|
||||
);
|
||||
|
||||
if (!iter->second)
|
||||
{
|
||||
iter->second = std::make_shared<transaction>(*tx.second);
|
||||
for (const auto& spend : tx.second->spends)
|
||||
images.emplace(spend.first, iter->second);
|
||||
|
||||
if (!rescanning)
|
||||
{
|
||||
iter->second->height = std::nullopt;
|
||||
iter->second->timestamp = std::nullopt;
|
||||
iter->second->failed = false;
|
||||
}
|
||||
for (const auto& spend : tx.second->spends)
|
||||
images.emplace(spend.first, iter->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -480,9 +514,12 @@ namespace lwsf { namespace internal { namespace backend
|
||||
for (const auto& spend : inserted.first->second->spends)
|
||||
{
|
||||
for (auto range = images.equal_range(spend.first); range.first != range.second; ++range.first)
|
||||
{
|
||||
if (inserted.first->second->id != range.first->second->id)
|
||||
range.first->second->failed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
updated_txes.erase(tx.hash);
|
||||
}
|
||||
@@ -529,39 +566,54 @@ namespace lwsf { namespace internal { namespace backend
|
||||
major lookahead is set to error state if `false` is returned, and set to
|
||||
the server lookahead iff true is returned.
|
||||
\return True iff the server has enough lookahead for all fields. */
|
||||
bool update_lookaheads(wallet& self, const boost::container::flat_set<rpc::subaddrs, std::less<>>& all)
|
||||
bool update_lookaheads(wallet& self, const boost::container::flat_set<rpc::subaddrs, std::less<>>& all) noexcept
|
||||
{
|
||||
const auto maj_lookahead = self.primary.maj_lookahead;
|
||||
const auto min_lookahead = self.primary.min_lookahead;
|
||||
if (!maj_lookahead && !min_lookahead)
|
||||
throw std::logic_error{"update_lookaheads does not work with lookahead {0, 0}"};
|
||||
self.server_lookahead = 0;
|
||||
self.lookahead_error = {};
|
||||
|
||||
const auto lookahead = self.primary.lookahead;
|
||||
if (no_subaddresses(epee::to_span(self.primary.subaccounts), lookahead))
|
||||
return true;
|
||||
|
||||
self.server_lookahead = {error::subaddr_server};
|
||||
static_assert(std::is_same<std::uint32_t, decltype(all.rbegin()->key)>());
|
||||
const std::uint32_t server_lookahead = all.empty() ?
|
||||
std::uint32_t(0) : all.rbegin()->key;
|
||||
|
||||
// Ensure major lookahead starts at zero, has no gaps, and "far" enough.
|
||||
const std::size_t last_account = std::max(self.primary.subaccounts.size(), std::size_t(1)) - 1;
|
||||
bool min_met = add_uint32_clamp(last_account, maj_lookahead) <= server_lookahead;
|
||||
bool min_met = add_uint32_clamp(last_account, lookahead.major) <= server_lookahead;
|
||||
min_met &= all.size() == std::uint64_t(server_lookahead) + 1;
|
||||
|
||||
// track the min lookahead we have
|
||||
if (!min_met)
|
||||
{
|
||||
std::uint32_t last = -1;
|
||||
for (const rpc::subaddrs& e : all)
|
||||
{
|
||||
if (last + 1 == e.key)
|
||||
self.server_lookahead = ++last;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
self.server_lookahead = server_lookahead;
|
||||
|
||||
for (std::size_t i = 0; i < self.primary.subaccounts.size(); ++i)
|
||||
{
|
||||
auto& acct = self.primary.subaccounts[i];
|
||||
const auto elem = all.find(i);
|
||||
const bool has_range = elem != all.end();
|
||||
|
||||
const std::uint32_t lookahead = has_range ? std::get<1>(elem->head) : 0;
|
||||
min_met &= add_uint32_clamp(acct.last, min_lookahead) <= lookahead;
|
||||
min_met &= has_range && std::get<0>(elem->head) == 0;
|
||||
const std::uint32_t this_lookahead = has_range ? std::get<1>(elem->head) : 0;
|
||||
min_met &= add_uint32_clamp(acct.last, lookahead.minor) <= this_lookahead;
|
||||
min_met &= !lookahead.minor || (has_range && std::get<0>(elem->head) == 0);
|
||||
|
||||
// Only increase lookahead, server has no delete subaddr command
|
||||
acct.server_lookahead = std::max(acct.server_lookahead, lookahead);
|
||||
acct.server_lookahead = this_lookahead;
|
||||
}
|
||||
|
||||
// Ensure that unused minor lookahead is far enough
|
||||
const std::uint32_t last_minor = std::max(min_lookahead, std::uint32_t(1)) - 1;
|
||||
const std::uint32_t last_minor = std::max(lookahead.minor, std::uint32_t(1)) - 1;
|
||||
for (std::size_t i = self.primary.subaccounts.size(); i < all.size(); ++i)
|
||||
{
|
||||
if (!min_met)
|
||||
@@ -572,8 +624,8 @@ namespace lwsf { namespace internal { namespace backend
|
||||
min_met &= std::get<0>(range) == 0;
|
||||
}
|
||||
|
||||
if (min_met)
|
||||
self.server_lookahead = server_lookahead;
|
||||
if (!min_met)
|
||||
self.lookahead_error = {error::subaddr_ahead};
|
||||
return min_met;
|
||||
}
|
||||
|
||||
@@ -585,14 +637,16 @@ namespace lwsf { namespace internal { namespace backend
|
||||
// remember upsert_subaddrs is inclusive in the minor ranges
|
||||
|
||||
const auto& accts = self.primary.subaccounts;
|
||||
const std::size_t end = accts.size() + self.primary.lookahead.major;
|
||||
out.reserve(end);
|
||||
|
||||
for (std::size_t i = 0; i < accts.size() && i <= max_index; ++i)
|
||||
out.insert(out.end(), rpc::subaddrs{std::uint32_t(i)})->head = {0, add_uint32_clamp(accts[i].last, self.primary.min_lookahead)};
|
||||
out.insert(out.end(), rpc::subaddrs{std::uint32_t(i)})->head = {0, add_uint32_clamp(accts[i].last, self.primary.lookahead.major)};
|
||||
|
||||
/* `0 < maj_lookahead & min_lookahead == 0` is a strange edge case.
|
||||
Assume `min_lookahead == 1` in that scenario. */
|
||||
/* `0 < lookahead.major & lookahead.minor == 0` is a strange edge case.
|
||||
Assume `lookahead.minor == 1` in that scenario. */
|
||||
|
||||
const std::uint32_t minor = std::max(std::uint32_t(1), self.primary.min_lookahead) - 1;
|
||||
const std::size_t end = accts.size() + self.primary.maj_lookahead;
|
||||
const std::uint32_t minor = std::max(std::uint32_t(1), self.primary.lookahead.minor) - 1;
|
||||
for (std::size_t i = accts.size(); i < end && i <= max_index; ++i)
|
||||
out.insert(out.end(), rpc::subaddrs{std::uint32_t(i)})->head = {0, minor};
|
||||
}
|
||||
@@ -600,7 +654,7 @@ namespace lwsf { namespace internal { namespace backend
|
||||
std::error_code handle_lookahead_error(std::error_code error) noexcept
|
||||
{
|
||||
if (error == rpc_max_subaddresses)
|
||||
error = {error::subaddr_server};
|
||||
error = {error::subaddr_ahead};
|
||||
else if (error == rpc_not_implemented)
|
||||
error = {error::subaddr_disabled};
|
||||
else if (error == wire::error::schema::array_max_element)
|
||||
@@ -644,6 +698,9 @@ namespace lwsf { namespace internal { namespace backend
|
||||
|
||||
std::string_view sub_account::sub_label(const std::uint32_t minor) const noexcept
|
||||
{
|
||||
if (!minor)
|
||||
return primary_label();
|
||||
|
||||
const auto elem = detail.find(minor);
|
||||
if (elem != detail.end())
|
||||
return elem->second.label;
|
||||
@@ -692,12 +749,13 @@ namespace lwsf { namespace internal { namespace backend
|
||||
: listener(nullptr),
|
||||
client(),
|
||||
primary{},
|
||||
refresh_status(),
|
||||
server_lookahead(default_subaddr_state),
|
||||
refresh_error(),
|
||||
lookahead_error(default_subaddr_state),
|
||||
last_sync(),
|
||||
blockchain_height(0),
|
||||
per_byte_fee(0),
|
||||
fee_mask(0),
|
||||
server_lookahead(0),
|
||||
sync(),
|
||||
sync_listener(),
|
||||
sync_refresh(),
|
||||
@@ -771,7 +829,9 @@ namespace lwsf { namespace internal { namespace backend
|
||||
// Remember that this function provides the strong exception guarantee.
|
||||
boost::unique_lock<boost::mutex> lock{sync};
|
||||
{
|
||||
server_lookahead = {default_subaddr_state};
|
||||
passed_login = false;
|
||||
server_lookahead = 0;
|
||||
lookahead_error = {error::subaddr_disabled};
|
||||
const rpc::login_request login{
|
||||
primary.address, primary.view.sec, true, primary.generated_locally
|
||||
};
|
||||
@@ -794,9 +854,9 @@ namespace lwsf { namespace internal { namespace backend
|
||||
if (response->start_height)
|
||||
primary.restore_height = *response->start_height;
|
||||
|
||||
if (!primary.maj_lookahead && !primary.min_lookahead)
|
||||
if (no_subaddresses(epee::to_span(primary.subaccounts), primary.lookahead))
|
||||
{
|
||||
server_lookahead = 0;
|
||||
lookahead_error = {};
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -814,26 +874,20 @@ namespace lwsf { namespace internal { namespace backend
|
||||
}
|
||||
|
||||
// lookaheads not far enough
|
||||
rpc::upsert_subaddrs_request request{std::move(login), {}, false /* get_all */};
|
||||
rpc::upsert_subaddrs_request request{std::move(login), {}, true /* get_all */};
|
||||
fill_upsert(*this, request.subaddrs_);
|
||||
|
||||
lock.unlock();
|
||||
const auto response = rpc::invoke<rpc::upsert_subaddrs_response>(client, request).value();
|
||||
lock.lock();
|
||||
update_lookaheads(*this, request.subaddrs_);
|
||||
update_lookaheads(*this, response.all_subaddrs);
|
||||
}
|
||||
catch (const std::system_error& e)
|
||||
{
|
||||
if (e.code() == rpc_max_subaddresses)
|
||||
server_lookahead = {error::subaddr_server};
|
||||
else if (e.code() == rpc_not_implemented)
|
||||
server_lookahead = {error::subaddr_disabled};
|
||||
else if (e.code() == rpc_unapproved) // happens on new account creation
|
||||
if (e.code() == rpc_unapproved) // happens on new account creation
|
||||
return {error::approval};
|
||||
else if (e.code() == wire::error::schema::array_max_element)
|
||||
server_lookahead = {error::subaddr_local};
|
||||
else
|
||||
server_lookahead = e.code();
|
||||
lookahead_error = handle_lookahead_error(e.code());
|
||||
}
|
||||
|
||||
return {};
|
||||
@@ -849,7 +903,7 @@ namespace lwsf { namespace internal { namespace backend
|
||||
if (!mandatory)
|
||||
{
|
||||
if (now - last_sync < config::refresh_interval_min)
|
||||
return refresh_status;
|
||||
return refresh_error;
|
||||
}
|
||||
|
||||
struct call_refreshed
|
||||
@@ -877,7 +931,7 @@ namespace lwsf { namespace internal { namespace backend
|
||||
{
|
||||
lock.lock();
|
||||
last_sync = now;
|
||||
return refresh_status = failed_login;
|
||||
return refresh_error = failed_login;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -896,24 +950,44 @@ namespace lwsf { namespace internal { namespace backend
|
||||
if (!txs_response)
|
||||
{
|
||||
if (txs_response == rpc_unapproved)
|
||||
return refresh_status = {error::approval};
|
||||
return refresh_status = txs_response.error();
|
||||
return refresh_error = {error::approval};
|
||||
return refresh_error = txs_response.error();
|
||||
}
|
||||
if (!outs_response)
|
||||
return refresh_status = outs_response.error();
|
||||
return refresh_error = outs_response.error();
|
||||
|
||||
const std::uint64_t orig_scan_height = primary.scan_height;
|
||||
auto merged = merge_response(*this, *txs_response, *outs_response);
|
||||
|
||||
if (!merged.new_subaddrs.empty() && server_lookahead)
|
||||
if (import_error || primary.requested_start < primary.restore_height)
|
||||
{
|
||||
server_lookahead = std::max(*server_lookahead, merged.new_subaddrs.rbegin()->key);
|
||||
const std::uint64_t from_height = primary.requested_start;
|
||||
lock.unlock();
|
||||
restore_height(from_height);
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
if (lookahead_error)
|
||||
{
|
||||
rpc::upsert_subaddrs_request request{login, {}, true /* get_all */};
|
||||
fill_upsert(*this, request.subaddrs_);
|
||||
|
||||
lock.unlock();
|
||||
const auto response = rpc::invoke<rpc::upsert_subaddrs_response>(client, request);
|
||||
lock.lock();
|
||||
if (response && update_lookaheads(*this, response->all_subaddrs))
|
||||
lookahead_error = {};
|
||||
}
|
||||
|
||||
if (!merged.new_subaddrs.empty() && !lookahead_error)
|
||||
{
|
||||
server_lookahead = std::max(server_lookahead, merged.new_subaddrs.rbegin()->key);
|
||||
|
||||
// ensure lookahead is from zero, or our logic is busted
|
||||
for (auto& sub : merged.new_subaddrs)
|
||||
{
|
||||
std::get<0>(sub.head) = 0;
|
||||
std::get<1>(sub.head) = add_uint32_clamp(std::get<1>(sub.head), primary.min_lookahead);
|
||||
std::get<1>(sub.head) = add_uint32_clamp(std::get<1>(sub.head), primary.lookahead.minor);
|
||||
}
|
||||
|
||||
const rpc::upsert_subaddrs_request upsert_request{
|
||||
@@ -932,12 +1006,12 @@ namespace lwsf { namespace internal { namespace backend
|
||||
acct.server_lookahead = std::max(acct.server_lookahead, std::get<1>(sub.head));
|
||||
}
|
||||
|
||||
if (primary.maj_lookahead)
|
||||
if (primary.lookahead.major)
|
||||
{
|
||||
const std::uint32_t last_used = upsert_request.subaddrs_.rbegin()->key;
|
||||
const std::uint32_t maj_i = add_uint32_clamp(unsigned(1), last_used);
|
||||
const std::uint32_t maj_count = primary.maj_lookahead;
|
||||
const std::uint32_t min_count = std::max(std::uint32_t(1), primary.min_lookahead);
|
||||
const std::uint32_t maj_count = primary.lookahead.major;
|
||||
const std::uint32_t min_count = std::max(std::uint32_t(1), primary.lookahead.minor);
|
||||
const rpc::provision_subaddrs_request provision_request{
|
||||
login, maj_i, 0, maj_count, min_count, false /* get_all */
|
||||
};
|
||||
@@ -947,23 +1021,22 @@ namespace lwsf { namespace internal { namespace backend
|
||||
lock.lock();
|
||||
|
||||
if (provision_response)
|
||||
{
|
||||
if (server_lookahead) // in case another thread jumped in
|
||||
server_lookahead = std::max(*server_lookahead, add_uint32_clamp(last_used, maj_count));
|
||||
}
|
||||
server_lookahead = std::max(server_lookahead, add_uint32_clamp(last_used, maj_count));
|
||||
else // if !provision_response
|
||||
server_lookahead = handle_lookahead_error(provision_response.error());
|
||||
lookahead_error = handle_lookahead_error(provision_response.error());
|
||||
}
|
||||
}
|
||||
else // if !upsert_response
|
||||
server_lookahead = handle_lookahead_error(upsert_response.error());
|
||||
lookahead_error = handle_lookahead_error(upsert_response.error());
|
||||
} // if new subaddress(es)
|
||||
|
||||
// return error if subaddresses enabled, and recovered wallet
|
||||
refresh_on_exit.release(); // release before acquiring `sync_listener`.
|
||||
const std::error_code rc = refresh_error =
|
||||
import_error ? import_error : lookahead_error;
|
||||
const boost::lock_guard<boost::mutex> lock_listener{sync_listener};
|
||||
if (!listener)
|
||||
return refresh_status = server_lookahead.error();
|
||||
return rc;
|
||||
|
||||
// Call listener functions without holding `sync`, in case a call is made
|
||||
// back into the library.
|
||||
@@ -993,7 +1066,7 @@ namespace lwsf { namespace internal { namespace backend
|
||||
listener->moneySpent(txid, tx->amount);
|
||||
}
|
||||
|
||||
return refresh_status = server_lookahead.error();
|
||||
return rc;
|
||||
}
|
||||
|
||||
std::error_code wallet::register_subaccount(const std::uint32_t maj_i)
|
||||
@@ -1011,30 +1084,26 @@ namespace lwsf { namespace internal { namespace backend
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
if (!server_lookahead)
|
||||
return server_lookahead.error();
|
||||
|
||||
const std::uint32_t needed_maj_i = add_uint32_clamp(maj_i, primary.maj_lookahead);
|
||||
if (needed_maj_i <= *server_lookahead)
|
||||
const std::uint32_t needed_maj_i = add_uint32_clamp(maj_i, primary.lookahead.major);
|
||||
if (!lookahead_error && needed_maj_i <= server_lookahead)
|
||||
return {};
|
||||
|
||||
// Increase the lookahead by one
|
||||
const std::uint32_t major_count = needed_maj_i - *server_lookahead;
|
||||
const std::uint32_t minor_count = std::max(std::uint32_t(1), primary.min_lookahead);
|
||||
const std::uint32_t major_count = needed_maj_i - server_lookahead;
|
||||
const std::uint32_t minor_count = std::max(std::uint32_t(1), primary.lookahead.minor);
|
||||
const rpc::provision_subaddrs_request request{
|
||||
rpc::login{primary.address, primary.view.sec},
|
||||
*server_lookahead + 1, 0, major_count, minor_count, false /* get_all */
|
||||
server_lookahead + 1, 0, major_count, minor_count, false /* get_all */
|
||||
};
|
||||
|
||||
lock.unlock();
|
||||
const auto response = rpc::invoke<rpc::provision_subaddrs_response>(client, request);
|
||||
lock.lock();
|
||||
if (response && server_lookahead)
|
||||
server_lookahead = std::max(*server_lookahead, needed_maj_i);
|
||||
else if (!response)
|
||||
server_lookahead = handle_lookahead_error(response.error());
|
||||
if (response)
|
||||
server_lookahead = std::max(server_lookahead, needed_maj_i);
|
||||
else if (!lookahead_error)
|
||||
lookahead_error = handle_lookahead_error(response.error());
|
||||
|
||||
return server_lookahead.error();
|
||||
return lookahead_error;
|
||||
}
|
||||
|
||||
std::error_code wallet::register_subaddress(const std::uint32_t maj_i, const std::uint32_t min_i)
|
||||
@@ -1056,8 +1125,8 @@ namespace lwsf { namespace internal { namespace backend
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
const std::uint32_t needed_min_i = add_uint32_clamp(min_i, primary.min_lookahead);
|
||||
if (needed_min_i <= maj->server_lookahead)
|
||||
const std::uint32_t needed_min_i = add_uint32_clamp(min_i, primary.lookahead.minor);
|
||||
if (!lookahead_error && needed_min_i <= maj->server_lookahead)
|
||||
return {};
|
||||
|
||||
const std::uint32_t needed_count = add_uint32_clamp(unsigned(1), needed_min_i);
|
||||
@@ -1075,29 +1144,21 @@ namespace lwsf { namespace internal { namespace backend
|
||||
// address could've changed during unlock
|
||||
maj = std::addressof(primary.subaccounts.at(maj_i));
|
||||
maj->server_lookahead = std::max(maj->server_lookahead, needed_min_i);
|
||||
return {};
|
||||
}
|
||||
else if (server_lookahead)
|
||||
server_lookahead = handle_lookahead_error(response.error());
|
||||
return server_lookahead.error();
|
||||
else if (!lookahead_error)
|
||||
lookahead_error = handle_lookahead_error(response.error());
|
||||
return lookahead_error;
|
||||
}
|
||||
|
||||
std::error_code wallet::set_lookahead(std::uint32_t major, std::uint32_t minor)
|
||||
{
|
||||
boost::unique_lock<boost::mutex> lock{sync};
|
||||
const bool extending =
|
||||
primary.maj_lookahead < major ||
|
||||
primary.min_lookahead < minor;
|
||||
primary.lookahead.major < major ||
|
||||
primary.lookahead.minor < minor;
|
||||
|
||||
primary.maj_lookahead = major;
|
||||
primary.min_lookahead = minor;
|
||||
|
||||
if ((!extending && server_lookahead) || (!major && !minor))
|
||||
{
|
||||
if (!server_lookahead)
|
||||
server_lookahead = 0;
|
||||
return {};
|
||||
}
|
||||
primary.lookahead.major = major;
|
||||
primary.lookahead.minor = minor;
|
||||
|
||||
if (!passed_login)
|
||||
{
|
||||
@@ -1108,8 +1169,11 @@ namespace lwsf { namespace internal { namespace backend
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
if (!extending && !lookahead_error)
|
||||
return {};
|
||||
|
||||
rpc::upsert_subaddrs_request request{
|
||||
rpc::login{primary.address, primary.view.sec}, {}, false /* get_all */
|
||||
rpc::login{primary.address, primary.view.sec}, {}, true /* get_all */
|
||||
};
|
||||
fill_upsert(*this, request.subaddrs_);
|
||||
|
||||
@@ -1117,16 +1181,83 @@ namespace lwsf { namespace internal { namespace backend
|
||||
const auto response = rpc::invoke<rpc::upsert_subaddrs_response>(client, request);
|
||||
lock.lock();
|
||||
if (!response)
|
||||
return (server_lookahead = handle_lookahead_error(response.error())).error();
|
||||
return (lookahead_error = handle_lookahead_error(response.error()));
|
||||
|
||||
update_lookaheads(*this, request.subaddrs_);
|
||||
update_lookaheads(*this, response->all_subaddrs);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::error_code wallet::restore_height(const std::uint64_t /*height not supported */)
|
||||
std::error_code wallet::restore_height(const std::uint64_t height)
|
||||
{
|
||||
/* TODO */
|
||||
throw std::logic_error{"restore_height not implemented"};
|
||||
boost::unique_lock<boost::mutex> lock{sync};
|
||||
|
||||
if (primary.requested_start < height)
|
||||
return import_error;
|
||||
|
||||
if (!passed_login)
|
||||
{
|
||||
lock.unlock();
|
||||
const std::error_code error = login();
|
||||
if (error)
|
||||
return error;
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
// current api does not allow height selection, defaults to 0
|
||||
rpc::login login{primary.address, primary.view.sec};
|
||||
|
||||
lock.unlock();
|
||||
const auto import = rpc::invoke<rpc::import_response>(client, login);
|
||||
lock.lock();
|
||||
|
||||
if (!import)
|
||||
return import_error = import.error();
|
||||
|
||||
if (import->request_fulfilled)
|
||||
return import_error = {};
|
||||
|
||||
const unsigned total =
|
||||
unsigned(bool(import->import_fee)) + bool(import->payment_address);
|
||||
switch (total)
|
||||
{
|
||||
default:
|
||||
case 0:
|
||||
return import_error = error::import_pending;
|
||||
case 1:
|
||||
return import_error = error::import_invalid;
|
||||
case 2:
|
||||
break;
|
||||
}
|
||||
|
||||
cryptonote::address_parse_info info{};
|
||||
if (!cryptonote::get_account_address_from_str(info, convert_net_type(primary.type), *import->payment_address))
|
||||
return import_error = error::import_invalid;
|
||||
if (info.has_payment_id && import->payment_id)
|
||||
return import_error = error::import_invalid;
|
||||
if (import->payment_id && (import->payment_id->empty() || (import->payment_id->size() != sizeof(crypto::hash8) && import->payment_id->size() != sizeof(crypto::hash))))
|
||||
return import_error = error::import_invalid;
|
||||
|
||||
std::string payment_id;
|
||||
if (import->payment_id)
|
||||
payment_id = epee::to_hex::string(epee::to_span(*import->payment_id));
|
||||
|
||||
std::size_t i = 0;
|
||||
for (; i < primary.addressbook.size(); ++i)
|
||||
{
|
||||
const bool existing =
|
||||
primary.addressbook[i].address == *import->payment_address &&
|
||||
primary.addressbook[i].payment_id == payment_id;
|
||||
if (existing)
|
||||
break;
|
||||
}
|
||||
|
||||
std::string description = "Payment of " + cryptonote::print_money(*import->import_fee) + " XMR is needed to import/restore height";
|
||||
if (i == primary.addressbook.size())
|
||||
primary.addressbook.push_back(address_book_entry{std::move(*import->payment_address), std::move(payment_id), std::move(description)});
|
||||
else
|
||||
primary.addressbook[i] = address_book_entry{std::move(*import->payment_address), std::move(payment_id), std::move(description)};
|
||||
|
||||
return import_error = {};
|
||||
}
|
||||
|
||||
std::error_code wallet::send_tx(epee::byte_slice tx_bytes)
|
||||
|
||||
+15
-10
@@ -199,16 +199,16 @@ namespace lwsf { namespace internal { namespace backend
|
||||
{
|
||||
account() = delete;
|
||||
|
||||
std::string address; //!> not serialized, recovered on read_bytes
|
||||
std::string address; //!< not serialized, recovered on read_bytes
|
||||
std::string language;
|
||||
std::vector<address_book_entry> addressbook;
|
||||
std::vector<sub_account> subaccounts; //! Enabled major subaccounts. Index indicates `sub.major` value.
|
||||
std::unordered_map<crypto::hash, std::shared_ptr<transaction>> txes;
|
||||
std::unordered_map<std::string, std::string> attributes;
|
||||
std::uint64_t scan_height;
|
||||
std::uint64_t restore_height;
|
||||
std::uint32_t maj_lookahead = config::subaddr_major_lookahead;
|
||||
std::uint32_t min_lookahead = config::subaddr_minor_lookahead;
|
||||
std::uint64_t scan_height; //!< last block scanned
|
||||
std::uint64_t restore_height; //!< aka scan start height
|
||||
std::uint64_t requested_start = std::numeric_limits<std::uint64_t>::max();
|
||||
config::lookahead lookahead = config::default_lookahead; //<! Lookahead as requested by user
|
||||
Monero::NetworkType type;
|
||||
keypair view;
|
||||
keypair spend;
|
||||
@@ -222,12 +222,14 @@ namespace lwsf { namespace internal { namespace backend
|
||||
Monero::WalletListener* listener;
|
||||
rpc::http_client client;
|
||||
account primary;
|
||||
std::error_code refresh_status; //!< Cached because `refresh(...)` is rate limited
|
||||
expect<std::uint32_t> server_lookahead; //!< Status of major lookahead server-side. Inclusive
|
||||
std::error_code refresh_error; //!< Cached because `refresh(...)` is rate limited
|
||||
std::error_code lookahead_error; //!< warnings/errors of `server_lookahead` value
|
||||
std::error_code import_error; //!< Error from `import_wallet_request`
|
||||
std::chrono::steady_clock::time_point last_sync;
|
||||
std::uint64_t blockchain_height;
|
||||
std::uint64_t per_byte_fee;
|
||||
std::uint64_t fee_mask;
|
||||
std::uint32_t server_lookahead; //!< Status of major lookahead server-side. Inclusive
|
||||
mutable boost::mutex sync;
|
||||
boost::mutex sync_listener;
|
||||
boost::mutex sync_refresh;
|
||||
@@ -239,16 +241,18 @@ namespace lwsf { namespace internal { namespace backend
|
||||
crypto::public_key get_spend_public(const rpc::address_meta& index) const;
|
||||
std::string get_spend_address(const rpc::address_meta& index) const;
|
||||
|
||||
//! Serializate `this` wallet to msgpack. Locks contents.
|
||||
//! Serialize `this` wallet to msgpack. Locks contents.
|
||||
expect<epee::byte_slice> to_bytes() const;
|
||||
|
||||
//! De-serialize `this` from msgpack. Locks+replaces contents.
|
||||
std::error_code from_bytes(epee::byte_slice source);
|
||||
|
||||
//! Attempt login and sync subaddresses. \return no errors if login succeeded
|
||||
/*! Attempt login and sync subaddresses. The result of subaddress syncing,
|
||||
including errors, is stored in `server_lookahead`.
|
||||
\return No errors if login succeeded. */
|
||||
std::error_code login();
|
||||
|
||||
//! Refreshes txes information. Strong exception guarantee. Spuriously returns success.
|
||||
//! Refreshes txes information. Strong exception guarantee.
|
||||
std::error_code refresh(bool mandatory = false);
|
||||
|
||||
//! Notify server that new major accounts need to be watched.
|
||||
@@ -257,6 +261,7 @@ namespace lwsf { namespace internal { namespace backend
|
||||
//! Notify server that new minor accounts need to be watched.
|
||||
std::error_code register_subaddress(std::uint32_t maj_i, std::uint32_t min_i);
|
||||
|
||||
//! Modify local and possibly server lookahead
|
||||
std::error_code set_lookahead(std::uint32_t major, std::uint32_t minor);
|
||||
|
||||
std::error_code restore_height(const std::uint64_t height);
|
||||
|
||||
+10
-16
@@ -41,28 +41,22 @@ namespace lwsf
|
||||
return "Configuration issue";
|
||||
case error::create:
|
||||
return "Account creation not possible";
|
||||
case error::crypto_failure:
|
||||
return "Cryptography issue";
|
||||
case error::invalid_encoding:
|
||||
return "File had invalid encoding";
|
||||
case error::invalid_scheme:
|
||||
return "Invalid URL scheme";
|
||||
case error::import_fee:
|
||||
return "Import/restore from height needs payment, see address book";
|
||||
case error::import_invalid:
|
||||
return "Import/restore from height failed due to invalid fee";
|
||||
case error::import_pending:
|
||||
return "Import/restore from height is awaiting approval";
|
||||
case error::network_type:
|
||||
return "Mismatch on network type";
|
||||
case error::read_failure:
|
||||
return "Failed to read file";
|
||||
case error::subaddr_ahead:
|
||||
return "server has limits too low for requested subaddress lookahead";
|
||||
case error::subaddr_disabled:
|
||||
return "light-wallet-server has subaddresses disabled";
|
||||
return "server has subaddresses disabled";
|
||||
case error::subaddr_local:
|
||||
return "lwsf limits on subaddresses too small";
|
||||
case error::subaddr_server:
|
||||
return "light-wallet-server has limits on subaddresses that are too small";
|
||||
return "lwsf (local) limits on subaddresses too small";
|
||||
case error::unexpected_userinfo:
|
||||
return "Unexpected user+pass field in URL";
|
||||
case error::unsupported_format:
|
||||
return "Could not unpack file";
|
||||
case error::write_failure:
|
||||
return "Failed to write file";
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
+4
-6
@@ -37,17 +37,15 @@ namespace lwsf
|
||||
approval, //!< Account needs approval
|
||||
configuration, //!< Bad Configuration
|
||||
create, //!< Account creation not possible
|
||||
crypto_failure, //!< Failure in crypto
|
||||
invalid_encoding, //!< Invalid percent encoding
|
||||
import_fee, //!< `import_wallet_request` has fee
|
||||
import_invalid, //!< `import_wallet_request` has invalid fee
|
||||
import_pending, //!< `import_wallet_request` not fulfilled/approved
|
||||
invalid_scheme, //!< Invalid network scheme
|
||||
network_type, //!< Mismatch on network type
|
||||
read_failure, //!< Failed to read file
|
||||
subaddr_ahead, //!< Server limits on subaddresses affects lookahead
|
||||
subaddr_disabled, //!< Server has subaddresses disabled
|
||||
subaddr_local, //!< Local limits on subaddresses too small
|
||||
subaddr_server, //!< Server limits on subaddresses too small
|
||||
unexpected_userinfo,//!< Unexpected user+pass provided
|
||||
unsupported_format, //!< File format could not be unpacked
|
||||
write_failure //!< Failed to write file
|
||||
};
|
||||
|
||||
//! \return Error message string.
|
||||
|
||||
+8
-4
@@ -34,9 +34,17 @@
|
||||
|
||||
namespace lwsf { namespace config
|
||||
{
|
||||
struct lookahead
|
||||
{
|
||||
std::uint32_t major = 0;
|
||||
std::uint32_t minor = 0;
|
||||
};
|
||||
|
||||
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"};
|
||||
constexpr const lookahead default_lookahead{50, 200};
|
||||
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_txes_in_rpc = 2048;
|
||||
@@ -44,8 +52,4 @@ namespace lwsf { namespace config
|
||||
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};
|
||||
constexpr const std::uint32_t subaddr_major_lookahead = 50;
|
||||
constexpr const std::uint32_t subaddr_minor_lookahead = 200;
|
||||
constexpr const unsigned subaddr_retry{10};
|
||||
|
||||
}} // lwsf // config
|
||||
|
||||
@@ -399,6 +399,8 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
void read_bytes(wire::json_reader& source, import_response& self)
|
||||
{
|
||||
wire::object(source,
|
||||
WIRE_OPTIONAL_FIELD(payment_address),
|
||||
WIRE_OPTIONAL_FIELD(payment_id),
|
||||
WIRE_OPTIONAL_FIELD(import_fee),
|
||||
WIRE_FIELD(request_fulfilled)
|
||||
);
|
||||
|
||||
@@ -229,11 +229,11 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
|
||||
struct subaddrs
|
||||
{
|
||||
subaddrs()
|
||||
constexpr subaddrs() noexcept
|
||||
: head({0, 0}), key(0)
|
||||
{}
|
||||
|
||||
explicit subaddrs(const std::uint32_t key)
|
||||
constexpr explicit subaddrs(const std::uint32_t key) noexcept
|
||||
: head({0, 0}), key(key)
|
||||
{}
|
||||
|
||||
@@ -243,11 +243,11 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
WIRE_JSON_DECLARE_OBJECT(subaddrs);
|
||||
void read_bytes(wire::json_reader&, subaddrs&);
|
||||
|
||||
inline bool operator<(const subaddrs& lhs, const subaddrs& rhs) noexcept
|
||||
constexpr inline bool operator<(const subaddrs& lhs, const subaddrs& rhs) noexcept
|
||||
{ return lhs.key < rhs.key; }
|
||||
|
||||
template<typename T>
|
||||
inline bool operator<(const subaddrs& lhs, const T rhs) noexcept
|
||||
constexpr inline bool operator<(const subaddrs& lhs, const T rhs) noexcept
|
||||
{
|
||||
static_assert(std::is_unsigned<T>());
|
||||
return lhs.key < rhs;
|
||||
@@ -337,6 +337,10 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
struct import_response
|
||||
{
|
||||
import_response() = delete;
|
||||
static constexpr const char* endpoint() noexcept { return "/import_wallet_request"; }
|
||||
|
||||
std::optional<std::string> payment_address;
|
||||
std::optional<epee::byte_slice> payment_id;
|
||||
std::optional<uint64_string> import_fee;
|
||||
bool request_fulfilled;
|
||||
};
|
||||
@@ -398,7 +402,7 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
static constexpr const char* endpoint() noexcept { return "/upsert_subaddrs"; }
|
||||
|
||||
boost::container::flat_set<subaddrs> new_subaddrs;
|
||||
boost::container::flat_set<subaddrs> all_subaddrs;
|
||||
boost::container::flat_set<subaddrs, std::less<>> all_subaddrs;
|
||||
};
|
||||
void read_bytes(wire::json_reader&, upsert_subaddrs_response&);
|
||||
|
||||
|
||||
@@ -107,6 +107,9 @@ namespace lwsf { namespace internal
|
||||
|
||||
for (const auto& tx : data_->primary.txes)
|
||||
{
|
||||
if (tx.second->failed)
|
||||
continue;
|
||||
|
||||
const bool unlocked = tx.second->is_unlocked(chain_height, net_type);
|
||||
for (const auto& receive : tx.second->receives)
|
||||
{
|
||||
|
||||
@@ -100,7 +100,11 @@ namespace lwsf { namespace internal
|
||||
{
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
data_->primary.subaccounts.at(accountIndex).detail[addressIndex].label = label;
|
||||
auto& acct = data_->primary.subaccounts.at(accountIndex);
|
||||
if (!addressIndex || !label.empty())
|
||||
acct.detail.try_emplace(addressIndex).first->second.label = label;
|
||||
else
|
||||
acct.detail.erase(addressIndex);
|
||||
}
|
||||
refresh(accountIndex);
|
||||
}
|
||||
|
||||
+63
-90
@@ -70,7 +70,9 @@ namespace lwsf { namespace internal
|
||||
boost::unique_future<boost::asio::ip::tcp::socket>
|
||||
operator()(const std::string&, const std::string&, boost::asio::steady_timer&) const
|
||||
{
|
||||
throw std::runtime_error{"Unable to connect"};
|
||||
return boost::make_exceptional_future<boost::asio::ip::tcp::socket>(
|
||||
std::runtime_error{"invalid proxy value"}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -344,31 +346,29 @@ namespace lwsf { namespace internal
|
||||
}
|
||||
} // anonymous
|
||||
|
||||
bool wallet::set_error(const std::error_code status, const bool update_iff_error) const
|
||||
bool wallet::set_error(const std::error_code error, const bool clear) const
|
||||
{
|
||||
if (clear || error)
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{error_sync_};
|
||||
if (!update_iff_error || status)
|
||||
status_ = status;
|
||||
return !status_;
|
||||
error_ = error;
|
||||
}
|
||||
return !error;
|
||||
}
|
||||
|
||||
void wallet::set_critical(const std::exception& e) const
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{error_sync_};
|
||||
exception_error_ = e.what();
|
||||
status_.clear();
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void wallet::queue_work(F&& f)
|
||||
{
|
||||
try
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{refresh_sync_};
|
||||
work_queue_.push_back(std::forward<F>(f));
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{ set_critical(e); }
|
||||
startRefresh();
|
||||
}
|
||||
|
||||
@@ -407,7 +407,7 @@ namespace lwsf { namespace internal
|
||||
work_queue_.pop_front();
|
||||
lock.unlock();
|
||||
if (work)
|
||||
set_error(work(), true);
|
||||
set_error(work());
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
@@ -415,11 +415,12 @@ namespace lwsf { namespace internal
|
||||
mandatory_refresh_ = false;
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (refresh_interval_ <= now - last_refresh && (thread_state_ == state::run || mandatory_refresh))
|
||||
if (mandatory_refresh_ || (refresh_interval_ <= now - last_refresh && thread_state_ == state::run))
|
||||
{
|
||||
// refresh has strong exception guarantee - never in partial state.
|
||||
lock.unlock();
|
||||
set_error(data_->refresh(mandatory_refresh), false);
|
||||
last_refresh = now;
|
||||
set_error(data_->refresh(mandatory_refresh), true /*clear*/);
|
||||
lock.lock();
|
||||
}
|
||||
else if (thread_state_ == state::skip_once)
|
||||
@@ -498,7 +499,7 @@ namespace lwsf { namespace internal
|
||||
password_(std::move(password)),
|
||||
work_queue_(),
|
||||
exception_error_(),
|
||||
status_(),
|
||||
error_(),
|
||||
thread_(),
|
||||
iterations_(kdf_rounds),
|
||||
mixin_(config::mixin_default),
|
||||
@@ -530,7 +531,7 @@ namespace lwsf { namespace internal
|
||||
password_(std::move(password)),
|
||||
work_queue_(),
|
||||
exception_error_(),
|
||||
status_(),
|
||||
error_(),
|
||||
thread_(),
|
||||
iterations_(kdf_rounds),
|
||||
mixin_(config::mixin_default),
|
||||
@@ -542,40 +543,29 @@ namespace lwsf { namespace internal
|
||||
thread_state_(state::stop),
|
||||
mandatory_refresh_(false)
|
||||
{
|
||||
if (!data_)
|
||||
throw std::invalid_argument{"lwsf::backend::wallet cannot be null"};
|
||||
|
||||
try
|
||||
{
|
||||
epee::byte_slice file = try_load(filename_ + ".new");
|
||||
if (file.empty())
|
||||
file = try_load(filename_);
|
||||
|
||||
if (!filename_.empty() && !file.empty())
|
||||
{
|
||||
if (file.empty())
|
||||
throw std::runtime_error{"Unable to open wallet file " + filename_};
|
||||
|
||||
encrypted_file contents{};
|
||||
if (!(status_ = wire::msgpack::from_bytes(std::move(file), contents)))
|
||||
{
|
||||
if (std::error_code error = wire::msgpack::from_bytes(std::move(file), contents))
|
||||
throw std::system_error{error};
|
||||
|
||||
epee::byte_slice payload = contents.get_payload(password_);
|
||||
if (!payload.empty())
|
||||
{
|
||||
if (!(status_ = data_->from_bytes(std::move(payload))))
|
||||
{
|
||||
if (std::error_code error = data_->from_bytes(std::move(payload)))
|
||||
throw std::system_error{error};
|
||||
|
||||
// lock not needed; data_ was created and unique to us
|
||||
if (nettype != data_->primary.type)
|
||||
throw std::runtime_error{"Wallet file NetworkType does not match requested"};
|
||||
}
|
||||
}
|
||||
else
|
||||
status_ = error::unsupported_format;
|
||||
}
|
||||
}
|
||||
else
|
||||
status_ = error::read_failure;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
status_.clear();
|
||||
exception_error_ = e.what();
|
||||
}
|
||||
}
|
||||
@@ -590,7 +580,7 @@ namespace lwsf { namespace internal
|
||||
password_(std::move(password)),
|
||||
work_queue_(),
|
||||
exception_error_(),
|
||||
status_(),
|
||||
error_(),
|
||||
thread_(),
|
||||
iterations_(kdf_rounds),
|
||||
mixin_(config::mixin_default),
|
||||
@@ -630,7 +620,7 @@ namespace lwsf { namespace internal
|
||||
password_(std::move(password)),
|
||||
work_queue_(),
|
||||
exception_error_(),
|
||||
status_(),
|
||||
error_(),
|
||||
thread_(),
|
||||
iterations_(kdf_rounds),
|
||||
mixin_(config::mixin_default),
|
||||
@@ -678,8 +668,6 @@ namespace lwsf { namespace internal
|
||||
wallet::~wallet() { stop_refresh_loop(); }
|
||||
|
||||
void wallet::add_subaddress(const std::uint32_t accountIndex, std::string label)
|
||||
{
|
||||
try
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
auto& accts = data_->primary.subaccounts;
|
||||
@@ -692,13 +680,11 @@ namespace lwsf { namespace internal
|
||||
throw std::runtime_error{"add_subaddress: exceeded minor indexes"};
|
||||
|
||||
const std::uint32_t min_i = ++acct.last;
|
||||
if (!label.empty())
|
||||
acct.detail.try_emplace(min_i).first->second.label = std::move(label);
|
||||
|
||||
queue_work([=] () { return data_->register_subaddress(accountIndex, min_i); });
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{ set_critical(e); }
|
||||
}
|
||||
|
||||
std::string wallet::seed(const std::string& seed_offset) const
|
||||
{
|
||||
@@ -761,10 +747,10 @@ namespace lwsf { namespace internal
|
||||
status = Status_Critical;
|
||||
errorString = exception_error_;
|
||||
}
|
||||
else if (status_)
|
||||
else if (error_)
|
||||
{
|
||||
status = Status_Error;
|
||||
errorString = status_.message();
|
||||
errorString = error_.message();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -816,39 +802,33 @@ namespace lwsf { namespace internal
|
||||
{
|
||||
const std::string& real_path = path.empty() ? filename_ : path;
|
||||
|
||||
std::error_code status{};
|
||||
try
|
||||
{
|
||||
expect<epee::byte_slice> payload = data_->to_bytes();
|
||||
if (payload)
|
||||
{
|
||||
const auto contents = encrypted_file::make(*payload, iterations_, password_);
|
||||
const auto payload_size = payload->size();
|
||||
*payload = nullptr; // free up some memory that is no longer needed
|
||||
epee::byte_slice payload = data_->to_bytes().value();
|
||||
const auto contents = encrypted_file::make(payload, iterations_, password_);
|
||||
const auto payload_size = payload.size();
|
||||
payload = nullptr; // free up some memory that is no longer needed
|
||||
|
||||
epee::byte_stream buffer;
|
||||
buffer.reserve(payload_size + 2048);
|
||||
buffer.write(file_magic.data(), file_magic.size());
|
||||
|
||||
if (!(status = wire::msgpack::to_bytes(buffer, contents)))
|
||||
{
|
||||
if (std::error_code error = wire::msgpack::to_bytes(buffer, contents))
|
||||
throw std::system_error{error};
|
||||
|
||||
const std::filesystem::path file = real_path;
|
||||
const std::filesystem::path new_file = real_path + ".new";
|
||||
const std::filesystem::path directory =
|
||||
std::filesystem::path{real_path}.remove_filename();
|
||||
|
||||
if (std::filesystem::exists(new_file))
|
||||
std::filesystem::rename(new_file, file, status);
|
||||
std::filesystem::rename(new_file, file);
|
||||
|
||||
// blocks until file and directory contents are synced
|
||||
if (atomic_file_write(new_file, directory, epee::byte_slice{std::move(buffer)}))
|
||||
std::filesystem::rename(new_file, file, status);
|
||||
else
|
||||
status = error::write_failure;
|
||||
}
|
||||
}
|
||||
else
|
||||
status = payload.error();
|
||||
if (!atomic_file_write(new_file, directory, epee::byte_slice{std::move(buffer)}))
|
||||
throw std::runtime_error{"Failed to write file " + real_path};
|
||||
|
||||
std::filesystem::rename(new_file, file);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
@@ -856,8 +836,7 @@ namespace lwsf { namespace internal
|
||||
return false;
|
||||
}
|
||||
|
||||
// this will clear any existing errors
|
||||
return set_error(status, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wallet::init(const std::string &daemon_address, uint64_t, const std::string &daemon_username, const std::string &daemon_password, bool use_ssl, bool light_wallet, const std::string &proxy_address)
|
||||
@@ -873,7 +852,6 @@ namespace lwsf { namespace internal
|
||||
if (!url.m_uri_content.m_path.empty())
|
||||
throw std::runtime_error{"LWS URL contains path (unsupported)"};
|
||||
|
||||
|
||||
if (!proxy_address.empty() && !setProxy(proxy_address))
|
||||
return false;
|
||||
|
||||
@@ -901,8 +879,10 @@ namespace lwsf { namespace internal
|
||||
return true;
|
||||
}
|
||||
|
||||
void wallet::setRefreshFromBlockHeight(uint64_t height)
|
||||
void wallet::setRefreshFromBlockHeight(const std::uint64_t height)
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
data_->primary.requested_start = std::min(data_->primary.requested_start, height);
|
||||
queue_work([this, height] () { return data_->restore_height(height); });
|
||||
}
|
||||
|
||||
@@ -919,28 +899,17 @@ namespace lwsf { namespace internal
|
||||
|
||||
bool wallet::connectToDaemon()
|
||||
{
|
||||
struct on_throw
|
||||
{
|
||||
void operator()(wallet* self) const
|
||||
{
|
||||
if (self)
|
||||
self->data_->client.disconnect();
|
||||
}
|
||||
};
|
||||
|
||||
if (data_->client.is_connected())
|
||||
const bool connected = data_->client.is_connected();
|
||||
boost::unique_lock<boost::mutex> lock{data_->sync};
|
||||
if (connected && data_->passed_login)
|
||||
return true;
|
||||
|
||||
std::unique_ptr<wallet, on_throw> disconnect{this};
|
||||
if (data_->client.connect(config::connect_timeout))
|
||||
if (connected || data_->client.connect(config::connect_timeout))
|
||||
{
|
||||
const std::error_code err = data_->login();
|
||||
if (!err)
|
||||
disconnect.release();
|
||||
return set_error(err, false);
|
||||
lock.unlock();
|
||||
return set_error(data_->login());
|
||||
}
|
||||
set_error(rpc::error::no_response, false);
|
||||
return false;
|
||||
return set_error(rpc::error::no_response);
|
||||
}
|
||||
|
||||
Monero::Wallet::ConnectionStatus wallet::connected() const
|
||||
@@ -954,14 +923,20 @@ namespace lwsf { namespace internal
|
||||
}
|
||||
|
||||
bool wallet::setProxy(const std::string &address)
|
||||
{
|
||||
data_->client.disconnect();
|
||||
if (address.empty())
|
||||
data_->client.set_connector(epee::net_utils::direct_connect{});
|
||||
else
|
||||
{
|
||||
auto endpoint = net::get_tcp_endpoint(address);
|
||||
if (!endpoint)
|
||||
{
|
||||
data_->client.set_connector(null_connector{});
|
||||
return set_error(endpoint.error(), false);
|
||||
return set_error(endpoint.error());
|
||||
}
|
||||
data_->client.set_connector(net::socks::connector{std::move(*endpoint)});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1082,7 +1057,7 @@ namespace lwsf { namespace internal
|
||||
|
||||
bool wallet::refresh()
|
||||
{
|
||||
try { return set_error(data_->refresh(true), true); }
|
||||
try { return set_error(data_->refresh(true), true /* clear */); }
|
||||
catch (const std::exception& e) { set_critical(e); }
|
||||
return false;
|
||||
}
|
||||
@@ -1117,8 +1092,6 @@ namespace lwsf { namespace internal
|
||||
}
|
||||
|
||||
void wallet::addSubaddressAccount(const std::string& label)
|
||||
{
|
||||
try
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
auto& accts = data_->primary.subaccounts;
|
||||
@@ -1130,9 +1103,6 @@ namespace lwsf { namespace internal
|
||||
accts.emplace_back().detail.try_emplace(0).first->second.label = label;
|
||||
queue_work([this, index] () { return data_->register_subaccount(index); });
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{ set_critical(e); }
|
||||
}
|
||||
|
||||
std::size_t wallet::numSubaddressAccounts() const
|
||||
{
|
||||
@@ -1169,7 +1139,10 @@ namespace lwsf { namespace internal
|
||||
auto& acct = data_->primary.subaccounts[accountIndex];
|
||||
if (addressIndex <= acct.last)
|
||||
{
|
||||
if (!addressIndex || !label.empty())
|
||||
acct.detail.try_emplace(addressIndex).first->second.label = label;
|
||||
else
|
||||
acct.detail.erase(addressIndex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -63,7 +63,7 @@ namespace internal
|
||||
std::string language_;
|
||||
std::deque<std::function<std::error_code()>> work_queue_;
|
||||
mutable std::string exception_error_;
|
||||
mutable std::error_code status_;
|
||||
mutable std::error_code error_;
|
||||
boost::thread thread_;
|
||||
const std::uint64_t iterations_;
|
||||
std::chrono::milliseconds refresh_interval_;
|
||||
@@ -75,7 +75,7 @@ namespace internal
|
||||
state thread_state_;
|
||||
bool mandatory_refresh_;
|
||||
|
||||
bool set_error(std::error_code status, bool update_iff_error) const;
|
||||
bool set_error(std::error_code status, bool clear = false) const;
|
||||
void set_critical(const std::exception& e) const;
|
||||
|
||||
template<typename F>
|
||||
|
||||
Reference in New Issue
Block a user