Added some more components, but still incomplete and only partially builds

This commit is contained in:
Lee *!* Clagett
2024-08-26 15:29:06 -04:00
parent baaa19f2f4
commit dff93153eb
20 changed files with 1033 additions and 292 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
BSD 3-Clause License
Copyright (c) 2024, Lee *!* Clagett
Copyright (c) 2024, The Monero Project
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
+19 -13
View File
@@ -425,22 +425,28 @@ struct WalletListener
*/
struct Wallet
{
enum class Device {
Software = 0,
Ledger = 1,
Trezor = 2
enum Device {
Device_Software = 0,
Device_Ledger = 1,
Device_Trezor = 2
};
enum class Status {
Ok,
Error,
Critical
enum Status {
Status_Ok,
Status_Error,
Status_Critical
};
enum class ConnectionStatus {
Disconnected,
Connected,
WrongVersion
enum ConnectionStatus {
ConnectionStatus_Disconnected,
ConnectionStatus_Connected,
ConnectionStatus_WrongVersion
};
enum BackgroundSyncType {
BackgroundSync_Off = 0,
BackgroundSync_ReusePassword = 1,
BackgroundSync_CustomPassword = 2
};
virtual ~Wallet() = 0;
@@ -938,7 +944,7 @@ struct Wallet
virtual std::shared_ptr<AddressBook> addressBook() = 0;
virtual std::shared_ptr<Subaddress> subaddress() = 0;
virtual std::shared_ptr<SubaddressAccount> subaddressAccount() = 0;
virtual void setListener(WalletListener *) = 0;
virtual void setListener(std::shared_ptr<WalletListener>) = 0;
/*!
* \brief defaultMixin - returns number of mixins used in transactions
* \return
+20 -2
View File
@@ -29,8 +29,26 @@
include_directories(.)
# For both the server and admin utility.
set(monero-lwsf_sources backend.cpp rpc.cpp transaction_history.cpp transaction_info.cpp wallet.cpp wallet_manager.cpp)
set(monero-lwsf_headers backend.h lwsf_config.h rpc.h transaction_history.h transaction_info.h wallet.h)
set(monero-lwsf_sources
backend.cpp
error.cpp
pending_transaction.cpp
rpc.cpp
transaction_history.cpp
transaction_info.cpp
wallet.cpp
wallet_manager.cpp
)
set(monero-lwsf_headers
backend.h
error.h
lwsf_config.h
pending_transaction.h
rpc.h
transaction_history.h
transaction_info.h
wallet.h
)
add_executable(monero-lwsf dummy_main.cpp ${monero-lwsf_sources} ${monero-lwsf_headers})
target_link_libraries(monero-lwsf
+207 -96
View File
@@ -31,8 +31,10 @@
#include <boost/thread/lock_guard.hpp>
#include "cryptonote_basic/cryptonote_basic_impl.h" // monero/src
#include "lwsf_config.h"
#include "ringct/rctOps.h"
#include "wire.h"
#include "wire/adapted/crypto.h"
#include "wire/adapted/pair.h"
#include "wire/msgpack.h"
#include "wire/wrapper/trusted_array.h"
@@ -81,24 +83,14 @@ namespace lwsf { namespace internal { namespace backend
WIRE_FIELD(amount),
WIRE_FIELD(recipient),
WIRE_FIELD(index),
WIRE_FIELD(output_pub),
WIRE_FIELD(rct_mask)
WIRE_OPTIONAL_FIELD(rct_mask),
WIRE_FIELD(tx_pub)
);
}
}
static void read_bytes(wire::reader& source, std::pair<crypto::public_key, transfer_in>& dest)
{
read_bytes(source, dest.second);
dest.first = dest.second.output_pub;
}
static void write_bytes(wire::writer& dest, const std::pair<const crypto::public_key, transfer_in>& source)
{
write_bytes(dest, source.second);
}
namespace
{
} // anonymous
namespace
{
template<typename F, typename T>
void map_transaction(F& format, T& self)
{
@@ -113,27 +105,29 @@ namespace
WIRE_OPTIONAL_FIELD(height),
WIRE_FIELD(unlock_time),
WIRE_FIELD(direction),
WIRE_OPTIONAL_FIELD(tx_secret),
WIRE_FIELD(id),
WIRE_FIELD(prefix),
WIRE_FIELD(coinbase)
);
}
}
} // anonymous
static void read_bytes(wire::reader& source, std::pair<crypto::hash, std::shared_ptr<transaction>>& dest)
{
if (!dest.second)
dest.second = std::make_shared<transaction>(transaction{});
read_bytes(source, *dest.second);
dest.first = dest.second->id;
}
static void write_bytes(wire::writer& dest, const std::pair<const crypto::hash, std::shared_ptr<transaction>>& source)
{
if (!source.second)
WIRE_DLOG_THROW(wire::error::schema::object, "Unexpected nullptr");
write_bytes(dest, *source.second);
}
namespace {
static void read_bytes(wire::reader& source, std::pair<crypto::hash, std::shared_ptr<transaction>>& dest)
{
if (!dest.second)
dest.second = std::make_shared<transaction>();
read_bytes(source, *dest.second);
dest.first = dest.second->id;
}
static void write_bytes(wire::writer& dest, const std::pair<crypto::hash, std::shared_ptr<transaction>>& source)
{
if (!source.second)
WIRE_DLOG_THROW(wire::error::schema::object, "Unexpected nullptr");
write_bytes(dest, *source.second);
}
namespace
{
template<typename F, typename T>
void map_keypair(F& format, T& self)
{
@@ -148,12 +142,64 @@ namespace {
wire::field("txes", wire::trusted_array(std::ref(self.txes))),
WIRE_FIELD(type),
WIRE_FIELD(view),
WIRE_FIELD(spend)
WIRE_FIELD(spend),
WIRE_FIELD(generated_locally)
);
}
void new_tx(const account& self, transaction& out, const rpc::transaction& source)
rct::key get_mask(const crypto::secret_key& view_key, const rpc::output& source)
{
crypto::key_derivation derived{};
if (!crypto::generate_key_derivation(source.tx_pub_key, view_key, derived))
throw std::runtime_error{"generate_key_derivation failure"};
crypto::secret_key scalar{};
crypto::derivation_to_scalar(derived, source.index, scalar);
rct::ecdhTuple commitment{source.rct.mask};
rct::ecdhDecode(commitment, rct::sk2rct(scalar), source.rct.type == rpc::ringct::format::recompute);
return commitment.mask;
}
void update_output(transfer_in& out, const rpc::output& source, const crypto::secret_key& view_key)
{
out.global_index = std::uint64_t(source.global_index);
out.amount = std::uint64_t(source.amount);
out.recipient = source.recipient.value_or(rpc::address_meta{});
out.index = source.index;
out.tx_pub = source.tx_pub_key;
switch (source.rct.type)
{
default:
throw std::runtime_error{"Unexpected ringct mask type"};
case rpc::ringct::format::none:
out.rct_mask = std::nullopt;
break;
case rpc::ringct::format::encrypted:
case rpc::ringct::format::recompute:
out.rct_mask = get_mask(view_key, source);
break;
case rpc::ringct::format::unencrypted:
out.rct_mask = source.rct.mask;
break;
}
}
void update_spend(transfer_out& out, const rpc::transaction_spend& source)
{
out.amount = std::uint64_t(source.amount);
out.sender = source.sender.value_or(rpc::address_meta{});
out.tx_pub = source.tx_pub_key;
}
void update_tx(const account& self, transaction& out, const rpc::transaction& source)
{
/* Let `receives` re-populate in `merge_output`. This works because the
server supplies all info - there is no local info to keep. */
out.receives.clear();
out.id = source.tx_hash;
out.timestamp = source.timestamp;
out.fee = std::uint64_t(source.fee.value_or(rpc::uint64_string(0)));
@@ -178,7 +224,9 @@ namespace {
crypto::generate_key_image(output_pub, output_secret, image);
if (image == spend.key_image)
{
out.spends.emplace_back(std::uint64_t(spend.amount), spend.tx_pub_key);
/* Frontend will typically know about spend before backend. So only
merge and never erase spends. */
update_spend(out.spends.try_emplace(image).first->second, spend);
total_spent += std::uint64_t(spend.amount);
}
}
@@ -195,46 +243,95 @@ namespace {
}
}
void merge_output(std::shared_ptr<transaction>& out, const rpc::output& output)
void merge_output(const std::shared_ptr<transaction>& out, const rpc::output& source, const crypto::secret_key& view_key)
{
if (!out)
throw std::logic_error{"nullptr transaction in merge_output"};
out->prefix = output.tx_prefix_hash;
const auto receive = out->receives.lower_bound(output.public_key);
if (receive == out->receives.end() || receive->first != output.public_key)
{
out->receives.emplace_hint(receive, output.public_key, transfer_in{output});
}
out->prefix = source.tx_prefix_hash;
update_output(out->receives.try_emplace(source.public_key).first->second, source, view_key);
}
bool 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)
{
bool updated_wallet = false;
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;
self.primary.txes.reserve(source.transactions.size());
for (const auto& tx : source.transactions)
/* 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. */
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())
);
/* The frontend will know about the spend first, iff the frontend was
used to perform the spend. We copy _all_ transactions that have a
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)
{
auto iter = self.primary.txes.lower_bound(tx.tx_hash);
if (iter == self.primary.txes.end() || iter->first != tx.tx_hash)
if (tx.second)
{
updated_wallet = true;
new_tx(self.primary, *self.primary.txes.emplace_hint(iter, tx.tx_hash, std::make_shared<transaction>(transaction{}))->second, tx);
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)
);
break; // inner loop
}
}
}
}
return updated_wallet;
for (const auto& tx : source.transactions)
{
auto inserted = self.primary.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;
}
if (existing != existing_txes.end())
out.push_back(inserted.first->second);
}
update_tx(self.primary, *inserted.first->second, tx);
}
return out;
}
void merge_response(account& self, const rpc::get_unspent_outs& source)
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)
{
auto iter = self.txes.find(output.tx_hash);
if (iter != self.txes.end())
merge_output(iter->second, output);
auto iter = self.primary.txes.find(output.tx_hash);
if (iter != self.primary.txes.end())
merge_output(iter->second, output, self.primary.view.sec);
}
}
} // anonymous
@@ -253,39 +350,21 @@ namespace {
void write_bytes(wire::writer& dest, const account& source)
{ map_account(dest, source); }
transfer_in::transfer_in()
: global_index(0),
amount(0),
recipient(),
index(0),
output_pub{},
rct_mask{}
{}
transfer_in::transfer_in(const rpc::output& source)
: global_index(std::uint64_t(source.global_index)),
amount(std::uint64_t(source.amount)),
recipient(source.recipient.value_or(rpc::address_meta{})),
index(source.index),
output_pub(source.public_key),
rct_mask(source.rct.mask)
{}
wallet::wallet(const bool generated_locally)
: client(),
wallet::wallet()
: listener(nullptr),
client(),
primary{},
status(),
last_sync(0),
scan_height(0),
blockchain_height(0),
sync(),
generated_locally(generated_locally)
sync()
{}
expect<epee::byte_slice> wallet::to_bytes() const
{
epee::byte_stream dest{};
dest.reserve(config::initial_buffer_size);
const boost::lock_guard<boost::mutex> lock{sync};
const std::error_code error = wire::msgpack::to_bytes(dest, primary);
if (error)
@@ -293,16 +372,13 @@ namespace {
return epee::byte_slice{std::move(dest)};
}
expect<void> wallet::from_bytes(epee::byte_slice source)
std::error_code wallet::from_bytes(epee::byte_slice source)
{
const boost::lock_guard<boost::mutex> lock{sync};
const std::error_code error = wire::msgpack::from_bytes(std::move(source), primary);
if (error)
return error;
return success();
return wire::msgpack::from_bytes(std::move(source), primary);
}
void wallet::refresh(const bool mandatory)
std::error_code wallet::refresh(const bool mandatory)
{
if (!mandatory)
{
@@ -311,7 +387,7 @@ namespace {
std::chrono::steady_clock::time_point::duration{last_sync.load()}
};
if (now - start < config::refresh_interval)
return;
return {};
}
expect<rpc::get_unspent_outs> outs_response{common_error::kInvalidArgument};
@@ -320,21 +396,56 @@ namespace {
if (txs_response)
outs_response = rpc::invoke<rpc::get_unspent_outs>(client, login);
const boost::lock_guard<boost::mutex> lock{sync};
boost::unique_lock<boost::mutex> lock{sync};
if (!txs_response)
{
status = txs_response.error();
return;
}
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);
last_sync = std::chrono::steady_clock::now().time_since_epoch().count();
if (!listener)
return {};
// 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);
lock.unlock();
listener->refreshed();
if (!new_txes.empty())
listener->updated();
for (std::uint64_t i = orig_scan_height; i < new_scan_height; ++i)
listener->newBlock(i);
for (const auto& tx : new_txes)
{
status = outs_response.error();
return;
const auto txid = epee::string_tools::pod_to_hex(tx->id);
if (tx->direction == TransactionInfo::Direction::In)
{
if (tx->height)
listener->moneyReceived(txid, tx->amount);
else
listener->unconfirmedMoneyReceived(txid, tx->amount);
}
else
listener->moneySpent(txid, tx->amount);
}
status = std::error_code{};
merge_response(*this, *txs_response);
merge_response(primary, *outs_response);
last_sync = std::chrono::steady_clock::now().time_since_epoch().count();
return {};
}
std::error_code wallet::send_tx(epee::byte_slice tx_bytes)
{
const rpc::submit_raw_tx_request request{std::move(tx_bytes)};
auto response = rpc::invoke<rpc::submit_raw_tx_response>(client, request);
if (!response)
return response.error();
return {};
}
}}} // lwsf // internal // backend
+51 -21
View File
@@ -35,11 +35,13 @@
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <memory>
#include <optional>
#include <type_traits>
#include <variant>
#include <vector>
#include "byte_slice.h" // monero/contrib/epee/include
#include "common/expect.h" // moneor/src
#include "crypto/crypto.h" // monero/src
#include "crypto/hash.h" // monero/src
@@ -70,11 +72,7 @@ namespace lwsf { namespace internal { namespace backend
crypto::public_key tx_pub;
transfer_out()
: address(), amount(0), sender(), secret(), tx_pub{}
{}
explicit transfer_out(const std::uint64_t amount, const crypto::public_key& tx_pub)
: address(), amount(amount), sender(), secret(), tx_pub(tx_pub)
: address(), amount(0), sender{}, secret(), tx_pub{}
{}
};
WIRE_DECLARE_OBJECT(transfer_out);
@@ -85,19 +83,49 @@ namespace lwsf { namespace internal { namespace backend
std::uint64_t amount;
rpc::address_meta recipient;
std::uint16_t index;
crypto::public_key output_pub;
rct::key rct_mask;
std::optional<rct::key> rct_mask;
crypto::public_key tx_pub;
transfer_in();
explicit transfer_in(const rpc::output& source);
transfer_in()
: global_index(0),
amount(0),
recipient{},
index(0),
rct_mask(),
tx_pub{}
{}
};
WIRE_DECLARE_OBJECT(transfer_in);
struct transaction
{
transaction() = delete;
std::vector<transfer_out> spends;
std::map<crypto::public_key, transfer_in, memory> receives;
transaction()
: spends(),
receives(),
description(),
label(),
timestamp(),
amount(0),
fee(0),
height(),
unlock_time(0),
direction(TransactionInfo::Direction::Out),
payment_id(),
id{},
prefix{},
coinbase(false)
{}
transaction(transaction&&) = default;
transaction(const transaction&) = default;
transaction& operator=(transaction&&) = default;
transaction& operator=(const transaction&) = default;
/*! flat_map is used here for faster copies/merging. A single allocation
is needed in the copy (done every refresh interval), instead of an
allocation per key. */
boost::container::flat_map<crypto::key_image, transfer_out, memory> spends;
boost::container::flat_map<crypto::public_key, transfer_in, memory> receives; //!< Key is output pub
std::string description;
std::string label;
std::optional<std::time_t> timestamp;
@@ -107,8 +135,6 @@ namespace lwsf { namespace internal { namespace backend
std::uint64_t unlock_time;
TransactionInfo::Direction direction;
std::variant<std::nullptr_t, crypto::hash8, crypto::hash> payment_id;
std::optional<crypto::secret_key> tx_secret;
crypto::public_key pub;
crypto::hash id;
crypto::hash prefix;
bool coinbase;
@@ -132,26 +158,30 @@ namespace lwsf { namespace internal { namespace backend
NetworkType type;
keypair view;
keypair spend;
bool generated_locally;
};
WIRE_DECLARE_OBJECT(account);
struct wallet
{
std::shared_ptr<WalletListener> listener;
rpc::http_client client;
account primary;
std::error_code status;
std::atomic<std::chrono::steady_clock::time_point::rep> last_sync;
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;
mutable boost::mutex sync;
const bool generated_locally;
explicit wallet(bool generated_locally);
wallet();
expect<epee::byte_slice> to_bytes() const;
expect<void> from_bytes(epee::byte_slice source);
std::error_code from_bytes(epee::byte_slice source);
bool login() const;
void refresh(bool mandatory = false);
std::error_code refresh(bool mandatory = false);
std::error_code send_tx(epee::byte_slice tx_bytes);
};
}}} // lwsf // internal // backend
+73 -12
View File
@@ -33,8 +33,10 @@
#include <boost/spirit/include/qi_parse_attr.hpp>
#include <boost/spirit/include/qi_sequence.hpp>
#include <ctime>
#include "hex.h" // monero/contrib/epee/include
#include "lwsf_config.h"
#include "net/http_client.h" // monero/contrib/epee/include
#include "ringct/rctOps.h" // monero/contrib/epee/include
#include "wire.h"
#include "wire/adapted/crypto.h"
#include "wire/field.h"
@@ -71,14 +73,14 @@ namespace lwsf { namespace internal { namespace rpc
struct category final : std::error_category
{
virtual const char* name() const noexcept override final
{
return "lwsf::internal::rpc::error_category()";
}
{
return "lwsf::internal::rpc::error_category()";
}
virtual std::string message(int value) const override final
{
return get_string(error(value));
}
virtual std::string message(int value) const override final
{
return get_string(error(value));
}
};
static const category instance{};
return instance;
@@ -95,7 +97,7 @@ namespace lwsf { namespace internal { namespace rpc
return {error::invoke_failure};
if (!response)
return {error::invoke_failure};
if(response->m_response_code != 200)
if(response->m_response_code != 200 && response->m_response_code != 201)
return {error::wrong_response_code};
return response->m_body;
@@ -149,7 +151,7 @@ namespace lwsf { namespace internal { namespace rpc
using min_spent = wire::min_element_sizeof<crypto::key_image>;
std::optional<std::string> timestamp;
std::optional<std::vector<std::uint8_t>> payment_id;
std::optional<epee::byte_slice> payment_id;
wire::object(source,
WIRE_FIELD_ARRAY(spent_outputs, min_spent),
wire::optional_field("payment_id", std::ref(payment_id)),
@@ -159,10 +161,10 @@ namespace lwsf { namespace internal { namespace rpc
WIRE_FIELD(unlock_time),
WIRE_OPTIONAL_FIELD(height),
WIRE_FIELD(tx_hash),
WIRE_FIELD(is_coinbase)
WIRE_FIELD(is_coinbase),
WIRE_FIELD(mempool)
);
if (payment_id && !payment_id->empty())
{
if (payment_id->size() == sizeof(crypto::hash8))
@@ -207,19 +209,67 @@ namespace lwsf { namespace internal { namespace rpc
);
}
namespace
{
// This field is a mess between implementations, this cleans it up a bit
struct convert_rct
{
ringct operator()(const std::nullptr_t&) const noexcept
{
return ringct{{}, ringct::format::none};
}
ringct operator()(const std::string& source) const
{
struct ringct_triplet
{
ringct_triplet() = delete;
rct::key commitment;
rct::key mask;
rct::key amount;
};
static_assert(sizeof(ringct_triplet) == 96);
if (source.empty())
return ringct{{}, ringct::format::none};
rct::key out{};
if (source == "coinbase") // non-standard, openmonero
return ringct{rct::identity(), ringct::format::unencrypted};
else if (source.size() == sizeof(ringct_triplet) * 2)
{
ringct_triplet rct{};
if (!epee::from_hex::to_buffer(epee::as_mut_byte_span(rct), source))
WIRE_DLOG_THROW(wire::error::schema::binary, "Invalid hex for ringct");
return ringct{rct.mask, ringct::format::encrypted};
}
return ringct{{}, ringct::format::recompute};
}
template<typename T>
ringct operator()(const T&) const
{
WIRE_DLOG_THROW(wire::error::schema::string, "Expected string or null");
}
};
}
void read_bytes(wire::json_reader& source, output& self)
{
wire::basic_value raw_rct{};
wire::object(source,
WIRE_FIELD(amount),
WIRE_FIELD(index),
WIRE_FIELD(global_index),
WIRE_OPTIONAL_FIELD(recipient),
WIRE_FIELD(rct),
wire::optional_field("rct", std::ref(raw_rct)),
WIRE_FIELD(tx_hash),
WIRE_FIELD(tx_prefix_hash),
WIRE_FIELD(public_key),
WIRE_FIELD(tx_pub_key)
);
self.rct = std::visit(convert_rct{}, raw_rct.value);
}
void read_bytes(wire::json_reader& source, get_unspent_outs& self)
@@ -233,5 +283,16 @@ namespace lwsf { namespace internal { namespace rpc
WIRE_FIELD(fee_mask)
);
}
void write_bytes(wire::json_writer& dest, const submit_raw_tx_request& self)
{
wire::object(dest, WIRE_FIELD(tx));
}
void read_bytes(wire::json_reader& source, submit_raw_tx_response& self)
{
wire::object(source, WIRE_FIELD(status));
}
}}} // lwsf // internal // rpc
+37 -9
View File
@@ -30,17 +30,17 @@
#include <boost/variant.hpp>
#include <ctime>
#include <optional>
#include <system_error>
#include "byte_slice.h" // monero/contrib/epee/include
#include "byte_stream.h" // moneor/contrib/epee/include
#include "common/expect.h" // monero/src
#include "crypto/crypto.h" // monero/src
#include "ringct/rctTypes.h" // monero/src
#include "wire/basic_value.h"
#include "wire/fwd.h"
#include "wire/json.h"
// Contains code for interact
namespace epee { namespace net_utils
{
class blocked_mode_client;
@@ -63,7 +63,7 @@ namespace lwsf { namespace internal { namespace rpc
inline std::error_code make_error_code(const error value) noexcept
{
return std::error_code{int(value), error_category()};
}
}
//! Send `payload` to `client` at uri `endpoint`, and \return payload response
@@ -84,7 +84,7 @@ namespace lwsf { namespace internal { namespace rpc
F out{};
error = wire::json::from_bytes(std::move(*result), out);
if (error)
return out;
return error;
return out;
}
@@ -123,9 +123,10 @@ namespace lwsf { namespace internal { namespace rpc
std::optional<bool> generated_locally;
};
inline void read_bytes(const wire::json_reader&, const login_response&)
inline void read_bytes(const wire::json_reader&, const login_response&) noexcept
{}
struct daemon_status
{
daemon_status() = delete;
@@ -181,6 +182,7 @@ namespace lwsf { namespace internal { namespace rpc
std::optional<std::uint64_t> height;
crypto::hash tx_hash;
bool is_coinbase;
bool mempool;
transaction()
: spent_outputs(),
@@ -191,7 +193,8 @@ namespace lwsf { namespace internal { namespace rpc
unlock_time(0),
height(),
tx_hash{},
is_coinbase(false)
is_coinbase(false),
mempool(false)
{}
};
void read_bytes(wire::json_reader&, transaction&);
@@ -212,11 +215,18 @@ namespace lwsf { namespace internal { namespace rpc
struct ringct
{
ringct() = delete;
rct::key commitment;
enum class format : std::uint8_t
{
none = 0, //!< Not ringct
encrypted,
recompute,
unencrypted
};
rct::key mask;
rct::key amount;
format type;
};
static_assert(sizeof(ringct) == 96);
struct output
{
@@ -254,6 +264,24 @@ namespace lwsf { namespace internal { namespace rpc
std::uint64_t fee_mask;
};
void read_bytes(wire::json_reader&, get_unspent_outs&);
struct submit_raw_tx_request
{
submit_raw_tx_request() = delete;
epee::byte_slice tx;
};
void write_bytes(wire::json_writer&, const submit_raw_tx_request&);
struct submit_raw_tx_response
{
submit_raw_tx_response() = delete;
static constexpr const char* endpoint() noexcept { return "/submit_raw_tx"; }
std::string status;
};
void read_bytes(wire::json_reader&, submit_raw_tx_response&);
}}} // lwsf // internal // rpc
WIRE_DECLARE_BLOB_NS(lwsf::internal::rpc::ringct);
+4 -6
View File
@@ -34,7 +34,7 @@
#include "transaction_info.h"
#include "string_tools.h" // monero/contrib/epee/include
#include "hex.h" // monero/contrib/epee/include
namespace lwsf { namespace internal
{
@@ -59,17 +59,15 @@ namespace lwsf { namespace internal
std::shared_ptr<TransactionInfo> transaction_history::transaction(int index) const
{
const boost::lock_guard<boost::mutex> lock{data_->sync};
if (std::numeric_limits<int>::max() < data_->primary.txes.size())
throw std::runtime_error{"Exceeded max int size in transaction_history:transaction"};
if (data_->primary.txes.size() < index || index < 0)
if (index < 0 || data_->primary.txes.size() < unsigned(index))
throw std::runtime_error{"index provided to transaction invalid"};
return std::make_shared<transaction_info>(data_, data_->primary.txes.nth(index)->second);
return std::make_shared<transaction_info>(data_, data_->primary.txes.nth(unsigned(index))->second);
}
std::shared_ptr<TransactionInfo> transaction_history::transaction(const std::string &id) const
{
crypto::hash binary_id{};
if (!epee::string_tools::hex_to_pod(id, binary_id))
if (!epee::from_hex::to_buffer(epee::as_mut_byte_span(binary_id), id))
throw std::runtime_error{"transaction_history given invalid hex id"};
const boost::lock_guard<boost::mutex> lock{data_->sync};
+10 -3
View File
@@ -54,17 +54,24 @@ namespace lwsf { namespace internal
transfers_.reserve(data_->spends.size());
for (const auto& spend : data_->spends)
transfers_.emplace_back(spend.amount, spend.address);
transfers_.emplace_back(spend.second.amount, spend.second.address);
}
transaction_info::~transaction_info()
{}
std::string transaction_info::description() const
{
const boost::lock_guard<boost::mutex> lock{wallet_->sync};
return data_->description;
}
uint64_t transaction_info::confirmations() const
{
const auto block_height = blockHeight();
if (!data_->height)
return 0;
const boost::lock_guard<boost::mutex> lock{wallet_->sync};
return std::max(wallet_->blockchain_height, block_height) - block_height;
return std::max(wallet_->blockchain_height, *data_->height) - *data_->height + 1;
}
std::string transaction_info::hash() const
+2 -2
View File
@@ -55,13 +55,13 @@ namespace lwsf { namespace internal
transaction_info& operator=(transaction_info&&) = delete;
virtual Direction direction() const override { return data_->direction; }
virtual bool isPending() const override { return confirmations() == 0; }
virtual bool isPending() const override { return !data_->height; }
virtual bool isFailed() const override { return false; }
virtual bool isCoinbase() const override { return data_->coinbase; }
virtual uint64_t amount() const override { return data_->amount; }
virtual uint64_t fee() const override { return data_->fee; }
virtual uint64_t blockHeight() const override { return data_->height.value_or(0); }
virtual std::string description() const override { return data_->description; }
virtual std::string description() const override;
virtual std::set<uint32_t> subaddrIndex() const override { return {}; };
virtual uint32_t subaddrAccount() const override { return 0; }
virtual std::string label() const override { return data_->label; }
+429 -24
View File
@@ -28,16 +28,30 @@
#include "wallet.h"
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
#include <fcntl.h>
#endif
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <sodium/core.h>
#include <sodium/crypto_pwhash_argon2id.h>
#include <sodium/randombytes.h>
#include <sodium/crypto_aead_chacha20poly1305.h>
#include "backend.h"
#include "error.h"
#include "hex.h" // monero/contrib/epee/include
#include "lwsf_config.h"
#include "net/parse.h" // monero/src
#include "net/socks_connect.h" // monero/src
#include "transaction_history.h"
#include "wire.h"
#include "wire/msgpack.h"
namespace lwsf { namespace internal
{
namespace
{
struct null_connector
@@ -48,29 +62,369 @@ namespace lwsf { namespace internal
throw std::runtime_error{"Unable to connect"};
}
};
struct encrypted_file
{
std::string cipher; //!< Name of cipher+authentication used
std::string pwhasher; //!< Name of pwhasher used
epee::byte_slice salt; //!< Pwhashing salt
epee::byte_slice nonce; //!< Encryption iv/nonce
epee::byte_slice epayload; //!< Encrypted contents
std::uint64_t iterations; //!< Pwhashing iterations
std::uint32_t memory; //!< Pwhashing memory
encrypted_file() = delete;
//! \pre `sodium_init()` has been called
static epee::byte_slice get_random(const std::size_t length)
{
epee::byte_stream out;
out.put_n(0, length);
randombytes_buf(out.data(), length);
return epee::byte_slice{std::move(out)};
}
static constexpr const char* pwhasher_name() noexcept
{
return "argon2id";
}
static constexpr int pwhash_algorithm() noexcept
{
return crypto_pwhash_argon2id_ALG_ARGON2ID13;
}
static constexpr std::size_t salt_size() noexcept
{
return crypto_pwhash_argon2id_SALTBYTES;
}
static constexpr std::uint64_t ops_min() noexcept { return 5; }
static constexpr std::size_t memory_limit() noexcept { return 7 * 1024 * 1024; }
static epee::byte_slice get_salt() { return get_random(salt_size()); }
static constexpr const char* cipher_name() noexcept
{
return "chacha20-poly1305_ietf";
}
static constexpr unsigned long long max_size() noexcept
{
return crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX;
}
static constexpr std::size_t key_size() noexcept
{
return crypto_aead_chacha20poly1305_ietf_KEYBYTES;
}
static constexpr std::size_t tag_size() noexcept
{
return crypto_aead_chacha20poly1305_ietf_ABYTES;
}
static constexpr std::size_t nonce_size() noexcept
{
return crypto_aead_chacha20poly1305_ietf_NPUBBYTES;
}
static epee::byte_slice get_nonce() { return get_random(nonce_size()); }
//! \pre `sodium_init()` has been called
std::array<std::uint8_t, 32> get_key(const std::string& password) const
{
std::array<std::uint8_t, key_size()> key{{}};
if (crypto_pwhash_argon2id(
key.data(), key.size(),
password.data(), password.size(),
salt.data(),
iterations, memory, pwhash_algorithm()) != 0)
{
throw std::runtime_error{std::string{pwhasher_name()} + " failed"};
}
return key;
}
static encrypted_file
make(const epee::byte_slice& upayload, const std::uint64_t iterations, const std::string& password)
{
if (sodium_init() < 0)
throw std::runtime_error{"Failed to initialize libsodium"};
if (max_size() < upayload.size())
throw std::runtime_error{std::string{"Exceeded max size for "} + cipher_name()};
if (tag_size() < std::numeric_limits<std::size_t>::max() - upayload.size())
throw std::runtime_error{"Exceeded max size_t after authentication tag"};
encrypted_file out{
cipher_name(),
pwhasher_name(),
get_salt(),
get_nonce(),
nullptr,
std::max(iterations, ops_min()),
memory_limit()
};
const auto key = out.get_key(password);
epee::byte_stream buffer;
buffer.put_n(0, upayload.size() + tag_size());
unsigned long long out_bytes = buffer.size();
if (crypto_aead_chacha20poly1305_ietf_encrypt(
buffer.data(), std::addressof(out_bytes),
upayload.data(), upayload.size(),
nullptr, 0,
nullptr,
out.nonce.data(), key.data()) != 0)
{
throw std::runtime_error{std::string{cipher_name()} + " encryption failed"};
}
out.epayload = epee::byte_slice{std::move(buffer)}.take_slice(out_bytes);
return out;
}
//! \return Unencrypted payload, or `nullptr`.
epee::byte_slice get_payload(const std::string& password) const
{
if (nonce.size() != nonce_size())
return nullptr;
if (salt.size() != salt_size())
return nullptr;
if (cipher != cipher_name())
return nullptr;
if (pwhasher != pwhasher_name())
return nullptr;
if (max_size() < epayload.size())
return nullptr;
if (sodium_init() < 0)
throw std::runtime_error{"Failed to initialize libsodium"};
const auto key = get_key(password);
epee::byte_stream buffer;
buffer.put_n(0, epayload.size());
static_assert(
std::numeric_limits<std::size_t>::max() <= std::numeric_limits<unsigned long long>::max()
);
unsigned long long out_bytes = buffer.size();
if (crypto_aead_chacha20poly1305_ietf_decrypt(
buffer.data(), std::addressof(out_bytes),
nullptr,
epayload.data(), epayload.size(),
nullptr, 0,
nonce.data(), key.data()) == 0)
{
throw std::runtime_error{std::string{cipher_name()} + " decryption failed"};
}
return epee::byte_slice{std::move(buffer)}.take_slice(out_bytes);
}
};
template<typename F, typename T>
void map_encrypted_file(F& format, T& self)
{
wire::object(format,
WIRE_FIELD(cipher),
WIRE_FIELD(pwhasher),
WIRE_FIELD(salt),
WIRE_FIELD(nonce),
WIRE_FIELD(epayload),
WIRE_FIELD(iterations),
WIRE_FIELD(memory)
);
}
WIRE_DEFINE_OBJECT(encrypted_file, map_encrypted_file);
epee::byte_slice try_load(const std::string& filename)
{
std::ifstream file{filename, std::ios::binary | std::ios::ate};
if (!file.is_open())
return nullptr;
const auto size = file.tellg();
if (size < 0)
return nullptr;
epee::byte_stream buffer;
buffer.put_n(0, std::size_t(size));
file.seekg(0);
file.read(reinterpret_cast<char*>(buffer.data()), buffer.size());
if (file.good())
return epee::byte_slice{std::move(buffer)};
return nullptr;
}
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
bool atomic_file_write(const std::filesystem::path& filename, const std::filesystem::path& directory, epee::byte_slice contents) noexcept
{
const int fd =
open(filename.c_str(), O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
if (fd < 0)
return false;
bool rc = false;
if (0 <= write(fd, contents.data(), contents.size()) && 0 <= fsync(fd))
{
const int dir = open(directory.c_str(), O_RDONLY);
if (0 <= dir)
{
if (0 <= fsync(dir))
rc = true;
close(dir);
}
}
close(fd);
return rc;
}
#else
#error atomic_file_write not implemented for this platform
#endif
} // anonymous
void wallet::set_error(const std::error_code status)
{
const boost::lock_guard<boost::mutex> lock{sync_};
status_ = status;
exception_error_.clear();
}
wallet::wallet(std::string filename, std::shared_ptr<backend::wallet> data)
: data_(std::move(data)), filename_(std::move(filename)), thread_()
void wallet::set_critical(const std::exception& e)
{
const boost::lock_guard<boost::mutex> lock{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)),
filename_(std::move(filename)),
password_(std::move(password)),
exception_error_(),
status_(),
thread_(),
iterations_(kdf_rounds),
sync_()
{
if (!data_)
throw std::logic_error{"Unepected nullptr in internal::wallet"};
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;
data_->primary.spend.sec = keys.m_spend_secret_key;
data_->primary.spend.pub = keys.m_account_address.m_spend_public_key;
data_->primary.generated_locally = true;
data_->primary.type = nettype;
store("");
}
wallet::wallet(open_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)),
filename_(std::move(filename)),
password_(std::move(password)),
exception_error_(),
status_(),
thread_(),
iterations_(kdf_rounds),
sync_()
{
if (!data_)
throw std::logic_error{"Unexpected nullptr in internal::wallet"};
try
{
epee::byte_slice file = try_load(filename + ".new");
if (file.empty())
file = try_load(filename);
if (!file.empty())
{
encrypted_file contents{};
if (!(status_ = wire::msgpack::from_bytes(std::move(file), contents)))
{
epee::byte_slice payload = contents.get_payload(password_);
if (!payload.empty())
status_ = data_->from_bytes(std::move(payload));
else
status_ = error::crypto_failure;
}
}
else
status_ = error::read_failure;
}
catch (const std::system_error& e)
{
status_ = e.code();
}
catch (const std::exception& e)
{
status_.clear();
exception_error_ = e.what();
}
}
wallet::~wallet()
{}
void wallet::statusWithErrorString(int& status, std::string& errorString) const
int wallet::status() const
{
const boost::unique_lock<boost::mutex> lock{data_->sync};
status = data_->status.value();
if (status)
errorString = data_->status.message();
else
errorString.clear();
int out = 0;
std::string unused;
statusWithErrorString(out, unused);
return out;
}
NetworkType wallet::nettype() const { data_->primary.type; }
std::string wallet::errorString() const
{
int unused = 0;
std::string out;
statusWithErrorString(unused, out);
return out;
}
void wallet::statusWithErrorString(int& status, std::string& errorString) const
{
const boost::lock_guard<boost::mutex> lock{sync_};
if (!exception_error_.empty())
{
status = Status_Critical;
errorString = exception_error_;
}
else if (status_)
{
status = Status_Error;
errorString = status_.message();
}
else
{
status = Status_Ok;
errorString.clear();
}
}
bool wallet::setPassword(const std::string &password)
{
password_ = password;
return true;
}
NetworkType wallet::nettype() const { return data_->primary.type; }
std::string wallet::secretViewKey() const
{
@@ -94,22 +448,62 @@ namespace lwsf { namespace internal
bool wallet::store(const std::string& path)
{
expect<epee::byte_slice> bytes = data_->to_bytes();
if (!bytes)
return false;
const std::string& real_path = path.empty() ? filename_ : path;
//! \TODO Write, fsync, then atomic swap filenames
std::error_code status{};
try
{
expect<epee::byte_slice> payload = data_->to_bytes();
if (payload)
{
const auto contents = encrypted_file::make(*payload, iterations_, password_);
const auto payload_size = payload->size();
*payload = nullptr; // free up some memory that is no longer needed
std::ofstream file{path, std::ios::out | std::ios::binary};
file.write(reinterpret_cast<const char*>(bytes->data()), bytes->size());
file.flush();
return file.good();
epee::byte_stream buffer;
buffer.reserve(payload_size + 2048);
if (!(status = wire::msgpack::to_bytes(buffer, contents)))
{
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, status))
std::filesystem::remove(file, status);
std::filesystem::rename(new_file, file, status);
// blocks until file and directory contents are synced
if (atomic_file_write(new_file, directory, epee::byte_slice{std::move(buffer)}))
{
std::filesystem::remove(file, status);
std::filesystem::rename(new_file, file, status);
}
else
status = error::write_failure;
}
}
else
status = payload.error();
}
catch (const std::system_error& e)
{
status = e.code();
}
catch (const std::exception& e)
{
set_critical(e);
}
// this will clear any existing errors
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)
{
if (!light_wallet)
throw std::runtime_error{"Only light_wallets are supported with this instance"};
throw std::logic_error{"Only light_wallets are supported with this instance"};
const auto split = daemon_address.rfind(':');
if (!setProxy(proxy_address))
@@ -148,7 +542,7 @@ namespace lwsf { namespace internal
Wallet::ConnectionStatus wallet::connected() const
{
return data_->client.is_connected() ?
ConnectionStatus::Connected : ConnectionStatus::Disconnected;
ConnectionStatus_Connected : ConnectionStatus_Disconnected;
}
bool wallet::setProxy(const std::string &address)
@@ -157,8 +551,7 @@ namespace lwsf { namespace internal
if (!endpoint)
{
data_->client.set_connector(null_connector{});
const boost::lock_guard<boost::mutex> lock{data_->sync};
data_->status = endpoint.error();
set_error(endpoint.error());
return false;
}
data_->client.set_connector(net::socks::connector{std::move(*endpoint)});
@@ -177,9 +570,21 @@ namespace lwsf { namespace internal
uint64_t wallet::daemonBlockChainTargetHeight() const
{ return blockChainHeight(); }
uint64_t wallet::estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations,
PendingTransaction::Priority priority) const
{
throw std::runtime_error{"Not Implemented yet"};
}
std::shared_ptr<TransactionHistory> wallet::history()
{
return std::make_shared<transaction_history>(data_);
}
void wallet::setListener(std::shared_ptr<WalletListener> listener)
{
const boost::lock_guard<boost::mutex> lock{data_->sync};
data_->listener = std::move(listener);
}
}} // lwsf // internal
+29 -11
View File
@@ -30,7 +30,9 @@
#include "lws_frontend.h"
#include <boost/thread/mutex.hpp>
#include <boost/thread/thread.hpp>
#include <memory>
#include <string>
#include "cryptonote_basic/account.h" // monero/src
#include "net/http_client.h" // monero/contrib/epee/include
@@ -41,15 +43,28 @@ namespace internal
{
namespace backend { struct wallet; }
//! \TODO Mark final when completely implemented
class wallet : public lwsf::Wallet
{
const std::shared_ptr<backend::wallet> data_;
std::string filename_;
const std::string filename_;
std::string password_;
std::string exception_error_;
std::error_code status_;
boost::thread thread_;
public:
explicit wallet(std::string filename, std::shared_ptr<backend::wallet> data);
const std::uint64_t iterations_;
mutable boost::mutex sync_;
void set_error(const std::error_code status);
void set_critical(const std::exception& e);
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(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;
wallet(wallet&&) = delete;
@@ -62,10 +77,12 @@ public:
virtual std::string getSeedLanguage() const = 0;
virtual void setSeedLanguage(const std::string &arg) = 0;
virtual int status() const override;
virtual std::string errorString() const override;
virtual void statusWithErrorString(int& status, std::string& errorString) const override;
virtual bool setPassword(const std::string &password) = 0;
virtual const std::string& getPassword() const = 0;
virtual bool setPassword(const std::string &password) override;
virtual const std::string& getPassword() const override { return password_; }
virtual bool setDevicePin(const std::string &) override { return false; };
virtual bool setDevicePassphrase(const std::string &) override { return false; };
@@ -212,8 +229,8 @@ public:
* @return - true if connected
*/
virtual ConnectionStatus connected() const override;
virtual void setTrustedDaemon(bool arg) = 0;
virtual bool trustedDaemon() const = 0;
virtual void setTrustedDaemon(bool) override {}
virtual bool trustedDaemon() const override { return true; }
virtual bool setProxy(const std::string &address) override;
virtual uint64_t balance(uint32_t accountIndex = 0) const = 0;
uint64_t balanceAll() const {
@@ -499,7 +516,8 @@ public:
* \brief disposeTransaction - destroys transaction object
* \param t - pointer to the "PendingTransaction" object. Pointer is not valid after function returned;
*/
virtual void disposeTransaction(PendingTransaction * t) = 0;
virtual void disposeTransaction(PendingTransaction * t) override final
{}
/*!
* \brief Estimates transaction fee.
@@ -507,7 +525,7 @@ public:
* \return Estimated fee.
*/
virtual uint64_t estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations,
PendingTransaction::Priority priority) const = 0;
PendingTransaction::Priority priority) const override final;
/*!
* \brief exportKeyImages - exports key images to file
@@ -549,7 +567,7 @@ public:
virtual std::shared_ptr<AddressBook> addressBook() = 0;
virtual std::shared_ptr<Subaddress> subaddress() = 0;
virtual std::shared_ptr<SubaddressAccount> subaddressAccount() = 0;
virtual void setListener(WalletListener *) = 0;
virtual void setListener(std::shared_ptr<WalletListener>) override;
/*!
* \brief defaultMixin - returns number of mixins used in transactions
* \return
+30 -8
View File
@@ -28,11 +28,14 @@
#include "lws_frontend.h"
#include <boost/utility/string_ref.hpp>
#include <chrono>
#include <filesystem>
#include <memory>
#include <string>
#include <system_error>
#include "backend.h"
#include "common/dns_utils.h" // monero/src
#include "lwsf_config.h"
#include "net/http_client.h" // monero/contrib/epee/include
#include "net/parse.h" // monero/src
@@ -91,8 +94,9 @@ namespace lwsf
*/
std::unique_ptr<Wallet> createWallet(const std::string &path, const std::string &password, const std::string &language, NetworkType nettype, uint64_t kdf_rounds) override
{
//! \TODO Complete
return std::make_unique<wallet>(path, std::make_shared<backend::wallet>(true));
return std::make_unique<wallet>(
wallet::create_tag{}, nettype, path, password, kdf_rounds, std::make_shared<backend::wallet>()
);
}
/*!
@@ -107,7 +111,11 @@ namespace lwsf
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
{
//! \TODO Complete
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)
);
}
/*!
@@ -219,7 +227,7 @@ namespace lwsf
* \param wallet previously opened / created wallet instance
* \return None
*/
bool closeWallet(std::unique_ptr<Wallet> wallet, bool store) override
bool closeWallet(std::unique_ptr<Wallet> wallet, const bool store) override
{
if (wallet && store)
wallet->store(wallet->filename());
@@ -236,7 +244,7 @@ namespace lwsf
*/
bool walletExists(const std::string &path) override
{
//! \TODO Complete
return std::filesystem::exists(std::filesystem::path{path});
}
/*!
@@ -324,11 +332,25 @@ namespace lwsf
std::string resolveOpenAlias(const std::string &address, bool &dnssec_valid) const override
{
//! \TODO Complete
const boost::string_ref prefix{"oa1:xmr "};
std::string real_address =
tools::DNSResolver::instance().get_dns_format_from_oa_address(address);
bool dnssec_available = false;
std::vector<std::string> records =
tools::DNSResolver::instance().get_txt_record(real_address, dnssec_available, dnssec_valid);
for (boost::string_ref record : records)
{
if (record.starts_with(prefix))
{
record = record.substr(prefix.size());
return std::string{record.data(), record.size()};
}
}
}
//! checks for an update and returns version, hash and url
static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(
//! checks for an update and returns version, hash and url
static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(
const std::string &software,
std::string subdir,
const char *buildtag = nullptr,
+2 -2
View File
@@ -26,8 +26,8 @@
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set(monero-lws-wire_sources error.cpp read.cpp write.cpp)
set(monero-lws-wire_headers error.h field.h filters.h fwd.h json.h read.h traits.h vector.h write.h)
set(monero-lws-wire_sources basic_value.cpp error.cpp read.cpp write.cpp)
set(monero-lws-wire_headers basic_value.h error.h field.h filters.h fwd.h json.h read.h traits.h vector.h write.h)
add_library(monero-lws-wire ${monero-lws-wire_sources} ${monero-lws-wire_headers})
target_include_directories(monero-lws-wire PUBLIC "${LMDB_INCLUDE}")
+1
View File
@@ -62,6 +62,7 @@
namespace wire
{
struct basic_value;
class reader;
struct writer;
}
+105 -74
View File
@@ -25,17 +25,18 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "read.h"
#include "serialization/wire/json/read.h"
#include <algorithm>
#include <limits>
#include <rapidjson/memorystream.h>
#include <stdexcept>
#include "common/expect.h" // monero/src
#include "hex.h" // monero/contrib/epee/include
#include "wire/error.h"
#include "wire/json/error.h"
#include "byte_stream.h"
#include "hex.h"
#include "serialization/wire/basic_value.h"
#include "serialization/wire/error.h"
#include "serialization/wire/json/error.h"
namespace
{
@@ -50,8 +51,7 @@ namespace
//! \throw std::system_error by converting `code` into a std::error_code
[[noreturn]] void throw_json_error(const epee::span<const std::uint8_t> source, const rapidjson::Reader& reader, const wire::error::schema expected)
{
const std::size_t offset = std::min(source.size(), reader.GetErrorOffset());
const std::size_t start = offset;//std::max(snippet_size / 2, offset) - (snippet_size / 2);
const std::size_t start = std::min(source.size(), reader.GetErrorOffset());
const std::size_t end = start + std::min(snippet_size, source.size() - start);
const boost::string_ref text{reinterpret_cast<const char*>(source.data()) + start, end - start};
@@ -64,7 +64,7 @@ namespace
case rapidjson::kParseErrorTermination: // the handler returned false
break;
}
WIRE_DLOG_THROW(expected, "near '" << text << '\'');
WIRE_DLOG_THROW(expected, "near \"" << text << '"');
}
}
@@ -72,36 +72,37 @@ namespace wire
{
struct json_reader::rapidjson_sax
{
struct string_contents
{
const char* ptr;
std::size_t length;
};
union
{
bool boolean;
std::intmax_t integer;
std::uintmax_t unsigned_integer;
double number;
string_contents string;
} value;
error::schema expected_;
std::string* const string_;
const error::schema expected_;
error::schema actual_;
bool negative;
explicit rapidjson_sax(error::schema expected) noexcept
: expected_(expected), negative(false)
rapidjson_sax(error::schema expected) noexcept
: string_(nullptr), expected_(expected), actual_(error::schema::maximum_depth), negative(false)
{}
bool Null() const noexcept
explicit rapidjson_sax(std::string& string, error::schema expected) noexcept
: string_(std::addressof(string)), expected_(expected), actual_(error::schema::maximum_depth), negative(false)
{}
bool Null() noexcept
{
actual_ = error::schema::none;
return expected_ == error::schema::none;
}
bool Bool(bool i) noexcept
{
value.boolean = i;
actual_ = error::schema::boolean;
return expected_ == error::schema::boolean || expected_ == error::schema::none;
}
@@ -120,13 +121,14 @@ namespace wire
{
default:
return false;
case error::schema::none:
case error::schema::integer:
value.integer = i;
actual_ = error::schema::integer;
break;
case error::schema::number:
value.number = i;
break;
case error::schema::none:
actual_ = error::schema::number;
break;
}
return true;
@@ -137,13 +139,14 @@ namespace wire
{
default:
return false;
case error::schema::none:
case error::schema::integer:
value.unsigned_integer = i;
actual_ = error::schema::integer;
break;
case error::schema::number:
value.number = i;
break;
case error::schema::none:
actual_ = error::schema::number;
break;
}
return true;
@@ -152,17 +155,20 @@ namespace wire
bool Double(double i) noexcept
{
value.number = i;
actual_ = error::schema::number;
return expected_ == error::schema::number || expected_ == error::schema::none;
}
bool RawNumber(const char*, std::size_t, bool) const noexcept
static constexpr bool RawNumber(const char*, std::size_t, bool) noexcept
{
return false;
}
bool String(const char* str, std::size_t length, bool) noexcept
{
value.string = {str, length};
actual_ = error::schema::string;
if (string_)
string_->assign(str, length);
return expected_ == error::schema::string || expected_ == error::schema::none;
}
bool Key(const char* str, std::size_t length, bool)
@@ -215,20 +221,59 @@ namespace wire
read_next_value(accept_all);
}
json_reader::json_reader(std::string&& source)
: reader(nullptr),
source_(std::move(source)),
reader_()
std::size_t json_reader::do_start_array(std::size_t)
{
remaining_ = {reinterpret_cast<const std::uint8_t*>(source_.data()), source_.size()};
if (get_next_token() != '[')
WIRE_DLOG_THROW_(error::schema::array);
remaining_.remove_prefix(1);
return 0;
}
std::size_t json_reader::do_start_object()
{
if (get_next_token() != '{')
WIRE_DLOG_THROW_(error::schema::object);
remaining_.remove_prefix(1);
return 0;
}
json_reader::json_reader(epee::span<const char> source)
: reader({reinterpret_cast<const std::uint8_t*>(source.data()), source.size()}),
str_buffer_(),
reader_()
{}
void json_reader::check_complete() const
{
if (depth())
WIRE_DLOG_THROW(error::rapidjson_e(rapidjson::kParseErrorUnspecificSyntaxError), "Unexpected end");
}
basic_value json_reader::basic()
{
rapidjson_sax json_any{str_buffer_, error::schema::none};
read_next_value(json_any);
switch(json_any.actual_)
{
default:
break;
case error::schema::none:
return {nullptr};
case error::schema::boolean:
return {json_any.value.boolean};
case error::schema::integer:
if (json_any.negative)
return {json_any.value.integer};
else
return {json_any.value.unsigned_integer};
case error::schema::number:
return {json_any.value.number};
case error::schema::string:
return {std::move(str_buffer_)};
};
WIRE_DLOG_THROW(error::schema::number, "expected a boolean, integer, float or string");
}
bool json_reader::boolean()
{
rapidjson_sax json_bool{error::schema::boolean};
@@ -264,24 +309,24 @@ namespace wire
WIRE_DLOG_THROW_(error::schema::larger_integer);
return static_cast<std::uintmax_t>(json_uint.value.integer);
}
/*
const std::vector<std::uintmax_t>& json_reader::unsigned_integer_array()
{
read_next_unsigned_array(
}*/
std::uintmax_t json_reader::safe_unsigned_integer()
{
if (get_next_token() != '"')
WIRE_DLOG_THROW_(error::schema::string);
remaining_.remove_prefix(1);
bool is_string = false;
if (get_next_token() == '"')
{
remaining_.remove_prefix(1);
is_string = true;
}
const std::uintmax_t out = unsigned_integer();
if (get_next_token() != '"')
WIRE_DLOG_THROW_(error::rapidjson_e(rapidjson::kParseErrorStringMissQuotationMark));
remaining_.remove_prefix(1);
if (is_string)
{
if (get_next_token() != '"')
WIRE_DLOG_THROW_(error::rapidjson_e(rapidjson::kParseErrorStringMissQuotationMark));
remaining_.remove_prefix(1);
}
return out;
}
@@ -294,38 +339,34 @@ namespace wire
std::string json_reader::string()
{
rapidjson_sax json_string{error::schema::string};
read_next_value(json_string);
return std::string{json_string.value.string.ptr, json_string.value.string.length};
{
rapidjson_sax json_string{str_buffer_, error::schema::string};
read_next_value(json_string);
}
return std::string{std::move(str_buffer_)};
}
std::vector<std::uint8_t> json_reader::binary()
epee::byte_slice json_reader::binary()
{
const boost::string_ref value = get_next_string();
std::vector<std::uint8_t> out;
out.resize(value.size() / 2);
epee::byte_stream out{};
out.put_n(0, value.size() / 2);
if (!epee::from_hex::to_buffer(epee::to_mut_span(out), value))
WIRE_DLOG_THROW_(error::schema::binary);
WIRE_DLOG_THROW(error::schema::binary, "invalid hex");
return out;
return epee::byte_slice{std::move(out)};
}
void json_reader::binary(epee::span<std::uint8_t> dest)
std::size_t json_reader::binary(epee::span<std::uint8_t> dest, const bool exact)
{
const boost::string_ref value = get_next_string();
if (!exact && value.size() / 2 <= dest.size())
dest = {dest.data(), value.size() / 2}; // `from_hex` will ensure exact size
if (!epee::from_hex::to_buffer(dest, value))
WIRE_DLOG_THROW(error::schema::fixed_binary, "of size" << dest.size() * 2 << " but got " << value.size());
}
std::size_t json_reader::start_array(std::size_t)
{
if (get_next_token() != '[')
WIRE_DLOG_THROW_(error::schema::array);
remaining_.remove_prefix(1);
increment_depth();
return 0;
WIRE_DLOG_THROW(exact ? error::schema::fixed_binary : error::schema::binary, "invalid hex and/or expected size " << dest.size() * 2 << " but got " << value.size());
return dest.size();
}
bool json_reader::is_array_end(const std::size_t count)
@@ -348,21 +389,11 @@ namespace wire
return false;
}
std::size_t json_reader::start_object()
{
if (get_next_token() != '{')
WIRE_DLOG_THROW_(error::schema::object);
remaining_.remove_prefix(1);
increment_depth();
return 0;
}
bool json_reader::key(const epee::span<const key_map> map, std::size_t& state, std::size_t& index)
{
rapidjson_sax json_key{error::schema::string};
const auto process_key = [map] (const rapidjson_sax::string_contents value)
rapidjson_sax json_key{str_buffer_, error::schema::string};
const auto process_key = [map] (const boost::string_ref key)
{
const boost::string_ref key{value.ptr, value.length};
for (std::size_t i = 0; i < map.size(); ++i)
{
if (map[i].name == key)
@@ -395,7 +426,7 @@ namespace wire
// parse key
read_next_value(json_key);
index = process_key(json_key.value.string);
index = process_key(str_buffer_);
if (get_next_token() != ':')
WIRE_DLOG_THROW_(error::rapidjson_e(rapidjson::kParseErrorObjectMissColon));
remaining_.remove_prefix(1);
+6 -2
View File
@@ -36,6 +36,7 @@
#include <vector>
#include "wire/field.h"
#include "wire/fwd.h"
#include "wire/json/base.h"
#include "wire/read.h"
#include "wire/traits.h"
@@ -63,6 +64,9 @@ namespace wire
//! \throw wire::exception if JSON parsing is incomplete.
void check_complete() const override final;
//! \throw wire::exception if array, object, or end of stream.
basic_value basic();
//! \throw wire::exception if next token not a boolean.
bool boolean() override final;
@@ -72,7 +76,7 @@ namespace wire
//! \throw wire::exception if next token not an unsigned integer.
std::uintmax_t unsigned_integer() override final;
//! \throw wire::exception if next token is not an integer encoded as string
//! \throw wire::exception if next token is not an integer encoded as string OR unsigned integer
std::uintmax_t safe_unsigned_integer();
//! \throw wire::exception if next token not a valid real number
@@ -82,7 +86,7 @@ namespace wire
std::string string() override final;
//! \throw wire::exception if next token cannot be read as hex
std::vector<std::uint8_t> binary() override final;
epee::byte_slice binary() override final;
//! \throw wire::exception if next token cannot be read as hex into `dest`.
void binary(epee::span<std::uint8_t> dest) override final;
+3 -2
View File
@@ -413,11 +413,12 @@ namespace wire
return std::string{reinterpret_cast<const char*>(bytes.data()), bytes.size()};
}
std::vector<std::uint8_t> msgpack_reader::binary()
epee::byte_slice msgpack_reader::binary()
{
update_tags_remaining();
const epee::span<const std::uint8_t> bytes = read_binary(remaining_, get_tag());
return std::vector<std::uint8_t>{bytes.begin(), bytes.end()};
const std::size_t begin = bytes.data() - source_.data();
return source_.get_slice(begin, begin + bytes.size());
}
void msgpack_reader::binary(epee::span<std::uint8_t> dest)
+1 -1
View File
@@ -115,7 +115,7 @@ namespace wire
std::string string() override final;
//! \throw wire::exception if next token cannot be read as hex
std::vector<std::uint8_t> binary() override final;
epee::byte_slice binary() override final;
//! \throw wire::exception if next token cannot be read as hex into `dest`.
void binary(epee::span<std::uint8_t> dest) override final;
+3 -3
View File
@@ -32,8 +32,8 @@
#include <string>
#include <tuple>
#include <type_traits>
#include <vector>
#include "byte_slice.h" // monero/contrib/epee/include
#include "common/expect.h" // monero/src
#include "span.h" // monero/contrib/epee/include
#include "wire/error.h"
@@ -106,7 +106,7 @@ namespace wire
virtual std::string string() = 0;
// ! \throw wire::exception if next value cannot be read as binary
virtual std::vector<std::uint8_t> binary() = 0;
virtual epee::byte_slice binary() = 0;
//! \throw wire::exception if next value cannot be read as binary into `dest`.
virtual void binary(epee::span<std::uint8_t> dest) = 0;
@@ -163,7 +163,7 @@ namespace wire
{ dest = source.string(); }
template<typename R>
inline void read_bytes(R& source, std::vector<std::uint8_t>& dest)
inline void read_bytes(R& source, epee::byte_slice& dest)
{ dest = source.binary(); }
template<typename R, typename T>