Various updates to a still WIP frontend

This commit is contained in:
Lee Clagett
2024-10-04 17:38:53 -04:00
committed by Lee *!* Clagett
parent dff93153eb
commit 1c3370cc6f
8 changed files with 372 additions and 125 deletions
+3 -3
View File
@@ -416,7 +416,7 @@ struct WalletListener
/**
* @brief If the listener is created before the wallet this enables to set created wallet object
*/
virtual void onSetWallet(Wallet * wallet) { (void)wallet; };
virtual void onSetWallet(std::shared_ptr<Wallet> wallet) { (void)wallet; };
};
@@ -1129,8 +1129,8 @@ struct WalletManager
* \param listener Wallet listener to set to the wallet after creation
* \return Wallet instance (Wallet::status() needs to be called to check if opened successfully)
*/
virtual std::unique_ptr<Wallet> openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds = 1, std::shared_ptr<WalletListener> listener = nullptr) = 0;
std::unique_ptr<Wallet> openWallet(const std::string &path, const std::string &password, bool testnet = false) // deprecated
virtual std::shared_ptr<Wallet> openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds = 1, std::shared_ptr<WalletListener> listener = nullptr) = 0;
std::shared_ptr<Wallet> openWallet(const std::string &path, const std::string &password, bool testnet = false) // deprecated
{
return openWallet(path, password, testnet ? TESTNET : MAINNET);
}
+88 -55
View File
@@ -71,7 +71,8 @@ namespace lwsf { namespace internal { namespace backend
WIRE_FIELD(amount),
WIRE_FIELD(sender),
WIRE_OPTIONAL_FIELD(secret),
WIRE_FIELD(tx_pub)
WIRE_FIELD(tx_pub),
WIRE_FIELD(output_pub)
);
}
@@ -138,8 +139,9 @@ namespace lwsf { namespace internal { namespace backend
void map_account(F& format, T& self)
{
wire::object(format,
WIRE_FIELD(restore_height),
wire::field("txes", wire::trusted_array(std::ref(self.txes))),
WIRE_FIELD(scan_height),
WIRE_FIELD(restore_height),
WIRE_FIELD(type),
WIRE_FIELD(view),
WIRE_FIELD(spend),
@@ -252,24 +254,24 @@ namespace lwsf { namespace internal { namespace backend
update_output(out->receives.try_emplace(source.public_key).first->second, source, view_key);
}
std::vector<std::shared_ptr<transaction>> merge_response(wallet& self, const rpc::get_address_txs& source)
std::vector<std::shared_ptr<transaction>>
merge_response(wallet& self, const rpc::get_address_txs& source, const rpc::get_unspent_outs& unspents)
{
std::vector<std::shared_ptr<transaction>> out;
self.scan_height = source.scanned_block_height;
self.blockchain_height = source.blockchain_height;
self.primary.restore_height = source.start_height;
/* Backend server could remove or modify txes (rescan or bug fix); the
easiest way to handle this is to start a new copy of the txes. This is
what the existing (JS) MyMonero frontend does. This has the benefit
of allowing `shared_ptr<transaction>` objects to be "given away" to
other parts of the frontend without a mutex. */
other parts of the frontend without a mutex.
const auto existing_txes = std::move(self.primary.txes);
self.primary.txes.clear();
self.primary.txes.reserve(
std::max(existing_txes.size(), source.transactions.size())
ADDITIONALLY, the strong exception guarantee is provided by the
`refresh()` method; the wallet is never in a partial-state. Swapping
the transactions at the end helps with this guarantee. */
boost::container::flat_map<crypto::hash, std::shared_ptr<transaction>, memory> updated_txes;
updated_txes.reserve(
std::max(self.primary.txes.size(), source.transactions.size())
);
/* The frontend will know about the spend first, iff the frontend was
@@ -277,17 +279,16 @@ namespace lwsf { namespace internal { namespace backend
spend secret, even if the backend doesn't acknowledge it, otherwise the
secret information will be lost in many situations. If the spend
never gets confirmed, this will just sit in the transaction list. */
for (const auto& tx : existing_txes)
for (const auto& tx : self.primary.txes)
{
if (tx.second)
{
bool copy = false;
for (const auto& spend : tx.second->spends)
{
if (spend.second.secret)
{
self.primary.txes.emplace_hint(
self.primary.txes.end(), tx.first, std::make_shared<transaction>(*tx.second)
updated_txes.emplace_hint(
updated_txes.end(), tx.first, std::make_shared<transaction>(*tx.second)
);
break; // inner loop
}
@@ -297,42 +298,39 @@ namespace lwsf { namespace internal { namespace backend
for (const auto& tx : source.transactions)
{
auto inserted = self.primary.txes.try_emplace(tx.tx_hash, nullptr);
auto inserted = updated_txes.try_emplace(tx.tx_hash, nullptr);
if (inserted.second)
{
const auto existing = existing_txes.find(tx.tx_hash);
try
{
if (existing != existing_txes.end() && existing->second)
inserted.first->second = std::make_shared<transaction>(*existing->second);
else
inserted.first->second = std::make_shared<transaction>();
}
catch (...)
{
self.primary.txes.erase(inserted.first);
throw;
}
const auto existing = self.primary.txes.find(tx.tx_hash);
if (existing != self.primary.txes.end() && existing->second)
inserted.first->second = std::make_shared<transaction>(*existing->second);
else
inserted.first->second = std::make_shared<transaction>();
if (existing != existing_txes.end())
if (existing != self.primary.txes.end())
out.push_back(inserted.first->second);
}
update_tx(self.primary, *inserted.first->second, tx);
}
return out;
}
void merge_response(wallet& self, const rpc::get_unspent_outs& source)
{
self.per_byte_fee = source.per_byte_fee;
self.fee_mask = source.fee_mask;
for (const auto& output : source.outputs)
for (const auto& output : unspents.outputs)
{
auto iter = self.primary.txes.find(output.tx_hash);
if (iter != self.primary.txes.end())
auto iter = updated_txes.find(output.tx_hash);
if (iter != updated_txes.end())
merge_output(iter->second, output, self.primary.view.sec);
}
// don't touch `self` until end to provide strong exception guarantee
self.primary.txes.swap(updated_txes);
self.blockchain_height = source.blockchain_height;
self.primary.restore_height = source.start_height;
self.primary.scan_height = source.scanned_block_height;
self.per_byte_fee = unspents.per_byte_fee;
self.fee_mask = unspents.fee_mask;
return out;
}
} // anonymous
@@ -355,8 +353,8 @@ namespace lwsf { namespace internal { namespace backend
client(),
primary{},
last_sync(0),
scan_height(0),
blockchain_height(0),
fee_mask(0),
sync()
{}
@@ -373,13 +371,44 @@ namespace lwsf { namespace internal { namespace backend
}
std::error_code wallet::from_bytes(epee::byte_slice source)
{
/* The move call shouldn't throw an exception. So either the entire
contents of the file get loaded, or the wallet remains unchanged. */
account reload{};
std::error_code status = wire::msgpack::from_bytes(std::move(source), reload);
if (!status)
{
const boost::lock_guard<boost::mutex> lock{sync};
primary = std::move(reload);
}
return status;
}
std::error_code wallet::login()
{
const boost::lock_guard<boost::mutex> lock{sync};
return wire::msgpack::from_bytes(std::move(source), primary);
const rpc::login_request login{
primary.address, primary.view.sec, true, primary.generated_locally
};
const auto response = rpc::invoke<rpc::login_response>(client, login);
if (!response)
return response.error();
if (response->start_height)
primary.restore_height = *response->start_height;
return {};
}
std::error_code wallet::refresh(const bool mandatory)
{
/* Remember that this function provides the strong exception guarantee.
The state of the wallet should always be valid and never in a partially
updated state. */
if (!mandatory)
{
const auto now = std::chrono::steady_clock::now();
@@ -391,21 +420,23 @@ namespace lwsf { namespace internal { namespace backend
}
expect<rpc::get_unspent_outs> outs_response{common_error::kInvalidArgument};
boost::unique_lock<boost::mutex> lock{sync};
const rpc::login login{primary.address, primary.view.sec};
lock.unlock();
const auto txs_response = rpc::invoke<rpc::get_address_txs>(client, login);
if (txs_response)
outs_response = rpc::invoke<rpc::get_unspent_outs>(client, login);
boost::unique_lock<boost::mutex> lock{sync};
if (!txs_response)
return txs_response.error();
if (!outs_response)
return outs_response.error();
const std::uint64_t orig_scan_height = scan_height;
const auto new_txes = merge_response(*this, *txs_response);
merge_response(*this, *outs_response);
lock.lock();
const std::uint64_t orig_scan_height = primary.scan_height;
const auto new_txes = merge_response(*this, *txs_response, *outs_response);
last_sync = std::chrono::steady_clock::now().time_since_epoch().count();
if (!listener)
@@ -413,15 +444,17 @@ namespace lwsf { namespace internal { namespace backend
// Call listener functions without holding lock, in case a call is made
// back into the library.
const std::uint64_t new_scan_height = std::max(orig_scan_height, scan_height);
const std::uint64_t new_scan_height =
std::max(orig_scan_height, primary.scan_height);
const std::shared_ptr<WalletListener> listener_copy = listener;
lock.unlock();
listener->refreshed();
listener_copy->refreshed();
if (!new_txes.empty())
listener->updated();
listener_copy->updated();
for (std::uint64_t i = orig_scan_height; i < new_scan_height; ++i)
listener->newBlock(i);
listener_copy->newBlock(i);
for (const auto& tx : new_txes)
{
@@ -429,12 +462,12 @@ namespace lwsf { namespace internal { namespace backend
if (tx->direction == TransactionInfo::Direction::In)
{
if (tx->height)
listener->moneyReceived(txid, tx->amount);
listener_copy->moneyReceived(txid, tx->amount);
else
listener->unconfirmedMoneyReceived(txid, tx->amount);
listener_copy->unconfirmedMoneyReceived(txid, tx->amount);
}
else
listener->moneySpent(txid, tx->amount);
listener_copy->moneySpent(txid, tx->amount);
}
return {};
+12 -3
View File
@@ -70,9 +70,10 @@ namespace lwsf { namespace internal { namespace backend
rpc::address_meta sender;
std::optional<crypto::secret_key> secret;
crypto::public_key tx_pub;
crypto::public_key output_pub;
transfer_out()
: address(), amount(0), sender{}, secret(), tx_pub{}
: address(), amount(0), sender{}, secret(), tx_pub{}, outpub_pub{}
{}
};
WIRE_DECLARE_OBJECT(transfer_out);
@@ -154,6 +155,7 @@ namespace lwsf { namespace internal { namespace backend
account() = delete;
std::string address; //!> not serialized, recovered on read_bytes
boost::container::flat_map<crypto::hash, std::shared_ptr<transaction>, memory> txes;
std::uint64_t scan_height;
std::uint64_t restore_height;
NetworkType type;
keypair view;
@@ -168,7 +170,6 @@ namespace lwsf { namespace internal { namespace backend
rpc::http_client client;
account primary;
std::atomic<std::chrono::steady_clock::time_point::duration::rep> last_sync;
std::uint64_t scan_height;
std::uint64_t blockchain_height;
std::uint64_t per_byte_fee;
std::uint64_t fee_mask;
@@ -176,12 +177,20 @@ namespace lwsf { namespace internal { namespace backend
wallet();
//! Serializate `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);
bool login() const;
//! Attempt
std::error_code login();
//! Refreshes txes information. Strong exception guarantee. Locks contents.
std::error_code refresh(bool mandatory = false);
std::error_code restore_height(const std::uint64_t height);
std::error_code send_tx(epee::byte_slice tx_bytes);
};
}}} // lwsf // internal // backend
+14
View File
@@ -119,6 +119,11 @@ namespace lwsf { namespace internal { namespace rpc
);
}
void read_bytes(wire::json_reader& source, login_response& self)
{
wire::object(source, WIRE_OPTIONAL_FIELD(start_height));
}
void read_bytes(wire::json_reader& source, uint64_string& dest)
{
dest = uint64_string(wire::integer::cast_unsigned<std::uint64_t>(source.safe_unsigned_integer()));
@@ -285,6 +290,15 @@ namespace lwsf { namespace internal { namespace rpc
}
void read_bytes(wire::json_reader& source, import_response& self)
{
wire::object(source,
WIRE_OPTIONAL_FIELD(import_fee),
WIRE_FIELD(request_fulfilled)
);
}
void write_bytes(wire::json_writer& dest, const submit_raw_tx_request& self)
{
wire::object(dest, WIRE_FIELD(tx));
+11 -3
View File
@@ -121,10 +121,9 @@ namespace lwsf { namespace internal { namespace rpc
login_response() = delete;
static constexpr const char* endpoint() noexcept { return "/login"; }
std::optional<bool> generated_locally;
std::optional<std::uint64_t> start_height;
};
inline void read_bytes(const wire::json_reader&, const login_response&) noexcept
{}
void read_bytes(wire::json_reader&, login_response&);
struct daemon_status
@@ -266,6 +265,15 @@ namespace lwsf { namespace internal { namespace rpc
void read_bytes(wire::json_reader&, get_unspent_outs&);
struct import_response
{
import_response() = delete;
std::optional<uint64_string> import_fee;
bool request_fulfilled;
};
void read_bytes(wire::json_reader&, import_response&);
struct submit_raw_tx_request
{
submit_raw_tx_request() = delete;
+214 -47
View File
@@ -39,11 +39,14 @@
#include <sodium/crypto_pwhash_argon2id.h>
#include <sodium/randombytes.h>
#include <sodium/crypto_aead_chacha20poly1305.h>
#include <string_view>
#include "backend.h"
#include "error.h"
#include "hex.h" // monero/contrib/epee/include
#include "lwsf_config.h"
#include "net/net_parse_helpers.h" // monero/contrib/epee/include
#include "net/parse.h" // monero/src
#include "net/socks.h" // monero/src
#include "net/socks_connect.h" // monero/src
#include "transaction_history.h"
#include "wire.h"
@@ -281,46 +284,114 @@ namespace lwsf { namespace internal
#else
#error atomic_file_write not implemented for this platform
#endif
struct unknown_exception : std::exception
{
unknown_exception() noexcept
: std::exception()
{}
const char* what() const noexcept override { return "unknown error"; }
};
constexpr boost::chrono::nanoseconds to_boost(const std::chrono::nanoseconds source) noexcept
{
static_assert(
std::is_same<boost::chrono::nanoseconds::rep, std::chrono::nanoseconds::rep>{}
);
return boost::chrono::nanoseconds{source.count()};
}
} // anonymous
void wallet::set_error(const std::error_code status)
bool wallet::set_error(const std::error_code status)
{
const boost::lock_guard<boost::mutex> lock{sync_};
const boost::lock_guard<boost::mutex> lock{error_sync_};
status_ = status;
exception_error_.clear();
return !status_;
}
void wallet::set_critical(const std::exception& e)
{
const boost::lock_guard<boost::mutex> lock{sync_};
const boost::lock_guard<boost::mutex> lock{error_sync_};
exception_error_ = e.what();
status_.clear();
}
wallet::wallet(create_tag, NetworkType nettype, std::string filename, std::string password, const std::uint64_t kdf_rounds, std::shared_ptr<backend::wallet> data)
: data_(std::move(data)),
void wallet::stop_refresh_loop()
{
{
const boost::lock_guard<boost::mutex> lock{refresh_sync_};
thread_state_ = state::stop;
}
refresh_notify_.notify_all();
if (thread_.joinable())
thread_.join();
}
void wallet::refresh_loop()
{
try
{
boost::unique_lock<boost::mutex> lock{refresh_sync_};
while (thread_state_ != state::stop)
{
const bool mandatory_refresh = mandatory_refresh_;
mandatory_refresh_ = false;
if (thread_state_ == state::run || mandatory_refresh)
{
// refresh has strong exception guarantee - never in partial state.
lock.unlock();
set_error(data_->refresh(mandatory_refresh));
lock.lock();
}
else if (thread_state_ == state::skip_once)
thread_state_ = state::run;
// check while holding lock and before a wait call
if (thread_state_ == state::stop)
return;
refresh_notify_.wait_for(lock, to_boost(config::refresh_interval));
}
}
catch (const std::exception& e)
{
set_critical(e);
}
catch (...)
{
set_critical(unknown_exception{});
}
}
wallet::wallet(create_tag, NetworkType nettype, std::string filename, std::string password, const std::uint64_t kdf_rounds)
: data_(std::make_shared<backend::wallet>()),
filename_(std::move(filename)),
password_(std::move(password)),
exception_error_(),
status_(),
thread_(),
iterations_(kdf_rounds),
sync_()
refresh_notify_(),
error_sync_(),
refresh_sync_(),
thread_state_(state::stop),
mandatory_refresh_(false)
{
if (!data_)
throw std::logic_error{"Unexpected nullptr in internal::wallet"};
if (sodium_init() < 0)
throw std::runtime_error{"Failed to initialize libsodium"};
// use libsodium random, more portable
crypto::secret_key recovery;
randombytes_buf(std::addressof(unwrap(unwrap(recovery))), sizeof(recovery));
cryptonote::account_base base{};
base.generate(recovery, true);
const auto& keys = base.get_keys();
const boost::lock_guard<boost::mutex> lock{data_->sync};
data_->primary.view.sec = keys.m_view_secret_key;
data_->primary.view.pub = keys.m_account_address.m_view_public_key;
@@ -331,7 +402,7 @@ namespace lwsf { namespace internal
data_->primary.generated_locally = true;
data_->primary.type = nettype;
store("");
store(std::string{});
}
wallet::wallet(open_tag, NetworkType nettype, std::string filename, std::string password, const std::uint64_t kdf_rounds, std::shared_ptr<backend::wallet> data)
@@ -342,16 +413,20 @@ namespace lwsf { namespace internal
status_(),
thread_(),
iterations_(kdf_rounds),
sync_()
refresh_notify_(),
error_sync_(),
refresh_sync_(),
thread_state_(state::stop),
mandatory_refresh_(false)
{
if (!data_)
throw std::logic_error{"Unexpected nullptr in internal::wallet"};
throw std::logic_error{"backend::wallet cannot be null"};
try
{
epee::byte_slice file = try_load(filename + ".new");
epee::byte_slice file = try_load(filename_ + ".new");
if (file.empty())
file = try_load(filename);
file = try_load(filename_);
if (!file.empty())
{
@@ -360,18 +435,21 @@ namespace lwsf { namespace internal
{
epee::byte_slice payload = contents.get_payload(password_);
if (!payload.empty())
status_ = data_->from_bytes(std::move(payload));
{
if (!(status_ = data_->from_bytes(std::move(payload))))
{
// 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::crypto_failure;
status_ = error::unsupported_format;
}
}
else
status_ = error::read_failure;
}
catch (const std::system_error& e)
{
status_ = e.code();
}
catch (const std::exception& e)
{
status_.clear();
@@ -379,8 +457,7 @@ namespace lwsf { namespace internal
}
}
wallet::~wallet()
{}
wallet::~wallet() { stop_refresh_loop(); }
int wallet::status() const
{
@@ -400,7 +477,7 @@ namespace lwsf { namespace internal
void wallet::statusWithErrorString(int& status, std::string& errorString) const
{
const boost::lock_guard<boost::mutex> lock{sync_};
const boost::lock_guard<boost::mutex> lock{error_sync_};
if (!exception_error_.empty())
{
status = Status_Critical;
@@ -446,6 +523,12 @@ namespace lwsf { namespace internal
return epee::to_hex::string(epee::as_byte_span(data_->primary.spend.pub));
}
void wallet::stop()
{
const boost::lock_guard<boost::mutex> lock{refresh_sync_};
thread_state_ = state::skip_once;
}
bool wallet::store(const std::string& path)
{
const std::string& real_path = path.empty() ? filename_ : path;
@@ -487,17 +570,14 @@ namespace lwsf { namespace internal
else
status = payload.error();
}
catch (const std::system_error& e)
{
status = e.code();
}
catch (const std::exception& e)
{
set_critical(e);
return false;
}
// this will clear any existing errors
set_error(status);
return set_error(status);
}
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)
@@ -505,26 +585,67 @@ namespace lwsf { namespace internal
if (!light_wallet)
throw std::logic_error{"Only light_wallets are supported with this instance"};
const auto split = daemon_address.rfind(':');
if (!setProxy(proxy_address))
return false; // set_status already set
std::string host = daemon_address.substr(0, split);
std::string port = use_ssl ? "443" : "80";
if (split != std::string::npos)
port = daemon_address.substr(split + 1);
epee::net_utils::http::login login{daemon_username, daemon_password};
const epee::net_utils::ssl_options_t options{
use_ssl ? epee::net_utils::ssl_support_t::e_ssl_support_disabled : epee::net_utils::ssl_support_t::e_ssl_support_enabled
// on exceptions, reset state
struct on_throw
{
void operator()(wallet* self) const
{
if (self)
{
self->stop_refresh_loop();
self->data_->client.disconnect();
}
}
};
data_->client.set_server(std::move(host), std::move(port), std::move(login), std::move(options));
try
{
std::unique_ptr<wallet, on_throw> rollback{this};
stop_refresh_loop();
data_->client.disconnect();
if (!setProxy(proxy_address))
return false;
epee::net_utils::http::url_content url{};
if (!epee::net_utils::parse_url(daemon_address, url))
throw std::runtime_error{"Invalid LWS URL: " + daemon_address};
if (!url.m_uri_content.m_path.empty())
throw std::runtime_error{"LWS URL contains path (unsupported)"};
if (url.schema == "https")
use_ssl = true;
epee::net_utils::http::login login{daemon_username, daemon_password};
const epee::net_utils::ssl_options_t options{
use_ssl ? epee::net_utils::ssl_support_t::e_ssl_support_disabled : epee::net_utils::ssl_support_t::e_ssl_support_enabled
};
data_->client.set_server(std::move(url.host), std::to_string(url.port), std::move(login), std::move(options));
if (!connectToDaemon())
return false;
thread_state_ = state::run;
thread_ = boost::thread{[this] () { this->refresh_loop(); }};
rollback.release();
}
catch (const std::exception& e)
{
set_critical(e);
return false;
}
//! \TODO Login!
return true;
}
void wallet::setRefreshFromBlockHeight(uint64_t refresh_from_block_height) override
{
try { set_error(data_->restore_height(refresh_from_block_height)); }
catch (const std::exception& e)
{
set_critical(e);
}
}
uint64_t wallet::getRefreshFromBlockHeight() const
{
@@ -534,9 +655,28 @@ 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())
return true;
return data_->client.connect(config::connect_timeout);
std::unique_ptr<wallet, on_throw> disconnect{this};
if (data_->client.connect(config::connect_timeout))
{
const std::error_code status = data_->login();
if (!status)
disconnect.reset();
return set_error(status);
}
set_error(error::connect_failure);
return false;
}
Wallet::ConnectionStatus wallet::connected() const
@@ -551,8 +691,7 @@ namespace lwsf { namespace internal
if (!endpoint)
{
data_->client.set_connector(null_connector{});
set_error(endpoint.error());
return false;
return set_error(endpoint.error());
}
data_->client.set_connector(net::socks::connector{std::move(*endpoint)});
return true;
@@ -570,6 +709,35 @@ namespace lwsf { namespace internal
uint64_t wallet::daemonBlockChainTargetHeight() const
{ return blockChainHeight(); }
void wallet::startRefresh()
{
{
const boost::lock_guard<boost::mutex> lock{refresh_sync_};
thread_state_ = state::run;
}
refresh_notify_.notify_all();
}
void wallet::pauseRefresh()
{
const boost::lock_guard<boost::mutex> lock{refresh_sync_};
thread_state_ = state::paused;
}
bool wallet::refresh()
{
return set_error(data_->refresh(true));
}
void wallet::refreshAsync()
{
{
const boost::lock_guard<boost::mutex> lock{refresh_sync_};
mandatory_refresh_ = true;
}
refresh_notify_.notify_all();
}
uint64_t wallet::estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations,
PendingTransaction::Priority priority) const
{
@@ -581,7 +749,6 @@ namespace lwsf { namespace internal
return std::make_shared<transaction_history>(data_);
}
void wallet::setListener(std::shared_ptr<WalletListener> listener)
{
const boost::lock_guard<boost::mutex> lock{data_->sync};
+20 -9
View File
@@ -30,6 +30,8 @@
#include "lws_frontend.h"
#include <atomic>
#include <boost/thread/condition_variable.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/thread.hpp>
#include <memory>
@@ -47,6 +49,8 @@ namespace internal
//! \TODO Mark final when completely implemented
class wallet : public lwsf::Wallet
{
enum class state : std::uint8_t { stop = 0, paused, skip_once, run };
const std::shared_ptr<backend::wallet> data_;
const std::string filename_;
std::string password_;
@@ -54,16 +58,23 @@ namespace internal
std::error_code status_;
boost::thread thread_;
const std::uint64_t iterations_;
mutable boost::mutex sync_;
boost::condition_variable refresh_notify_;
mutable boost::mutex error_sync_;
boost::mutex refresh_sync_;
state thread_state_;
bool mandatory_refresh_;
void set_error(const std::error_code status);
bool set_error(std::error_code status);
void set_critical(const std::exception& e);
void stop_refresh_loop();
void refresh_loop();
public:
struct create_tag{};
struct open_tag{};
wallet(create_tag, NetworkType nettype, std::string filename, std::string password, std::uint64_t kdf_rounds, std::shared_ptr<backend::wallet> data);
wallet(create_tag, NetworkType nettype, std::string filename, std::string password, std::uint64_t kdf_rounds);
wallet(open_tag, NetworkType nettpe, std::string filename, std::string password, std::uint64_t kdf_rounds, std::shared_ptr<backend::wallet> data);
wallet(const wallet&) = delete;
@@ -142,7 +153,7 @@ namespace internal
/*!
* \brief stop - interrupts wallet refresh() loop once (doesn't stop background refresh thread)
*/
virtual void stop() = 0;
virtual void stop() override;
/*!
* \brief store - stores wallet to file.
@@ -188,7 +199,7 @@ namespace internal
*
* \param refresh_from_block_height - blockchain start height
*/
virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0;
virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) override;
/*!
* \brief getRestoreHeight - get wallet creation height
@@ -329,22 +340,22 @@ namespace internal
/**
* @brief StartRefresh - Start/resume refresh thread (refresh every 10 seconds)
*/
virtual void startRefresh() = 0;
virtual void startRefresh() override;
/**
* @brief pauseRefresh - pause refresh thread
*/
virtual void pauseRefresh() = 0;
virtual void pauseRefresh() override;
/**
* @brief refresh - refreshes the wallet, updating transactions from daemon
* @return - true if refreshed successfully;
*/
virtual bool refresh() = 0;
virtual bool refresh() override;
/**
* @brief refreshAsync - refreshes wallet asynchronously.
*/
virtual void refreshAsync() = 0;
virtual void refreshAsync() override;
/**
* @brief rescanBlockchain - rescans the wallet, updating transactions from daemon
+10 -5
View File
@@ -109,13 +109,18 @@ namespace lwsf
* \return Wallet instance (Wallet::status() needs to be called to check if opened successfully)
*/
std::unique_ptr<Wallet> openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds, std::shared_ptr<WalletListener> listener) override
std::shared_ptr<Wallet> openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds, std::shared_ptr<WalletListener> listener) override
{
auto back = std::make_shared<backend::wallet>();
back->listener = std::move(listener);
return std::make_unique<wallet>(
wallet::open_tag{}, nettype, path, password, kdf_rounds, std::move(back)
auto data = std::make_shared<backend::wallet>();
data->listener = std::move(listener);
auto out = std::make_shared<wallet>(
wallet::open_tag{}, nettype, path, password, kdf_rounds, data
);
// wallet file should be fully loaded in construction of `wallet`.
if (data->listener)
data->listener->onSetWallet(out);
return out;
}
/*!