mirror of
https://github.com/wownero/lwsf-wow.git
synced 2026-10-17 07:21:28 +02:00
Various updates to a still WIP frontend
This commit is contained in:
committed by
Lee *!* Clagett
parent
dff93153eb
commit
1c3370cc6f
@@ -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
@@ -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
@@ -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
@@ -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));
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
Reference in New Issue
Block a user