Complete subaddresses and restore height. Fix error reporting

This commit is contained in:
Lee Clagett
2025-05-07 00:31:44 -04:00
parent 38f44770b8
commit 78a7333dc9
11 changed files with 394 additions and 276 deletions
+235 -104
View File
@@ -31,8 +31,10 @@
#include <boost/thread/lock_guard.hpp> #include <boost/thread/lock_guard.hpp>
#include "crypto/crypto.h" // monero/src #include "crypto/crypto.h" // monero/src
#include "crypto/crypto-ops.h" // monero/src #include "crypto/crypto-ops.h" // monero/src
#include "cryptonote_basic/cryptonote_basic_impl.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 "error.h"
#include "hex.h" // monero/src
#include "lwsf_config.h" #include "lwsf_config.h"
#include "ringct/rctOps.h" #include "ringct/rctOps.h"
#include "wire.h" #include "wire.h"
@@ -49,6 +51,26 @@ namespace Monero
WIRE_AS_INTEGER(Monero::NetworkType); 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 lwsf { namespace internal { namespace backend
{ {
namespace namespace
@@ -125,6 +147,12 @@ namespace lwsf { namespace internal { namespace backend
return m; 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> template<typename F, typename T>
void map_address_book_entry(F& format, T& self) 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::optional_field("attributes", wire::trusted_array(std::ref(self.attributes))),
WIRE_FIELD(scan_height), WIRE_FIELD(scan_height),
WIRE_FIELD(restore_height), WIRE_FIELD(restore_height),
WIRE_FIELD_DEFAULTED(maj_lookahead, config::subaddr_major_lookahead), WIRE_FIELD_DEFAULTED(lookahead, config::default_lookahead),
WIRE_FIELD_DEFAULTED(min_lookahead, config::subaddr_minor_lookahead), WIRE_FIELD_DEFAULTED(type, Monero::MAINNET),
WIRE_FIELD(type),
WIRE_FIELD(view), WIRE_FIELD(view),
WIRE_FIELD(spend), 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)
{ {
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( const auto iter = updated_txes.emplace_hint(
updated_txes.end(), tx.first, nullptr updated_txes.end(), tx.first, nullptr
); );
if (!iter->second) if (!iter->second)
{ {
iter->second = std::make_shared<transaction>(*tx.second); iter->second = std::make_shared<transaction>(*tx.second);
iter->second->height = std::nullopt; for (const auto& spend : tx.second->spends)
iter->second->timestamp = std::nullopt; images.emplace(spend.first, iter->second);
iter->second->failed = false;
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,7 +514,10 @@ namespace lwsf { namespace internal { namespace backend
for (const auto& spend : inserted.first->second->spends) for (const auto& spend : inserted.first->second->spends)
{ {
for (auto range = images.equal_range(spend.first); range.first != range.second; ++range.first) for (auto range = images.equal_range(spend.first); range.first != range.second; ++range.first)
range.first->second->failed = true; {
if (inserted.first->second->id != range.first->second->id)
range.first->second->failed = true;
}
} }
} }
else else
@@ -529,39 +566,54 @@ namespace lwsf { namespace internal { namespace backend
major lookahead is set to error state if `false` is returned, and set to major lookahead is set to error state if `false` is returned, and set to
the server lookahead iff true is returned. the server lookahead iff true is returned.
\return True iff the server has enough lookahead for all fields. */ \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; self.server_lookahead = 0;
const auto min_lookahead = self.primary.min_lookahead; self.lookahead_error = {};
if (!maj_lookahead && !min_lookahead)
throw std::logic_error{"update_lookaheads does not work with lookahead {0, 0}"}; 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)>()); static_assert(std::is_same<std::uint32_t, decltype(all.rbegin()->key)>());
const std::uint32_t server_lookahead = all.empty() ? const std::uint32_t server_lookahead = all.empty() ?
std::uint32_t(0) : all.rbegin()->key; std::uint32_t(0) : all.rbegin()->key;
// Ensure major lookahead starts at zero, has no gaps, and "far" enough. // 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; 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; 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) for (std::size_t i = 0; i < self.primary.subaccounts.size(); ++i)
{ {
auto& acct = self.primary.subaccounts[i]; auto& acct = self.primary.subaccounts[i];
const auto elem = all.find(i); const auto elem = all.find(i);
const bool has_range = elem != all.end(); const bool has_range = elem != all.end();
const std::uint32_t lookahead = has_range ? std::get<1>(elem->head) : 0; const std::uint32_t this_lookahead = has_range ? std::get<1>(elem->head) : 0;
min_met &= add_uint32_clamp(acct.last, min_lookahead) <= lookahead; min_met &= add_uint32_clamp(acct.last, lookahead.minor) <= this_lookahead;
min_met &= has_range && std::get<0>(elem->head) == 0; min_met &= !lookahead.minor || (has_range && std::get<0>(elem->head) == 0);
// Only increase lookahead, server has no delete subaddr command acct.server_lookahead = this_lookahead;
acct.server_lookahead = std::max(acct.server_lookahead, lookahead);
} }
// Ensure that unused minor lookahead is far enough // 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) for (std::size_t i = self.primary.subaccounts.size(); i < all.size(); ++i)
{ {
if (!min_met) if (!min_met)
@@ -572,8 +624,8 @@ namespace lwsf { namespace internal { namespace backend
min_met &= std::get<0>(range) == 0; min_met &= std::get<0>(range) == 0;
} }
if (min_met) if (!min_met)
self.server_lookahead = server_lookahead; self.lookahead_error = {error::subaddr_ahead};
return min_met; return min_met;
} }
@@ -585,14 +637,16 @@ namespace lwsf { namespace internal { namespace backend
// remember upsert_subaddrs is inclusive in the minor ranges // remember upsert_subaddrs is inclusive in the minor ranges
const auto& accts = self.primary.subaccounts; 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) 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. /* `0 < lookahead.major & lookahead.minor == 0` is a strange edge case.
Assume `min_lookahead == 1` in that scenario. */ 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::uint32_t minor = std::max(std::uint32_t(1), self.primary.lookahead.minor) - 1;
const std::size_t end = accts.size() + self.primary.maj_lookahead;
for (std::size_t i = accts.size(); i < end && i <= max_index; ++i) 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}; 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 std::error_code handle_lookahead_error(std::error_code error) noexcept
{ {
if (error == rpc_max_subaddresses) if (error == rpc_max_subaddresses)
error = {error::subaddr_server}; error = {error::subaddr_ahead};
else if (error == rpc_not_implemented) else if (error == rpc_not_implemented)
error = {error::subaddr_disabled}; error = {error::subaddr_disabled};
else if (error == wire::error::schema::array_max_element) 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 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); const auto elem = detail.find(minor);
if (elem != detail.end()) if (elem != detail.end())
return elem->second.label; return elem->second.label;
@@ -692,12 +749,13 @@ namespace lwsf { namespace internal { namespace backend
: listener(nullptr), : listener(nullptr),
client(), client(),
primary{}, primary{},
refresh_status(), refresh_error(),
server_lookahead(default_subaddr_state), lookahead_error(default_subaddr_state),
last_sync(), last_sync(),
blockchain_height(0), blockchain_height(0),
per_byte_fee(0), per_byte_fee(0),
fee_mask(0), fee_mask(0),
server_lookahead(0),
sync(), sync(),
sync_listener(), sync_listener(),
sync_refresh(), sync_refresh(),
@@ -771,7 +829,9 @@ namespace lwsf { namespace internal { namespace backend
// Remember that this function provides the strong exception guarantee. // Remember that this function provides the strong exception guarantee.
boost::unique_lock<boost::mutex> lock{sync}; 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{ const rpc::login_request login{
primary.address, primary.view.sec, true, primary.generated_locally primary.address, primary.view.sec, true, primary.generated_locally
}; };
@@ -794,9 +854,9 @@ namespace lwsf { namespace internal { namespace backend
if (response->start_height) if (response->start_height)
primary.restore_height = *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 {}; return {};
} }
} }
@@ -814,26 +874,20 @@ namespace lwsf { namespace internal { namespace backend
} }
// lookaheads not far enough // 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_); fill_upsert(*this, request.subaddrs_);
lock.unlock(); lock.unlock();
const auto response = rpc::invoke<rpc::upsert_subaddrs_response>(client, request).value(); const auto response = rpc::invoke<rpc::upsert_subaddrs_response>(client, request).value();
lock.lock(); lock.lock();
update_lookaheads(*this, request.subaddrs_); update_lookaheads(*this, response.all_subaddrs);
} }
catch (const std::system_error& e) catch (const std::system_error& e)
{ {
if (e.code() == rpc_max_subaddresses) if (e.code() == rpc_unapproved) // happens on new account creation
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
return {error::approval}; return {error::approval};
else if (e.code() == wire::error::schema::array_max_element)
server_lookahead = {error::subaddr_local};
else else
server_lookahead = e.code(); lookahead_error = handle_lookahead_error(e.code());
} }
return {}; return {};
@@ -849,7 +903,7 @@ namespace lwsf { namespace internal { namespace backend
if (!mandatory) if (!mandatory)
{ {
if (now - last_sync < config::refresh_interval_min) if (now - last_sync < config::refresh_interval_min)
return refresh_status; return refresh_error;
} }
struct call_refreshed struct call_refreshed
@@ -877,7 +931,7 @@ namespace lwsf { namespace internal { namespace backend
{ {
lock.lock(); lock.lock();
last_sync = now; 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)
{ {
if (txs_response == rpc_unapproved) if (txs_response == rpc_unapproved)
return refresh_status = {error::approval}; return refresh_error = {error::approval};
return refresh_status = txs_response.error(); return refresh_error = txs_response.error();
} }
if (!outs_response) 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; const std::uint64_t orig_scan_height = primary.scan_height;
auto merged = merge_response(*this, *txs_response, *outs_response); 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 // ensure lookahead is from zero, or our logic is busted
for (auto& sub : merged.new_subaddrs) for (auto& sub : merged.new_subaddrs)
{ {
std::get<0>(sub.head) = 0; 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{ 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)); 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 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_i = add_uint32_clamp(unsigned(1), last_used);
const std::uint32_t maj_count = primary.maj_lookahead; const std::uint32_t maj_count = primary.lookahead.major;
const std::uint32_t min_count = std::max(std::uint32_t(1), primary.min_lookahead); const std::uint32_t min_count = std::max(std::uint32_t(1), primary.lookahead.minor);
const rpc::provision_subaddrs_request provision_request{ const rpc::provision_subaddrs_request provision_request{
login, maj_i, 0, maj_count, min_count, false /* get_all */ login, maj_i, 0, maj_count, min_count, false /* get_all */
}; };
@@ -947,23 +1021,22 @@ namespace lwsf { namespace internal { namespace backend
lock.lock(); lock.lock();
if (provision_response) if (provision_response)
{ server_lookahead = std::max(server_lookahead, add_uint32_clamp(last_used, maj_count));
if (server_lookahead) // in case another thread jumped in
server_lookahead = std::max(*server_lookahead, add_uint32_clamp(last_used, maj_count));
}
else // if !provision_response else // if !provision_response
server_lookahead = handle_lookahead_error(provision_response.error()); lookahead_error = handle_lookahead_error(provision_response.error());
} }
} }
else // if !upsert_response else // if !upsert_response
server_lookahead = handle_lookahead_error(upsert_response.error()); lookahead_error = handle_lookahead_error(upsert_response.error());
} // if new subaddress(es) } // if new subaddress(es)
// return error if subaddresses enabled, and recovered wallet // return error if subaddresses enabled, and recovered wallet
refresh_on_exit.release(); // release before acquiring `sync_listener`. 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}; const boost::lock_guard<boost::mutex> lock_listener{sync_listener};
if (!listener) if (!listener)
return refresh_status = server_lookahead.error(); return rc;
// Call listener functions without holding `sync`, in case a call is made // Call listener functions without holding `sync`, in case a call is made
// back into the library. // back into the library.
@@ -993,7 +1066,7 @@ namespace lwsf { namespace internal { namespace backend
listener->moneySpent(txid, tx->amount); 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) std::error_code wallet::register_subaccount(const std::uint32_t maj_i)
@@ -1011,30 +1084,26 @@ namespace lwsf { namespace internal { namespace backend
lock.lock(); lock.lock();
} }
if (!server_lookahead) const std::uint32_t needed_maj_i = add_uint32_clamp(maj_i, primary.lookahead.major);
return server_lookahead.error(); if (!lookahead_error && needed_maj_i <= server_lookahead)
const std::uint32_t needed_maj_i = add_uint32_clamp(maj_i, primary.maj_lookahead);
if (needed_maj_i <= *server_lookahead)
return {}; return {};
// Increase the lookahead by one const std::uint32_t major_count = needed_maj_i - server_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 std::uint32_t minor_count = std::max(std::uint32_t(1), primary.min_lookahead);
const rpc::provision_subaddrs_request request{ const rpc::provision_subaddrs_request request{
rpc::login{primary.address, primary.view.sec}, 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(); lock.unlock();
const auto response = rpc::invoke<rpc::provision_subaddrs_response>(client, request); const auto response = rpc::invoke<rpc::provision_subaddrs_response>(client, request);
lock.lock(); lock.lock();
if (response && server_lookahead) if (response)
server_lookahead = std::max(*server_lookahead, needed_maj_i); server_lookahead = std::max(server_lookahead, needed_maj_i);
else if (!response) else if (!lookahead_error)
server_lookahead = handle_lookahead_error(response.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) 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(); lock.lock();
} }
const std::uint32_t needed_min_i = add_uint32_clamp(min_i, primary.min_lookahead); const std::uint32_t needed_min_i = add_uint32_clamp(min_i, primary.lookahead.minor);
if (needed_min_i <= maj->server_lookahead) if (!lookahead_error && needed_min_i <= maj->server_lookahead)
return {}; return {};
const std::uint32_t needed_count = add_uint32_clamp(unsigned(1), needed_min_i); 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 // address could've changed during unlock
maj = std::addressof(primary.subaccounts.at(maj_i)); maj = std::addressof(primary.subaccounts.at(maj_i));
maj->server_lookahead = std::max(maj->server_lookahead, needed_min_i); maj->server_lookahead = std::max(maj->server_lookahead, needed_min_i);
return {};
} }
else if (server_lookahead) else if (!lookahead_error)
server_lookahead = handle_lookahead_error(response.error()); lookahead_error = handle_lookahead_error(response.error());
return server_lookahead.error(); return lookahead_error;
} }
std::error_code wallet::set_lookahead(std::uint32_t major, std::uint32_t minor) std::error_code wallet::set_lookahead(std::uint32_t major, std::uint32_t minor)
{ {
boost::unique_lock<boost::mutex> lock{sync}; boost::unique_lock<boost::mutex> lock{sync};
const bool extending = const bool extending =
primary.maj_lookahead < major || primary.lookahead.major < major ||
primary.min_lookahead < minor; primary.lookahead.minor < minor;
primary.maj_lookahead = major; primary.lookahead.major = major;
primary.min_lookahead = minor; primary.lookahead.minor = minor;
if ((!extending && server_lookahead) || (!major && !minor))
{
if (!server_lookahead)
server_lookahead = 0;
return {};
}
if (!passed_login) if (!passed_login)
{ {
@@ -1108,8 +1169,11 @@ namespace lwsf { namespace internal { namespace backend
lock.lock(); lock.lock();
} }
if (!extending && !lookahead_error)
return {};
rpc::upsert_subaddrs_request request{ 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_); 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); const auto response = rpc::invoke<rpc::upsert_subaddrs_response>(client, request);
lock.lock(); lock.lock();
if (!response) 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 {}; 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 */ boost::unique_lock<boost::mutex> lock{sync};
throw std::logic_error{"restore_height not implemented"};
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) std::error_code wallet::send_tx(epee::byte_slice tx_bytes)
+15 -10
View File
@@ -199,16 +199,16 @@ namespace lwsf { namespace internal { namespace backend
{ {
account() = delete; account() = delete;
std::string address; //!> not serialized, recovered on read_bytes std::string address; //!< not serialized, recovered on read_bytes
std::string language; std::string language;
std::vector<address_book_entry> addressbook; std::vector<address_book_entry> addressbook;
std::vector<sub_account> subaccounts; //! Enabled major subaccounts. Index indicates `sub.major` value. 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<crypto::hash, std::shared_ptr<transaction>> txes;
std::unordered_map<std::string, std::string> attributes; std::unordered_map<std::string, std::string> attributes;
std::uint64_t scan_height; std::uint64_t scan_height; //!< last block scanned
std::uint64_t restore_height; std::uint64_t restore_height; //!< aka scan start height
std::uint32_t maj_lookahead = config::subaddr_major_lookahead; std::uint64_t requested_start = std::numeric_limits<std::uint64_t>::max();
std::uint32_t min_lookahead = config::subaddr_minor_lookahead; config::lookahead lookahead = config::default_lookahead; //<! Lookahead as requested by user
Monero::NetworkType type; Monero::NetworkType type;
keypair view; keypair view;
keypair spend; keypair spend;
@@ -222,12 +222,14 @@ namespace lwsf { namespace internal { namespace backend
Monero::WalletListener* listener; Monero::WalletListener* listener;
rpc::http_client client; rpc::http_client client;
account primary; account primary;
std::error_code refresh_status; //!< Cached because `refresh(...)` is rate limited std::error_code refresh_error; //!< Cached because `refresh(...)` is rate limited
expect<std::uint32_t> server_lookahead; //!< Status of major lookahead server-side. Inclusive 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::chrono::steady_clock::time_point last_sync;
std::uint64_t blockchain_height; std::uint64_t blockchain_height;
std::uint64_t per_byte_fee; std::uint64_t per_byte_fee;
std::uint64_t fee_mask; std::uint64_t fee_mask;
std::uint32_t server_lookahead; //!< Status of major lookahead server-side. Inclusive
mutable boost::mutex sync; mutable boost::mutex sync;
boost::mutex sync_listener; boost::mutex sync_listener;
boost::mutex sync_refresh; 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; crypto::public_key get_spend_public(const rpc::address_meta& index) const;
std::string get_spend_address(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; expect<epee::byte_slice> to_bytes() const;
//! De-serialize `this` from msgpack. Locks+replaces contents. //! De-serialize `this` from msgpack. Locks+replaces contents.
std::error_code from_bytes(epee::byte_slice source); 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(); 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); std::error_code refresh(bool mandatory = false);
//! Notify server that new major accounts need to be watched. //! 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. //! 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); 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 set_lookahead(std::uint32_t major, std::uint32_t minor);
std::error_code restore_height(const std::uint64_t height); std::error_code restore_height(const std::uint64_t height);
+10 -16
View File
@@ -41,28 +41,22 @@ namespace lwsf
return "Configuration issue"; return "Configuration issue";
case error::create: case error::create:
return "Account creation not possible"; return "Account creation not possible";
case error::crypto_failure: case error::import_fee:
return "Cryptography issue"; return "Import/restore from height needs payment, see address book";
case error::invalid_encoding: case error::import_invalid:
return "File had invalid encoding"; return "Import/restore from height failed due to invalid fee";
case error::invalid_scheme: case error::import_pending:
return "Invalid URL scheme"; return "Import/restore from height is awaiting approval";
case error::network_type: case error::network_type:
return "Mismatch on network type"; return "Mismatch on network type";
case error::read_failure: case error::subaddr_ahead:
return "Failed to read file"; return "server has limits too low for requested subaddress lookahead";
case error::subaddr_disabled: case error::subaddr_disabled:
return "light-wallet-server has subaddresses disabled"; return "server has subaddresses disabled";
case error::subaddr_local: case error::subaddr_local:
return "lwsf limits on subaddresses too small"; return "lwsf (local) limits on subaddresses too small";
case error::subaddr_server:
return "light-wallet-server has limits on subaddresses that are too small";
case error::unexpected_userinfo: case error::unexpected_userinfo:
return "Unexpected user+pass field in URL"; 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: default:
break; break;
+4 -6
View File
@@ -37,17 +37,15 @@ namespace lwsf
approval, //!< Account needs approval approval, //!< Account needs approval
configuration, //!< Bad Configuration configuration, //!< Bad Configuration
create, //!< Account creation not possible create, //!< Account creation not possible
crypto_failure, //!< Failure in crypto import_fee, //!< `import_wallet_request` has fee
invalid_encoding, //!< Invalid percent encoding import_invalid, //!< `import_wallet_request` has invalid fee
import_pending, //!< `import_wallet_request` not fulfilled/approved
invalid_scheme, //!< Invalid network scheme invalid_scheme, //!< Invalid network scheme
network_type, //!< Mismatch on network type 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_disabled, //!< Server has subaddresses disabled
subaddr_local, //!< Local limits on subaddresses too small subaddr_local, //!< Local limits on subaddresses too small
subaddr_server, //!< Server limits on subaddresses too small
unexpected_userinfo,//!< Unexpected user+pass provided unexpected_userinfo,//!< Unexpected user+pass provided
unsupported_format, //!< File format could not be unpacked
write_failure //!< Failed to write file
}; };
//! \return Error message string. //! \return Error message string.
+8 -4
View File
@@ -34,9 +34,17 @@
namespace lwsf { namespace config 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 connect_timeout{5};
constexpr const std::chrono::seconds daemon_status_cache{10}; constexpr const std::chrono::seconds daemon_status_cache{10};
constexpr const std::string_view default_account_name{"Untitled account"}; 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::string_view default_primary_name{"Primary account"};
constexpr const std::size_t initial_buffer_size = 1024 * 64; // 64 KiB constexpr const std::size_t initial_buffer_size = 1024 * 64; // 64 KiB
constexpr const std::size_t max_txes_in_rpc = 2048; 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{30};
constexpr const std::chrono::seconds refresh_interval_min{5}; constexpr const std::chrono::seconds refresh_interval_min{5};
constexpr const std::chrono::seconds rpc_timeout{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 }} // lwsf // config
+2
View File
@@ -399,6 +399,8 @@ namespace lwsf { namespace internal { namespace rpc
void read_bytes(wire::json_reader& source, import_response& self) void read_bytes(wire::json_reader& source, import_response& self)
{ {
wire::object(source, wire::object(source,
WIRE_OPTIONAL_FIELD(payment_address),
WIRE_OPTIONAL_FIELD(payment_id),
WIRE_OPTIONAL_FIELD(import_fee), WIRE_OPTIONAL_FIELD(import_fee),
WIRE_FIELD(request_fulfilled) WIRE_FIELD(request_fulfilled)
); );
+9 -5
View File
@@ -229,11 +229,11 @@ namespace lwsf { namespace internal { namespace rpc
struct subaddrs struct subaddrs
{ {
subaddrs() constexpr subaddrs() noexcept
: head({0, 0}), key(0) : 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) : head({0, 0}), key(key)
{} {}
@@ -243,11 +243,11 @@ namespace lwsf { namespace internal { namespace rpc
WIRE_JSON_DECLARE_OBJECT(subaddrs); WIRE_JSON_DECLARE_OBJECT(subaddrs);
void read_bytes(wire::json_reader&, 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; } { return lhs.key < rhs.key; }
template<typename T> 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>()); static_assert(std::is_unsigned<T>());
return lhs.key < rhs; return lhs.key < rhs;
@@ -337,6 +337,10 @@ namespace lwsf { namespace internal { namespace rpc
struct import_response struct import_response
{ {
import_response() = delete; 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; std::optional<uint64_string> import_fee;
bool request_fulfilled; bool request_fulfilled;
}; };
@@ -398,7 +402,7 @@ namespace lwsf { namespace internal { namespace rpc
static constexpr const char* endpoint() noexcept { return "/upsert_subaddrs"; } static constexpr const char* endpoint() noexcept { return "/upsert_subaddrs"; }
boost::container::flat_set<subaddrs> new_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&); void read_bytes(wire::json_reader&, upsert_subaddrs_response&);
+3
View File
@@ -107,6 +107,9 @@ namespace lwsf { namespace internal
for (const auto& tx : data_->primary.txes) for (const auto& tx : data_->primary.txes)
{ {
if (tx.second->failed)
continue;
const bool unlocked = tx.second->is_unlocked(chain_height, net_type); const bool unlocked = tx.second->is_unlocked(chain_height, net_type);
for (const auto& receive : tx.second->receives) for (const auto& receive : tx.second->receives)
{ {
+5 -1
View File
@@ -100,7 +100,11 @@ namespace lwsf { namespace internal
{ {
{ {
const boost::lock_guard<boost::mutex> lock{data_->sync}; 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); refresh(accountIndex);
} }
+101 -128
View File
@@ -70,7 +70,9 @@ namespace lwsf { namespace internal
boost::unique_future<boost::asio::ip::tcp::socket> boost::unique_future<boost::asio::ip::tcp::socket>
operator()(const std::string&, const std::string&, boost::asio::steady_timer&) const 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 } // 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
{ {
const boost::lock_guard<boost::mutex> lock{error_sync_}; if (clear || error)
if (!update_iff_error || status) {
status_ = status; const boost::lock_guard<boost::mutex> lock{error_sync_};
return !status_; error_ = error;
}
return !error;
} }
void wallet::set_critical(const std::exception& e) const void wallet::set_critical(const std::exception& e) const
{ {
const boost::lock_guard<boost::mutex> lock{error_sync_}; const boost::lock_guard<boost::mutex> lock{error_sync_};
exception_error_ = e.what(); exception_error_ = e.what();
status_.clear();
} }
template<typename F> template<typename F>
void wallet::queue_work(F&& f) void wallet::queue_work(F&& f)
{ {
try
{ {
const boost::lock_guard<boost::mutex> lock{refresh_sync_}; const boost::lock_guard<boost::mutex> lock{refresh_sync_};
work_queue_.push_back(std::forward<F>(f)); work_queue_.push_back(std::forward<F>(f));
} }
catch (const std::exception& e)
{ set_critical(e); }
startRefresh(); startRefresh();
} }
@@ -407,7 +407,7 @@ namespace lwsf { namespace internal
work_queue_.pop_front(); work_queue_.pop_front();
lock.unlock(); lock.unlock();
if (work) if (work)
set_error(work(), true); set_error(work());
lock.lock(); lock.lock();
} }
@@ -415,11 +415,12 @@ namespace lwsf { namespace internal
mandatory_refresh_ = false; mandatory_refresh_ = false;
const auto now = std::chrono::steady_clock::now(); 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. // refresh has strong exception guarantee - never in partial state.
lock.unlock(); lock.unlock();
set_error(data_->refresh(mandatory_refresh), false); last_refresh = now;
set_error(data_->refresh(mandatory_refresh), true /*clear*/);
lock.lock(); lock.lock();
} }
else if (thread_state_ == state::skip_once) else if (thread_state_ == state::skip_once)
@@ -498,7 +499,7 @@ namespace lwsf { namespace internal
password_(std::move(password)), password_(std::move(password)),
work_queue_(), work_queue_(),
exception_error_(), exception_error_(),
status_(), error_(),
thread_(), thread_(),
iterations_(kdf_rounds), iterations_(kdf_rounds),
mixin_(config::mixin_default), mixin_(config::mixin_default),
@@ -530,7 +531,7 @@ namespace lwsf { namespace internal
password_(std::move(password)), password_(std::move(password)),
work_queue_(), work_queue_(),
exception_error_(), exception_error_(),
status_(), error_(),
thread_(), thread_(),
iterations_(kdf_rounds), iterations_(kdf_rounds),
mixin_(config::mixin_default), mixin_(config::mixin_default),
@@ -542,40 +543,29 @@ namespace lwsf { namespace internal
thread_state_(state::stop), thread_state_(state::stop),
mandatory_refresh_(false) mandatory_refresh_(false)
{ {
if (!data_)
throw std::invalid_argument{"lwsf::backend::wallet cannot be null"};
try try
{ {
epee::byte_slice file = try_load(filename_ + ".new"); epee::byte_slice file = try_load(filename_ + ".new");
if (file.empty()) if (file.empty())
file = try_load(filename_); 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))) encrypted_file contents{};
{ if (std::error_code error = wire::msgpack::from_bytes(std::move(file), contents))
epee::byte_slice payload = contents.get_payload(password_); throw std::system_error{error};
if (!payload.empty())
{ epee::byte_slice payload = contents.get_payload(password_);
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) // lock not needed; data_ was created and unique to us
throw std::runtime_error{"Wallet file NetworkType does not match requested"}; 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) catch (const std::exception& e)
{ {
status_.clear();
exception_error_ = e.what(); exception_error_ = e.what();
} }
} }
@@ -590,7 +580,7 @@ namespace lwsf { namespace internal
password_(std::move(password)), password_(std::move(password)),
work_queue_(), work_queue_(),
exception_error_(), exception_error_(),
status_(), error_(),
thread_(), thread_(),
iterations_(kdf_rounds), iterations_(kdf_rounds),
mixin_(config::mixin_default), mixin_(config::mixin_default),
@@ -630,7 +620,7 @@ namespace lwsf { namespace internal
password_(std::move(password)), password_(std::move(password)),
work_queue_(), work_queue_(),
exception_error_(), exception_error_(),
status_(), error_(),
thread_(), thread_(),
iterations_(kdf_rounds), iterations_(kdf_rounds),
mixin_(config::mixin_default), mixin_(config::mixin_default),
@@ -679,25 +669,21 @@ namespace lwsf { namespace internal
void wallet::add_subaddress(const std::uint32_t accountIndex, std::string label) 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;
const boost::lock_guard<boost::mutex> lock{data_->sync};
auto& accts = data_->primary.subaccounts;
if (accts.size() <= accountIndex) if (accts.size() <= accountIndex)
throw std::runtime_error{"add_subaddress: account does not exist"}; throw std::runtime_error{"add_subaddress: account does not exist"};
auto& acct = accts.at(accountIndex); auto& acct = accts.at(accountIndex);
if (std::numeric_limits<std::uint32_t>::max() <= acct.last) if (std::numeric_limits<std::uint32_t>::max() <= acct.last)
throw std::runtime_error{"add_subaddress: exceeded minor indexes"}; throw std::runtime_error{"add_subaddress: exceeded minor indexes"};
const std::uint32_t min_i = ++acct.last; const std::uint32_t min_i = ++acct.last;
if (!label.empty())
acct.detail.try_emplace(min_i).first->second.label = std::move(label); acct.detail.try_emplace(min_i).first->second.label = std::move(label);
queue_work([=] () { return data_->register_subaddress(accountIndex, min_i); }); 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 std::string wallet::seed(const std::string& seed_offset) const
@@ -761,10 +747,10 @@ namespace lwsf { namespace internal
status = Status_Critical; status = Status_Critical;
errorString = exception_error_; errorString = exception_error_;
} }
else if (status_) else if (error_)
{ {
status = Status_Error; status = Status_Error;
errorString = status_.message(); errorString = error_.message();
} }
else else
{ {
@@ -816,39 +802,33 @@ namespace lwsf { namespace internal
{ {
const std::string& real_path = path.empty() ? filename_ : path; const std::string& real_path = path.empty() ? filename_ : path;
std::error_code status{};
try try
{ {
expect<epee::byte_slice> payload = data_->to_bytes(); epee::byte_slice payload = data_->to_bytes().value();
if (payload) const auto contents = encrypted_file::make(payload, iterations_, password_);
{ const auto payload_size = payload.size();
const auto contents = encrypted_file::make(*payload, iterations_, password_); payload = nullptr; // free up some memory that is no longer needed
const auto payload_size = payload->size();
*payload = nullptr; // free up some memory that is no longer needed
epee::byte_stream buffer; epee::byte_stream buffer;
buffer.reserve(payload_size + 2048); buffer.reserve(payload_size + 2048);
buffer.write(file_magic.data(), file_magic.size()); 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)) const std::filesystem::path file = real_path;
std::filesystem::rename(new_file, file, status); const std::filesystem::path new_file = real_path + ".new";
const std::filesystem::path directory =
// blocks until file and directory contents are synced std::filesystem::path{real_path}.remove_filename();
if (atomic_file_write(new_file, directory, epee::byte_slice{std::move(buffer)}))
std::filesystem::rename(new_file, file, status); if (std::filesystem::exists(new_file))
else std::filesystem::rename(new_file, file);
status = error::write_failure;
} // blocks until file and directory contents are synced
} if (!atomic_file_write(new_file, directory, epee::byte_slice{std::move(buffer)}))
else throw std::runtime_error{"Failed to write file " + real_path};
status = payload.error();
std::filesystem::rename(new_file, file);
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {
@@ -856,8 +836,7 @@ namespace lwsf { namespace internal
return false; return false;
} }
// this will clear any existing errors return true;
return set_error(status, false);
} }
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) 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()) if (!url.m_uri_content.m_path.empty())
throw std::runtime_error{"LWS URL contains path (unsupported)"}; throw std::runtime_error{"LWS URL contains path (unsupported)"};
if (!proxy_address.empty() && !setProxy(proxy_address)) if (!proxy_address.empty() && !setProxy(proxy_address))
return false; return false;
@@ -901,8 +879,10 @@ namespace lwsf { namespace internal
return true; 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); }); queue_work([this, height] () { return data_->restore_height(height); });
} }
@@ -919,28 +899,17 @@ namespace lwsf { namespace internal
bool wallet::connectToDaemon() bool wallet::connectToDaemon()
{ {
struct on_throw const bool connected = data_->client.is_connected();
{ boost::unique_lock<boost::mutex> lock{data_->sync};
void operator()(wallet* self) const if (connected && data_->passed_login)
{
if (self)
self->data_->client.disconnect();
}
};
if (data_->client.is_connected())
return true; return true;
std::unique_ptr<wallet, on_throw> disconnect{this}; if (connected || data_->client.connect(config::connect_timeout))
if (data_->client.connect(config::connect_timeout))
{ {
const std::error_code err = data_->login(); lock.unlock();
if (!err) return set_error(data_->login());
disconnect.release();
return set_error(err, false);
} }
set_error(rpc::error::no_response, false); return set_error(rpc::error::no_response);
return false;
} }
Monero::Wallet::ConnectionStatus wallet::connected() const Monero::Wallet::ConnectionStatus wallet::connected() const
@@ -955,13 +924,19 @@ namespace lwsf { namespace internal
bool wallet::setProxy(const std::string &address) bool wallet::setProxy(const std::string &address)
{ {
auto endpoint = net::get_tcp_endpoint(address); data_->client.disconnect();
if (!endpoint) if (address.empty())
data_->client.set_connector(epee::net_utils::direct_connect{});
else
{ {
data_->client.set_connector(null_connector{}); auto endpoint = net::get_tcp_endpoint(address);
return set_error(endpoint.error(), false); if (!endpoint)
{
data_->client.set_connector(null_connector{});
return set_error(endpoint.error());
}
data_->client.set_connector(net::socks::connector{std::move(*endpoint)});
} }
data_->client.set_connector(net::socks::connector{std::move(*endpoint)});
return true; return true;
} }
@@ -1082,7 +1057,7 @@ namespace lwsf { namespace internal
bool wallet::refresh() 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); } catch (const std::exception& e) { set_critical(e); }
return false; return false;
} }
@@ -1118,20 +1093,15 @@ namespace lwsf { namespace internal
void wallet::addSubaddressAccount(const std::string& label) void wallet::addSubaddressAccount(const std::string& label)
{ {
try const boost::lock_guard<boost::mutex> lock{data_->sync};
{ auto& accts = data_->primary.subaccounts;
const boost::lock_guard<boost::mutex> lock{data_->sync};
auto& accts = data_->primary.subaccounts;
const std::size_t index = accts.size(); const std::size_t index = accts.size();
if (std::numeric_limits<std::uint32_t>::max() < index) if (std::numeric_limits<std::uint32_t>::max() < index)
throw std::runtime_error{"addSubddressAccount exceeded subaddress indexes"}; throw std::runtime_error{"addSubddressAccount exceeded subaddress indexes"};
accts.emplace_back().detail.try_emplace(0).first->second.label = label; accts.emplace_back().detail.try_emplace(0).first->second.label = label;
queue_work([this, index] () { return data_->register_subaccount(index); }); queue_work([this, index] () { return data_->register_subaccount(index); });
}
catch (const std::exception& e)
{ set_critical(e); }
} }
std::size_t wallet::numSubaddressAccounts() const std::size_t wallet::numSubaddressAccounts() const
@@ -1169,7 +1139,10 @@ namespace lwsf { namespace internal
auto& acct = data_->primary.subaccounts[accountIndex]; auto& acct = data_->primary.subaccounts[accountIndex];
if (addressIndex <= acct.last) if (addressIndex <= acct.last)
{ {
acct.detail.try_emplace(addressIndex).first->second.label = label; if (!addressIndex || !label.empty())
acct.detail.try_emplace(addressIndex).first->second.label = label;
else
acct.detail.erase(addressIndex);
return; return;
} }
} }
+2 -2
View File
@@ -63,7 +63,7 @@ namespace internal
std::string language_; std::string language_;
std::deque<std::function<std::error_code()>> work_queue_; std::deque<std::function<std::error_code()>> work_queue_;
mutable std::string exception_error_; mutable std::string exception_error_;
mutable std::error_code status_; mutable std::error_code error_;
boost::thread thread_; boost::thread thread_;
const std::uint64_t iterations_; const std::uint64_t iterations_;
std::chrono::milliseconds refresh_interval_; std::chrono::milliseconds refresh_interval_;
@@ -75,7 +75,7 @@ namespace internal
state thread_state_; state thread_state_;
bool mandatory_refresh_; 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; void set_critical(const std::exception& e) const;
template<typename F> template<typename F>